mirror of
https://github.com/irmen/prog8.git
synced 2025-02-16 22:30:46 +00:00
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:
parent
4831fad27a
commit
17bedac96c
@ -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<StSubroutineParameter>, 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<RegisterOrStatusflag>,
|
||||
position: Position) :
|
||||
StNode(name, StNodeType.ROMSUB, position) {
|
||||
override fun printProperties() {
|
||||
print("$name address=${address.toHex()}")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -19,6 +19,8 @@ class TestLaunchEmu: FunSpec({
|
||||
<ASMSYMBOLS>
|
||||
</ASMSYMBOLS>
|
||||
|
||||
<BSS>
|
||||
</BSS>
|
||||
<VARIABLES>
|
||||
</VARIABLES>
|
||||
|
||||
|
@ -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:
|
||||
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
@ -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<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> {
|
||||
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
|
||||
|
@ -19,7 +19,7 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
|
||||
out.write("<PROGRAM NAME=\"${irProgram.name}\">\n")
|
||||
writeOptions()
|
||||
writeAsmSymbols()
|
||||
writeVariableAllocations()
|
||||
writeVariables()
|
||||
|
||||
out.write("\n<INITGLOBALS>\n")
|
||||
if(!irProgram.options.dontReinitGlobals) {
|
||||
@ -137,10 +137,17 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
|
||||
out.write("</OPTIONS>\n")
|
||||
}
|
||||
|
||||
private fun writeVariableAllocations() {
|
||||
private fun writeVariables() {
|
||||
|
||||
out.write("\n<VARIABLES>\n")
|
||||
for (variable in irProgram.st.allVariables()) {
|
||||
out.write("\n<BSS>\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("</BSS>\n<VARIABLES>\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()
|
||||
|
@ -49,15 +49,18 @@ output=PRG
|
||||
launcher=BASIC
|
||||
zeropage=KERNALSAFE
|
||||
loadAddress=0
|
||||
dontReinitGlobals=false
|
||||
dontReinitGlobals=true
|
||||
evalStackBaseAddress=null
|
||||
</OPTIONS>
|
||||
|
||||
<ASMSYMBOLS>
|
||||
</ASMSYMBOLS>
|
||||
|
||||
<BSS>
|
||||
uword sys.bssvar zp=DONTCARE
|
||||
</BSS>
|
||||
<VARIABLES>
|
||||
uword sys.wait.jiffies= zp=DONTCARE
|
||||
uword sys.wait.jiffies=10 zp=DONTCARE
|
||||
</VARIABLES>
|
||||
|
||||
<MEMORYMAPPEDVARIABLES>
|
||||
@ -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<StStaticVariable>()
|
||||
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
|
||||
}
|
||||
})
|
@ -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<UByte>(64 * 1024) { 0u }
|
||||
private val mem = Array<UByte>(64 * 1024) { Random.nextInt().toUByte() }
|
||||
|
||||
fun reset() {
|
||||
mem.fill(0u)
|
||||
|
@ -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())
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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( {
|
||||
<ASMSYMBOLS>
|
||||
</ASMSYMBOLS>
|
||||
|
||||
<BSS>
|
||||
</BSS>
|
||||
<VARIABLES>
|
||||
</VARIABLES>
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user