From 9219ec539da294ae8336ada1df7c2d605f57ad95 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Fri, 21 Jan 2022 22:46:10 +0100 Subject: [PATCH] allow "goto pointervar" for indirect jumps --- .../codegen/target/cpu6502/codegen/AsmGen.kt | 48 ++++++++++++----- .../compiler/astprocessing/AstChecker.kt | 14 ++++- compiler/test/TestScoping.kt | 53 +++++++++++++++++++ .../src/prog8/ast/AstToSourceTextConverter.kt | 2 +- .../src/prog8/ast/statements/AstStatements.kt | 10 +++- docs/source/syntaxreference.rst | 5 ++ docs/source/todo.rst | 1 - examples/test.p8 | 24 +++++---- 8 files changed, 129 insertions(+), 28 deletions(-) diff --git a/codeGeneration/src/prog8/codegen/target/cpu6502/codegen/AsmGen.kt b/codeGeneration/src/prog8/codegen/target/cpu6502/codegen/AsmGen.kt index 36b4723cd..a149e6093 100644 --- a/codeGeneration/src/prog8/codegen/target/cpu6502/codegen/AsmGen.kt +++ b/codeGeneration/src/prog8/codegen/target/cpu6502/codegen/AsmGen.kt @@ -875,7 +875,10 @@ class AsmGen(private val program: Program, } } is Assignment -> assignmentAsmGen.translate(stmt) - is Jump -> jmp(getJumpTarget(stmt)) + is Jump -> { + val (asmLabel, indirect) = getJumpTarget(stmt) + jmp(asmLabel, indirect) + } is GoSub -> translate(stmt) is PostIncrDecr -> postincrdecrAsmGen.translate(stmt) is Label -> translate(stmt) @@ -1563,7 +1566,17 @@ $repeatLabel lda $counterVar if(jump!=null) { // branch with only a jump (goto) val instruction = branchInstruction(stmt.condition, false) - out(" $instruction ${getJumpTarget(jump)}") + val (asmLabel, indirect) = getJumpTarget(jump) + if(indirect) { + val complementedInstruction = branchInstruction(stmt.condition, true) + out(""" + $complementedInstruction + + jmp ($asmLabel) ++""") + } + else { + out(" $instruction $asmLabel") + } translate(stmt.elsepart) } else { if(stmt.elsepart.isEmpty()) { @@ -1638,15 +1651,22 @@ $repeatLabel lda $counterVar } } - private fun getJumpTarget(jump: Jump): String { + private fun getJumpTarget(jump: Jump): Pair { val ident = jump.identifier val label = jump.generatedLabel val addr = jump.address return when { - ident!=null -> asmSymbolName(ident) - label!=null -> label - addr!=null -> addr.toHex() - else -> "????" + ident!=null -> { + // can be a label, or a pointer variable + val target = ident.targetVarDecl(program) + if(target!=null) + Pair(asmSymbolName(ident), true) // indirect + else + Pair(asmSymbolName(ident), false) + } + label!=null -> Pair(label, false) + addr!=null -> Pair(addr.toHex(), false) + else -> Pair("????", false) } } @@ -1758,11 +1778,15 @@ $repeatLabel lda $counterVar return zeropage.allocatedZeropageVariable(vardecl.scopedName)!=null } - internal fun jmp(asmLabel: String) { - if(isTargetCpu(CpuType.CPU65c02)) - out(" bra $asmLabel") // note: 64tass will convert this automatically to a jmp if the relative distance is too large - else - out(" jmp $asmLabel") + internal fun jmp(asmLabel: String, indirect: Boolean=false) { + if(indirect) { + out(" jmp ($asmLabel)") + } else { + if (isTargetCpu(CpuType.CPU65c02)) + out(" bra $asmLabel") // note: 64tass will convert this automatically to a jmp if the relative distance is too large + else + out(" jmp $asmLabel") + } } internal fun pointerViaIndexRegisterPossible(pointerOffsetExpr: Expression): Pair? { diff --git a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt index 4658dc015..84da647af 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt @@ -1308,8 +1308,18 @@ internal class AstChecker(private val program: Program, private fun checkFunctionOrLabelExists(target: IdentifierReference, statement: Statement): Statement? { when (val targetStatement = target.targetStatement(program)) { is Label, is Subroutine, is BuiltinFunctionPlaceholder -> return targetStatement - null -> errors.err("undefined function or subroutine: ${target.nameInSource.joinToString(".")}", statement.position) - else -> errors.err("cannot call that: ${target.nameInSource.joinToString(".")}", statement.position) + is VarDecl -> { + if(statement is Jump) { + if (targetStatement.datatype == DataType.UWORD) + return targetStatement + else + errors.err("wrong address variable datatype, expected uword", target.position) + } + else + errors.err("cannot call that: ${target.nameInSource.joinToString(".")}", target.position) + } + null -> errors.err("undefined function or subroutine: ${target.nameInSource.joinToString(".")}", target.position) + else -> errors.err("cannot call that: ${target.nameInSource.joinToString(".")}", target.position) } return null } diff --git a/compiler/test/TestScoping.kt b/compiler/test/TestScoping.kt index f792f93e5..9c7f9df61 100644 --- a/compiler/test/TestScoping.kt +++ b/compiler/test/TestScoping.kt @@ -332,4 +332,57 @@ class TestScoping: FunSpec({ errors.errors[3] shouldContain "undefined symbol: routine.nested.nestedvalue" errors.errors[4] shouldContain "undefined symbol: nested.nestedvalue" } + + test("various good goto targets") { + val text=""" + main { + sub start() { + uword address = $4000 + + goto ${'$'}c000 + goto address ; indirect jump + goto main.routine + goto main.jumplabel + + if_cc + goto ${'$'}c000 + if_cc + goto address ; indirect jump + if_cc + goto main.routine + if_cc + goto main.jumplabel + } + + jumplabel: + %asm {{ + rts + }} + sub routine() { + } + } + """ + compileText(C64Target, false, text, writeAssembly = false).assertSuccess() + } + + test("various wrong goto targets") { + val text = """ + main { + sub start() { + byte wrongaddress = 100 + + goto wrongaddress ; must be uword + goto main.routine ; can't take args + } + + sub routine(ubyte arg) { + } + } + """ + val errors = ErrorReporterForTests() + compileText(C64Target, false, text, writeAssembly = false, errors = errors).assertFailure() + errors.errors.size shouldBe 2 + errors.errors[0] shouldContain "wrong address" + errors.errors[1] shouldContain "takes parameters" + } }) diff --git a/compilerAst/src/prog8/ast/AstToSourceTextConverter.kt b/compilerAst/src/prog8/ast/AstToSourceTextConverter.kt index 3d2078785..4adbee302 100644 --- a/compilerAst/src/prog8/ast/AstToSourceTextConverter.kt +++ b/compilerAst/src/prog8/ast/AstToSourceTextConverter.kt @@ -231,7 +231,7 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program: override fun visit(jump: Jump) { output("goto ") when { - jump.address!=null -> output(jump.address.toHex()) + jump.address!=null -> output(jump.address!!.toHex()) jump.generatedLabel!=null -> output(jump.generatedLabel) jump.identifier!=null -> jump.identifier.accept(this) } diff --git a/compilerAst/src/prog8/ast/statements/AstStatements.kt b/compilerAst/src/prog8/ast/statements/AstStatements.kt index 7cd310141..d8a7c9e65 100644 --- a/compilerAst/src/prog8/ast/statements/AstStatements.kt +++ b/compilerAst/src/prog8/ast/statements/AstStatements.kt @@ -527,7 +527,7 @@ class PostIncrDecr(var target: AssignTarget, val operator: String, override val override fun toString() = "PostIncrDecr(op: $operator, target: $target, pos=$position)" } -class Jump(val address: UInt?, +class Jump(var address: UInt?, val identifier: IdentifierReference?, val generatedLabel: String?, // can be used in code generation scenarios override val position: Position) : Statement() { @@ -538,7 +538,13 @@ class Jump(val address: UInt?, identifier?.linkParents(this) } - override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here") + override fun replaceChildNode(node: Node, replacement: Node) { + if(node===identifier && replacement is NumericLiteralValue) { + address = replacement.number.toUInt() + } + else + throw FatalAstException("can't replace $node") + } override fun copy() = Jump(address, identifier?.copy(), generatedLabel, position) override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent) diff --git a/docs/source/syntaxreference.rst b/docs/source/syntaxreference.rst index 21f524d66..3c2054fa3 100644 --- a/docs/source/syntaxreference.rst +++ b/docs/source/syntaxreference.rst @@ -784,10 +784,15 @@ of a label or subroutine:: goto $c000 ; address goto name ; label or subroutine + uword address = $4000 + goto address ; jump via address variable Notice that this is a valid way to end a subroutine (you can either ``return`` from it, or jump 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. + Conditional execution ^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 21985fac5..4497efb4b 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -3,7 +3,6 @@ TODO For next release ^^^^^^^^^^^^^^^^ -- allow goto pointervar (goto $a000 already works...) then use this in cx16assem's run_file() - allow "xxx" * constexpr (where constexpr is not a number literal, now gives expression error not same type) - is * lower prio than bitwise & ? fix prios if so! diff --git a/examples/test.p8 b/examples/test.p8 index e5647e373..4ac786e99 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -3,16 +3,20 @@ main { sub start() { - cx16.mouse_config(1, 0) + txt.print("yo\n") + uword jumps = $4000 + if_cc + goto jumps - repeat { - ubyte mb = cx16.mouse_pos() - txt.print_uw(cx16.r0) - txt.spc() - txt.print_uw(cx16.r1) - txt.spc() - txt.print_ub(mb) - txt.nl() - } + goto jumps } } + +test $4000 { + %option force_output + +jumper: + %asm {{ + rts + }} +}