ir: keep order of children in block

This commit is contained in:
Irmen de Jong 2022-11-22 02:04:24 +01:00
parent 77e956a29f
commit c21913a66b
10 changed files with 217 additions and 168 deletions

View File

@ -75,36 +75,36 @@ class IRCodeGen(
// make sure that first chunks in Blocks and Subroutines share the name of the block/sub as label. // make sure that first chunks in Blocks and Subroutines share the name of the block/sub as label.
irProg.blocks.forEach { block -> irProg.blocks.forEach { block ->
if(block.inlineAssemblies.isNotEmpty()) { block.children.firstOrNull { it is IRInlineAsmChunk }?.let { first->
val first = block.inlineAssemblies.first() first as IRInlineAsmChunk
if(first.label==null) { if(first.label==null) {
val replacement = IRInlineAsmChunk(block.name, first.assembly, first.isIR, first.next) val replacement = IRInlineAsmChunk(block.name, first.assembly, first.isIR, first.next)
block.inlineAssemblies.removeAt(0) block.children.removeAt(0)
block.inlineAssemblies.add(0, replacement) block.children.add(0, replacement)
} else if(first.label != block.name) { } else if(first.label != block.name) {
throw AssemblyError("first chunk in block has label that differs from block name") throw AssemblyError("first chunk in block has label that differs from block name")
} }
} }
block.subroutines.forEach { sub -> block.children.filterIsInstance<IRSubroutine>().forEach { sub ->
if(sub.chunks.isNotEmpty()) { if(sub.chunks.isNotEmpty()) {
val first = sub.chunks.first() val first = sub.chunks.first()
if(first.label==null) { if(first.label==null) {
val replacement = when(first) { val replacement = when(first) {
is IRCodeChunk -> { is IRCodeChunk -> {
val replacement = IRCodeChunk(sub.name, first.next) val replacement = IRCodeChunk(sub.label, first.next)
replacement.instructions += first.instructions replacement.instructions += first.instructions
replacement replacement
} }
is IRInlineAsmChunk -> IRInlineAsmChunk(sub.name, first.assembly, first.isIR, first.next) is IRInlineAsmChunk -> IRInlineAsmChunk(sub.label, first.assembly, first.isIR, first.next)
is IRInlineBinaryChunk -> IRInlineBinaryChunk(sub.name, first.data, first.next) is IRInlineBinaryChunk -> IRInlineBinaryChunk(sub.label, first.data, first.next)
else -> throw AssemblyError("invalid chunk") else -> throw AssemblyError("invalid chunk")
} }
sub.chunks.removeAt(0) sub.chunks.removeAt(0)
sub.chunks.add(0, replacement) 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 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 // 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. // for instance when a piece of inlined assembly references them.
val replacements = mutableListOf<Triple<IRCodeChunkBase, Int, UInt>>() val replacements = mutableListOf<Triple<IRCodeChunkBase, Int, UInt>>()
irProg.blocks.asSequence().flatMap { it.subroutines }.flatMap { it.chunks }.forEach { chunk -> irProg.blocks.asSequence().flatMap { it.children.filterIsInstance<IRSubroutine>() }.flatMap { it.chunks }.forEach { chunk ->
chunk.instructions.withIndex().forEach { chunk.instructions.withIndex().forEach {
(idx, instr) -> (idx, instr) ->
val symbolExpr = instr.labelSymbol val symbolExpr = instr.labelSymbol

View File

@ -4,7 +4,7 @@ import prog8.intermediate.*
internal class IRPeepholeOptimizer(private val irprog: IRProgram) { internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
fun optimize() { fun optimize() {
irprog.blocks.asSequence().flatMap { it.subroutines }.forEach { sub -> irprog.blocks.asSequence().flatMap { it.children.filterIsInstance<IRSubroutine>() }.forEach { sub ->
removeEmptyChunks(sub) removeEmptyChunks(sub)
joinChunks(sub) joinChunks(sub)
sub.chunks.withIndex().forEach { (index, chunk1) -> sub.chunks.withIndex().forEach { (index, chunk1) ->

View File

@ -9,7 +9,7 @@ internal class IRUnusedCodeRemover(private val irprog: IRProgram, private val er
fun optimize(): Int { fun optimize(): Int {
val allLabeledChunks = mutableMapOf<String, IRCodeChunkBase>() val allLabeledChunks = mutableMapOf<String, IRCodeChunkBase>()
irprog.blocks.asSequence().flatMap { it.subroutines }.forEach { sub -> irprog.blocks.asSequence().flatMap { it.children.filterIsInstance<IRSubroutine>() }.forEach { sub ->
sub.chunks.forEach { chunk -> sub.chunks.forEach { chunk ->
chunk.label?.let { allLabeledChunks[it] = chunk } chunk.label?.let { allLabeledChunks[it] = chunk }
} }
@ -19,11 +19,11 @@ internal class IRUnusedCodeRemover(private val irprog: IRProgram, private val er
// remove empty subs // remove empty subs
irprog.blocks.forEach { block -> irprog.blocks.forEach { block ->
block.subroutines.reversed().forEach { sub -> block.children.filterIsInstance<IRSubroutine>().reversed().forEach { sub ->
if(sub.isEmpty()) { if(sub.isEmpty()) {
if(!sub.position.file.startsWith(libraryFilePrefix)) if(!sub.position.file.startsWith(libraryFilePrefix))
errors.warn("unused subroutine ${sub.name}", sub.position) errors.warn("unused subroutine ${sub.label}", sub.position)
block.subroutines.remove(sub) block.children.remove(sub)
numRemoved++ numRemoved++
} }
} }
@ -41,7 +41,8 @@ internal class IRUnusedCodeRemover(private val irprog: IRProgram, private val er
} }
private fun removeUnreachable(allLabeledChunks: MutableMap<String, IRCodeChunkBase>): Int { private fun removeUnreachable(allLabeledChunks: MutableMap<String, IRCodeChunkBase>): 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() { fun grow() {
val new = mutableSetOf<IRCodeChunkBase>() val new = mutableSetOf<IRCodeChunkBase>()
@ -71,7 +72,7 @@ internal class IRUnusedCodeRemover(private val irprog: IRProgram, private val er
private fun removeSimpleUnlinked(allLabeledChunks: Map<String, IRCodeChunkBase>): Int { private fun removeSimpleUnlinked(allLabeledChunks: Map<String, IRCodeChunkBase>): Int {
val linkedChunks = mutableSetOf<IRCodeChunkBase>() val linkedChunks = mutableSetOf<IRCodeChunkBase>()
irprog.blocks.asSequence().flatMap { it.subroutines }.forEach { sub -> irprog.blocks.asSequence().flatMap { it.children.filterIsInstance<IRSubroutine>() }.forEach { sub ->
sub.chunks.forEach { chunk -> sub.chunks.forEach { chunk ->
chunk.next?.let { next -> linkedChunks += next } chunk.next?.let { next -> linkedChunks += next }
chunk.instructions.forEach { chunk.instructions.forEach {
@ -93,7 +94,7 @@ internal class IRUnusedCodeRemover(private val irprog: IRProgram, private val er
linkedChunks: MutableSet<IRCodeChunkBase> linkedChunks: MutableSet<IRCodeChunkBase>
): Int { ): Int {
var numRemoved = 0 var numRemoved = 0
irprog.blocks.asSequence().flatMap { it.subroutines }.forEach { sub -> irprog.blocks.asSequence().flatMap { it.children.filterIsInstance<IRSubroutine>() }.forEach { sub ->
sub.chunks.withIndex().reversed().forEach { (index, chunk) -> sub.chunks.withIndex().reversed().forEach { (index, chunk) ->
if (chunk !in linkedChunks) { if (chunk !in linkedChunks) {
if (chunk === sub.chunks[0]) { if (chunk === sub.chunks[0]) {

View File

@ -36,7 +36,7 @@ class TestIRPeepholeOpt: FunSpec({
return makeIRProgram(listOf(chunk)) return makeIRProgram(listOf(chunk))
} }
fun IRProgram.chunks(): List<IRCodeChunkBase> = this.blocks.flatMap { it.subroutines }.flatMap { it.chunks } fun IRProgram.chunks(): List<IRCodeChunkBase> = this.blocks.flatMap { it.children.filterIsInstance<IRSubroutine>() }.flatMap { it.chunks }
test("remove nops") { test("remove nops") {
val irProg = makeIRProgram(listOf( val irProg = makeIRProgram(listOf(

View File

@ -3,12 +3,13 @@ TODO
For next release For next release
^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^
- ir/vm: allow label in block scope (correct order of block nodes!) - ir/vm: check weird asm chunks appearing in block?
- regression test the various projects
- attempt to fix the expression codegen bug with reused temp vars (github #89) - 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) - 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. 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 - 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: 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 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!)

View File

@ -4,6 +4,14 @@
main { main {
alsostart: alsostart:
%asm {{
; inline asm in block #1
nop
}}
%asmbinary "../gradle.properties"
sub start() { sub start() {
internalstart: internalstart:
@ -30,6 +38,18 @@ alsostart:
internalend: internalend:
} }
%asm {{
; inline asm in block #2
nop
}}
startend: startend:
%asmbinary "../settings.gradle"
%asm {{
; inline asm in block #3
nop
}}
} }

View File

@ -46,47 +46,45 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
private fun writeBlocks() { private fun writeBlocks() {
irProgram.blocks.forEach { block -> irProgram.blocks.forEach { block ->
out.write("\n<BLOCK NAME=\"${block.name}\" ADDRESS=\"${block.address?.toHex()}\" ALIGN=\"${block.alignment}\" POS=\"${block.position}\">\n") out.write("\n<BLOCK NAME=\"${block.name}\" ADDRESS=\"${block.address?.toHex()}\" ALIGN=\"${block.alignment}\" POS=\"${block.position}\">\n")
block.inlineAssemblies.forEach { block.children.forEach { child ->
writeInlineAsm(it) when(child) {
} is IRAsmSubroutine -> {
block.labels.forEach { val clobbers = child.clobbers.joinToString(",")
writeCodeChunk(it) // TODO doing it like this isn't useful, block needs to have a list of nodes rather than a few separate collections val returns = child.returns.map { ret ->
} if(ret.reg.registerOrPair!=null) "${ret.reg.registerOrPair}:${ret.dt.toString().lowercase()}"
block.subroutines.forEach { else "${ret.reg.statusflag}:${ret.dt.toString().lowercase()}"
out.write("<SUB NAME=\"${it.name}\" RETURNTYPE=\"${it.returnType.toString().lowercase()}\" POS=\"${it.position}\">\n") }.joinToString(",")
out.write("<PARAMS>\n") out.write("<ASMSUB NAME=\"${child.label}\" ADDRESS=\"${child.address?.toHex()}\" CLOBBERS=\"$clobbers\" RETURNS=\"$returns\" POS=\"${child.position}\">\n")
it.parameters.forEach { param -> out.write("${getTypeString(param.dt)} ${param.name}\n") } out.write("<ASMPARAMS>\n")
out.write("</PARAMS>\n") child.parameters.forEach { ret ->
it.chunks.forEach { chunk -> val reg = if(ret.reg.registerOrPair!=null) ret.reg.registerOrPair.toString()
numChunks++ else ret.reg.statusflag.toString()
when (chunk) { out.write("${ret.dt.toString().lowercase()} $reg\n")
is IRInlineAsmChunk -> writeInlineAsm(chunk) }
is IRInlineBinaryChunk -> writeInlineBytes(chunk) out.write("</ASMPARAMS>\n")
is IRCodeChunk -> writeCodeChunk(chunk) writeInlineAsm(child.asmChunk)
else -> throw InternalCompilerException("invalid chunk") out.write("</ASMSUB>\n")
}
is IRCodeChunk -> writeCodeChunk(child)
is IRInlineAsmChunk -> writeInlineAsm(child)
is IRInlineBinaryChunk -> writeInlineBytes(child)
is IRSubroutine -> {
out.write("<SUB NAME=\"${child.label}\" RETURNTYPE=\"${child.returnType.toString().lowercase()}\" POS=\"${child.position}\">\n")
out.write("<PARAMS>\n")
child.parameters.forEach { param -> out.write("${getTypeString(param.dt)} ${param.name}\n") }
out.write("</PARAMS>\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("</SUB>\n")
} }
} }
out.write("</SUB>\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("<ASMSUB NAME=\"${it.name}\" ADDRESS=\"${it.address?.toHex()}\" CLOBBERS=\"$clobbers\" RETURNS=\"$returns\" POS=\"${it.position}\">\n")
out.write("<ASMPARAMS>\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("</ASMPARAMS>\n")
writeInlineAsm(it.asmChunk)
out.write("</ASMSUB>\n")
}
block.inlineBinaries.forEach {
writeInlineBytes(it)
} }
out.write("</BLOCK>\n") out.write("</BLOCK>\n")
} }

View File

@ -70,30 +70,45 @@ class IRProgram(val name: String,
fun linkChunks() { fun linkChunks() {
fun getLabeledChunks(): Map<String?, IRCodeChunkBase> { fun getLabeledChunks(): Map<String?, IRCodeChunkBase> {
return blocks.flatMap { it.subroutines }.flatMap { it.chunks }.associateBy { it.label } + val result = mutableMapOf<String?, IRCodeChunkBase>()
blocks.flatMap { it.asmSubroutines }.map { it.asmChunk }.associateBy { it.label } 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() val labeledChunks = getLabeledChunks()
if(globalInits.isNotEmpty()) { if(globalInits.isNotEmpty()) {
if(globalInits.next==null) { if(globalInits.next==null) {
// link globalinits to subsequent chunk
val firstBlock = blocks.firstOrNull() val firstBlock = blocks.firstOrNull()
if(firstBlock!=null) { if(firstBlock!=null && firstBlock.isNotEmpty()) {
// TODO what is the first chunk in a block? firstBlock.children.forEach { child ->
if(firstBlock.inlineAssemblies.isNotEmpty()) { when(child) {
globalInits.next = firstBlock.inlineAssemblies.first() is IRAsmSubroutine -> throw AssemblyError("cannot link next to asmsub $child")
} else if(firstBlock.subroutines.isNotEmpty()) { is IRCodeChunk -> globalInits.next = child
val firstSub = firstBlock.subroutines.first() is IRInlineAsmChunk -> globalInits.next = child
if(firstSub.chunks.isNotEmpty()) is IRInlineBinaryChunk -> globalInits.next = child
globalInits.next = firstSub.chunks.first() 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) -> sub.chunks.withIndex().forEach { (index, chunk) ->
fun nextChunk(): IRCodeChunkBase? = if(index<sub.chunks.size-1) sub.chunks[index + 1] else null fun nextChunk(): IRCodeChunkBase? = if(index<sub.chunks.size-1) sub.chunks[index + 1] else null
@ -137,37 +152,46 @@ class IRProgram(val name: String,
} }
} }
} }
blocks.forEach { block ->
block.children.forEachIndexed { index, child ->
val next = if(index<block.children.size-1) block.children[index+1] as? IRCodeChunkBase else null
when (child) {
is IRAsmSubroutine -> 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() { fun validate() {
blocks.forEach { block -> blocks.forEach { block ->
// TODO what is the *first* chunk in the block? if(block.isNotEmpty()) {
if(block.inlineAssemblies.isNotEmpty()) { block.children.filterIsInstance<IRInlineAsmChunk>().forEach { chunk -> require(chunk.instructions.isEmpty()) }
require(block.inlineAssemblies.first().label == block.name) { "first block chunk should have block name as its label" } block.children.filterIsInstance<IRSubroutine>().forEach { sub ->
} if(sub.chunks.isNotEmpty()) {
block.inlineAssemblies.forEach { chunk -> require(sub.chunks.first().label == sub.label) { "first chunk in subroutine should have sub name (label) as its label" }
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" }
}
} }
else sub.chunks.forEach { chunk ->
require(chunk.instructions.isEmpty()) if (chunk is IRCodeChunk) {
chunk.instructions.forEach { require(chunk.instructions.isNotEmpty() || chunk.label != null)
if(it.labelSymbol!=null && it.opcode in OpcodesThatBranch) if(chunk.instructions.lastOrNull()?.opcode in OpcodesThatJump)
require(it.branchTarget != null) { "branching instruction to label should have branchTarget set" } 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) } globalInits.instructions.forEach { it.addUsedRegistersCounts(inputRegs, outputRegs, inputFpRegs, outputFpRegs) }
blocks.forEach { blocks.forEach {block ->
it.inlineAssemblies.forEach { chunk -> addUsed(chunk.usedRegisters()) } block.children.forEach { child ->
it.subroutines.flatMap { sub->sub.chunks }.forEach { chunk -> addUsed(chunk.usedRegisters()) } when(child) {
it.asmSubroutines.forEach { asmsub -> addUsed(asmsub.usedRegisters()) } 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) return RegistersUsed(inputRegs, outputRegs, inputFpRegs, outputFpRegs)
@ -205,11 +235,7 @@ class IRBlock(
val position: Position val position: Position
) { ) {
// TODO not separate lists but just a single list of chunks, like IRSubroutine? (but these are not all chunks...) // TODO not separate lists but just a single list of chunks, like IRSubroutine? (but these are not all chunks...)
val inlineAssemblies = mutableListOf<IRInlineAsmChunk>() val children = mutableListOf<IIRBlockElement>()
val subroutines = mutableListOf<IRSubroutine>()
val asmSubroutines = mutableListOf<IRAsmSubroutine>()
val inlineBinaries = mutableListOf<IRInlineBinaryChunk>()
val labels = mutableListOf<IRCodeChunk>() // empty code chunks having just a label.
enum class BlockAlignment { enum class BlockAlignment {
NONE, NONE,
@ -217,40 +243,41 @@ class IRBlock(
PAGE PAGE
} }
operator fun plusAssign(sub: IRSubroutine) { operator fun plusAssign(sub: IRSubroutine) { children += sub }
subroutines += sub operator fun plusAssign(sub: IRAsmSubroutine) { children += sub }
} operator fun plusAssign(asm: IRInlineAsmChunk) { children += asm }
operator fun plusAssign(sub: IRAsmSubroutine) { asmSubroutines += sub } operator fun plusAssign(binary: IRInlineBinaryChunk) { children += binary }
operator fun plusAssign(asm: IRInlineAsmChunk) { inlineAssemblies += asm }
operator fun plusAssign(binary: IRInlineBinaryChunk) { inlineBinaries += binary }
operator fun plusAssign(irCodeChunk: IRCodeChunk) { operator fun plusAssign(irCodeChunk: IRCodeChunk) {
// this is for a separate label in the block scope. (random code statements are not allowed) // this is for a separate label in the block scope. (random code statements are not allowed)
require(irCodeChunk.isEmpty() && irCodeChunk.label!=null) require(irCodeChunk.isEmpty() && irCodeChunk.label!=null)
labels += irCodeChunk children += 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
} }
fun isEmpty(): Boolean = children.isEmpty() || children.all { it.isEmpty() }
fun isNotEmpty(): Boolean = !isEmpty()
} }
class IRSubroutine(val name: String,
val parameters: List<IRParam>, sealed interface IIRBlockElement {
val returnType: DataType?, val label: String?
val position: Position) { fun isEmpty(): Boolean
fun isNotEmpty(): Boolean
}
class IRSubroutine(
override val label: String,
val parameters: List<IRParam>,
val returnType: DataType?,
val position: Position): IIRBlockElement {
class IRParam(val name: String, val dt: DataType) class IRParam(val name: String, val dt: DataType)
val chunks = mutableListOf<IRCodeChunkBase>() val chunks = mutableListOf<IRCodeChunkBase>()
init { init {
require('.' in name) {"subroutine name is not scoped: $name"} require('.' in label) {"subroutine name is not scoped: $label"}
require(!name.startsWith("main.main.")) {"subroutine name invalid main prefix: $name"} require(!label.startsWith("main.main.")) {"subroutine name invalid main prefix: $label"}
// params and return value should not be str // params and return value should not be str
require(parameters.all{ it.dt in NumericDatatypes }) {"non-numeric parameter"} require(parameters.all{ it.dt in NumericDatatypes }) {"non-numeric parameter"}
@ -264,37 +291,41 @@ class IRSubroutine(val name: String,
chunks+= chunk 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( class IRAsmSubroutine(
val name: String, override val label: String,
val address: UInt?, val address: UInt?,
val clobbers: Set<CpuRegister>, val clobbers: Set<CpuRegister>,
val parameters: List<IRAsmParam>, val parameters: List<IRAsmParam>,
val returns: List<IRAsmParam>, val returns: List<IRAsmParam>,
val asmChunk: IRInlineAsmChunk, val asmChunk: IRInlineAsmChunk,
val position: Position val position: Position
) { ): IIRBlockElement {
class IRAsmParam(val reg: RegisterOrStatusflag, val dt: DataType) class IRAsmParam(val reg: RegisterOrStatusflag, val dt: DataType)
init { init {
require('.' in name) { "subroutine name is not scoped: $name" } require('.' in label) { "subroutine name is not scoped: $label" }
require(!name.startsWith("main.main.")) { "subroutine name invalid main prefix: $name" } require(!label.startsWith("main.main.")) { "subroutine name invalid main prefix: $label" }
} }
private val registersUsed by lazy { registersUsedInAssembly(asmChunk.isIR, asmChunk.assembly) } private val registersUsed by lazy { registersUsedInAssembly(asmChunk.isIR, asmChunk.assembly) }
fun usedRegisters() = registersUsed 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<IRInstruction>() val instructions = mutableListOf<IRInstruction>()
abstract fun isEmpty(): Boolean abstract override fun isEmpty(): Boolean
abstract fun isNotEmpty(): Boolean abstract override fun isNotEmpty(): Boolean
abstract fun usedRegisters(): RegistersUsed abstract fun usedRegisters(): RegistersUsed
} }

View File

@ -23,7 +23,7 @@ class VmProgramLoader {
// make sure that if there is a "main.start" entrypoint, we jump to it // make sure that if there is a "main.start" entrypoint, we jump to it
irProgram.blocks.firstOrNull()?.let { 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) val chunk = IRCodeChunk(null, null)
placeholders[Pair(chunk, 0)] = "main.start" placeholders[Pair(chunk, 0)] = "main.start"
chunk += IRInstruction(Opcode.JUMP, labelSymbol = "main.start") chunk += IRInstruction(Opcode.JUMP, labelSymbol = "main.start")
@ -37,37 +37,36 @@ class VmProgramLoader {
if(block.address!=null) if(block.address!=null)
throw IRParseException("blocks cannot have a load address for vm: ${block.name}") throw IRParseException("blocks cannot have a load address for vm: ${block.name}")
block.inlineAssemblies.forEach { block.children.forEach { child ->
val replacement = addAssemblyToProgram(it, programChunks, variableAddresses) when(child) {
chunkReplacements += replacement is IRAsmSubroutine -> {
} if(!child.asmChunk.isIR)
block.labels.forEach { throw IRParseException("vm currently does not support non-IR asmsubs: ${child.label}")
programChunks += it // TODO doing it like this isn't useful, block needs to have a list of nodes rather than a few separate collections else {
} val replacement = addAssemblyToProgram(child.asmChunk, programChunks, variableAddresses)
block.subroutines.forEach {
it.chunks.forEach { chunk ->
when (chunk) {
is IRInlineAsmChunk -> {
val replacement = addAssemblyToProgram(chunk, programChunks, variableAddresses)
chunkReplacements += replacement 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) pass2translateSyscalls(programChunks)

View File

@ -42,7 +42,7 @@ class TestVm: FunSpec( {
val program = IRProgram("test", IRSymbolTable(null), getTestOptions(), VMTarget()) val program = IRProgram("test", IRSymbolTable(null), getTestOptions(), VMTarget())
val block = IRBlock("testmain", null, IRBlock.BlockAlignment.NONE, Position.DUMMY) val block = IRBlock("testmain", null, IRBlock.BlockAlignment.NONE, Position.DUMMY)
val startSub = IRSubroutine("testmain.testsub", emptyList(), null, 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.NOP)
code += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=1, value=12345) code += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=1, value=12345)
code += IRInstruction(Opcode.STOREM, IRDataType.WORD, reg1=1, value=1000) 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 program = IRProgram("test", IRSymbolTable(null), getTestOptions(), VMTarget())
val block = IRBlock("testmain", null, IRBlock.BlockAlignment.NONE, Position.DUMMY) val block = IRBlock("testmain", null, IRBlock.BlockAlignment.NONE, Position.DUMMY)
val startSub = IRSubroutine("testmain.testsub", emptyList(), null, 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.BINARYDATA, binaryData = listOf(1u,2u,3u))
code += IRInstruction(Opcode.RETURN) code += IRInstruction(Opcode.RETURN)
startSub += code startSub += code