vm: memory is randomized on start instead of 0. P8ir file now has BSS segment. Vm clears BSS vars to 0.

This commit is contained in:
Irmen de Jong 2022-12-03 17:46:06 +01:00
parent 4831fad27a
commit 17bedac96c
11 changed files with 149 additions and 97 deletions

View File

@ -8,10 +8,6 @@ import prog8.code.core.*
* (blocks, subroutines, variables (all types), memoryslabs, and labels). * (blocks, subroutines, variables (all types), memoryslabs, and labels).
*/ */
class SymbolTable : StNode("", StNodeType.GLOBAL, Position.DUMMY) { 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. * The table as a flat mapping of scoped names to the StNode.
* This gives the fastest lookup possible (no need to traverse tree nodes) * 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) { fun add(child: StNode) {
children[child.name] = child children[child.name] = child
child.parent = this child.parent = this
@ -201,18 +174,11 @@ class StStaticVariable(name: String,
require(length == onetimeInitializationStringValue.first.length+1) 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) : class StConstant(name: String, val dt: DataType, val value: Double, position: Position) :
StNode(name, StNodeType.CONSTANT, 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 val length: Int?, // for arrays: the number of elements, for strings: number of characters *including* the terminating 0-byte
position: Position) : position: Position) :
StNode(name, StNodeType.MEMVAR, position) { StNode(name, StNodeType.MEMVAR, position) {
override fun printProperties() {
print("$name dt=$dt address=${address.toHex()}")
}
} }
class StMemorySlab( class StMemorySlab(
@ -234,16 +197,10 @@ class StMemorySlab(
position: Position position: Position
): ):
StNode(name, StNodeType.MEMORYSLAB, position) { StNode(name, StNodeType.MEMORYSLAB, position) {
override fun printProperties() {
print("$name size=$size align=$align")
}
} }
class StSub(name: String, val parameters: List<StSubroutineParameter>, val returnType: DataType?, position: Position) : class StSub(name: String, val parameters: List<StSubroutineParameter>, val returnType: DataType?, position: Position) :
StNode(name, StNodeType.SUBROUTINE, position) { StNode(name, StNodeType.SUBROUTINE, position) {
override fun printProperties() {
print(name)
}
} }
@ -253,9 +210,6 @@ class StRomSub(name: String,
val returns: List<RegisterOrStatusflag>, val returns: List<RegisterOrStatusflag>,
position: Position) : position: Position) :
StNode(name, StNodeType.ROMSUB, position) { StNode(name, StNodeType.ROMSUB, position) {
override fun printProperties() {
print("$name address=${address.toHex()}")
}
} }

View File

@ -19,6 +19,8 @@ class TestLaunchEmu: FunSpec({
<ASMSYMBOLS> <ASMSYMBOLS>
</ASMSYMBOLS> </ASMSYMBOLS>
<BSS>
</BSS>
<VARIABLES> <VARIABLES>
</VARIABLES> </VARIABLES>

View File

@ -3,15 +3,10 @@ TODO
For next release For next release
^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^
- Think this through/ ask opinions: add a mechanism to allocate variables into golden ram (see GoldenRam class) - add more descriptions to require() calls (at least line number?)
- block "golden" treated specially: every var in here will be allocated in the Golden ram area - 6502 codegen: create BSS section in output assembly code and put StStaticVariables in there with bss=true.
- that block can only contain variables. 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.
- the variables can NOT have initialization values, they will all be set to zero on startup (simple memset) Note that bss can still contain variables that have @zp tag and those are already dealt with differently
- 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)
- regression test the various projects before release - regression test the various projects before release
... ...
@ -28,7 +23,6 @@ Future Things and Ideas
^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^
Compiler: 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 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: 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!) - 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. 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) - 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 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: Libraries:

View File

@ -1,37 +1,54 @@
%import textio %import textio
%import cx16logo %zeropage basicsafe
%option no_sysinit
nop {
sub lda(ubyte sec) -> ubyte {
asl:
ubyte brk = sec
sec++
brk += sec
return brk
}
}
main { main {
sub ffalse(ubyte arg) -> ubyte {
arg++
return 0
}
sub ftrue(ubyte arg) -> ubyte {
arg++
return 128
}
sub start() { sub start() {
ubyte col = 10 ; TODO ALSO TEST AS GLOBALS
ubyte row = 20 ubyte @requirezp zpvar = 10
cx16logo.logo_at(col, row) ubyte @zp zpvar2 = 20
txt.setcc(10, 10, 2, 3) uword empty
txt.print_ub(nop.lda(42)) ubyte[10] bssarray
txt.nl() uword[10] bsswordarray
txt.print_uw(nop.lda.asl) ubyte[10] nonbssarray = 99
str name="irmen"
void ffalse(99) txt.print("10 ")
void ftrue(99) 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")
} }
} }

View File

@ -44,6 +44,7 @@ class IRFileReader {
val programName = start.attributes.asSequence().single { it.name.localPart == "NAME" }.value val programName = start.attributes.asSequence().single { it.name.localPart == "NAME" }.value
val options = parseOptions(reader) val options = parseOptions(reader)
val asmsymbols = parseAsmSymbols(reader) val asmsymbols = parseAsmSymbols(reader)
val bss = parseBss(reader)
val variables = parseVariables(reader, options.dontReinitGlobals) val variables = parseVariables(reader, options.dontReinitGlobals)
val memorymapped = parseMemMapped(reader) val memorymapped = parseMemMapped(reader)
val slabs = parseSlabs(reader) val slabs = parseSlabs(reader)
@ -52,6 +53,7 @@ class IRFileReader {
val st = IRSymbolTable(null) val st = IRSymbolTable(null)
asmsymbols.forEach { (name, value) -> st.addAsmSymbol(name, value)} asmsymbols.forEach { (name, value) -> st.addAsmSymbol(name, value)}
bss.forEach { st.add(it) }
variables.forEach { st.add(it) } variables.forEach { st.add(it) }
memorymapped.forEach { st.add(it) } memorymapped.forEach { st.add(it) }
slabs.forEach { st.add(it) } slabs.forEach { st.add(it) }
@ -147,6 +149,33 @@ class IRFileReader {
} }
} }
private fun parseBss(reader: XMLEventReader): List<StStaticVariable> {
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<StStaticVariable>()
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<StStaticVariable> { private fun parseVariables(reader: XMLEventReader, dontReinitGlobals: Boolean): List<StStaticVariable> {
skipText(reader) skipText(reader)
val start = reader.nextEvent().asStartElement() val start = reader.nextEvent().asStartElement()
@ -165,6 +194,8 @@ class IRFileReader {
// ubyte[6] main.start.namestring=105,114,109,101,110,0 // ubyte[6] main.start.namestring=105,114,109,101,110,0
val match = varPattern.matchEntire(line) ?: throw IRParseException("invalid VARIABLE $line") val match = varPattern.matchEntire(line) ?: throw IRParseException("invalid VARIABLE $line")
val (type, arrayspec, name, value, _, zpwish) = match.destructured 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 arraysize = if(arrayspec.isNotBlank()) arrayspec.substring(1, arrayspec.length-1).toInt() else null
val dt: DataType = parseDatatype(type, arraysize!=null) val dt: DataType = parseDatatype(type, arraysize!=null)
val zp = if(zpwish.isBlank()) ZeropageWish.DONTCARE else ZeropageWish.valueOf(zpwish) 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") DataType.STR -> throw IRParseException("STR should have been converted to byte array")
else -> throw IRParseException("weird dt") 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)) variables.add(StStaticVariable(name, dt, bss, initNumeric, null, initArray, arraysize, zp, Position.DUMMY))
} }
return variables return variables

View File

@ -19,7 +19,7 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
out.write("<PROGRAM NAME=\"${irProgram.name}\">\n") out.write("<PROGRAM NAME=\"${irProgram.name}\">\n")
writeOptions() writeOptions()
writeAsmSymbols() writeAsmSymbols()
writeVariableAllocations() writeVariables()
out.write("\n<INITGLOBALS>\n") out.write("\n<INITGLOBALS>\n")
if(!irProgram.options.dontReinitGlobals) { if(!irProgram.options.dontReinitGlobals) {
@ -137,10 +137,17 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
out.write("</OPTIONS>\n") out.write("</OPTIONS>\n")
} }
private fun writeVariableAllocations() { private fun writeVariables() {
out.write("\n<VARIABLES>\n") out.write("\n<BSS>\n")
for (variable in irProgram.st.allVariables()) { 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("</BSS>\n<VARIABLES>\n")
for (variable in irProgram.st.allVariables().filter { !it.bss }) {
val typeStr = getTypeString(variable) val typeStr = getTypeString(variable)
val value: String = when(variable.dt) { val value: String = when(variable.dt) {
DataType.FLOAT -> (variable.onetimeInitializationNumericValue ?: "").toString() DataType.FLOAT -> (variable.onetimeInitializationNumericValue ?: "").toString()

View File

@ -49,15 +49,18 @@ output=PRG
launcher=BASIC launcher=BASIC
zeropage=KERNALSAFE zeropage=KERNALSAFE
loadAddress=0 loadAddress=0
dontReinitGlobals=false dontReinitGlobals=true
evalStackBaseAddress=null evalStackBaseAddress=null
</OPTIONS> </OPTIONS>
<ASMSYMBOLS> <ASMSYMBOLS>
</ASMSYMBOLS> </ASMSYMBOLS>
<BSS>
uword sys.bssvar zp=DONTCARE
</BSS>
<VARIABLES> <VARIABLES>
uword sys.wait.jiffies= zp=DONTCARE uword sys.wait.jiffies=10 zp=DONTCARE
</VARIABLES> </VARIABLES>
<MEMORYMAPPEDVARIABLES> <MEMORYMAPPEDVARIABLES>
@ -105,7 +108,10 @@ return
tempfile.deleteExisting() tempfile.deleteExisting()
program.name shouldBe "test-ir-reader" program.name shouldBe "test-ir-reader"
program.blocks.size shouldBe 2 program.blocks.size shouldBe 2
program.st.allVariables().count() shouldBe 1 program.st.allVariables().count() shouldBe 2
program.st.lookup("sys.wait.jiffies") shouldBe instanceOf<StStaticVariable>() 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
} }
}) })

View File

@ -1,10 +1,12 @@
package prog8.vm 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 { class Memory {
private val mem = Array<UByte>(64 * 1024) { 0u } private val mem = Array<UByte>(64 * 1024) { Random.nextInt().toUByte() }
fun reset() { fun reset() {
mem.fill(0u) mem.fill(0u)

View File

@ -1,5 +1,6 @@
package prog8.vm package prog8.vm
import prog8.code.core.ArrayDatatypes
import prog8.code.core.AssemblyError import prog8.code.core.AssemblyError
import prog8.code.core.DataType import prog8.code.core.DataType
import prog8.intermediate.* import prog8.intermediate.*
@ -183,6 +184,29 @@ class VmProgramLoader {
) { ) {
program.st.allVariables().forEach { variable -> program.st.allVariables().forEach { variable ->
var addr = allocations.allocations.getValue(variable.name) 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 { variable.onetimeInitializationNumericValue?.let {
when(variable.dt) { when(variable.dt) {
DataType.UBYTE -> memory.setUB(addr, it.toInt().toUByte()) DataType.UBYTE -> memory.setUB(addr, it.toInt().toUByte())

View File

@ -1,6 +1,7 @@
import io.kotest.assertions.throwables.shouldThrow import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.FunSpec import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import prog8.vm.Memory import prog8.vm.Memory
@ -46,7 +47,9 @@ class TestMemory: FunSpec({
test("32 bits float access") { test("32 bits float access") {
val mem = Memory() 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.setFloat(1000, -9.876543f)
mem.getFloat(1000) shouldBe -9.876543f mem.getFloat(1000) shouldBe -9.876543f
} }

View File

@ -2,6 +2,7 @@ import io.kotest.assertions.throwables.shouldThrowWithMessage
import io.kotest.core.spec.style.FunSpec import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.collections.shouldBeEmpty import io.kotest.matchers.collections.shouldBeEmpty
import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import prog8.code.core.* import prog8.code.core.*
import prog8.code.target.VMTarget import prog8.code.target.VMTarget
import prog8.intermediate.* import prog8.intermediate.*
@ -51,6 +52,7 @@ class TestVm: FunSpec( {
block += startSub block += startSub
program.addBlock(block) program.addBlock(block)
val vm = VirtualMachine(program) val vm = VirtualMachine(program)
vm.memory.setUW(1000, 0u)
vm.memory.getUW(1000) shouldBe 0u vm.memory.getUW(1000) shouldBe 0u
vm.callStack.shouldBeEmpty() vm.callStack.shouldBeEmpty()
@ -128,6 +130,8 @@ class TestVm: FunSpec( {
<ASMSYMBOLS> <ASMSYMBOLS>
</ASMSYMBOLS> </ASMSYMBOLS>
<BSS>
</BSS>
<VARIABLES> <VARIABLES>
</VARIABLES> </VARIABLES>