mirror of
https://github.com/irmen/prog8.git
synced 2026-10-22 05:02:58 +00:00
some romable tests, fix romable callfar crash and update stale VM label address test
This commit is contained in:
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 {
|
||||
|
||||
@@ -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") {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user