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
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

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
@ -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" [, <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
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 <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
------------
@ -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 <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
----------------------------

View File

@ -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
}