From c21913a66b0ffce8096bacf59627a5a7f211a800 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Tue, 22 Nov 2022 02:04:24 +0100 Subject: [PATCH] ir: keep order of children in block --- .../prog8/codegen/intermediate/IRCodeGen.kt | 22 +-- .../intermediate/IRPeepholeOptimizer.kt | 2 +- .../intermediate/IRUnusedCodeRemover.kt | 15 +- codeGenIntermediate/test/TestIRPeepholeOpt.kt | 2 +- docs/source/todo.rst | 6 +- examples/test.p8 | 20 ++ .../src/prog8/intermediate/IRFileWriter.kt | 76 ++++--- .../src/prog8/intermediate/IRProgram.kt | 185 ++++++++++-------- .../src/prog8/vm/VmProgramLoader.kt | 53 +++-- virtualmachine/test/TestVm.kt | 4 +- 10 files changed, 217 insertions(+), 168 deletions(-) diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt index 14e64b743..b2179599e 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt @@ -75,36 +75,36 @@ class IRCodeGen( // make sure that first chunks in Blocks and Subroutines share the name of the block/sub as label. irProg.blocks.forEach { block -> - if(block.inlineAssemblies.isNotEmpty()) { - val first = block.inlineAssemblies.first() + block.children.firstOrNull { it is IRInlineAsmChunk }?.let { first-> + first as IRInlineAsmChunk if(first.label==null) { val replacement = IRInlineAsmChunk(block.name, first.assembly, first.isIR, first.next) - block.inlineAssemblies.removeAt(0) - block.inlineAssemblies.add(0, replacement) + block.children.removeAt(0) + block.children.add(0, replacement) } else if(first.label != block.name) { throw AssemblyError("first chunk in block has label that differs from block name") } } - block.subroutines.forEach { sub -> + block.children.filterIsInstance().forEach { sub -> if(sub.chunks.isNotEmpty()) { val first = sub.chunks.first() if(first.label==null) { val replacement = when(first) { is IRCodeChunk -> { - val replacement = IRCodeChunk(sub.name, first.next) + val replacement = IRCodeChunk(sub.label, first.next) replacement.instructions += first.instructions replacement } - is IRInlineAsmChunk -> IRInlineAsmChunk(sub.name, first.assembly, first.isIR, first.next) - is IRInlineBinaryChunk -> IRInlineBinaryChunk(sub.name, first.data, first.next) + is IRInlineAsmChunk -> IRInlineAsmChunk(sub.label, first.assembly, first.isIR, first.next) + is IRInlineBinaryChunk -> IRInlineBinaryChunk(sub.label, first.data, first.next) else -> throw AssemblyError("invalid chunk") } sub.chunks.removeAt(0) sub.chunks.add(0, replacement) - } else if(first.label != sub.name) { + } else if(first.label != sub.label) { val next = if(first is IRCodeChunk) first else null - sub.chunks.add(0, IRCodeChunk(sub.name, next)) + sub.chunks.add(0, IRCodeChunk(sub.label, next)) } } } @@ -116,7 +116,7 @@ class IRCodeGen( // note: we do still export the memory mapped symbols so a code generator can use those // for instance when a piece of inlined assembly references them. val replacements = mutableListOf>() - irProg.blocks.asSequence().flatMap { it.subroutines }.flatMap { it.chunks }.forEach { chunk -> + irProg.blocks.asSequence().flatMap { it.children.filterIsInstance() }.flatMap { it.chunks }.forEach { chunk -> chunk.instructions.withIndex().forEach { (idx, instr) -> val symbolExpr = instr.labelSymbol diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/IRPeepholeOptimizer.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/IRPeepholeOptimizer.kt index 61eb91612..fd0478bbf 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/IRPeepholeOptimizer.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/IRPeepholeOptimizer.kt @@ -4,7 +4,7 @@ import prog8.intermediate.* internal class IRPeepholeOptimizer(private val irprog: IRProgram) { fun optimize() { - irprog.blocks.asSequence().flatMap { it.subroutines }.forEach { sub -> + irprog.blocks.asSequence().flatMap { it.children.filterIsInstance() }.forEach { sub -> removeEmptyChunks(sub) joinChunks(sub) sub.chunks.withIndex().forEach { (index, chunk1) -> diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/IRUnusedCodeRemover.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/IRUnusedCodeRemover.kt index e5b9a34d4..daca934e6 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/IRUnusedCodeRemover.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/IRUnusedCodeRemover.kt @@ -9,7 +9,7 @@ internal class IRUnusedCodeRemover(private val irprog: IRProgram, private val er fun optimize(): Int { val allLabeledChunks = mutableMapOf() - irprog.blocks.asSequence().flatMap { it.subroutines }.forEach { sub -> + irprog.blocks.asSequence().flatMap { it.children.filterIsInstance() }.forEach { sub -> sub.chunks.forEach { chunk -> chunk.label?.let { allLabeledChunks[it] = chunk } } @@ -19,11 +19,11 @@ internal class IRUnusedCodeRemover(private val irprog: IRProgram, private val er // remove empty subs irprog.blocks.forEach { block -> - block.subroutines.reversed().forEach { sub -> + block.children.filterIsInstance().reversed().forEach { sub -> if(sub.isEmpty()) { if(!sub.position.file.startsWith(libraryFilePrefix)) - errors.warn("unused subroutine ${sub.name}", sub.position) - block.subroutines.remove(sub) + errors.warn("unused subroutine ${sub.label}", sub.position) + block.children.remove(sub) numRemoved++ } } @@ -41,7 +41,8 @@ internal class IRUnusedCodeRemover(private val irprog: IRProgram, private val er } private fun removeUnreachable(allLabeledChunks: MutableMap): Int { - val reachable = mutableSetOf(irprog.blocks.single { it.name=="main" }.subroutines.single { it.name=="main.start" }.chunks.first()) + val entrypointSub = irprog.blocks.single { it.name=="main" }.children.single { it is IRSubroutine && it.label=="main.start" } + val reachable = mutableSetOf((entrypointSub as IRSubroutine).chunks.first()) fun grow() { val new = mutableSetOf() @@ -71,7 +72,7 @@ internal class IRUnusedCodeRemover(private val irprog: IRProgram, private val er private fun removeSimpleUnlinked(allLabeledChunks: Map): Int { val linkedChunks = mutableSetOf() - irprog.blocks.asSequence().flatMap { it.subroutines }.forEach { sub -> + irprog.blocks.asSequence().flatMap { it.children.filterIsInstance() }.forEach { sub -> sub.chunks.forEach { chunk -> chunk.next?.let { next -> linkedChunks += next } chunk.instructions.forEach { @@ -93,7 +94,7 @@ internal class IRUnusedCodeRemover(private val irprog: IRProgram, private val er linkedChunks: MutableSet ): Int { var numRemoved = 0 - irprog.blocks.asSequence().flatMap { it.subroutines }.forEach { sub -> + irprog.blocks.asSequence().flatMap { it.children.filterIsInstance() }.forEach { sub -> sub.chunks.withIndex().reversed().forEach { (index, chunk) -> if (chunk !in linkedChunks) { if (chunk === sub.chunks[0]) { diff --git a/codeGenIntermediate/test/TestIRPeepholeOpt.kt b/codeGenIntermediate/test/TestIRPeepholeOpt.kt index 105c845b3..91a186bbf 100644 --- a/codeGenIntermediate/test/TestIRPeepholeOpt.kt +++ b/codeGenIntermediate/test/TestIRPeepholeOpt.kt @@ -36,7 +36,7 @@ class TestIRPeepholeOpt: FunSpec({ return makeIRProgram(listOf(chunk)) } - fun IRProgram.chunks(): List = this.blocks.flatMap { it.subroutines }.flatMap { it.chunks } + fun IRProgram.chunks(): List = this.blocks.flatMap { it.children.filterIsInstance() }.flatMap { it.chunks } test("remove nops") { val irProg = makeIRProgram(listOf( diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 7d4f73dc6..6a9d7237d 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -3,12 +3,13 @@ TODO For next release ^^^^^^^^^^^^^^^^ -- ir/vm: allow label in block scope (correct order of block nodes!) -- regression test the various projects +- ir/vm: check weird asm chunks appearing in block? - attempt to fix the expression codegen bug with reused temp vars (github #89) +- AstIdentifiersChecker: can a subroutine really not have the same name as its enclosing block? 64tass problem? - 6502 codegen: make it possible to use cpu opcodes such as 'nop' as variable names by prefixing all asm vars with something such as ``p8v_``? Or not worth it (most 3 letter opcodes as variables are nonsensical anyway) then we can get rid of the instruction lists in the machinedefinitions as well. This is already no problem at all in the IR codegen. - 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 +- regression test the various projects ... @@ -24,7 +25,6 @@ Future Things and Ideas ^^^^^^^^^^^^^^^^^^^^^^^ Compiler: -- AstIdentifiersChecker: can a subroutine really not have the same name as its enclosing block? - 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!) diff --git a/examples/test.p8 b/examples/test.p8 index ef433868f..bb44b0352 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -4,6 +4,14 @@ main { alsostart: + + %asm {{ + ; inline asm in block #1 + nop + }} + + %asmbinary "../gradle.properties" + sub start() { internalstart: @@ -30,6 +38,18 @@ alsostart: internalend: } + %asm {{ + ; inline asm in block #2 + nop + }} + startend: + %asmbinary "../settings.gradle" + + %asm {{ + ; inline asm in block #3 + nop + }} + } diff --git a/intermediate/src/prog8/intermediate/IRFileWriter.kt b/intermediate/src/prog8/intermediate/IRFileWriter.kt index a28936e93..5d0a0a60b 100644 --- a/intermediate/src/prog8/intermediate/IRFileWriter.kt +++ b/intermediate/src/prog8/intermediate/IRFileWriter.kt @@ -46,47 +46,45 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) { private fun writeBlocks() { irProgram.blocks.forEach { block -> out.write("\n\n") - block.inlineAssemblies.forEach { - writeInlineAsm(it) - } - block.labels.forEach { - writeCodeChunk(it) // TODO doing it like this isn't useful, block needs to have a list of nodes rather than a few separate collections - } - block.subroutines.forEach { - out.write("\n") - out.write("\n") - it.parameters.forEach { param -> out.write("${getTypeString(param.dt)} ${param.name}\n") } - out.write("\n") - it.chunks.forEach { chunk -> - numChunks++ - when (chunk) { - is IRInlineAsmChunk -> writeInlineAsm(chunk) - is IRInlineBinaryChunk -> writeInlineBytes(chunk) - is IRCodeChunk -> writeCodeChunk(chunk) - else -> throw InternalCompilerException("invalid chunk") + block.children.forEach { child -> + when(child) { + is IRAsmSubroutine -> { + val clobbers = child.clobbers.joinToString(",") + val returns = child.returns.map { ret -> + if(ret.reg.registerOrPair!=null) "${ret.reg.registerOrPair}:${ret.dt.toString().lowercase()}" + else "${ret.reg.statusflag}:${ret.dt.toString().lowercase()}" + }.joinToString(",") + out.write("\n") + out.write("\n") + child.parameters.forEach { ret -> + val reg = if(ret.reg.registerOrPair!=null) ret.reg.registerOrPair.toString() + else ret.reg.statusflag.toString() + out.write("${ret.dt.toString().lowercase()} $reg\n") + } + out.write("\n") + writeInlineAsm(child.asmChunk) + out.write("\n") + } + is IRCodeChunk -> writeCodeChunk(child) + is IRInlineAsmChunk -> writeInlineAsm(child) + is IRInlineBinaryChunk -> writeInlineBytes(child) + is IRSubroutine -> { + out.write("\n") + out.write("\n") + child.parameters.forEach { param -> out.write("${getTypeString(param.dt)} ${param.name}\n") } + out.write("\n") + child.chunks.forEach { chunk -> + numChunks++ + when (chunk) { + is IRInlineAsmChunk -> writeInlineAsm(chunk) + is IRInlineBinaryChunk -> writeInlineBytes(chunk) + is IRCodeChunk -> writeCodeChunk(chunk) + else -> throw InternalCompilerException("invalid chunk") + } + } + out.write("\n") } } - out.write("\n") - } - block.asmSubroutines.forEach { - val clobbers = it.clobbers.joinToString(",") - val returns = it.returns.map { ret -> - if(ret.reg.registerOrPair!=null) "${ret.reg.registerOrPair}:${ret.dt.toString().lowercase()}" - else "${ret.reg.statusflag}:${ret.dt.toString().lowercase()}" - }.joinToString(",") - out.write("\n") - out.write("\n") - it.parameters.forEach { ret -> - val reg = if(ret.reg.registerOrPair!=null) ret.reg.registerOrPair.toString() - else ret.reg.statusflag.toString() - out.write("${ret.dt.toString().lowercase()} $reg\n") - } - out.write("\n") - writeInlineAsm(it.asmChunk) - out.write("\n") - } - block.inlineBinaries.forEach { - writeInlineBytes(it) } out.write("\n") } diff --git a/intermediate/src/prog8/intermediate/IRProgram.kt b/intermediate/src/prog8/intermediate/IRProgram.kt index 23887b41c..8cac9ec92 100644 --- a/intermediate/src/prog8/intermediate/IRProgram.kt +++ b/intermediate/src/prog8/intermediate/IRProgram.kt @@ -70,30 +70,45 @@ class IRProgram(val name: String, fun linkChunks() { fun getLabeledChunks(): Map { - return blocks.flatMap { it.subroutines }.flatMap { it.chunks }.associateBy { it.label } + - blocks.flatMap { it.asmSubroutines }.map { it.asmChunk }.associateBy { it.label } + val result = mutableMapOf() + blocks.forEach { block -> + block.children.forEach { child -> + when(child) { + is IRAsmSubroutine -> result[child.asmChunk.label] = child.asmChunk + is IRCodeChunk -> result[child.label] = child + is IRInlineAsmChunk -> result[child.label] = child + is IRInlineBinaryChunk -> result[child.label] = child + is IRSubroutine -> result.putAll(child.chunks.associateBy { it.label }) + } + } + } + return result } val labeledChunks = getLabeledChunks() if(globalInits.isNotEmpty()) { if(globalInits.next==null) { + // link globalinits to subsequent chunk val firstBlock = blocks.firstOrNull() - if(firstBlock!=null) { - // TODO what is the first chunk in a block? - if(firstBlock.inlineAssemblies.isNotEmpty()) { - globalInits.next = firstBlock.inlineAssemblies.first() - } else if(firstBlock.subroutines.isNotEmpty()) { - val firstSub = firstBlock.subroutines.first() - if(firstSub.chunks.isNotEmpty()) - globalInits.next = firstSub.chunks.first() + if(firstBlock!=null && firstBlock.isNotEmpty()) { + firstBlock.children.forEach { child -> + when(child) { + is IRAsmSubroutine -> throw AssemblyError("cannot link next to asmsub $child") + is IRCodeChunk -> globalInits.next = child + is IRInlineAsmChunk -> globalInits.next = child + is IRInlineBinaryChunk -> globalInits.next = child + is IRSubroutine -> { + if(child.chunks.isNotEmpty()) + globalInits.next = child.chunks.first() + } + } } } } } - blocks.asSequence().flatMap { it.subroutines }.forEach { sub -> - + fun linkSubroutineChunks(sub: IRSubroutine) { sub.chunks.withIndex().forEach { (index, chunk) -> fun nextChunk(): IRCodeChunkBase? = if(index + block.children.forEachIndexed { index, child -> + val next = if(index child.asmChunk.next = next + is IRCodeChunk -> child.next = next + is IRInlineAsmChunk -> child.next = next + is IRInlineBinaryChunk -> child.next = next + is IRSubroutine -> linkSubroutineChunks(child) + } + } + } } fun validate() { blocks.forEach { block -> - // TODO what is the *first* chunk in the block? - if(block.inlineAssemblies.isNotEmpty()) { - require(block.inlineAssemblies.first().label == block.name) { "first block chunk should have block name as its label" } - } - block.inlineAssemblies.forEach { chunk -> - require(chunk.instructions.isEmpty()) - } - block.subroutines.forEach { sub -> - if(sub.chunks.isNotEmpty()) { - require(sub.chunks.first().label == sub.name) { "first chunk in subroutine should have sub name as its label" } - } - sub.chunks.forEach { chunk -> - if (chunk is IRCodeChunk) { - require(chunk.instructions.isNotEmpty() || chunk.label != null) - if(chunk.instructions.lastOrNull()?.opcode in OpcodesThatJump) - require(chunk.next == null) { "chunk ending with a jump shouldn't be linked to next" } - else { - // if chunk is NOT the last in the block, it needs to link to next. - val isLast = sub.chunks.last() === chunk - require(isLast || chunk.next != null) { "chunk needs to be linked to next" } - } + if(block.isNotEmpty()) { + block.children.filterIsInstance().forEach { chunk -> require(chunk.instructions.isEmpty()) } + block.children.filterIsInstance().forEach { sub -> + if(sub.chunks.isNotEmpty()) { + require(sub.chunks.first().label == sub.label) { "first chunk in subroutine should have sub name (label) as its label" } } - else - require(chunk.instructions.isEmpty()) - chunk.instructions.forEach { - if(it.labelSymbol!=null && it.opcode in OpcodesThatBranch) - require(it.branchTarget != null) { "branching instruction to label should have branchTarget set" } + sub.chunks.forEach { chunk -> + if (chunk is IRCodeChunk) { + require(chunk.instructions.isNotEmpty() || chunk.label != null) + if(chunk.instructions.lastOrNull()?.opcode in OpcodesThatJump) + require(chunk.next == null) { "chunk ending with a jump shouldn't be linked to next" } + else { + // if chunk is NOT the last in the block, it needs to link to next. + val isLast = sub.chunks.last() === chunk + require(isLast || chunk.next != null) { "chunk needs to be linked to next" } + } + } + else + require(chunk.instructions.isEmpty()) + chunk.instructions.forEach { + if(it.labelSymbol!=null && it.opcode in OpcodesThatBranch) + require(it.branchTarget != null) { "branching instruction to label should have branchTarget set" } + } } } } @@ -188,10 +212,16 @@ class IRProgram(val name: String, } globalInits.instructions.forEach { it.addUsedRegistersCounts(inputRegs, outputRegs, inputFpRegs, outputFpRegs) } - blocks.forEach { - it.inlineAssemblies.forEach { chunk -> addUsed(chunk.usedRegisters()) } - it.subroutines.flatMap { sub->sub.chunks }.forEach { chunk -> addUsed(chunk.usedRegisters()) } - it.asmSubroutines.forEach { asmsub -> addUsed(asmsub.usedRegisters()) } + blocks.forEach {block -> + block.children.forEach { child -> + when(child) { + is IRAsmSubroutine -> addUsed(child.usedRegisters()) + is IRCodeChunk -> addUsed(child.usedRegisters()) + is IRInlineAsmChunk -> addUsed(child.usedRegisters()) + is IRInlineBinaryChunk -> addUsed(child.usedRegisters()) + is IRSubroutine -> child.chunks.forEach { chunk -> addUsed(chunk.usedRegisters()) } + } + } } return RegistersUsed(inputRegs, outputRegs, inputFpRegs, outputFpRegs) @@ -205,11 +235,7 @@ class IRBlock( val position: Position ) { // TODO not separate lists but just a single list of chunks, like IRSubroutine? (but these are not all chunks...) - val inlineAssemblies = mutableListOf() - val subroutines = mutableListOf() - val asmSubroutines = mutableListOf() - val inlineBinaries = mutableListOf() - val labels = mutableListOf() // empty code chunks having just a label. + val children = mutableListOf() enum class BlockAlignment { NONE, @@ -217,40 +243,41 @@ class IRBlock( PAGE } - operator fun plusAssign(sub: IRSubroutine) { - subroutines += sub - } - operator fun plusAssign(sub: IRAsmSubroutine) { asmSubroutines += sub } - operator fun plusAssign(asm: IRInlineAsmChunk) { inlineAssemblies += asm } - operator fun plusAssign(binary: IRInlineBinaryChunk) { inlineBinaries += binary } + operator fun plusAssign(sub: IRSubroutine) { children += sub } + operator fun plusAssign(sub: IRAsmSubroutine) { children += sub } + operator fun plusAssign(asm: IRInlineAsmChunk) { children += asm } + operator fun plusAssign(binary: IRInlineBinaryChunk) { children += binary } operator fun plusAssign(irCodeChunk: IRCodeChunk) { // this is for a separate label in the block scope. (random code statements are not allowed) require(irCodeChunk.isEmpty() && irCodeChunk.label!=null) - labels += irCodeChunk - } - - fun isEmpty(): Boolean { - val noAsm = inlineAssemblies.isEmpty() || inlineAssemblies.all { it.isEmpty() } - val noSubs = subroutines.isEmpty() || subroutines.all { it.isEmpty() } - val noAsmSubs = asmSubroutines.isEmpty() || asmSubroutines.all { it.isEmpty() } - val noBins = inlineBinaries.isEmpty() || inlineBinaries.all { it.isEmpty() } - return noAsm && noSubs && noAsmSubs && noBins + children += irCodeChunk } + fun isEmpty(): Boolean = children.isEmpty() || children.all { it.isEmpty() } + fun isNotEmpty(): Boolean = !isEmpty() } -class IRSubroutine(val name: String, - val parameters: List, - val returnType: DataType?, - val position: Position) { + +sealed interface IIRBlockElement { + val label: String? + fun isEmpty(): Boolean + fun isNotEmpty(): Boolean +} + + +class IRSubroutine( + override val label: String, + val parameters: List, + val returnType: DataType?, + val position: Position): IIRBlockElement { class IRParam(val name: String, val dt: DataType) val chunks = mutableListOf() init { - require('.' in name) {"subroutine name is not scoped: $name"} - require(!name.startsWith("main.main.")) {"subroutine name invalid main prefix: $name"} + require('.' in label) {"subroutine name is not scoped: $label"} + require(!label.startsWith("main.main.")) {"subroutine name invalid main prefix: $label"} // params and return value should not be str require(parameters.all{ it.dt in NumericDatatypes }) {"non-numeric parameter"} @@ -264,37 +291,41 @@ class IRSubroutine(val name: String, chunks+= chunk } - fun isEmpty(): Boolean = chunks.isEmpty() || chunks.all { it.isEmpty() } + override fun isEmpty(): Boolean = chunks.isEmpty() || chunks.all { it.isEmpty() } + override fun isNotEmpty(): Boolean = !isEmpty() } + class IRAsmSubroutine( - val name: String, + override val label: String, val address: UInt?, val clobbers: Set, val parameters: List, val returns: List, val asmChunk: IRInlineAsmChunk, val position: Position -) { +): IIRBlockElement { class IRAsmParam(val reg: RegisterOrStatusflag, val dt: DataType) init { - require('.' in name) { "subroutine name is not scoped: $name" } - require(!name.startsWith("main.main.")) { "subroutine name invalid main prefix: $name" } + require('.' in label) { "subroutine name is not scoped: $label" } + require(!label.startsWith("main.main.")) { "subroutine name invalid main prefix: $label" } } private val registersUsed by lazy { registersUsedInAssembly(asmChunk.isIR, asmChunk.assembly) } fun usedRegisters() = registersUsed - fun isEmpty(): Boolean = if(address==null) asmChunk.isEmpty() else false + override fun isEmpty(): Boolean = if(address==null) asmChunk.isEmpty() else false + override fun isNotEmpty(): Boolean = !isEmpty() } -sealed class IRCodeChunkBase(val label: String?, var next: IRCodeChunkBase?) { + +sealed class IRCodeChunkBase(override val label: String?, var next: IRCodeChunkBase?): IIRBlockElement { val instructions = mutableListOf() - abstract fun isEmpty(): Boolean - abstract fun isNotEmpty(): Boolean + abstract override fun isEmpty(): Boolean + abstract override fun isNotEmpty(): Boolean abstract fun usedRegisters(): RegistersUsed } diff --git a/virtualmachine/src/prog8/vm/VmProgramLoader.kt b/virtualmachine/src/prog8/vm/VmProgramLoader.kt index 885d6e006..d0390f49f 100644 --- a/virtualmachine/src/prog8/vm/VmProgramLoader.kt +++ b/virtualmachine/src/prog8/vm/VmProgramLoader.kt @@ -23,7 +23,7 @@ class VmProgramLoader { // make sure that if there is a "main.start" entrypoint, we jump to it irProgram.blocks.firstOrNull()?.let { - if(it.subroutines.any { sub -> sub.name=="main.start" }) { + if(it.children.any { sub -> sub is IRSubroutine && sub.label=="main.start" }) { val chunk = IRCodeChunk(null, null) placeholders[Pair(chunk, 0)] = "main.start" chunk += IRInstruction(Opcode.JUMP, labelSymbol = "main.start") @@ -37,37 +37,36 @@ class VmProgramLoader { if(block.address!=null) throw IRParseException("blocks cannot have a load address for vm: ${block.name}") - block.inlineAssemblies.forEach { - val replacement = addAssemblyToProgram(it, programChunks, variableAddresses) - chunkReplacements += replacement - } - block.labels.forEach { - programChunks += it // TODO doing it like this isn't useful, block needs to have a list of nodes rather than a few separate collections - } - block.subroutines.forEach { - it.chunks.forEach { chunk -> - when (chunk) { - is IRInlineAsmChunk -> { - val replacement = addAssemblyToProgram(chunk, programChunks, variableAddresses) + block.children.forEach { child -> + when(child) { + is IRAsmSubroutine -> { + if(!child.asmChunk.isIR) + throw IRParseException("vm currently does not support non-IR asmsubs: ${child.label}") + else { + val replacement = addAssemblyToProgram(child.asmChunk, programChunks, variableAddresses) chunkReplacements += replacement } - is IRInlineBinaryChunk -> throw IRParseException("inline binary data not yet supported in the VM") // TODO - is IRCodeChunk -> programChunks += chunk - else -> throw AssemblyError("weird chunk type") } + is IRCodeChunk -> programChunks += child + is IRInlineAsmChunk -> { + val replacement = addAssemblyToProgram(child, programChunks, variableAddresses) + chunkReplacements += replacement + } + is IRInlineBinaryChunk -> throw IRParseException("inline binary data not yet supported in the VM") // TODO + is IRSubroutine -> { + child.chunks.forEach { chunk -> + when (chunk) { + is IRInlineAsmChunk -> { + val replacement = addAssemblyToProgram(chunk, programChunks, variableAddresses) + chunkReplacements += replacement + } + is IRInlineBinaryChunk -> throw IRParseException("inline binary data not yet supported in the VM") // TODO + is IRCodeChunk -> programChunks += chunk + else -> throw AssemblyError("weird chunk type") + } + } } } } - block.asmSubroutines.forEach { - if(!it.asmChunk.isIR) - throw IRParseException("vm currently does not support non-IR asmsubs: ${block.asmSubroutines.first().name}") - else { - val replacement = addAssemblyToProgram(it.asmChunk, programChunks, variableAddresses) - chunkReplacements += replacement - } - } - block.inlineBinaries.forEach { - throw IRParseException("inline binary data not yet supported in the VM") // TODO - } } pass2translateSyscalls(programChunks) diff --git a/virtualmachine/test/TestVm.kt b/virtualmachine/test/TestVm.kt index 16b5a686e..57543d344 100644 --- a/virtualmachine/test/TestVm.kt +++ b/virtualmachine/test/TestVm.kt @@ -42,7 +42,7 @@ class TestVm: FunSpec( { val program = IRProgram("test", IRSymbolTable(null), getTestOptions(), VMTarget()) val block = IRBlock("testmain", null, IRBlock.BlockAlignment.NONE, Position.DUMMY) val startSub = IRSubroutine("testmain.testsub", emptyList(), null, Position.DUMMY) - val code = IRCodeChunk(startSub.name, null) + val code = IRCodeChunk(startSub.label, null) code += IRInstruction(Opcode.NOP) code += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=1, value=12345) code += IRInstruction(Opcode.STOREM, IRDataType.WORD, reg1=1, value=1000) @@ -70,7 +70,7 @@ class TestVm: FunSpec( { val program = IRProgram("test", IRSymbolTable(null), getTestOptions(), VMTarget()) val block = IRBlock("testmain", null, IRBlock.BlockAlignment.NONE, Position.DUMMY) val startSub = IRSubroutine("testmain.testsub", emptyList(), null, Position.DUMMY) - val code = IRCodeChunk(startSub.name, null) + val code = IRCodeChunk(startSub.label, null) code += IRInstruction(Opcode.BINARYDATA, binaryData = listOf(1u,2u,3u)) code += IRInstruction(Opcode.RETURN) startSub += code