diff --git a/compiler/test/TestCompilerOnExamples.kt b/compiler/test/TestCompilerOnExamples.kt index 6814e8f40..5c6deae73 100644 --- a/compiler/test/TestCompilerOnExamples.kt +++ b/compiler/test/TestCompilerOnExamples.kt @@ -3,18 +3,16 @@ package prog8tests import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldNotBe import prog8.code.core.ICompilationTarget -import prog8.code.target.C64Target -import prog8.code.target.Cx16Target +import prog8.code.target.* import prog8.compiler.CompilationResult import prog8.compiler.CompilerArguments import prog8.compiler.compileProgram -import prog8tests.helpers.assumeDirectory -import prog8tests.helpers.cartesianProduct -import prog8tests.helpers.outputDir -import prog8tests.helpers.workingDir +import prog8tests.helpers.* +import prog8tests.helpers.compileText import java.nio.file.Path import kotlin.io.path.absolute import kotlin.io.path.exists +import kotlin.io.path.readText /** @@ -47,8 +45,11 @@ private fun compileTheThing(filepath: Path, optimize: Boolean, target: ICompilat private fun prepareTestFiles(source: String, optimize: Boolean, target: ICompilationTarget): Pair { val searchIn = mutableListOf(examplesDir) - if (target is Cx16Target) { - searchIn.add(0, assumeDirectory(examplesDir, "cx16")) + when (target) { + is Cx16Target -> searchIn.add(0, assumeDirectory(examplesDir, "cx16")) + is VMTarget -> searchIn.add(0, assumeDirectory(examplesDir, "vm")) + is C128Target -> searchIn.add(0, assumeDirectory(examplesDir, "c128")) + is AtariTarget -> searchIn.add(0, assumeDirectory(examplesDir, "atari")) } val filepath = searchIn.asSequence() .map { it.resolve("$source.p8") } @@ -168,3 +169,24 @@ class TestCompilerOnExamplesBothC64andCx16: FunSpec({ } } }) + +class TestCompilerOnExamplesVirtual: FunSpec({ + + val onlyVirtual = listOf( + "bouncegfx", + "bsieve", + "pixelshader", + "sincos", + "textelite" + ) + + onlyVirtual.forEach { + val target = VMTarget() + val (displayName, filepath) = prepareTestFiles(it, false, target) + test(displayName) { + val src = filepath.readText() + compileText(target, false, src, writeAssembly = true, keepIR=false) shouldNotBe null + compileText(target, false, src, writeAssembly = true, keepIR=true) shouldNotBe null + } + } +}) diff --git a/compiler/test/vm/TestCompilerVirtual.kt b/compiler/test/vm/TestCompilerVirtual.kt index ccf7c449d..8cb9f9de7 100644 --- a/compiler/test/vm/TestCompilerVirtual.kt +++ b/compiler/test/vm/TestCompilerVirtual.kt @@ -28,6 +28,9 @@ main { val result = compileText(target, true, src, writeAssembly = true, keepIR=true)!! val virtfile = result.compilationOptions.outputDir.resolve(result.program.name + ".p8virt") VmRunner().runProgram(virtfile.readText()) + val result2 = compileText(target, true, src, writeAssembly = true, keepIR=false)!! + val virtfile2 = result2.compilationOptions.outputDir.resolve(result2.program.name + ".p8virt") + VmRunner().runProgram(virtfile2.readText()) } test("compile virtual: array with pointers") { @@ -50,6 +53,9 @@ main { val result = compileText(target, true, src, writeAssembly = true, keepIR=true)!! val virtfile = result.compilationOptions.outputDir.resolve(result.program.name + ".p8virt") VmRunner().runProgram(virtfile.readText()) + val result2 = compileText(target, true, src, writeAssembly = true, keepIR=false)!! + val virtfile2 = result2.compilationOptions.outputDir.resolve(result2.program.name + ".p8virt") + VmRunner().runProgram(virtfile2.readText()) } test("compile virtual: str args and return type") { @@ -68,6 +74,9 @@ main { val result = compileText(target, true, src, writeAssembly = true, keepIR=true)!! val virtfile = result.compilationOptions.outputDir.resolve(result.program.name + ".p8virt") VmRunner().runProgram(virtfile.readText()) + val result2 = compileText(target, true, src, writeAssembly = true, keepIR=false)!! + val virtfile2 = result2.compilationOptions.outputDir.resolve(result2.program.name + ".p8virt") + VmRunner().runProgram(virtfile2.readText()) } test("compile virtual: nested labels") { @@ -108,6 +117,9 @@ mylabel_inside: val result = compileText(target, true, src, writeAssembly = true, keepIR=true)!! val virtfile = result.compilationOptions.outputDir.resolve(result.program.name + ".p8virt") VmRunner().runProgram(virtfile.readText()) + val result2 = compileText(target, true, src, writeAssembly = true, keepIR=false)!! + val virtfile2 = result2.compilationOptions.outputDir.resolve(result2.program.name + ".p8virt") + VmRunner().runProgram(virtfile2.readText()) } test("case sensitive symbols") { @@ -136,5 +148,12 @@ skipLABEL: vm.memory.getUB(0) shouldBe 42u vm.memory.getUB(3) shouldBe 66u } + + val result2 = compileText(target, true, src, writeAssembly = true, keepIR=false)!! + val virtfile2 = result2.compilationOptions.outputDir.resolve(result2.program.name + ".p8virt") + VmRunner().runAndTestProgram(virtfile2.readText()) { vm -> + vm.memory.getUB(0) shouldBe 42u + vm.memory.getUB(3) shouldBe 66u + } } }) \ No newline at end of file diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 9cdc963af..83d4c0e73 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -3,9 +3,8 @@ TODO For next release ^^^^^^^^^^^^^^^^ -- fix vm symbol case insensitivity (TestCompilerVirtual) -- fix vm crash in TestCompilerVirtual: array with pointers -- fix vm crash (parseValue error) in examples/vm/textelite.p8 (Assembler, caused by above?) +- fix vm assembler bug in TestCompilerVirtual when no IR code is written (IRWriter.writeVariableAllocations TODO) +- fix examples/vm/textelite.p8 having wrong randomization? (starts with wrong planet) ... @@ -21,6 +20,7 @@ Future Things and Ideas ^^^^^^^^^^^^^^^^^^^^^^^ Compiler: +- vm: get rid of p8virt format and Assembler, run p8ir directly - vm/ir: put variables and arrays in BSS section (unless -noreinit is specified) - vm: Jumps go to a code block rather than a specific address(label) -> also helps future dead code elimination? - vm: the above means that every label introduces a new code block. This eliminates the use of actual labels altogether. diff --git a/examples/vm/textelite.p8 b/examples/vm/textelite.p8 index 13e25d3df..6110597cd 100644 --- a/examples/vm/textelite.p8 +++ b/examples/vm/textelite.p8 @@ -14,7 +14,7 @@ main { sub start() { txt.lowercase() - txt.print("\u000c\n --- TextElite v1.2 ---\n") + txt.print("--- TextElite v1.2 ---\n") txt.print("VirtualMachine edition: no disk saving, bad text layout!\n") galaxy.travel_to(1, numforLave) diff --git a/intermediate/src/prog8/intermediate/IRFileWriter.kt b/intermediate/src/prog8/intermediate/IRFileWriter.kt index db8d12673..2c66d93bd 100644 --- a/intermediate/src/prog8/intermediate/IRFileWriter.kt +++ b/intermediate/src/prog8/intermediate/IRFileWriter.kt @@ -122,6 +122,7 @@ class IRFileWriter(private val irProgram: IRProgram) { if(it.number!=null) it.number!!.toInt().toString() else { + // TODO : don't do a lookup; addressOf should be scoped properly already! val target = variable.lookup(it.addressOf!!) ?: throw InternalCompilerException("symbol not found: ${it.addressOf} in ${variable.scopedName}") "&${target.scopedName.joinToString(".")}" diff --git a/virtualmachine/src/prog8/vm/Assembler.kt b/virtualmachine/src/prog8/vm/Assembler.kt index f362b7269..0851db810 100644 --- a/virtualmachine/src/prog8/vm/Assembler.kt +++ b/virtualmachine/src/prog8/vm/Assembler.kt @@ -180,6 +180,18 @@ class Assembler { var fpReg3: Int? = null var value: Float? = null var operand: String? + + fun parseValueOrPlaceholder(operand: String, pc: Int, rest: String, restIndex: Int, opcode: Opcode): Float { + return if(operand.startsWith('_')) { + placeholders[pc] = rest.split(",")[restIndex].trim().drop(1) + 0f + } else if(operand[0].isLetter()) { + placeholders[pc] = rest.split(",")[restIndex].trim() + 0f + } else + parseValue(opcode, operand, pc) + } + if(operands.isNotEmpty() && operands[0].isNotEmpty()) { operand = operands.removeFirst().trim() if(operand[0]=='r') @@ -187,13 +199,7 @@ class Assembler { else if(operand[0]=='f' && operand[1]=='r') fpReg1 = operand.substring(2).toInt() else { - value = if(operand.startsWith('_')) { - // it's a label, keep the original case! - val labelname = rest.split(",").first().trim() - parseValue(opcode, labelname, program.size) - } else { - parseValue(opcode, operand, program.size) - } + value = parseValueOrPlaceholder(operand, program.size, rest, 0, opcode) operands.clear() } if(operands.isNotEmpty()) { @@ -203,7 +209,7 @@ class Assembler { else if(operand[0]=='f' && operand[1]=='r') fpReg2 = operand.substring(2).toInt() else { - value = parseValue(opcode, operand, program.size) + value = parseValueOrPlaceholder(operand, program.size, rest, 1, opcode) operands.clear() } if(operands.isNotEmpty()) { @@ -213,13 +219,11 @@ class Assembler { else if(operand[0]=='f' && operand[1]=='r') fpReg3 = operand.substring(2).toInt() else { - val symbol=rest.split(',').last() - value = parseValue(opcode, symbol, program.size) + value = parseValueOrPlaceholder(operand, program.size, rest, 2, opcode) operands.clear() } if(operands.isNotEmpty()) { - val symbol=rest.split(',').last() - value = parseValue(opcode, symbol, program.size) + TODO("placeholder symbol? $operands rest=$rest'") operands.clear() } } @@ -319,26 +323,18 @@ class Assembler { } private fun parseValue(opcode: Opcode, value: String, pc: Int): Float { - if(value.startsWith("-")) { - return -parseValue(opcode, value.substring(1), pc) - } - if(value.startsWith('$')) - return value.substring(1).toInt(16).toFloat() - if(value.startsWith('%')) - return value.substring(1).toInt(2).toFloat() - if(value.startsWith("0x")) - return value.substring(2).toInt(16).toFloat() - if(value.startsWith('_')) { - if(opcode !in OpcodesForCpuRegisters) - placeholders[pc] = value.substring(1) - return 0f - } - if(value[0].isLetter()) { - if(opcode !in OpcodesForCpuRegisters) - placeholders[pc] = value - return 0f - } - return value.toFloat() + return if(value.startsWith("-")) + -parseValue(opcode, value.substring(1), pc) + else if(value.startsWith('$')) + value.substring(1).toInt(16).toFloat() + else if(value.startsWith('%')) + value.substring(1).toInt(2).toFloat() + else if(value.startsWith("0x")) + value.substring(2).toInt(16).toFloat() + else if(value.startsWith('_') || value[0].isLetter()) + throw IllegalArgumentException("attempt to parse non-numeric value $value") + else + value.toFloat() } private fun convertType(typestr: String): VmDataType? { diff --git a/virtualmachine/src/prog8/vm/SysCalls.kt b/virtualmachine/src/prog8/vm/SysCalls.kt index c0cf2b567..f2199824e 100644 --- a/virtualmachine/src/prog8/vm/SysCalls.kt +++ b/virtualmachine/src/prog8/vm/SysCalls.kt @@ -1,6 +1,7 @@ package prog8.vm import prog8.code.core.AssemblyError +import java.lang.NumberFormatException import kotlin.math.min /* @@ -253,13 +254,23 @@ object SysCalls { } Syscall.STR_TO_UWORD -> { val stringAddr = vm.registers.getUW(0) - val string = vm.memory.getString(stringAddr.toInt()) - vm.registers.setUW(0, string.toUShort()) + val string = vm.memory.getString(stringAddr.toInt()).takeWhile { it.isDigit() } + val value = try { + string.toUShort() + } catch(_: NumberFormatException) { + 0u + } + vm.registers.setUW(0, value) } Syscall.STR_TO_WORD -> { val stringAddr = vm.registers.getUW(0) - val string = vm.memory.getString(stringAddr.toInt()) - vm.registers.setSW(0, string.toShort()) + val string = vm.memory.getString(stringAddr.toInt()).takeWhile { it.isDigit() } + val value = try { + string.toShort() + } catch(_: NumberFormatException) { + 0 + } + vm.registers.setSW(0, value) } Syscall.COMPARE_STRINGS -> { val firstAddr = vm.registers.getUW(0)