allow "goto pointervar" for indirect jumps

This commit is contained in:
Irmen de Jong 2022-01-21 22:46:10 +01:00
parent c8bd57cd4d
commit 9219ec539d
8 changed files with 129 additions and 28 deletions

View File

@ -875,7 +875,10 @@ class AsmGen(private val program: Program,
} }
} }
is Assignment -> assignmentAsmGen.translate(stmt) 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 GoSub -> translate(stmt)
is PostIncrDecr -> postincrdecrAsmGen.translate(stmt) is PostIncrDecr -> postincrdecrAsmGen.translate(stmt)
is Label -> translate(stmt) is Label -> translate(stmt)
@ -1563,7 +1566,17 @@ $repeatLabel lda $counterVar
if(jump!=null) { if(jump!=null) {
// branch with only a jump (goto) // branch with only a jump (goto)
val instruction = branchInstruction(stmt.condition, false) 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) translate(stmt.elsepart)
} else { } else {
if(stmt.elsepart.isEmpty()) { if(stmt.elsepart.isEmpty()) {
@ -1638,15 +1651,22 @@ $repeatLabel lda $counterVar
} }
} }
private fun getJumpTarget(jump: Jump): String { private fun getJumpTarget(jump: Jump): Pair<String, Boolean> {
val ident = jump.identifier val ident = jump.identifier
val label = jump.generatedLabel val label = jump.generatedLabel
val addr = jump.address val addr = jump.address
return when { return when {
ident!=null -> asmSymbolName(ident) ident!=null -> {
label!=null -> label // can be a label, or a pointer variable
addr!=null -> addr.toHex() val target = ident.targetVarDecl(program)
else -> "????" 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,12 +1778,16 @@ $repeatLabel lda $counterVar
return zeropage.allocatedZeropageVariable(vardecl.scopedName)!=null return zeropage.allocatedZeropageVariable(vardecl.scopedName)!=null
} }
internal fun jmp(asmLabel: String) { internal fun jmp(asmLabel: String, indirect: Boolean=false) {
if(indirect) {
out(" jmp ($asmLabel)")
} else {
if (isTargetCpu(CpuType.CPU65c02)) if (isTargetCpu(CpuType.CPU65c02))
out(" bra $asmLabel") // note: 64tass will convert this automatically to a jmp if the relative distance is too large out(" bra $asmLabel") // note: 64tass will convert this automatically to a jmp if the relative distance is too large
else else
out(" jmp $asmLabel") out(" jmp $asmLabel")
} }
}
internal fun pointerViaIndexRegisterPossible(pointerOffsetExpr: Expression): Pair<Expression, Expression>? { internal fun pointerViaIndexRegisterPossible(pointerOffsetExpr: Expression): Pair<Expression, Expression>? {
if(pointerOffsetExpr is BinaryExpression && pointerOffsetExpr.operator=="+") { if(pointerOffsetExpr is BinaryExpression && pointerOffsetExpr.operator=="+") {

View File

@ -1308,8 +1308,18 @@ internal class AstChecker(private val program: Program,
private fun checkFunctionOrLabelExists(target: IdentifierReference, statement: Statement): Statement? { private fun checkFunctionOrLabelExists(target: IdentifierReference, statement: Statement): Statement? {
when (val targetStatement = target.targetStatement(program)) { when (val targetStatement = target.targetStatement(program)) {
is Label, is Subroutine, is BuiltinFunctionPlaceholder -> return targetStatement is Label, is Subroutine, is BuiltinFunctionPlaceholder -> return targetStatement
null -> errors.err("undefined function or subroutine: ${target.nameInSource.joinToString(".")}", statement.position) is VarDecl -> {
else -> errors.err("cannot call that: ${target.nameInSource.joinToString(".")}", statement.position) 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 return null
} }

View File

@ -332,4 +332,57 @@ class TestScoping: FunSpec({
errors.errors[3] shouldContain "undefined symbol: routine.nested.nestedvalue" errors.errors[3] shouldContain "undefined symbol: routine.nested.nestedvalue"
errors.errors[4] shouldContain "undefined symbol: 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"
}
}) })

View File

@ -231,7 +231,7 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
override fun visit(jump: Jump) { override fun visit(jump: Jump) {
output("goto ") output("goto ")
when { when {
jump.address!=null -> output(jump.address.toHex()) jump.address!=null -> output(jump.address!!.toHex())
jump.generatedLabel!=null -> output(jump.generatedLabel) jump.generatedLabel!=null -> output(jump.generatedLabel)
jump.identifier!=null -> jump.identifier.accept(this) jump.identifier!=null -> jump.identifier.accept(this)
} }

View File

@ -527,7 +527,7 @@ class PostIncrDecr(var target: AssignTarget, val operator: String, override val
override fun toString() = "PostIncrDecr(op: $operator, target: $target, pos=$position)" override fun toString() = "PostIncrDecr(op: $operator, target: $target, pos=$position)"
} }
class Jump(val address: UInt?, class Jump(var address: UInt?,
val identifier: IdentifierReference?, val identifier: IdentifierReference?,
val generatedLabel: String?, // can be used in code generation scenarios val generatedLabel: String?, // can be used in code generation scenarios
override val position: Position) : Statement() { override val position: Position) : Statement() {
@ -538,7 +538,13 @@ class Jump(val address: UInt?,
identifier?.linkParents(this) 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 copy() = Jump(address, identifier?.copy(), generatedLabel, position)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent) override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)

View File

@ -784,10 +784,15 @@ of a label or subroutine::
goto $c000 ; address goto $c000 ; address
goto name ; label or subroutine 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 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). 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 Conditional execution
^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^

View File

@ -3,7 +3,6 @@ TODO
For next release 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) - 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! - is * lower prio than bitwise & ? fix prios if so!

View File

@ -3,16 +3,20 @@
main { main {
sub start() { sub start() {
cx16.mouse_config(1, 0) txt.print("yo\n")
uword jumps = $4000
if_cc
goto jumps
repeat { goto jumps
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()
} }
} }
test $4000 {
%option force_output
jumper:
%asm {{
rts
}}
} }