some romable tests, fix romable callfar crash and update stale VM label address test

This commit is contained in:
Irmen de Jong committed 2026-09-25 22:40:36 +02:00
1 parent d703d678b4
commit 9fce07565c
6 files changed
+194 -9

No files matched your search

@@ -426,7 +426,7 @@ import prog8.codegen.cpu6502.assignment.*
.byte $constBank""")
} else {
if(asmgen.options.romable)
TODO("non-const callfar (jsrfar) yet that's usable in ROM ${fcall.position}")
asmgen.errors.err("non-const callfar (jsrfar) has no ROMable code generation", fcall.position)
// self-modifying code: set jsrfar arguments
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A) // bank
asmgen.out(" sta (++)+0")
@@ -483,7 +483,7 @@ import prog8.codegen.cpu6502.assignment.*
.byte $constBank""")
} else {
if(asmgen.options.romable)
TODO("non-const callfar2 (jsrfar) yet that's usable in ROM ${fcall.position}")
asmgen.errors.err("non-const callfar2 (jsrfar) has no ROMable code generation", fcall.position)
// self-modifying code: set jsrfar arguments
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A) // bank
asmgen.out(" sta (++)+0")
@@ -2204,6 +2204,11 @@ internal class AstChecker(private val program: Program,
if(target.name=="callfar" || target.name=="callfar2") {
if(!options.compTarget.supportsBankedCalls) {
errors.err("banked subroutine call is not supported on the selected compilation target", position)
} else if(options.romable && options.compTarget.cpu.is6502 &&
(args[0].constValue(program)==null || args[1].constValue(program)==null)) {
// the 6502 codegen patches the jsrfar bank/address operands with self-modifying
// code unless both are compile-time constants, which is incompatible with romable mode
errors.err("${target.name} with a non-constant bank or address has no ROMable code generation", position)
}
}
}
@@ -4,6 +4,7 @@ import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.FunSpec
import io.kotest.engine.spec.tempdir
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.string.shouldContain
import prog8.code.core.AssemblyError
import prog8.code.target.Cx16Target
@@ -45,6 +46,116 @@ class TestVariableStepForLoops6502: FunSpec({
machine.assertMemory(0x0200, 18)
}
test("romable variable-step byte loop") {
val errors = ErrorReporterForTests(throwExceptionAtReportIfErrors = false, keepMessagesAfterReporting = true)
val result = compileText(Cx16Target(), false, $$"""
%option no_sysinit
%option romable
%launcher none
%address $1000
main {
sub start() {
ubyte @shared step = 3
ubyte i
ubyte sum
for i in 0 to 10 step step {
sum += i
}
}
}
""", outputDir, errors = errors, varsAddress = 0x8000u, assemble = true)
errors.errors shouldBe emptyList()
errors.printedErrors shouldBe emptyList()
errors.warnings.none { it.contains("forloop") } shouldBe true
result shouldNotBe null
}
test("romable variable-step word loop") {
val errors = ErrorReporterForTests(throwExceptionAtReportIfErrors = false, keepMessagesAfterReporting = true)
val result = compileText(Cx16Target(), false, $$"""
%option no_sysinit
%option romable
%launcher none
%address $1000
main {
sub start() {
uword @shared step = 100
uword i
uword sum
for i in 0 to 500 step step {
sum += i
}
}
}
""", outputDir, errors = errors, varsAddress = 0x8000u, assemble = true)
errors.errors shouldBe emptyList()
errors.printedErrors shouldBe emptyList()
errors.warnings.none { it.contains("forloop") } shouldBe true
result shouldNotBe null
}
test("romable constant-step byte loop is rejected") {
val source = $$"""
%option no_sysinit
%option romable
%launcher none
%address $1000
main {
sub start() {
ubyte @shared from
ubyte @shared upper
from = 0
upper = 10
ubyte i
ubyte sum
for i in from to upper step 3 {
sum += i
}
}
}
""".trimIndent()
val errors = ErrorReporterForTests(throwExceptionAtReportIfErrors = false, keepMessagesAfterReporting = true)
val result = compileText(Cx16Target(), false, source, outputDir, errors = errors, varsAddress = 0x8000u, assemble = true)
errors.errors shouldBe emptyList()
errors.printedErrors shouldBe emptyList()
errors.warnings.joinToString("\n") shouldContain "self-modifying code (forloop over bytes range)"
result shouldBe null
}
test("romable constant-step word loop is rejected") {
val source = $$"""
%option no_sysinit
%option romable
%launcher none
%address $1000
main {
sub start() {
uword @shared from
uword @shared upper
from = 0
upper = 500
uword i
uword sum
for i in from to upper step 100 {
sum += i
}
}
}
""".trimIndent()
val errors = ErrorReporterForTests(throwExceptionAtReportIfErrors = false, keepMessagesAfterReporting = true)
val result = compileText(Cx16Target(), false, source, outputDir, errors = errors, varsAddress = 0x8000u, assemble = true)
errors.errors shouldBe emptyList()
errors.printedErrors shouldBe emptyList()
errors.warnings.joinToString("\n") shouldContain "self-modifying code (forloop over word range)"
result shouldBe null
}
test("signed negative step with unsigned byte descending") {
val machine = run($$"""
%option no_sysinit
@@ -179,6 +179,57 @@ class TestBankedSubroutines : FunSpec({
errors.errors.any { it.contains("variable bank extsub has no romable code-generation") } shouldBe true
}
test("callfar with non-constant bank or address is rejected in romable mode") {
val text = $$"""
%option romable
main {
ubyte @shared bank
uword @shared addr
sub start() {
bank = 1
addr = $ffd2
uword r = callfar(bank, addr, 0)
}
}
""".trimIndent()
val errors = ErrorReporterForTests()
compileText(Cx16Target(), false, text, outputDir, errors = errors, writeAssembly = false, varshigh = 1) shouldBe null
errors.errors.any { it.contains("callfar with a non-constant bank or address has no ROMable code generation") } shouldBe true
}
test("callfar2 with non-constant bank or address is rejected in romable mode") {
val text = $$"""
%option romable
main {
ubyte @shared bank
uword @shared addr
sub start() {
bank = 1
addr = $ffd2
uword r = callfar2(bank, addr, 1, 2, 3, true)
}
}
""".trimIndent()
val errors = ErrorReporterForTests()
compileText(Cx16Target(), false, text, outputDir, errors = errors, writeAssembly = false, varshigh = 1) shouldBe null
errors.errors.any { it.contains("callfar2 with a non-constant bank or address has no ROMable code generation") } shouldBe true
}
test("callfar with constant bank and address is allowed in romable mode") {
val text = $$"""
%option romable
main {
sub start() {
uword r = callfar(1, $ffd2, 0)
uword s = callfar2(1, $ffd2, 1, 2, 3, true)
}
}
""".trimIndent()
val errors = ErrorReporterForTests(throwExceptionAtReportIfErrors = false, keepMessagesAfterReporting = true)
compileText(Cx16Target(), false, text, outputDir, errors = errors, writeAssembly = false, varshigh = 1).shouldNotBeNull()
errors.errors shouldBe emptyList()
}
test(".bankedcalls file creation and content (6502)") {
val text = $$"""
main {
+23 -4
View File
@@ -13,6 +13,7 @@ import io.kotest.matchers.string.shouldContain
import io.kotest.matchers.string.shouldNotContain
import prog8.ast.expressions.MemorySlabRef
import prog8.ast.statements.Assignment
import prog8.code.SymbolNames
import prog8.code.target.C64Target
import prog8.code.target.Cx16Target
import prog8.code.target.VMTarget
@@ -375,7 +376,7 @@ main {
VmRunner().runProgram(irSrc, false)
}
test("addresses from labels/subroutines not yet supported in VM") {
test("addresses from labels/subroutines are loaded as artificial label addresses") {
val src = """
main {
sub start() {
@@ -393,10 +394,28 @@ mylabel:
"""
val result = compileText(VMTarget(), false, src, outputDir, writeAssembly = true)!!
val virtfile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".p8ir")
val exc = shouldThrow<Exception> {
VmRunner().runProgram(virtfile.readText(), false)
val irSrc = virtfile.readText()
val irProgram = IRFileReader().read(irSrc)
irProgram.st.stripAllPrefixes()
val allocations = VmVariableAllocator(irProgram.st, irProgram.encoding, irProgram.options.compTarget).allocations
VmRunner().runAndTestProgram(irSrc) { vm ->
val startAddr = vm.artificialLabelAddresses.entries.single { SymbolNames.stripPrefixes(it.value.label ?: "") == "main.start" }.key
val mylabelAddr = vm.artificialLabelAddresses.entries.single { SymbolNames.stripPrefixes(it.value.label ?: "") == "main.start.mylabel" }.key
startAddr shouldNotBe 0u
mylabelAddr shouldNotBe 0u
mylabelAddr shouldNotBe startAddr
val varAddr = allocations["main.start.variable"]!!
vm.memory.getUL(allocations["main.start.pointer1"]!!) shouldBe startAddr
vm.memory.getUL(allocations["main.start.pointer2"]!!) shouldBe startAddr
vm.memory.getUL(allocations["main.start.pointer3"]!!) shouldBe mylabelAddr
vm.memory.getUL(allocations["main.start.pointer4"]!!) shouldBe mylabelAddr
val ptrsAddr = allocations["main.start.ptrs"]!!
vm.memory.getUL(ptrsAddr) shouldBe varAddr
vm.memory.getUL(ptrsAddr + 4u) shouldBe startAddr
vm.memory.getUL(ptrsAddr + 8u) shouldBe startAddr
vm.memory.getUL(ptrsAddr + 12u) shouldBe mylabelAddr
vm.memory.getUL(ptrsAddr + 16u) shouldBe mylabelAddr
}
exc.message shouldContain("cannot yet load a label address as a value")
}
test("address of a block produces LOAD with block label in IR") {
+2 -3
View File
@@ -35,10 +35,9 @@ Romable (%option romable)
- ``forOverBytesRangeStepGreaterOne`` (byte, abs(step)>=2)
- ``forOverWordsRangeStepGreaterOne`` (word, step>=2)
- ``forOverWordsRangeStepGreaterOneDescending`` (word, step<=-2)
The new variable-step byte/word paths are already ROM-safe because they use temporary variables rather than self-modifying code. Add more ROMable tests for both behaviors as needed.
The new variable-step byte/word paths are already ROM-safe because they use temporary variables rather than self-modifying code.
- BuiltinFunctionsAsmGen: ``callfar`` / ``callfar2`` with non-const bank/addr. Uses self-modifying ``sta +0`` / ``sty +1`` to patch JSRFAR operands. Needs a RAM trampoline approach (copy stub with variable args into RAM, JSR to that).
- FunctionCallAsmGen: ``extsub`` with variable bank. Same JSRFAR operand patching issue. Needs RAM trampoline.
- Add more test coverage for the romable option.
- FunctionCallAsmGen: subroutine with a variable bank (``@bank``). Same JSRFAR operand patching issue. In romable mode this is already rejected up front by ``AstChecker`` ("variable bank extsub has no romable code-generation for the required jsrfar call"), so the codegen ``TODO`` is only a safety net; what's actually missing to make the feature work is the RAM trampoline.
IR/VM