From f690f58bd44bb445b2a9297c0f4788673a2572ae Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Thu, 14 Jul 2022 19:25:08 +0200 Subject: [PATCH] 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.) --- .../codegen/cpu6502/BuiltinFunctionsAsmGen.kt | 23 +++++++++---------- docs/source/programming.rst | 7 ++++-- docs/source/syntaxreference.rst | 10 ++++---- examples/test.p8 | 13 +++++++---- 4 files changed, 30 insertions(+), 23 deletions(-) diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/BuiltinFunctionsAsmGen.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/BuiltinFunctionsAsmGen.kt index 705ee16c0..05f5d5380 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/BuiltinFunctionsAsmGen.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/BuiltinFunctionsAsmGen.kt @@ -139,20 +139,19 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, throw AssemblyError("callfar only works on cx16 target at this time") val bank = fcall.args[0].constValue(program)?.number?.toInt() - val address = fcall.args[1].constValue(program)?.number?.toInt() - 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 address = fcall.args[1].constValue(program)?.number?.toInt() ?: 0 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) { asmgen.out(""" jsr cx16.jsrfar - .word ${address.toHex()} ++ .word ${address.toHex()} .byte ${bank.toHex()}""") } else { when(argAddrArg) { @@ -162,7 +161,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, asmgen.out(""" lda ${asmgen.asmVariableName(argAddrArg.identifier)} jsr cx16.jsrfar - .word ${address.toHex()} ++ .word ${address.toHex()} .byte ${bank.toHex()} sta ${asmgen.asmVariableName(argAddrArg.identifier)}""") } @@ -170,7 +169,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, asmgen.out(""" lda ${argAddrArg.number.toHex()} jsr cx16.jsrfar - .word ${address.toHex()} ++ .word ${address.toHex()} .byte ${bank.toHex()} sta ${argAddrArg.number.toHex()}""") } diff --git a/docs/source/programming.rst b/docs/source/programming.rst index e9b5f7c3f..3b235e62b 100644 --- a/docs/source/programming.rst +++ b/docs/source/programming.rst @@ -899,14 +899,17 @@ memory(name, size, alignment) 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) - The banked RAM is located in the address range $A000-$BFFF (8 kilobyte). - Notice that bank $00 is used by the Kernal and should not be used by user code. + The banked RAM is located in the address range $A000-$BFFF (8 kilobyte), but you can specify + 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 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, no argument passing will be done. 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. + 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 Calls an assembly routine in another rom-bank on the CommanderX16 diff --git a/docs/source/syntaxreference.rst b/docs/source/syntaxreference.rst index 6bdef3717..2edf4f52b 100644 --- a/docs/source/syntaxreference.rst +++ b/docs/source/syntaxreference.rst @@ -753,14 +753,14 @@ You can still ``break`` out of such a loop if you want though. 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 of a label or subroutine:: - goto $c000 ; address - goto name ; label or subroutine + goto $c000 ; address + goto name ; label or subroutine uword address = $4000 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 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 diff --git a/examples/test.p8 b/examples/test.p8 index 03cecfd88..c7260b6f0 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -1,12 +1,15 @@ %import textio %zeropage basicsafe - main { sub start() { - byte tx = 1 - uword @shared zzzz= $2000 + (tx as ubyte) - txt.print_uwhex(zzzz,true) + ubyte derp=2 + callfar(0, &func, 0) +continue: + txt.print("main again.") + } + + sub func() { + txt.print("func.") } } -