callfar() now accepts a variable as address, so it can be used to indirect JSR to a subroutine whose address is not fixed. ('goto' already could indirect JMP to a variable address.)

This commit is contained in:
Irmen de Jong 2022-07-14 19:25:08 +02:00
parent 4bc65e9ef7
commit f690f58bd4
4 changed files with 30 additions and 23 deletions

View File

@ -139,20 +139,19 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
throw AssemblyError("callfar only works on cx16 target at this time") throw AssemblyError("callfar only works on cx16 target at this time")
val bank = fcall.args[0].constValue(program)?.number?.toInt() val bank = fcall.args[0].constValue(program)?.number?.toInt()
val address = fcall.args[1].constValue(program)?.number?.toInt() val address = fcall.args[1].constValue(program)?.number?.toInt() ?: 0
if(bank==null || address==null)
throw AssemblyError("callfar (jsrfar) requires constant arguments")
if(address !in 0xa000..0xbfff)
throw AssemblyError("callfar done on address outside of cx16 banked ram")
if(bank==0)
throw AssemblyError("callfar done on bank 0 which is reserved for the kernal")
val argAddrArg = fcall.args[2] val argAddrArg = fcall.args[2]
if(bank==null)
throw AssemblyError("callfar (jsrfar) bank has to be a constant")
if(fcall.args[1].constValue(program) == null) {
assignAsmGen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AY, false)
asmgen.out(" sta (+)+0 | sty (+)+1 ; store jsrfar address word")
}
if(argAddrArg.constValue(program)?.number == 0.0) { if(argAddrArg.constValue(program)?.number == 0.0) {
asmgen.out(""" asmgen.out("""
jsr cx16.jsrfar jsr cx16.jsrfar
.word ${address.toHex()} + .word ${address.toHex()}
.byte ${bank.toHex()}""") .byte ${bank.toHex()}""")
} else { } else {
when(argAddrArg) { when(argAddrArg) {
@ -162,7 +161,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
asmgen.out(""" asmgen.out("""
lda ${asmgen.asmVariableName(argAddrArg.identifier)} lda ${asmgen.asmVariableName(argAddrArg.identifier)}
jsr cx16.jsrfar jsr cx16.jsrfar
.word ${address.toHex()} + .word ${address.toHex()}
.byte ${bank.toHex()} .byte ${bank.toHex()}
sta ${asmgen.asmVariableName(argAddrArg.identifier)}""") sta ${asmgen.asmVariableName(argAddrArg.identifier)}""")
} }
@ -170,7 +169,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
asmgen.out(""" asmgen.out("""
lda ${argAddrArg.number.toHex()} lda ${argAddrArg.number.toHex()}
jsr cx16.jsrfar jsr cx16.jsrfar
.word ${address.toHex()} + .word ${address.toHex()}
.byte ${bank.toHex()} .byte ${bank.toHex()}
sta ${argAddrArg.number.toHex()}""") sta ${argAddrArg.number.toHex()}""")
} }

View File

@ -899,14 +899,17 @@ memory(name, size, alignment)
callfar(bank, address, argumentaddress) ; NOTE: specific to cx16 target for now callfar(bank, address, argumentaddress) ; NOTE: specific to cx16 target for now
Calls an assembly routine in another ram-bank on the CommanderX16 (using the ``jsrfar`` routine) Calls an assembly routine in another ram-bank on the CommanderX16 (using the ``jsrfar`` routine)
The banked RAM is located in the address range $A000-$BFFF (8 kilobyte). The banked RAM is located in the address range $A000-$BFFF (8 kilobyte), but you can specify
Notice that bank $00 is used by the Kernal and should not be used by user code. any address in system ram (why this can be useful is explained at the end of this paragraph)
The third argument can be used to designate the memory address The third argument can be used to designate the memory address
of an argument for the routine; it will be loaded into the A register and will of an argument for the routine; it will be loaded into the A register and will
receive the result value returned by the routine in the A register. If you leave this at zero, receive the result value returned by the routine in the A register. If you leave this at zero,
no argument passing will be done. no argument passing will be done.
If the routine requires different arguments or return values, ``callfar`` cannot be used If the routine requires different arguments or return values, ``callfar`` cannot be used
and you'll have to set up a call to ``jsrfar`` yourself to process this. and you'll have to set up a call to ``jsrfar`` yourself to process this.
Note: the address can be a variable or other expression, which allows you to use ``callfar`` with bank 0 to do an indirect JSR to a subroutine
whose address can vary (jump table, etc. ``goto`` can do an indirect JMP to a variable address): ``callfar(0, &routine, &argument)``
This is not very efficient though, so maybe you should write a small piece of inline assembly for this instead.
callrom(bank, address, argumentaddress) ; NOTE: specific to cx16 target for now callrom(bank, address, argumentaddress) ; NOTE: specific to cx16 target for now
Calls an assembly routine in another rom-bank on the CommanderX16 Calls an assembly routine in another rom-bank on the CommanderX16

View File

@ -753,14 +753,14 @@ You can still ``break`` out of such a loop if you want though.
Conditional Execution and Jumps Conditional Execution and Jumps
------------------------------- -------------------------------
Unconditional jump Unconditional jump: goto
^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^
To jump to another part of the program, you use a ``goto`` statement with an addres or the name To jump to another part of the program, you use a ``goto`` statement with an addres or the name
of a label or subroutine:: of a label or subroutine::
goto $c000 ; address goto $c000 ; address
goto name ; label or subroutine goto name ; label or subroutine
uword address = $4000 uword address = $4000
goto address ; jump via address variable goto address ; jump via address variable
@ -770,6 +770,8 @@ to another piece of code that eventually returns).
If you jump to an address variable (uword), it is doing an 'indirect' jump: the jump will be done If you jump to an address variable (uword), it is doing an 'indirect' jump: the jump will be done
to the address that's currently in the variable. to the address that's currently in the variable.
Note: to do an indirect *JSR* to a routine with a varying address, you can use the ``callfar`` builtin function
(which is not very efficient) or you have to write a small piece of inline assembly.
Conditional execution Conditional execution

View File

@ -1,12 +1,15 @@
%import textio %import textio
%zeropage basicsafe %zeropage basicsafe
main { main {
sub start() { sub start() {
byte tx = 1 ubyte derp=2
uword @shared zzzz= $2000 + (tx as ubyte) callfar(0, &func, 0)
txt.print_uwhex(zzzz,true) continue:
txt.print("main again.")
}
sub func() {
txt.print("func.")
} }
} }