diff --git a/codeCore/src/prog8/code/core/BuiltinFunctions.kt b/codeCore/src/prog8/code/core/BuiltinFunctions.kt index 1bf78e375..b9a65d49b 100644 --- a/codeCore/src/prog8/code/core/BuiltinFunctions.kt +++ b/codeCore/src/prog8/code/core/BuiltinFunctions.kt @@ -133,6 +133,7 @@ val BuiltinFunctions: Map = mapOf( "rrestore" to FSignature(false, emptyList(), null), "memory" to FSignature(true, listOf(FParam("name", arrayOf(DataType.STR)), FParam("size", arrayOf(DataType.UWORD)), FParam("alignment", arrayOf(DataType.UWORD))), DataType.UWORD), "callfar" to FSignature(false, listOf(FParam("bank", arrayOf(DataType.UBYTE)), FParam("address", arrayOf(DataType.UWORD)), FParam("arg", arrayOf(DataType.UWORD))), DataType.UWORD), + "call" to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD))), null), ) val InplaceModifyingBuiltinFunctions = setOf("setlsb", "setmsb", "rol", "ror", "rol2", "ror2", "sort", "reverse") diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/BuiltinFunctionsAsmGen.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/BuiltinFunctionsAsmGen.kt index ad87d3f07..9f62390c0 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/BuiltinFunctionsAsmGen.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/BuiltinFunctionsAsmGen.kt @@ -85,6 +85,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram, "rrestore" -> funcRrestore() "cmp" -> funcCmp(fcall) "callfar" -> funcCallFar(fcall, resultRegister) + "call" -> funcCall(fcall) "prog8_lib_stringcompare" -> funcStringCompare(fcall, resultRegister) "prog8_lib_square_byte" -> funcSquare(fcall, DataType.UBYTE, resultRegister) "prog8_lib_square_word" -> funcSquare(fcall, DataType.UWORD, resultRegister) @@ -194,6 +195,19 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram, plp""") } + private fun funcCall(fcall: PtBuiltinFunctionCall) { + val constAddr = fcall.args[0].asConstInteger() + if(constAddr!=null) { + asmgen.out(" jsr ${constAddr.toHex()}") + } else { + asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY) // jump address + asmgen.out(""" + sta (+)+1 + sty (+)+2 ++ jsr 0 ; modified""") + } + } + private fun funcCallFar(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) { if(asmgen.options.compTarget.name != "cx16") throw AssemblyError("callfar only works on cx16 target at this time") diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/ForLoopsAsmGen.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/ForLoopsAsmGen.kt index d00a42d36..df6d9ed9a 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/ForLoopsAsmGen.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/ForLoopsAsmGen.kt @@ -436,7 +436,6 @@ $loopLabel sty $indexVar asmgen.out(endLabel) } DataType.ARRAY_UW_SPLIT, DataType.ARRAY_W_SPLIT -> { - numElements!! val indexVar = asmgen.makeLabel("for_index") val loopvarName = asmgen.asmVariableName(stmt.variable) asmgen.out(""" diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/FunctionCallAsmGen.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/FunctionCallAsmGen.kt index 7b45dfa6b..381b1e90e 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/FunctionCallAsmGen.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/FunctionCallAsmGen.kt @@ -26,7 +26,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as // (you can use subroutine.shouldSaveX() and saveX()/restoreX() routines as a help for this) val symbol = asmgen.symbolTable.lookup(call.name) - val sub = symbol!!.astNode as IPtSubroutine + val sub = symbol?.astNode as IPtSubroutine val subAsmName = asmgen.asmSymbolName(call.name) if(sub is PtAsmSub) { diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt index 742536daf..a45eb12a4 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt @@ -26,6 +26,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe "pushw" -> funcPushw(call) "rsave", "rrestore" -> ExpressionCodeResult.EMPTY // vm doesn't have registers to save/restore "callfar" -> funcCallfar(call) + "call" -> funcCall(call) "msb" -> funcMsb(call) "lsb" -> funcLsb(call) "memory" -> funcMemory(call) @@ -71,6 +72,14 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe } } + private fun funcCall(call: PtBuiltinFunctionCall): ExpressionCodeResult { + val result = mutableListOf() + val addressTr = exprGen.translateExpression(call.args[0]) + addToResult(result, addressTr, addressTr.resultReg, -1) + addInstr(result, IRInstruction(Opcode.CALLI, reg1 = addressTr.resultReg), null) + return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1) + } + private fun funcCallfar(call: PtBuiltinFunctionCall): ExpressionCodeResult { val result = mutableListOf() addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 3), null) diff --git a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt index c66170669..adb21da3f 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt @@ -1254,6 +1254,15 @@ internal class AstChecker(private val program: Program, private fun checkFunctionCall(target: Statement, args: List, position: Position) { if(target is BuiltinFunctionPlaceholder) { + if(target.name=="call") { + if(args[0] is AddressOf) + errors.err("can't call this indirectly, just use normal function call syntax", args[0].position) + if(args[0] is IdentifierReference) { + val callTarget = (args[0] as IdentifierReference).targetStatement(program) + if(callTarget !is VarDecl) + errors.err("can't call this indirectly, just use normal function call syntax", args[0].position) + } + } if(!compilerOptions.floats) { if (target.name == "peekf" || target.name == "pokef") errors.err("floating point used, but that is not enabled via options", position) diff --git a/compiler/src/prog8/compiler/astprocessing/AstIdentifiersChecker.kt b/compiler/src/prog8/compiler/astprocessing/AstIdentifiersChecker.kt index bd186cc23..4c02b9344 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstIdentifiersChecker.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstIdentifiersChecker.kt @@ -7,10 +7,7 @@ import prog8.ast.expressions.FunctionCallExpression import prog8.ast.expressions.StringLiteral import prog8.ast.statements.* import prog8.ast.walk.IAstVisitor -import prog8.code.core.BuiltinFunctions -import prog8.code.core.ICompilationTarget -import prog8.code.core.IErrorReporter -import prog8.code.core.Position +import prog8.code.core.* import prog8.code.target.VMTarget /** @@ -191,6 +188,10 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter, errors.err("cannot use arguments when calling a label", pos) } } + is VarDecl -> { + if(target.type!=VarDeclType.VAR || target.datatype!=DataType.UWORD) + errors.err("wrong address variable datatype, expected uword", call.target.position) + } null -> {} else -> errors.err("cannot call this as a subroutine or function", call.target.position) } diff --git a/compiler/test/ast/TestSubroutines.kt b/compiler/test/ast/TestSubroutines.kt index 45227e62c..223fd3e12 100644 --- a/compiler/test/ast/TestSubroutines.kt +++ b/compiler/test/ast/TestSubroutines.kt @@ -1,18 +1,21 @@ package prog8tests.ast -import io.kotest.core.spec.style.AnnotationSpec +import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldBe +import io.kotest.matchers.string.shouldContain import prog8.ast.statements.Block import prog8.ast.statements.Subroutine import prog8.code.core.DataType import prog8.code.core.SourceCode +import prog8.code.target.Cx16Target import prog8.parser.Prog8Parser.parseModule +import prog8tests.helpers.ErrorReporterForTests +import prog8tests.helpers.compileText -class TestSubroutines: AnnotationSpec() { +class TestSubroutines: FunSpec({ - @Test - fun stringParameterAcceptedInParser() { + test("stringParameter AcceptedInParser") { // note: the *parser* should accept this as it is valid *syntax*, // however, the compiler itself may or may not complain about it later. val text = """ @@ -37,8 +40,7 @@ class TestSubroutines: AnnotationSpec() { func.statements.isEmpty() shouldBe true } - @Test - fun arrayParameterAcceptedInParser() { + test("arrayParameter AcceptedInParser") { // note: the *parser* should accept this as it is valid *syntax*, // however, the compiler itself may or may not complain about it later. val text = """ @@ -62,4 +64,42 @@ class TestSubroutines: AnnotationSpec() { func.parameters.single().type shouldBe DataType.ARRAY_UB func.statements.isEmpty() shouldBe true } -} + + test("cannot call a subroutine via pointer") { + val src=""" +main { + sub start() { + uword func = 12345 + func() ; error + func(1,2,3) ; error + cx16.r0 = func() ; error + } +}""" + val errors = ErrorReporterForTests() + compileText(Cx16Target(), false, src, errors, false) shouldBe null + errors.errors.size shouldBe 3 + errors.errors[0] shouldContain "cannot call that" + errors.errors[1] shouldContain "cannot call that" + errors.errors[2] shouldContain "cannot call that" + } + + test("can call a subroutine pointer using call") { + val src=""" +main { + sub start() { + uword func = 12345 + call(func) ; ok + call(12345) ; ok + cx16.r0 = call(func) ; error + call(&start) ; error + call(start) ; error + } +}""" + val errors = ErrorReporterForTests() + compileText(Cx16Target(), false, src, errors, false) shouldBe null + errors.errors.size shouldBe 3 + errors.errors[0] shouldContain ":7:19: assignment right hand side doesn't result in a value" + errors.errors[1] shouldContain "can't call this indirectly" + errors.errors[2] shouldContain "can't call this indirectly" + } +}) diff --git a/docs/source/programming.rst b/docs/source/programming.rst index 08377c08f..5c33a2db5 100644 --- a/docs/source/programming.rst +++ b/docs/source/programming.rst @@ -966,13 +966,20 @@ memory (name, size, alignment) The return value is just a simple uword address so it cannot be used as an array in your program. You can only treat it as a pointer or use it in inline assembly. +call (address) + Calls a subroutine given by its memory address. You cannot pass arguments and result values + directly, although it is ofcourse possible to do this via the global ``cx16.r0...`` registers for example. + This function effectively creates an "indirect JSR" if you use it on a ``uword`` pointer variable. + But because it doesn't handle bank switching + etcetera by itself, it is a lot faster than ``callfar``. And it works on other systems than just the Commander X16. + callfar (bank, address, argumentword) -> uword ; NOTE: specific to cx16 target for now Calls an assembly routine in another bank on the Commander X16 (using its ``JSRFAR`` routine) Be aware that ram OR rom bank may be changed depending on the address it jumps to! The argumentword will be loaded into the A+Y registers before calling the routine. The uword value that the routine returns in the A+Y registers, will be returned. NOTE: this routine is very inefficient, so don't use it to call often. Set the bank yourself - or even write a custom tailored trampoline routine if you need to. + or even write a custom tailored trampoline routine if you need to. Or use ``call`` if you can. syscall (callnr), syscall1 (callnr, arg), syscall2 (callnr, arg1, arg2), syscall3 (callnr, arg1, arg2, arg3) Functions for doing a system call on targets that support this. Currently no actual target diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 67407cf9e..4a304be3f 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -11,9 +11,6 @@ Assuming your writes are aligned to 32-bit boundaries, do four reads from the in (ex: lda DATA1 ; 4 times) and then stz the other one (stz DATA0). The cache is loaded by the DATA1 reads, and the contents are written out with the DATA0 write, 4 bytes at once. -- [on branch: call-pointers] allow calling a subroutine via a pointer variable (indirect JSR, optimized form of callfar()) - modify programs (shell, paint) that now use callfar - - [on branch: shortcircuit] investigate McCarthy evaluation again? this may also reduce code size perhaps for things like if a>4 or a<2 .... ... diff --git a/examples/test.p8 b/examples/test.p8 index f06fb8a49..deffc5cde 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -1,13 +1,15 @@ %import textio -%import floats %zeropage basicsafe main { sub start() { - const uword vera_freq = (136.5811 / 0.3725290298461914) as uword - const uword v_f_10 = (136.5811 / 0.3725290298461914 + 0.5) as uword - - txt.print_uw(v_f_10) - txt.print_uw(vera_freq) + uword ptr = &test + call(ptr) + call(ptr) + call(ptr) } - } + + sub test() { + txt.print("test!\n") + } +} diff --git a/intermediate/src/prog8/intermediate/IRInstructions.kt b/intermediate/src/prog8/intermediate/IRInstructions.kt index 5711b4c6e..15c9401c3 100644 --- a/intermediate/src/prog8/intermediate/IRInstructions.kt +++ b/intermediate/src/prog8/intermediate/IRInstructions.kt @@ -54,6 +54,7 @@ CONTROL FLOW jump location - continue running at instruction at 'location' (label/memory address) jumpi reg1 - continue running at memory address in reg1 (indirect jump) preparecall numparams - indicator that the next instructions are the param setup and function call/syscall with parameters +calli reg1 - calls a subroutine (without arguments and without return valus) at memory addres in reg1 (indirect jsr) call label(argument register list) [: resultreg.type] - calls a subroutine with the given arguments and return value (optional). save current instruction location+1, continue execution at instruction nr of the label. @@ -252,6 +253,7 @@ enum class Opcode { JUMP, JUMPI, PREPARECALL, + CALLI, CALL, SYSCALL, RETURN, @@ -397,6 +399,7 @@ val OpcodesThatBranch = setOf( Opcode.JUMPI, Opcode.RETURN, Opcode.RETURNR, + Opcode.CALLI, Opcode.CALL, Opcode.SYSCALL, Opcode.BSTCC, @@ -543,6 +546,7 @@ val instructionFormats = mutableMapOf( Opcode.JUMP to InstructionFormat.from("N, + - + \ No newline at end of file diff --git a/syntax-files/NotepadPlusPlus/Prog8.xml b/syntax-files/NotepadPlusPlus/Prog8.xml index db4c8a7e9..f61b458dc 100644 --- a/syntax-files/NotepadPlusPlus/Prog8.xml +++ b/syntax-files/NotepadPlusPlus/Prog8.xml @@ -27,7 +27,7 @@ void const str byte ubyte bool word uword float zp shared split requirezp %address %asm %ir %asmbinary %asminclude %breakpoint %encoding %import %launcher %option %output %zeropage %zpreserved %zpallowed inline sub asmsub romsub clobbers asm if when else if_cc if_cs if_eq if_mi if_neg if_nz if_pl if_pos if_vc if_vs if_z for in step do while repeat unroll break continue return goto - abs all any callfar clamp cmp divmod len lsb lsl lsr memory mkword min max msb peek peekw peekf poke pokew pokef push pushw pop popw rsave rsavex rrestore rrestorex reverse rnd rndw rol rol2 ror ror2 setlsb setmsb sgn sizeof sort sqrtw swap + abs all any call callfar clamp cmp divmod len lsb lsl lsr memory mkword min max msb peek peekw peekf poke pokew pokef push pushw pop popw rsave rsavex rrestore rrestorex reverse rnd rndw rol rol2 ror ror2 setlsb setmsb sgn sizeof sort sqrtw swap true false not and or xor as to downto |> diff --git a/syntax-files/Vim/prog8_builtins.vim b/syntax-files/Vim/prog8_builtins.vim index e37aeb919..9a1e8cc0b 100644 --- a/syntax-files/Vim/prog8_builtins.vim +++ b/syntax-files/Vim/prog8_builtins.vim @@ -15,7 +15,7 @@ syn keyword prog8BuiltInFunc any all len reverse sort " Miscellaneous functions syn keyword prog8BuiltInFunc cmp divmod lsb msb mkword min max peek peekw peekf poke pokew pokef push pushw pop popw rsave rsavex rrestore rrestorex syn keyword prog8BuiltInFunc rol rol2 ror ror2 sizeof setlsb setmsb -syn keyword prog8BuiltInFunc swap memory callfar clamp +syn keyword prog8BuiltInFunc swap memory call callfar clamp " c64/floats.p8 diff --git a/virtualmachine/src/prog8/vm/VirtualMachine.kt b/virtualmachine/src/prog8/vm/VirtualMachine.kt index d061a82dd..dd18029db 100644 --- a/virtualmachine/src/prog8/vm/VirtualMachine.kt +++ b/virtualmachine/src/prog8/vm/VirtualMachine.kt @@ -181,6 +181,7 @@ class VirtualMachine(irProgram: IRProgram) { Opcode.STOREZI -> InsSTOREZI(ins) Opcode.JUMP, Opcode.JUMPI -> InsJUMP(ins) Opcode.PREPARECALL -> nextPc() + Opcode.CALLI -> throw IllegalArgumentException("VM cannot run code from memory") Opcode.CALL -> InsCALL(ins) Opcode.SYSCALL -> InsSYSCALL(ins) Opcode.RETURN -> InsRETURN()