diff --git a/il65/codegen.py b/il65/codegen.py index 5d4f506f8..05d02cc76 100644 --- a/il65/codegen.py +++ b/il65/codegen.py @@ -753,6 +753,7 @@ class CodeGenerator: @contextlib.contextmanager def preserving_registers(self, registers: Set[str]): + # @todo option to avoid the sta $03/lda$03 when a is loaded anyway # this clobbers a ZP scratch register and is therefore safe to use in interrupts # see http://6502.org/tutorials/register_preservation.html if registers == {'A'}: @@ -827,33 +828,61 @@ class CodeGenerator: raise CodeError("can only assign a byte or word to a register pair") def generate_assign_mem_to_mem(self, lv: ParseResult.MemMappedValue, rvalue: ParseResult.MemMappedValue) -> None: - r_str = rvalue.name if rvalue.name else "${:x}".format(rvalue.address) + r_str = rvalue.name or Parser.to_hex(rvalue.address) + l_str = lv.name or Parser.to_hex(lv.address) if lv.datatype == DataType.BYTE: if rvalue.datatype != DataType.BYTE: raise CodeError("can only assign a byte to a byte", str(rvalue)) with self.preserving_registers({'A'}): self.p("\t\tlda " + r_str) - self.p("\t\tsta " + (lv.name or Parser.to_hex(lv.address))) + self.p("\t\tsta " + l_str) elif lv.datatype == DataType.WORD: if rvalue.datatype == DataType.BYTE: with self.preserving_registers({'A'}): - l_str = lv.name or Parser.to_hex(lv.address) self.p("\t\tlda " + r_str) self.p("\t\tsta " + l_str) self.p("\t\tlda #0") self.p("\t\tsta {:s}+1".format(l_str)) elif rvalue.datatype == DataType.WORD: with self.preserving_registers({'A'}): - l_str = lv.name or Parser.to_hex(lv.address) self.p("\t\tlda {:s}".format(r_str)) self.p("\t\tsta {:s}".format(l_str)) self.p("\t\tlda {:s}+1".format(r_str)) self.p("\t\tsta {:s}+1".format(l_str)) else: raise CodeError("can only assign a byte or word to a word", str(rvalue)) + elif lv.datatype == DataType.FLOAT: + if rvalue.datatype == DataType.FLOAT: + with self.preserving_registers({'A'}): + self.p("\t\tlda " + r_str) + self.p("\t\tsta " + l_str) + self.p("\t\tlda {:s}+1".format(r_str)) + self.p("\t\tsta {:s}+1".format(l_str)) + self.p("\t\tlda {:s}+2".format(r_str)) + self.p("\t\tsta {:s}+2".format(l_str)) + self.p("\t\tlda {:s}+3".format(r_str)) + self.p("\t\tsta {:s}+3".format(l_str)) + self.p("\t\tlda {:s}+4".format(r_str)) + self.p("\t\tsta {:s}+4".format(l_str)) + elif rvalue.datatype == DataType.BYTE: + with self.preserving_registers({'A', 'X', 'Y'}): + self.p("\t\tldy " + r_str) + self.p("\t\tjsr c64.FREADUY") # ubyte Y -> fac1 + self.p("\t\tldx #<" + l_str) + self.p("\t\tldy #>" + l_str) + self.p("\t\tjsr c64.FTOMEMXY") # fac1 -> memory XY + elif rvalue.datatype == DataType.WORD: + with self.preserving_registers({'A', 'X', 'Y'}): + self.p("\t\tlda " + r_str) + self.p("\t\tldy {:s}+1".format(r_str)) + self.p("\t\tjsr c64util.GIVUAYF") # uword AY -> fac1 + self.p("\t\tldx #<" + l_str) + self.p("\t\tldy #>" + l_str) + self.p("\t\tjsr c64.FTOMEMXY") # fac1 -> memory XY + else: + raise CodeError("unsupported rvalue to memfloat", str(rvalue)) else: - raise CodeError("can only assign memory to a memory mapped value for now " - "(if you need other types, can't you use a var?)", str(rvalue)) + raise CodeError("invalid lvalue memmapped datatype", str(lv)) def generate_assign_char_to_memory(self, lv: ParseResult.MemMappedValue, char_str: str) -> None: # Memory = Character diff --git a/reference.txt b/reference.txt index 6de20b102..6c931a88d 100644 --- a/reference.txt +++ b/reference.txt @@ -11,7 +11,7 @@ It uses the 64tass macro cross assembler to assemble it into binary files. -Memory Model +MEMORY MODEL ------------ Zero page: $00 - $ff @@ -56,8 +56,8 @@ Free zero page addresses on the C-64: -IL program parsing structure: ------------------------------ +PROGRAM STRUCTURE +----------------- OUTPUT MODES: @@ -132,14 +132,16 @@ asmbinary "filename.bin" [, [, ]] -MACROS ------- +ASSIGNMENTS +----------- +Assignment statements assign a single value to one or more variables or memory locations. +If you know that you have to assign the same value to more than one thing at once, it is more +efficient to write it as a multi-assign instead of several separate assignments. The compiler +tries to detect this situation however and optimize it itself if it finds the case. -@todo macros are meta-code (written in Python syntax) that actually runs in a preprecessing step -during the compilation, and produces output value that is then replaced on that point in the input source. -Allows us to create pre calculated sine tables and such. Something like: + target = value-expression + target1 = target2 = target3 [,...] = value-expression - var .array sinetable ``[sin(x) * 10 for x in range(100)]`` @@ -164,7 +166,60 @@ unless it is part of a subroutine call statement. For an indirect goto call, the Everything after a semicolon ';' is a comment and is ignored, however the comment (if it is the only thing on the line) is copied into the resulting assembly source code. - + + +SUBROUTINES DEFINITIONS +----------------------- + +Subroutines are parts of the code that can be repeatedly invoked using a subroutine call from elsewhere. +Their definition, using the sub statement, includes the specification of the required input- and output parameters. +For now, only register based parameters are supported (A, X, Y and paired registers, and the carry status bit SC as a special). +The syntax is: + + sub ([proc_parameters]) -> ([proc_results]) { + ... statements ... + } + + proc_parameters = comma separated list of ":" pairs specifying the input parameters + proc_results = comma separated list of names specifying in which register(s) the output is returned. + If the register name ends with a '?', that means the register doesn't contain a real return value but + is clobbered in the process so the original value it had before calling the sub is no longer valid. + This is not immediately useful for your own code, but the compiler needs this information to + emit the correct assembly code to preserve the cpu registers if needed when the call is made. + + +Subroutines that are pre-defined on a specific memory location (usually routines from ROM), +can also be defined using the 'sub' statement. But in this case you don't supply a block with statements, +but instead assign a memory address to it: + + sub ([proc_parameters]) -> ([proc_results]) =
+ + example: "sub CLOSE (logical: A) -> (A?, X?, Y?) = $FFC3" + + +SUBROUTINE CALLS +---------------- + +You call a subroutine like this: + subroutinename_or_address [!] ( [arguments...] ) + +Normally, the registers are preserved when calling the subroutine and restored on return. +If you add a '!' after the name, no register preserving is done and the call essentially +is just a single JSR instruction. +Arguments should match the subroutine definition. You are allowed to omit the parameter names. +If no definition is available (because you're directly calling memory or a label or something else), +you can freely add arguments (but in this case they all have to be named). + +To jump to a subroutine (without returning), prefix the subroutine call with the word 'goto'. +Unlike gotos in other languages, here it take arguments as well, because it +essentially is the same as calling a subroutine and only doing something different when it's finished. + + +@todo support call non-register args (variable parameter passing) +@todo support assigning call return values (so that you can assign these to other variables, and allows the subroutine call be an actual expression) + + + FLOW CONTROL ------------ @@ -302,7 +357,18 @@ il65_for_999_end ; code continues after this -MEMORY BLOCK OPERATIONS: +MACROS +------ + +@todo macros are meta-code (written in Python syntax) that actually runs in a preprecessing step +during the compilation, and produces output value that is then replaced on that point in the input source. +Allows us to create pre calculated sine tables and such. Something like: + + var .array sinetable ``[sin(x) * 10 for x in range(100)]`` + + +MEMORY BLOCK OPERATIONS +----------------------- @todo matrix,list,string memory block operations: - matrix type operations (whole matrix, per row, per column, individual row/column) @@ -323,57 +389,6 @@ these should call (or emit inline) optimized pieces of assembly code, so they ru -SUBROUTINES DEFINITIONS ------------------------ - -Subroutines are parts of the code that can be repeatedly invoked using a subroutine call from elsewhere. -Their definition, using the sub statement, includes the specification of the required input- and output parameters. -For now, only register based parameters are supported (A, X, Y and paired registers, and the carry status bit SC as a special). -The syntax is: - - sub ([proc_parameters]) -> ([proc_results]) { - ... statements ... - } - - proc_parameters = comma separated list of ":" pairs specifying the input parameters - proc_results = comma separated list of names specifying in which register(s) the output is returned. - If the register name ends with a '?', that means the register doesn't contain a real return value but - is clobbered in the process so the original value it had before calling the sub is no longer valid. - This is not immediately useful for your own code, but the compiler needs this information to - emit the correct assembly code to preserve the cpu registers if needed when the call is made. - - -Subroutines that are pre-defined on a specific memory location (usually routines from ROM), -can also be defined using the 'sub' statement. But in this case you don't supply a block with statements, -but instead assign a memory address to it: - - sub ([proc_parameters]) -> ([proc_results]) =
- - example: "sub CLOSE (logical: A) -> (A?, X?, Y?) = $FFC3" - - -SUBROUTINE CALLS ----------------- - -You call a subroutine like this: - subroutinename_or_address [!] ( [arguments...] ) - -Normally, the registers are preserved when calling the subroutine and restored on return. -If you add a '!' after the name, no register preserving is done and the call essentially -is just a single JSR instruction. -Arguments should match the subroutine definition. You are allowed to omit the parameter names. -If no definition is available (because you're directly calling memory or a label or something else), -you can freely add arguments (but in this case they all have to be named). - -To jump to a subroutine (without returning), prefix the subroutine call with the word 'goto'. -Unlike gotos in other languages, here it take arguments as well, because it -essentially is the same as calling a subroutine and only doing something different when it's finished. - - -@todo support call non-register args (variable parameter passing) -@todo support assigning call return values (so that you can assign these to other variables, and allows the subroutine call be an actual expression) - - REGISTER PRESERVATION BLOCK: @todo (no)preserve ---------------------------- diff --git a/testsource/dtypes.ill b/testsource/dtypes.ill index 79390b6b1..3fc1df899 100644 --- a/testsource/dtypes.ill +++ b/testsource/dtypes.ill @@ -3,6 +3,7 @@ output raw clobberzp + ~ ZP { ; ZeroPage block definition: ; base address is set to $04 (because $00 and $01 are used by the hardware, and $02/$03 are scratch) @@ -271,17 +272,17 @@ start memfloat = cbyte3 memfloat = cword2 - ; @todo float assignments that require ROM functions or shims: + ; float assignments that require ROM functions from c64lib: memfloat = Y memfloat = XY uninitfloat = Y uninitfloat = XY initfloat2 = Y initfloat2 = XY - ;initfloat2 = initbyte2 ; @todo support - ;initfloat2 = initword2 ; @todo support - ;initfloat1 = uninitfloat ; @todo support - ;initfloat1 = initfloat2 ; @todo support + initfloat2 = initbyte2 + initfloat2 = initword2 + initfloat1 = uninitfloat + initfloat1 = initfloat2 return }