fix long argument @R0R1 register usage in regular subroutines

This commit is contained in:
Irmen de Jong
2026-02-15 14:49:15 +01:00
parent d1383813d2
commit 07be7f0154
9 changed files with 34 additions and 14 deletions
+2 -1
View File
@@ -440,12 +440,13 @@ enum class RegisterOrPair {
}
fun asScopedNameVirtualReg(type: DataType?): List<String> {
require(this in Cx16VirtualRegisters)
require(this in Cx16VirtualRegisters || this in CombinedLongRegisters)
val suffix = when(type?.base) {
BaseDataType.UBYTE, BaseDataType.BOOL -> "L"
BaseDataType.BYTE -> "sL"
BaseDataType.WORD -> "s"
BaseDataType.UWORD, BaseDataType.POINTER, null -> ""
BaseDataType.LONG -> "sl"
else -> throw IllegalArgumentException("invalid register param type for cx16 virtual reg")
}
return listOf("cx16", name.lowercase()+suffix)
@@ -268,8 +268,8 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
val reg = parameter.register
if(reg!=null) {
require(reg in Cx16VirtualRegisters) { "can only use R0-R15 'registers' here" }
val varName = "cx16.${reg.name.lowercase()}"
require(reg in Cx16VirtualRegisters || reg in CombinedLongRegisters) { "can only use R0-R15 'registers' here" }
val varName = reg.asScopedNameVirtualReg(value.type).joinToString(".")
asmgen.assignExpressionToVariable(value, varName, parameter.type)
} else {
val varName = asmgen.asmVariableName(sub.scopedName + "." + parameter.name)
@@ -924,7 +924,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
else
argRegisters.add(FunctionCallArgs.ArgumentSpec(parameter.name, null, FunctionCallArgs.RegSpec(paramDt, tr.resultReg, null)))
} else {
require(parameter.register in Cx16VirtualRegisters) { "can only use R0-R15 'registers' here" }
require(parameter.register in Cx16VirtualRegisters || parameter.register in CombinedLongRegisters) { "can only use R0-R15 'registers' here" }
val regname = parameter.register!!.asScopedNameVirtualReg(parameter.type).joinToString(".")
val assign = PtAssignment(fcall.position)
val target = PtAssignTarget(false, fcall.position)
@@ -1934,7 +1934,7 @@ class IRCodeGen(
result += IRSubroutine.IRParam(it.name, orig.dt)
} else {
val reg = it.register
require(reg in Cx16VirtualRegisters) { "can only use R0-R15 'registers' here" }
require(reg in Cx16VirtualRegisters || reg in CombinedLongRegisters) { "can only use R0-R15 'registers' here" }
val regname = it.register!!.asScopedNameVirtualReg(it.type).joinToString(".")
val targetVar = symbolTable.lookup(regname) as StMemVar
result += IRSubroutine.IRParam(regname, targetVar.dt)
@@ -247,11 +247,15 @@ class VarConstantValueTypeAdjuster(
dt.isLong -> {
val value = functionCallExpr.args[0].constValue(program)?.number
if(value!=null && value<0) {
errors.err("expected unsigned or float numeric argument", functionCallExpr.args[0].position)
errors.err("expected positive integer or float numeric argument", functionCallExpr.args[0].position)
return noModifications
}
"sqrt__long"
}
dt.isSigned -> {
errors.err("expected unsigned (positive) numeric argument", functionCallExpr.args[0].position)
return noModifications
}
else -> {
errors.err("expected numeric argument", functionCallExpr.args[0].position)
return noModifications
@@ -584,12 +584,13 @@ internal class AstChecker(private val program: Program,
// Instead, their reference (address) should be passed (as an UWORD).
for(p in subroutine.parameters) {
if (!subroutine.isAsmSubroutine && p.registerOrPair!=null) {
if (p.registerOrPair !in Cx16VirtualRegisters) errors.err("can only use R0-R15 as register param for normal subroutines", p.position)
if (p.registerOrPair !in Cx16VirtualRegisters && p.registerOrPair !in CombinedLongRegisters)
errors.err("can only use R0-R15 as register param for normal subroutines", p.position)
else {
if(!compilerOptions.ignoreFootguns)
errors.warn("\uD83D\uDCA3 footgun: reusing R0-R15 as parameters risks overwriting due to clobbering or no callstack", subroutine.position)
if(!p.type.isWordOrByteOrBool && !p.type.isPointer) {
errors.err("can only use register param when type is boolean, byte, word or pointer", p.position)
if(!p.type.isInteger && !p.type.isBool && !p.type.isPointer) {
errors.err("can only use register param when type is boolean, integer or pointer", p.position)
}
}
}
@@ -281,8 +281,8 @@ class AstPreprocessor(val program: Program,
registerParams
.filter { it.name !in namesInSub && it.name !in existingAliases }
.forEach {
if (it.registerOrPair in Cx16VirtualRegisters) {
if(it.type.isWordOrByteOrBool || it.type.isPointer) {
if (it.registerOrPair in Cx16VirtualRegisters || it.registerOrPair in CombinedLongRegisters) {
if(it.type.isInteger || it.type.isBool || it.type.isPointer) {
val mappedParamVar = VarDecl.fromParameter(it)
mods += IAstModification.InsertFirst(mappedParamVar, subroutine)
} else {
+16 -2
View File
@@ -337,6 +337,7 @@ main {
sub start() {
foo(42)
bar(9999,55)
bar2(9999999)
}
sub foo(ubyte arg @R2) {
@@ -346,14 +347,27 @@ main {
sub bar(uword arg @R0, ubyte arg2 @R1) {
arg += arg2
}
sub bar2(long arg @R14R15) {
arg++
}
}"""
val errors = ErrorReporterForTests(keepMessagesAfterReporting = true)
compileText(C64Target(), false, src, outputDir, writeAssembly = false, errors = errors) shouldNotBe null
compileText(C64Target(), false, src, outputDir, writeAssembly = true, errors = errors) shouldNotBe null
errors.errors.size shouldBe 0
errors.warnings.size shouldBe 2
errors.warnings.size shouldBe 3
errors.warnings[0] shouldContain "footgun"
errors.warnings[1] shouldContain "footgun"
errors.warnings[2] shouldContain "footgun"
errors.clear()
compileText(VMTarget(), false, src, outputDir, writeAssembly = true, errors = errors) shouldNotBe null
errors.errors.size shouldBe 0
errors.warnings.size shouldBe 3
errors.warnings[0] shouldContain "footgun"
errors.warnings[1] shouldContain "footgun"
errors.warnings[2] shouldContain "footgun"
}
test("reg params R0-R15 cannot be used for invalid types") {
+1 -1
View File
@@ -4,4 +4,4 @@ org.gradle.parallel=true
org.gradle.daemon=true
org.gradle.configuration-cache=false
kotlin.code.style=official
version=12.1.1
version=12.2-SNAPSHOT