From 17bedac96cc98e6e054b8ace589a0f3f0d9bec19 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Sat, 3 Dec 2022 17:46:06 +0100 Subject: [PATCH] vm: memory is randomized on start instead of 0. P8ir file now has BSS segment. Vm clears BSS vars to 0. --- codeCore/src/prog8/code/SymbolTable.kt | 46 ------------ compiler/test/TestLaunchEmu.kt | 2 + docs/source/todo.rst | 22 +++--- examples/test.p8 | 75 ++++++++++++------- .../src/prog8/intermediate/IRFileReader.kt | 33 +++++++- .../src/prog8/intermediate/IRFileWriter.kt | 15 +++- intermediate/test/TestIRFileInOut.kt | 14 +++- virtualmachine/src/prog8/vm/Memory.kt | 6 +- .../src/prog8/vm/VmProgramLoader.kt | 24 ++++++ virtualmachine/test/TestMemory.kt | 5 +- virtualmachine/test/TestVm.kt | 4 + 11 files changed, 149 insertions(+), 97 deletions(-) diff --git a/codeCore/src/prog8/code/SymbolTable.kt b/codeCore/src/prog8/code/SymbolTable.kt index 19e00291d..1abc222bb 100644 --- a/codeCore/src/prog8/code/SymbolTable.kt +++ b/codeCore/src/prog8/code/SymbolTable.kt @@ -8,10 +8,6 @@ import prog8.code.core.* * (blocks, subroutines, variables (all types), memoryslabs, and labels). */ class SymbolTable : StNode("", StNodeType.GLOBAL, Position.DUMMY) { - fun print() = printIndented(0) - - override fun printProperties() { } - /** * The table as a flat mapping of scoped names to the StNode. * This gives the fastest lookup possible (no need to traverse tree nodes) @@ -138,29 +134,6 @@ open class StNode(val name: String, } } - fun printIndented(indent: Int) { - print(" ".repeat(indent)) - when(type) { - StNodeType.GLOBAL -> print("SYMBOL-TABLE:") - StNodeType.BLOCK -> print("(B) ") - StNodeType.SUBROUTINE -> print("(S) ") - StNodeType.LABEL -> print("(L) ") - StNodeType.STATICVAR -> print("(V) ") - StNodeType.MEMVAR -> print("(M) ") - StNodeType.MEMORYSLAB -> print("(MS) ") - StNodeType.CONSTANT -> print("(C) ") - StNodeType.BUILTINFUNC -> print("(F) ") - StNodeType.ROMSUB -> print("(R) ") - } - printProperties() - println() - children.forEach { (_, node) -> node.printIndented(indent+1) } - } - - open fun printProperties() { - print("$name ") - } - fun add(child: StNode) { children[child.name] = child child.parent = this @@ -201,18 +174,11 @@ class StStaticVariable(name: String, require(length == onetimeInitializationStringValue.first.length+1) } } - - override fun printProperties() { - print("$name dt=$dt zpw=$zpwish bss=$bss") - } } class StConstant(name: String, val dt: DataType, val value: Double, position: Position) : StNode(name, StNodeType.CONSTANT, position) { - override fun printProperties() { - print("$name dt=$dt value=$value") - } } @@ -222,9 +188,6 @@ class StMemVar(name: String, val length: Int?, // for arrays: the number of elements, for strings: number of characters *including* the terminating 0-byte position: Position) : StNode(name, StNodeType.MEMVAR, position) { - override fun printProperties() { - print("$name dt=$dt address=${address.toHex()}") - } } class StMemorySlab( @@ -234,16 +197,10 @@ class StMemorySlab( position: Position ): StNode(name, StNodeType.MEMORYSLAB, position) { - override fun printProperties() { - print("$name size=$size align=$align") - } } class StSub(name: String, val parameters: List, val returnType: DataType?, position: Position) : StNode(name, StNodeType.SUBROUTINE, position) { - override fun printProperties() { - print(name) - } } @@ -253,9 +210,6 @@ class StRomSub(name: String, val returns: List, position: Position) : StNode(name, StNodeType.ROMSUB, position) { - override fun printProperties() { - print("$name address=${address.toHex()}") - } } diff --git a/compiler/test/TestLaunchEmu.kt b/compiler/test/TestLaunchEmu.kt index 365b7e7b6..bfea754d6 100644 --- a/compiler/test/TestLaunchEmu.kt +++ b/compiler/test/TestLaunchEmu.kt @@ -19,6 +19,8 @@ class TestLaunchEmu: FunSpec({ + + diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 3c44eae2c..b929a10c8 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -3,15 +3,10 @@ TODO For next release ^^^^^^^^^^^^^^^^ -- Think this through/ ask opinions: add a mechanism to allocate variables into golden ram (see GoldenRam class) - - block "golden" treated specially: every var in here will be allocated in the Golden ram area - - that block can only contain variables. - - the variables can NOT have initialization values, they will all be set to zero on startup (simple memset) - - just initialize them yourself in start() if you need a non-zero value - - OR.... do all this automatically if 'golden' is enabled as a compiler option? So compiler allocates in ZP first, then Golden Ram, then regular ram - - (need separate step in codegen and IR to write the "golden" variables) - - +- add more descriptions to require() calls (at least line number?) +- 6502 codegen: create BSS section in output assembly code and put StStaticVariables in there with bss=true. + Don't forget to add init code to zero out everything that was put in bss. If array in bss->only zero ONCE if possible. + Note that bss can still contain variables that have @zp tag and those are already dealt with differently - regression test the various projects before release ... @@ -28,7 +23,6 @@ Future Things and Ideas ^^^^^^^^^^^^^^^^^^^^^^^ Compiler: -- create BSS section in output program and put StStaticVariables in there with bss=true. Don't forget to add init code to zero out everything that was put in bss. If array in bss->only zero ONCE! So requires self-modifying code - ir: mechanism to determine for chunks which registers are getting input values from "outside" - ir: mechanism to determine for chunks which registers are passing values out? (i.e. are used again in another chunk) - ir: peephole opt: renumber registers in chunks to start with 1 again every time (but keep entry values in mind!) @@ -48,6 +42,14 @@ Compiler: Once new codegen is written that is based on the IR, this point is moot anyway as that will have its own dead code removal. - Zig-like try-based error handling where the V flag could indicate error condition? and/or BRK to jump into monitor on failure? (has to set BRK vector for that) - add special (u)word array type (or modifier?) that puts the array into memory as 2 separate byte-arrays 1 for LSB 1 for MSB -> allows for word arrays of length 256 and faster indexing +- Add a mechanism to allocate variables into golden ram (or segments really) (see GoldenRam class) + - block "golden" treated specially: every var in here will be allocated in the Golden ram area + - that block can only contain variables. + - the variables can NOT have initialization values, they will all be set to zero on startup (simple memset) + - just initialize them yourself in start() if you need a non-zero value + - OR.... do all this automatically if 'golden' is enabled as a compiler option? So compiler allocates in ZP first, then Golden Ram, then regular ram + - OR.... make all this more generic and use some %segment option to create real segments for 64tass? + - (need separate step in codegen and IR to write the "golden" variables) Libraries: diff --git a/examples/test.p8 b/examples/test.p8 index 0aaed45f7..34653ead5 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -1,37 +1,54 @@ %import textio -%import cx16logo - -nop { - sub lda(ubyte sec) -> ubyte { -asl: - ubyte brk = sec - sec++ - brk += sec - return brk - } -} +%zeropage basicsafe +%option no_sysinit main { - sub ffalse(ubyte arg) -> ubyte { - arg++ - return 0 - } - sub ftrue(ubyte arg) -> ubyte { - arg++ - return 128 - } - sub start() { - ubyte col = 10 - ubyte row = 20 - cx16logo.logo_at(col, row) - txt.setcc(10, 10, 2, 3) - txt.print_ub(nop.lda(42)) - txt.nl() - txt.print_uw(nop.lda.asl) + ; TODO ALSO TEST AS GLOBALS + ubyte @requirezp zpvar = 10 + ubyte @zp zpvar2 = 20 + uword empty + ubyte[10] bssarray + uword[10] bsswordarray + ubyte[10] nonbssarray = 99 + str name="irmen" - void ffalse(99) - void ftrue(99) + txt.print("10 ") + txt.print_ub(zpvar) + txt.nl() + zpvar++ + + txt.print("20 ") + txt.print_ub(zpvar2) + txt.nl() + zpvar2++ + + txt.print("0 ") + txt.print_uw(empty) + txt.nl() + empty++ + + txt.print("0 ") + txt.print_ub(bssarray[1]) + txt.nl() + bssarray[1]++ + + txt.print("0 ") + txt.print_uw(bsswordarray[1]) + txt.nl() + bsswordarray[1]++ + + txt.print("99 ") + txt.print_ub(nonbssarray[1]) + txt.nl() + nonbssarray[1]++ + + txt.print("r ") + txt.chrout(name[1]) + txt.nl() + name[1] = (name[1] as ubyte +1) + + txt.print("try running again.\n") } } diff --git a/intermediate/src/prog8/intermediate/IRFileReader.kt b/intermediate/src/prog8/intermediate/IRFileReader.kt index feca9c63e..c2de307b5 100644 --- a/intermediate/src/prog8/intermediate/IRFileReader.kt +++ b/intermediate/src/prog8/intermediate/IRFileReader.kt @@ -44,6 +44,7 @@ class IRFileReader { val programName = start.attributes.asSequence().single { it.name.localPart == "NAME" }.value val options = parseOptions(reader) val asmsymbols = parseAsmSymbols(reader) + val bss = parseBss(reader) val variables = parseVariables(reader, options.dontReinitGlobals) val memorymapped = parseMemMapped(reader) val slabs = parseSlabs(reader) @@ -52,6 +53,7 @@ class IRFileReader { val st = IRSymbolTable(null) asmsymbols.forEach { (name, value) -> st.addAsmSymbol(name, value)} + bss.forEach { st.add(it) } variables.forEach { st.add(it) } memorymapped.forEach { st.add(it) } slabs.forEach { st.add(it) } @@ -147,6 +149,33 @@ class IRFileReader { } } + private fun parseBss(reader: XMLEventReader): List { + skipText(reader) + val start = reader.nextEvent().asStartElement() + require(start.name.localPart=="BSS") { "missing BSS" } + val text = readText(reader).trim() + require(reader.nextEvent().isEndElement) + + return if(text.isBlank()) + emptyList() + else { + val varPattern = Regex("(.+?)(\\[.+?\\])? (.+) (zp=(.+))?") + val bssVariables = mutableListOf() + text.lineSequence().forEach { line -> + // example: uword main.start.qq2 zp=DONTCARE + val match = varPattern.matchEntire(line) ?: throw IRParseException("invalid BSS $line") + val (type, arrayspec, name, _, zpwish) = match.destructured + if('.' !in name) + throw IRParseException("unscoped varname: $name") + val arraysize = if(arrayspec.isNotBlank()) arrayspec.substring(1, arrayspec.length-1).toInt() else null + val dt: DataType = parseDatatype(type, arraysize!=null) + val zp = if(zpwish.isBlank()) ZeropageWish.DONTCARE else ZeropageWish.valueOf(zpwish) + bssVariables.add(StStaticVariable(name, dt, true, null, null, null, arraysize, zp, Position.DUMMY)) + } + return bssVariables + } + } + private fun parseVariables(reader: XMLEventReader, dontReinitGlobals: Boolean): List { skipText(reader) val start = reader.nextEvent().asStartElement() @@ -165,6 +194,8 @@ class IRFileReader { // ubyte[6] main.start.namestring=105,114,109,101,110,0 val match = varPattern.matchEntire(line) ?: throw IRParseException("invalid VARIABLE $line") val (type, arrayspec, name, value, _, zpwish) = match.destructured + if('.' !in name) + throw IRParseException("unscoped varname: $name") val arraysize = if(arrayspec.isNotBlank()) arrayspec.substring(1, arrayspec.length-1).toInt() else null val dt: DataType = parseDatatype(type, arraysize!=null) val zp = if(zpwish.isBlank()) ZeropageWish.DONTCARE else ZeropageWish.valueOf(zpwish) @@ -199,7 +230,7 @@ class IRFileReader { DataType.STR -> throw IRParseException("STR should have been converted to byte array") else -> throw IRParseException("weird dt") } - + require(!bss) { "bss var should be in BSS section" } variables.add(StStaticVariable(name, dt, bss, initNumeric, null, initArray, arraysize, zp, Position.DUMMY)) } return variables diff --git a/intermediate/src/prog8/intermediate/IRFileWriter.kt b/intermediate/src/prog8/intermediate/IRFileWriter.kt index 5d0a0a60b..893e10093 100644 --- a/intermediate/src/prog8/intermediate/IRFileWriter.kt +++ b/intermediate/src/prog8/intermediate/IRFileWriter.kt @@ -19,7 +19,7 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) { out.write("\n") writeOptions() writeAsmSymbols() - writeVariableAllocations() + writeVariables() out.write("\n\n") if(!irProgram.options.dontReinitGlobals) { @@ -137,10 +137,17 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) { out.write("\n") } - private fun writeVariableAllocations() { + private fun writeVariables() { - out.write("\n\n") - for (variable in irProgram.st.allVariables()) { + out.write("\n\n") + for (variable in irProgram.st.allVariables().filter { it.bss }) { + val typeStr = getTypeString(variable) + // bss variables have no initialization value + out.write("$typeStr ${variable.name} zp=${variable.zpwish}\n") + } + + out.write("\n\n") + for (variable in irProgram.st.allVariables().filter { !it.bss }) { val typeStr = getTypeString(variable) val value: String = when(variable.dt) { DataType.FLOAT -> (variable.onetimeInitializationNumericValue ?: "").toString() diff --git a/intermediate/test/TestIRFileInOut.kt b/intermediate/test/TestIRFileInOut.kt index 512822b6e..8c06d17ec 100644 --- a/intermediate/test/TestIRFileInOut.kt +++ b/intermediate/test/TestIRFileInOut.kt @@ -49,15 +49,18 @@ output=PRG launcher=BASIC zeropage=KERNALSAFE loadAddress=0 -dontReinitGlobals=false +dontReinitGlobals=true evalStackBaseAddress=null + +uword sys.bssvar zp=DONTCARE + -uword sys.wait.jiffies= zp=DONTCARE +uword sys.wait.jiffies=10 zp=DONTCARE @@ -105,7 +108,10 @@ return tempfile.deleteExisting() program.name shouldBe "test-ir-reader" program.blocks.size shouldBe 2 - program.st.allVariables().count() shouldBe 1 - program.st.lookup("sys.wait.jiffies") shouldBe instanceOf() + program.st.allVariables().count() shouldBe 2 + val var1 = program.st.lookup("sys.wait.jiffies") as StStaticVariable + val var2 = program.st.lookup("sys.bssvar") as StStaticVariable + var1.bss shouldBe false + var2.bss shouldBe true } }) \ No newline at end of file diff --git a/virtualmachine/src/prog8/vm/Memory.kt b/virtualmachine/src/prog8/vm/Memory.kt index 74f7b4a89..f8410d518 100644 --- a/virtualmachine/src/prog8/vm/Memory.kt +++ b/virtualmachine/src/prog8/vm/Memory.kt @@ -1,10 +1,12 @@ package prog8.vm +import kotlin.random.Random + /** - * 64 Kb of random access memory. + * 64 Kb of random access memory. Initialized to random values. */ class Memory { - private val mem = Array(64 * 1024) { 0u } + private val mem = Array(64 * 1024) { Random.nextInt().toUByte() } fun reset() { mem.fill(0u) diff --git a/virtualmachine/src/prog8/vm/VmProgramLoader.kt b/virtualmachine/src/prog8/vm/VmProgramLoader.kt index d0390f49f..5bbbc0f80 100644 --- a/virtualmachine/src/prog8/vm/VmProgramLoader.kt +++ b/virtualmachine/src/prog8/vm/VmProgramLoader.kt @@ -1,5 +1,6 @@ package prog8.vm +import prog8.code.core.ArrayDatatypes import prog8.code.core.AssemblyError import prog8.code.core.DataType import prog8.intermediate.* @@ -183,6 +184,29 @@ class VmProgramLoader { ) { program.st.allVariables().forEach { variable -> var addr = allocations.allocations.getValue(variable.name) + + if(variable.bss && variable.dt in ArrayDatatypes) { + // zero out the array bss variable. + // non-array variables are reset using explicit assignment instructions. + repeat(variable.length!!) { + when(variable.dt) { + DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_BOOL -> { + memory.setUB(addr, 0u) + addr++ + } + DataType.ARRAY_UW, DataType.ARRAY_W -> { + memory.setUW(addr, 0u) + addr += 2 + } + DataType.ARRAY_F -> { + memory.setFloat(addr, 0.0f) + addr += program.options.compTarget.machine.FLOAT_MEM_SIZE + } + else -> throw IRParseException("invalid dt") + } + } + } + variable.onetimeInitializationNumericValue?.let { when(variable.dt) { DataType.UBYTE -> memory.setUB(addr, it.toInt().toUByte()) diff --git a/virtualmachine/test/TestMemory.kt b/virtualmachine/test/TestMemory.kt index 2f85a6b11..da7880b63 100644 --- a/virtualmachine/test/TestMemory.kt +++ b/virtualmachine/test/TestMemory.kt @@ -1,6 +1,7 @@ import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldBe +import io.kotest.matchers.shouldNotBe import prog8.vm.Memory @@ -46,7 +47,9 @@ class TestMemory: FunSpec({ test("32 bits float access") { val mem = Memory() - mem.getFloat(1000) shouldBe 0.0 + mem.getFloat(1000) shouldNotBe 0.0 + mem.setFloat(1000, 0.0f) + mem.getFloat(1000) shouldBe 0.0f mem.setFloat(1000, -9.876543f) mem.getFloat(1000) shouldBe -9.876543f } diff --git a/virtualmachine/test/TestVm.kt b/virtualmachine/test/TestVm.kt index 57543d344..1eed3887a 100644 --- a/virtualmachine/test/TestVm.kt +++ b/virtualmachine/test/TestVm.kt @@ -2,6 +2,7 @@ import io.kotest.assertions.throwables.shouldThrowWithMessage import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.collections.shouldBeEmpty import io.kotest.matchers.shouldBe +import io.kotest.matchers.shouldNotBe import prog8.code.core.* import prog8.code.target.VMTarget import prog8.intermediate.* @@ -51,6 +52,7 @@ class TestVm: FunSpec( { block += startSub program.addBlock(block) val vm = VirtualMachine(program) + vm.memory.setUW(1000, 0u) vm.memory.getUW(1000) shouldBe 0u vm.callStack.shouldBeEmpty() @@ -128,6 +130,8 @@ class TestVm: FunSpec( { + +