float assign improvements

This commit is contained in:
Irmen de Jong 2017-12-25 12:58:52 +01:00
parent 468c080859
commit b4d82ba8e6
3 changed files with 118 additions and 73 deletions

View File

@ -753,6 +753,7 @@ class CodeGenerator:
@contextlib.contextmanager @contextlib.contextmanager
def preserving_registers(self, registers: Set[str]): 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 # this clobbers a ZP scratch register and is therefore safe to use in interrupts
# see http://6502.org/tutorials/register_preservation.html # see http://6502.org/tutorials/register_preservation.html
if registers == {'A'}: if registers == {'A'}:
@ -827,33 +828,61 @@ class CodeGenerator:
raise CodeError("can only assign a byte or word to a register pair") 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: 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 lv.datatype == DataType.BYTE:
if rvalue.datatype != DataType.BYTE: if rvalue.datatype != DataType.BYTE:
raise CodeError("can only assign a byte to a byte", str(rvalue)) raise CodeError("can only assign a byte to a byte", str(rvalue))
with self.preserving_registers({'A'}): with self.preserving_registers({'A'}):
self.p("\t\tlda " + r_str) 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: elif lv.datatype == DataType.WORD:
if rvalue.datatype == DataType.BYTE: if rvalue.datatype == DataType.BYTE:
with self.preserving_registers({'A'}): with self.preserving_registers({'A'}):
l_str = lv.name or Parser.to_hex(lv.address)
self.p("\t\tlda " + r_str) self.p("\t\tlda " + r_str)
self.p("\t\tsta " + l_str) self.p("\t\tsta " + l_str)
self.p("\t\tlda #0") self.p("\t\tlda #0")
self.p("\t\tsta {:s}+1".format(l_str)) self.p("\t\tsta {:s}+1".format(l_str))
elif rvalue.datatype == DataType.WORD: elif rvalue.datatype == DataType.WORD:
with self.preserving_registers({'A'}): 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\tlda {:s}".format(r_str))
self.p("\t\tsta {:s}".format(l_str)) self.p("\t\tsta {:s}".format(l_str))
self.p("\t\tlda {:s}+1".format(r_str)) self.p("\t\tlda {:s}+1".format(r_str))
self.p("\t\tsta {:s}+1".format(l_str)) self.p("\t\tsta {:s}+1".format(l_str))
else: else:
raise CodeError("can only assign a byte or word to a word", str(rvalue)) 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: else:
raise CodeError("can only assign memory to a memory mapped value for now " raise CodeError("invalid lvalue memmapped datatype", str(lv))
"(if you need other types, can't you use a var?)", str(rvalue))
def generate_assign_char_to_memory(self, lv: ParseResult.MemMappedValue, char_str: str) -> None: def generate_assign_char_to_memory(self, lv: ParseResult.MemMappedValue, char_str: str) -> None:
# Memory = Character # Memory = Character

View File

@ -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 Zero page: $00 - $ff
@ -56,8 +56,8 @@ Free zero page addresses on the C-64:
IL program parsing structure: PROGRAM STRUCTURE
----------------------------- -----------------
OUTPUT MODES: OUTPUT MODES:
@ -132,14 +132,16 @@ asmbinary "filename.bin" [, <offset>[, <length>]]
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 target = value-expression
during the compilation, and produces output value that is then replaced on that point in the input source. target1 = target2 = target3 [,...] = value-expression
Allows us to create pre calculated sine tables and such. Something like:
var .array sinetable ``[sin(x) * 10 for x in range(100)]``
@ -165,6 +167,59 @@ Everything after a semicolon ';' is a comment and is ignored, however the commen
on the line) is copied into the resulting assembly source code. 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 <identifier> ([proc_parameters]) -> ([proc_results]) {
... statements ...
}
proc_parameters = comma separated list of "<parametername>:<register>" pairs specifying the input parameters
proc_results = comma separated list of <register> 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 <identifier> ([proc_parameters]) -> ([proc_results]) = <address>
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 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: @todo matrix,list,string memory block operations:
- matrix type operations (whole matrix, per row, per column, individual row/column) - 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 <identifier> ([proc_parameters]) -> ([proc_results]) {
... statements ...
}
proc_parameters = comma separated list of "<parametername>:<register>" pairs specifying the input parameters
proc_results = comma separated list of <register> 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 <identifier> ([proc_parameters]) -> ([proc_results]) = <address>
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 REGISTER PRESERVATION BLOCK: @todo (no)preserve
---------------------------- ----------------------------

View File

@ -3,6 +3,7 @@
output raw output raw
clobberzp clobberzp
~ ZP { ~ ZP {
; ZeroPage block definition: ; ZeroPage block definition:
; base address is set to $04 (because $00 and $01 are used by the hardware, and $02/$03 are scratch) ; 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 = cbyte3
memfloat = cword2 memfloat = cword2
; @todo float assignments that require ROM functions or shims: ; float assignments that require ROM functions from c64lib:
memfloat = Y memfloat = Y
memfloat = XY memfloat = XY
uninitfloat = Y uninitfloat = Y
uninitfloat = XY uninitfloat = XY
initfloat2 = Y initfloat2 = Y
initfloat2 = XY initfloat2 = XY
;initfloat2 = initbyte2 ; @todo support initfloat2 = initbyte2
;initfloat2 = initword2 ; @todo support initfloat2 = initword2
;initfloat1 = uninitfloat ; @todo support initfloat1 = uninitfloat
;initfloat1 = initfloat2 ; @todo support initfloat1 = initfloat2
return return
} }