mirror of
https://github.com/irmen/prog8.git
synced 2024-11-29 17:50:35 +00:00
float assign improvements
This commit is contained in:
parent
468c080859
commit
b4d82ba8e6
@ -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:
|
else:
|
||||||
raise CodeError("can only assign memory to a memory mapped value for now "
|
raise CodeError("unsupported rvalue to memfloat", str(rvalue))
|
||||||
"(if you need other types, can't you use a var?)", str(rvalue))
|
else:
|
||||||
|
raise CodeError("invalid lvalue memmapped datatype", str(lv))
|
||||||
|
|
||||||
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
|
||||||
|
137
reference.txt
137
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
|
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
|
||||||
----------------------------
|
----------------------------
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user