ir: ensure that block and sub labels are also on the first chunk in said block/sub

This commit is contained in:
Irmen de Jong 2022-10-23 17:29:03 +02:00
parent 76428b16f0
commit 30ee65fd14
13 changed files with 212 additions and 198 deletions

View File

@ -42,7 +42,7 @@ class PtNodeGroup : PtNode(Position.DUMMY) {
} }
abstract class PtNamedNode(val name: String, position: Position): PtNode(position) { sealed class PtNamedNode(val name: String, position: Position): PtNode(position) {
val scopedName: List<String> by lazy { val scopedName: List<String> by lazy {
var namedParent: PtNode = this.parent var namedParent: PtNode = this.parent
if(namedParent is PtProgram) if(namedParent is PtProgram)

View File

@ -1,43 +0,0 @@
package prog8.codegen.intermediate
import prog8.code.core.AssemblyError
import prog8.intermediate.*
internal class IRChunkLinker(private val irprog: IRProgram) {
private val labeledChunks = irprog.blocks.flatMap { it.subroutines }.flatMap { it.chunks }.associateBy { it.label }
fun link() {
irprog.blocks.asSequence().flatMap { it.subroutines }.forEach { sub ->
sub.chunks.withIndex().forEach { (index, chunk) ->
if(chunk is IRCodeChunk) {
// link sequential chunks
val jump = chunk.instructions.lastOrNull()?.opcode
if (jump == null || jump !in setOf(Opcode.JUMP, Opcode.JUMPA, Opcode.RETURN)) {
// no jump at the end, so link to next chunk (if it exists)
if(index<sub.chunks.size-1) {
val nextChunk = sub.chunks[index + 1]
if (nextChunk is IRCodeChunk)
chunk.next = nextChunk
else
throw AssemblyError("code chunk flows into following non-code chunk")
}
}
// link all jump and branching instructions to their target
chunk.instructions.forEach {
if(it.opcode in OpcodesThatBranch && it.opcode!=Opcode.RETURN && it.labelSymbol!=null) {
val targetChunk = labeledChunks.getValue(it.labelSymbol)
require(targetChunk is IRCodeChunk) { "target $targetChunk with label ${targetChunk.label} has to be a code chunk" }
it.branchTarget = targetChunk
}
// note: branches with an address value cannot be linked to something...
}
}
}
}
}
}

View File

@ -49,19 +49,59 @@ class IRCodeGen(
irProg.addBlock(translate(block)) irProg.addBlock(translate(block))
replaceMemoryMappedVars(irProg) replaceMemoryMappedVars(irProg)
IRChunkLinker(irProg).link() ensureFirstChunkLabels(irProg)
irProg.linkChunks()
if(options.optimize) { if(options.optimize) {
val optimizer = IRPeepholeOptimizer(irProg) val optimizer = IRPeepholeOptimizer(irProg)
optimizer.optimize() optimizer.optimize()
IRChunkLinker(irProg).link() // re-link irProg.linkChunks() // re-link
} }
irProg.validate() irProg.validate()
return irProg return irProg
} }
private fun ensureFirstChunkLabels(irProg: IRProgram) {
// make sure that first chunks in Blocks and Subroutines share the name of the block/sub as label.
irProg.blocks.forEach { block ->
if(block.inlineAssembly.isNotEmpty()) {
val first = block.inlineAssembly.first()
if(first.label==null) {
val replacement = IRInlineAsmChunk(block.name, first.assembly, first.isIR, first.position)
block.inlineAssembly.removeAt(0)
block.inlineAssembly.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 ->
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.position, first.next)
replacement.instructions += first.instructions
replacement
}
is IRInlineAsmChunk -> IRInlineAsmChunk(sub.name, first.assembly, first.isIR, first.position)
is IRInlineBinaryChunk -> IRInlineBinaryChunk(sub.name, first.data, first.position)
else -> throw AssemblyError("invalid chunk")
}
sub.chunks.removeAt(0)
sub.chunks.add(0, replacement)
} else if(first.label != sub.name) {
val next = if(first is IRCodeChunk) first else null
sub.chunks.add(0, IRCodeChunk(sub.name, sub.position, next))
}
}
}
}
}
private fun replaceMemoryMappedVars(irProg: IRProgram) { private fun replaceMemoryMappedVars(irProg: IRProgram) {
// replace memory mapped variable symbols with the memory address directly. // replace memory mapped variable symbols with the memory address directly.
// 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
@ -309,7 +349,7 @@ class IRCodeGen(
val labeledFirstChunk = when(val first=chunks[0]) { val labeledFirstChunk = when(val first=chunks[0]) {
is IRCodeChunk -> { is IRCodeChunk -> {
val newChunk = IRCodeChunk(label, first.position, null) val newChunk = IRCodeChunk(label, first.position, null)
first.instructions.forEach { newChunk += it } newChunk.instructions += first.instructions
newChunk newChunk
} }
is IRInlineAsmChunk -> { is IRInlineAsmChunk -> {

View File

@ -2,7 +2,6 @@ import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldBe
import prog8.code.core.* import prog8.code.core.*
import prog8.code.target.VMTarget import prog8.code.target.VMTarget
import prog8.codegen.intermediate.IRChunkLinker
import prog8.codegen.intermediate.IRPeepholeOptimizer import prog8.codegen.intermediate.IRPeepholeOptimizer
import prog8.intermediate.* import prog8.intermediate.*
@ -25,6 +24,7 @@ class TestIRPeepholeOpt: FunSpec({
) )
val prog = IRProgram("test", IRSymbolTable(null), options, target) val prog = IRProgram("test", IRSymbolTable(null), options, target)
prog.addBlock(block) prog.addBlock(block)
prog.linkChunks()
prog.validate() prog.validate()
return prog return prog
} }
@ -44,7 +44,6 @@ class TestIRPeepholeOpt: FunSpec({
IRInstruction(Opcode.NOP) IRInstruction(Opcode.NOP)
)) ))
irProg.chunks().single().instructions.size shouldBe 3 irProg.chunks().single().instructions.size shouldBe 3
IRChunkLinker(irProg).link()
val opt = IRPeepholeOptimizer(irProg) val opt = IRPeepholeOptimizer(irProg)
opt.optimize() opt.optimize()
irProg.chunks().single().instructions.size shouldBe 1 irProg.chunks().single().instructions.size shouldBe 1
@ -64,7 +63,6 @@ class TestIRPeepholeOpt: FunSpec({
irProg.chunks().size shouldBe 4 irProg.chunks().size shouldBe 4
irProg.chunks().flatMap { it.instructions }.size shouldBe 5 irProg.chunks().flatMap { it.instructions }.size shouldBe 5
IRChunkLinker(irProg).link()
val opt = IRPeepholeOptimizer(irProg) val opt = IRPeepholeOptimizer(irProg)
opt.optimize() opt.optimize()
irProg.chunks().size shouldBe 3 irProg.chunks().size shouldBe 3
@ -87,7 +85,6 @@ class TestIRPeepholeOpt: FunSpec({
IRInstruction(Opcode.CLC) IRInstruction(Opcode.CLC)
)) ))
irProg.chunks().single().instructions.size shouldBe 6 irProg.chunks().single().instructions.size shouldBe 6
IRChunkLinker(irProg).link()
val opt = IRPeepholeOptimizer(irProg) val opt = IRPeepholeOptimizer(irProg)
opt.optimize() opt.optimize()
val instr = irProg.chunks().single().instructions val instr = irProg.chunks().single().instructions
@ -103,7 +100,6 @@ class TestIRPeepholeOpt: FunSpec({
IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=222) IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=222)
)) ))
irProg.chunks().single().instructions.size shouldBe 4 irProg.chunks().single().instructions.size shouldBe 4
IRChunkLinker(irProg).link()
val opt = IRPeepholeOptimizer(irProg) val opt = IRPeepholeOptimizer(irProg)
opt.optimize() opt.optimize()
val instr = irProg.chunks().single().instructions val instr = irProg.chunks().single().instructions
@ -127,7 +123,6 @@ class TestIRPeepholeOpt: FunSpec({
IRInstruction(Opcode.SUB, IRDataType.BYTE, reg1=42, value = 0) IRInstruction(Opcode.SUB, IRDataType.BYTE, reg1=42, value = 0)
)) ))
irProg.chunks().single().instructions.size shouldBe 10 irProg.chunks().single().instructions.size shouldBe 10
IRChunkLinker(irProg).link()
val opt = IRPeepholeOptimizer(irProg) val opt = IRPeepholeOptimizer(irProg)
opt.optimize() opt.optimize()
irProg.chunks().single().instructions.size shouldBe 4 irProg.chunks().single().instructions.size shouldBe 4
@ -139,7 +134,6 @@ class TestIRPeepholeOpt: FunSpec({
IRInstruction(Opcode.SUB, IRDataType.BYTE, reg1=42, value = 1) IRInstruction(Opcode.SUB, IRDataType.BYTE, reg1=42, value = 1)
)) ))
irProg.chunks().single().instructions.size shouldBe 2 irProg.chunks().single().instructions.size shouldBe 2
IRChunkLinker(irProg).link()
val opt = IRPeepholeOptimizer(irProg) val opt = IRPeepholeOptimizer(irProg)
opt.optimize() opt.optimize()
val instr = irProg.chunks().single().instructions val instr = irProg.chunks().single().instructions
@ -160,7 +154,6 @@ class TestIRPeepholeOpt: FunSpec({
IRInstruction(Opcode.XOR, IRDataType.BYTE, reg1=42, value = 1) IRInstruction(Opcode.XOR, IRDataType.BYTE, reg1=42, value = 1)
)) ))
irProg.chunks().single().instructions.size shouldBe 8 irProg.chunks().single().instructions.size shouldBe 8
IRChunkLinker(irProg).link()
val opt = IRPeepholeOptimizer(irProg) val opt = IRPeepholeOptimizer(irProg)
opt.optimize() opt.optimize()
irProg.chunks().single().instructions.size shouldBe 4 irProg.chunks().single().instructions.size shouldBe 4
@ -174,7 +167,6 @@ class TestIRPeepholeOpt: FunSpec({
IRInstruction(Opcode.OR, IRDataType.WORD, reg1=42, value = 65535) IRInstruction(Opcode.OR, IRDataType.WORD, reg1=42, value = 65535)
)) ))
irProg.chunks().single().instructions.size shouldBe 4 irProg.chunks().single().instructions.size shouldBe 4
IRChunkLinker(irProg).link()
val opt = IRPeepholeOptimizer(irProg) val opt = IRPeepholeOptimizer(irProg)
opt.optimize() opt.optimize()
val instr = irProg.chunks().single().instructions val instr = irProg.chunks().single().instructions

View File

@ -3,13 +3,13 @@ TODO
For next release For next release
^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^
- ir: check that block and sub labels are also on the first chunk in said block/sub - vm: program is list of chunks, fix dispatcher
- ir: link all sequential chunks to another (exiting one chunk 'calls' the next chunk) - ir: see TODO replace IM syscalls by their VM Syscall equivalent
- ir: fix JUMP, RETURN and all branching instructions to reference a chunk + index instead of just a single programcounter index - ir: fix JUMP, RETURN and all branching instructions to reference a chunk + index instead of just a single programcounter index
- ir: fix removeWeirdBranches in IR optimizer - ir: fix removeWeirdBranches in IR optimizer
- ir: fix unit tests - ir: fix unit tests
- ir: fix joinChunks() in the IR optimizer - ir: fix joinChunks() in the IR optimizer - there are WAY too many chunks with 1 instruction in them only
- vm: program is list of chunks, fix dispatcher - ir: next in IRCodeChunk can also be a Asm Chunk which can have next as well
- add cx16diskio.vload_raw() to load headerless files into vram - add cx16diskio.vload_raw() to load headerless files into vram
... ...

View File

@ -44,6 +44,7 @@ class IRFileReader {
program.addGlobalInits(initGlobals) program.addGlobalInits(initGlobals)
blocks.forEach{ program.addBlock(it) } blocks.forEach{ program.addBlock(it) }
program.linkChunks()
program.validate() program.validate()
return program return program
} }

View File

@ -25,10 +25,8 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
out.write("\n<INITGLOBALS>\n") out.write("\n<INITGLOBALS>\n")
if(!irProgram.options.dontReinitGlobals) { if(!irProgram.options.dontReinitGlobals) {
out.write("<C>\n")
// note: this a block of code that loads values and stores them into the global variables to reset their values. // note: this a block of code that loads values and stores them into the global variables to reset their values.
irProgram.globalInits.forEach { out.write(it.toString()) } writeCodeChunk(irProgram.globalInits)
out.write("</C>\n")
} }
out.write("</INITGLOBALS>\n") out.write("</INITGLOBALS>\n")
writeBlocks() writeBlocks()
@ -63,18 +61,8 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
when (chunk) { when (chunk) {
is IRInlineAsmChunk -> writeInlineAsm(chunk) is IRInlineAsmChunk -> writeInlineAsm(chunk)
is IRInlineBinaryChunk -> writeInlineBytes(chunk) is IRInlineBinaryChunk -> writeInlineBytes(chunk)
else -> { is IRCodeChunk -> writeCodeChunk(chunk)
if(chunk.label!=null) else -> throw InternalCompilerException("invalid chunk")
out.write("<C LABEL=${chunk.label}>\n")
else
out.write("<C>\n")
chunk.instructions.forEach { instr ->
numInstr++
out.write(instr.toString())
out.write("\n")
}
out.write("</C>\n")
}
} }
} }
out.write("</SUB>\n") out.write("</SUB>\n")
@ -101,6 +89,19 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
} }
} }
private fun writeCodeChunk(chunk: IRCodeChunk) {
if(chunk.label!=null)
out.write("<C LABEL=${chunk.label}>\n")
else
out.write("<C>\n")
chunk.instructions.forEach { instr ->
numInstr++
out.write(instr.toString())
out.write("\n")
}
out.write("</C>\n")
}
private fun writeInlineBytes(chunk: IRInlineBinaryChunk) { private fun writeInlineBytes(chunk: IRInlineBinaryChunk) {
out.write("<BYTES LABEL=${chunk.label ?: ""} POS=${chunk.position}>\n") out.write("<BYTES LABEL=${chunk.label ?: ""} POS=${chunk.position}>\n")
chunk.data.withIndex().forEach {(index, byte) -> chunk.data.withIndex().forEach {(index, byte) ->

View File

@ -353,60 +353,6 @@ enum class Opcode {
BINARYDATA BINARYDATA
} }
val OpcodesWithAddress = setOf(
Opcode.LOADM,
Opcode.LOADX,
Opcode.LOADIX,
Opcode.STOREM,
Opcode.STOREX,
Opcode.STOREIX,
Opcode.STOREZM,
Opcode.STOREZX,
Opcode.JUMP,
Opcode.JUMPA,
Opcode.CALL,
Opcode.INCM,
Opcode.DECM,
Opcode.NEGM,
Opcode.ADDM,
Opcode.SUBM,
Opcode.MULM,
Opcode.DIVM,
Opcode.DIVSM,
Opcode.INVM,
Opcode.ORM,
Opcode.XORM,
Opcode.ANDM,
Opcode.ASRM,
Opcode.LSRM,
Opcode.LSLM,
Opcode.LSLNM,
Opcode.LSRNM,
Opcode.ASRNM,
Opcode.ROLM,
Opcode.RORM,
Opcode.ROXLM,
Opcode.ROXRM,
Opcode.BSTCC,
Opcode.BSTCS,
Opcode.BSTEQ,
Opcode.BSTNE,
Opcode.BSTNEG,
Opcode.BSTPOS,
Opcode.BZ,
Opcode.BNZ,
Opcode.BEQ,
Opcode.BNE,
Opcode.BLT,
Opcode.BLTS,
Opcode.BLE,
Opcode.BLES,
Opcode.BGT,
Opcode.BGTS,
Opcode.BGE,
Opcode.BGES
)
val OpcodesThatBranch = setOf( val OpcodesThatBranch = setOf(
Opcode.JUMP, Opcode.JUMP,
Opcode.JUMPA, Opcode.JUMPA,
@ -681,7 +627,7 @@ data class IRInstruction(
require(reg2==null || reg2 in 0..65536) {"reg2 out of bounds"} require(reg2==null || reg2 in 0..65536) {"reg2 out of bounds"}
require(fpReg1==null || fpReg1 in 0..65536) {"fpReg1 out of bounds"} require(fpReg1==null || fpReg1 in 0..65536) {"fpReg1 out of bounds"}
require(fpReg2==null || fpReg2 in 0..65536) {"fpReg2 out of bounds"} require(fpReg2==null || fpReg2 in 0..65536) {"fpReg2 out of bounds"}
if(value!=null && opcode !in OpcodesWithAddress) { if(value!=null) {
when (type) { when (type) {
IRDataType.BYTE -> require(value in -128..255) {"value out of range for byte: $value"} IRDataType.BYTE -> require(value in -128..255) {"value out of range for byte: $value"}
IRDataType.WORD -> require(value in -32768..65535) {"value out of range for word: $value"} IRDataType.WORD -> require(value in -32768..65535) {"value out of range for word: $value"}

View File

@ -52,10 +52,13 @@ class IRProgram(val name: String,
val encoding: IStringEncoding) { val encoding: IStringEncoding) {
val asmSymbols = mutableMapOf<String, String>() val asmSymbols = mutableMapOf<String, String>()
val globalInits = mutableListOf<IRInstruction>() val globalInits = IRCodeChunk(null, Position.DUMMY, null)
val blocks = mutableListOf<IRBlock>() val blocks = mutableListOf<IRBlock>()
fun addGlobalInits(chunk: IRCodeChunk) = globalInits.addAll(chunk.instructions) fun addGlobalInits(chunk: IRCodeChunk) {
globalInits += chunk
}
fun addBlock(block: IRBlock) { fun addBlock(block: IRBlock) {
require(blocks.all { it.name != block.name}) { "duplicate block ${block.name} ${block.position}" } require(blocks.all { it.name != block.name}) { "duplicate block ${block.name} ${block.position}" }
blocks.add(block) blocks.add(block)
@ -65,15 +68,84 @@ class IRProgram(val name: String,
asmSymbols += symbolDefs asmSymbols += symbolDefs
} }
fun linkChunks() {
val labeledChunks = blocks.flatMap { it.subroutines }.flatMap { it.chunks }.associateBy { it.label }
if(globalInits.isNotEmpty()) {
if(globalInits.next==null) {
val firstBlock = blocks.firstOrNull()
if(firstBlock!=null) {
if(firstBlock.inlineAssembly.isNotEmpty()) {
TODO("link to inline assembly block")
// irprog.globalInits.next = firstBlock.inlineAssembly.first()
} else if(firstBlock.subroutines.isNotEmpty()) {
val firstSub = firstBlock.subroutines.first()
if(firstSub.chunks.isNotEmpty()) {
val firstChunk = firstSub.chunks.first()
when(firstChunk) {
is IRCodeChunk -> globalInits.next = firstChunk
else -> TODO("link to other type of chunk")
}
}
}
}
}
}
blocks.asSequence().flatMap { it.subroutines }.forEach { sub ->
sub.chunks.withIndex().forEach { (index, chunk) ->
when (chunk) {
is IRCodeChunk -> {
// link sequential chunks
val jump = chunk.instructions.lastOrNull()?.opcode
if (jump == null || jump !in setOf(Opcode.JUMP, Opcode.JUMPA, Opcode.RETURN)) {
// no jump at the end, so link to next chunk (if it exists)
if(index<sub.chunks.size-1) {
val nextChunk = sub.chunks[index + 1]
if (nextChunk is IRCodeChunk)
chunk.next = nextChunk
else
throw AssemblyError("code chunk flows into following non-code chunk")
} else {
TODO("???")
}
}
// link all jump and branching instructions to their target
chunk.instructions.forEach {
if(it.opcode in OpcodesThatBranch && it.opcode!=Opcode.RETURN && it.labelSymbol!=null) {
val targetChunk = labeledChunks.getValue(it.labelSymbol)
require(targetChunk is IRCodeChunk) { "target $targetChunk with label ${targetChunk.label} has to be a code chunk" }
it.branchTarget = targetChunk
}
// note: branches with an address value cannot be linked to something...
}
}
is IRInlineAsmChunk -> {
// TODO("link next of asm chunk")
}
is IRInlineBinaryChunk -> {
// TODO("link next of binary chunk")
}
else -> throw AssemblyError("invalid chunk")
}
}
}
}
fun validate() { fun validate() {
blocks.forEach { block ->
// TODO: check that block and sub labels are also on the first chunk in said block/sub if(block.inlineAssembly.isNotEmpty()) {
require(block.inlineAssembly.first().label == block.name) { "first block chunk should have block name as its label" }
blocks.forEach { }
it.inlineAssembly.forEach { chunk -> block.inlineAssembly.forEach { chunk ->
require(chunk.instructions.isEmpty()) require(chunk.instructions.isEmpty())
} }
it.subroutines.forEach { sub -> 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 -> sub.chunks.forEach { chunk ->
if (chunk is IRCodeChunk) require(chunk.instructions.isNotEmpty() || chunk.label!=null) if (chunk is IRCodeChunk) require(chunk.instructions.isNotEmpty() || chunk.label!=null)
else require(chunk.instructions.isEmpty()) else require(chunk.instructions.isEmpty())
@ -95,7 +167,7 @@ class IRProgram(val name: String,
usedRegisters.outputFpRegs.forEach{ (reg, count) -> outputFpRegs[reg] = outputFpRegs.getValue(reg) + count } usedRegisters.outputFpRegs.forEach{ (reg, count) -> outputFpRegs[reg] = outputFpRegs.getValue(reg) + count }
} }
globalInits.forEach { it.addUsedRegistersCounts(inputRegs, outputRegs, inputFpRegs, outputFpRegs) } globalInits.instructions.forEach { it.addUsedRegistersCounts(inputRegs, outputRegs, inputFpRegs, outputFpRegs) }
blocks.forEach { blocks.forEach {
it.inlineAssembly.forEach { chunk -> addUsed(chunk.usedRegisters()) } it.inlineAssembly.forEach { chunk -> addUsed(chunk.usedRegisters()) }
it.subroutines.flatMap { sub->sub.chunks }.forEach { chunk -> addUsed(chunk.usedRegisters()) } it.subroutines.flatMap { sub->sub.chunks }.forEach { chunk -> addUsed(chunk.usedRegisters()) }
@ -112,9 +184,9 @@ class IRBlock(
val alignment: BlockAlignment, val alignment: BlockAlignment,
val position: Position val position: Position
) { ) {
val inlineAssembly = mutableListOf<IRInlineAsmChunk>()
val subroutines = mutableListOf<IRSubroutine>() val subroutines = mutableListOf<IRSubroutine>()
val asmSubroutines = mutableListOf<IRAsmSubroutine>() val asmSubroutines = mutableListOf<IRAsmSubroutine>()
val inlineAssembly = mutableListOf<IRInlineAsmChunk>()
enum class BlockAlignment { enum class BlockAlignment {
NONE, NONE,
@ -177,7 +249,7 @@ class IRAsmSubroutine(
fun usedRegisters() = registersUsed fun usedRegisters() = registersUsed
} }
abstract class IRCodeChunkBase(val label: String?, val position: Position) { sealed class IRCodeChunkBase(val label: String?, val position: Position) {
val instructions = mutableListOf<IRInstruction>() val instructions = mutableListOf<IRInstruction>()
abstract fun isEmpty(): Boolean abstract fun isEmpty(): Boolean
@ -187,7 +259,7 @@ abstract class IRCodeChunkBase(val label: String?, val position: Position) {
class IRCodeChunk(label: String?, class IRCodeChunk(label: String?,
position: Position, position: Position,
var next: IRCodeChunk?): IRCodeChunkBase(label, position) { var next: IRCodeChunk?): IRCodeChunkBase(label, position) { // TODO next can also be InlineAsmChunk!! which can also have a next again.
override fun isEmpty() = instructions.isEmpty() override fun isEmpty() = instructions.isEmpty()
override fun isNotEmpty() = instructions.isNotEmpty() override fun isNotEmpty() = instructions.isNotEmpty()

View File

@ -206,7 +206,7 @@ fun parseIRCodeLine(line: String, location: Pair<IRCodeChunk, Int>?, placeholder
throw IRParseException("invalid reg1 for $line") throw IRParseException("invalid reg1 for $line")
if(format.reg2==OperandDirection.UNUSED && reg2!=null) if(format.reg2==OperandDirection.UNUSED && reg2!=null)
throw IRParseException("invalid reg2 for $line") throw IRParseException("invalid reg2 for $line")
if(value!=null && opcode !in OpcodesWithAddress) { if(value!=null) {
when (type) { when (type) {
IRDataType.BYTE -> { IRDataType.BYTE -> {
if (value < -128 || value > 255) if (value < -128 || value > 255)

View File

@ -75,7 +75,7 @@ load.b r1,42
<SUB NAME=main.start RETURNTYPE=null POS=[examples/test.p8: line 4 col 6-8]> <SUB NAME=main.start RETURNTYPE=null POS=[examples/test.p8: line 4 col 6-8]>
<PARAMS> <PARAMS>
</PARAMS> </PARAMS>
<C> <C LABEL=main.start>
return return
</C> </C>
</SUB> </SUB>
@ -86,7 +86,7 @@ return
<PARAMS> <PARAMS>
uword sys.wait.jiffies uword sys.wait.jiffies
</PARAMS> </PARAMS>
<INLINEASM LABEL= IR=true POS=[library:/prog8lib/virtual/syslib.p8: line 17 col 10-13]> <INLINEASM LABEL=sys.wait IR=true POS=[library:/prog8lib/virtual/syslib.p8: line 17 col 10-13]>
loadm.w r0,sys.wait.jiffies loadm.w r0,sys.wait.jiffies
syscall 13 syscall 13
</INLINEASM> </INLINEASM>

View File

@ -117,10 +117,12 @@ class VirtualMachine(irProgram: IRProgram) {
} }
} }
private inline fun nextPc() { private fun nextPc() {
pcIndex ++ pcIndex ++
if(pcIndex>=pcChunk.instructions.size) { if(pcIndex>=pcChunk.instructions.size) {
pcIndex = 0 pcIndex = 0
if(pcChunk.next==null)
TODO("huh no next chunk in $pcChunk")
pcChunk = pcChunk.next!! pcChunk = pcChunk.next!!
} }
} }

View File

@ -1,5 +1,6 @@
package prog8.vm package prog8.vm
import prog8.code.core.AssemblyError
import prog8.code.core.DataType import prog8.code.core.DataType
import prog8.code.core.Position import prog8.code.core.Position
import prog8.intermediate.* import prog8.intermediate.*
@ -11,14 +12,14 @@ class VmProgramLoader {
placeholders.clear() placeholders.clear()
irProgram.validate() irProgram.validate()
val allocations = VmVariableAllocator(irProgram.st, irProgram.encoding, irProgram.options.compTarget) val allocations = VmVariableAllocator(irProgram.st, irProgram.encoding, irProgram.options.compTarget)
val symbolAddresses = allocations.allocations.toMutableMap() val variableAddresses = allocations.allocations.toMutableMap()
val programChunks = mutableListOf<IRCodeChunk>() val programChunks = mutableListOf<IRCodeChunk>()
varsToMemory(irProgram, allocations, symbolAddresses, memory) varsToMemory(irProgram, allocations, variableAddresses, memory)
if(!irProgram.options.dontReinitGlobals) { if(!irProgram.options.dontReinitGlobals) {
if(irProgram.globalInits.isNotEmpty()) if(irProgram.globalInits.isNotEmpty())
addToProgram(irProgram.globalInits, programChunks, symbolAddresses) programChunks += irProgram.globalInits
} }
// 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
@ -31,18 +32,19 @@ class VmProgramLoader {
} }
} }
// TODO load rest of the program // load rest of the program into the list
irProgram.blocks.forEach { block -> irProgram.blocks.forEach { block ->
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.inlineAssembly.forEach { addAssemblyToProgram(it, programChunks, symbolAddresses) } block.inlineAssembly.forEach { addAssemblyToProgram(it, programChunks, variableAddresses) }
block.subroutines.forEach { block.subroutines.forEach {
it.chunks.forEach { chunk -> it.chunks.forEach { chunk ->
when (chunk) { when (chunk) {
is IRInlineAsmChunk -> addAssemblyToProgram(chunk, programChunks, symbolAddresses) is IRInlineAsmChunk -> addAssemblyToProgram(chunk, programChunks, variableAddresses)
is IRInlineBinaryChunk -> TODO("inline binary data not yet supported in the VM") is IRInlineBinaryChunk -> TODO("inline binary data not yet supported in the VM")
else -> addToProgram(chunk.instructions, programChunks, symbolAddresses) is IRCodeChunk -> programChunks += chunk
else -> throw AssemblyError("weird chunk type")
} }
} }
} }
@ -50,33 +52,34 @@ class VmProgramLoader {
throw IRParseException("vm currently does not support asmsubs: ${block.asmSubroutines.first().name}") throw IRParseException("vm currently does not support asmsubs: ${block.asmSubroutines.first().name}")
} }
pass2replaceLabelsByProgIndex(programChunks, symbolAddresses) pass2replaceLabelsByProgIndex(programChunks, variableAddresses)
programChunks.asSequence().flatMap { it.instructions }.forEach {
if(it.opcode in OpcodesWithAddress && it.value==null) {
throw IRParseException("instruction missing numeric value, label not replaced? $it")
}
}
return programChunks return programChunks
} }
private fun pass2replaceLabelsByProgIndex( private fun pass2replaceLabelsByProgIndex(
chunks: MutableList<IRCodeChunk>, chunks: MutableList<IRCodeChunk>,
symbolAddresses: MutableMap<String, Int> variableAddresses: MutableMap<String, Int>
) { ) {
for((ref, label) in placeholders) { for((ref, label) in placeholders) {
val (chunk, line) = ref val (chunk, line) = ref
val replacement = symbolAddresses[label] val replacement = variableAddresses[label]
if(replacement==null) { if(replacement==null) {
// it could be an address + index: symbol+42 // it could be an address + index: symbol+42
if('+' in label) { if('+' in label) {
val (symbol, indexStr) = label.split('+') val (symbol, indexStr) = label.split('+')
val index = indexStr.toInt() val index = indexStr.toInt()
val address = symbolAddresses.getValue(symbol) + index val address = variableAddresses.getValue(symbol) + index
chunk.instructions[line] = chunk.instructions[line].copy(value = address) chunk.instructions[line] = chunk.instructions[line].copy(value = address)
} else { } else {
throw IRParseException("placeholder not found in labels: $label") // placeholder is not a variable, so it must be a label of a code chunk instead
val target: IRCodeChunk? = chunks.firstOrNull { it.label==label }
if(target==null)
throw IRParseException("placeholder not found in variables nor labels: $label")
else {
require(chunk.instructions[line].opcode in OpcodesThatBranch)
chunk.instructions[line] = chunk.instructions[line].copy(branchTarget = target, value = null)
}
} }
} else { } else {
chunk.instructions[line] = chunk.instructions[line].copy(value = replacement) chunk.instructions[line] = chunk.instructions[line].copy(value = replacement)
@ -84,39 +87,39 @@ class VmProgramLoader {
} }
} }
private fun addToProgram( // TODO replace IM syscalls by their VM Syscall equivalent
instructions: Iterable<IRInstruction>, // private fun addToProgram(
program: MutableList<IRCodeChunk>, // instructions: Iterable<IRInstruction>,
symbolAddresses: MutableMap<String, Int> // program: MutableList<IRCodeChunk>
) { // ) {
val chunk = IRCodeChunk(null, Position.DUMMY, null) // val chunk = IRCodeChunk(null, Position.DUMMY, null)
instructions.map { // instructions.map {
it.labelSymbol?.let { symbol -> placeholders[Pair(chunk, chunk.instructions.size)]=symbol } // it.labelSymbol?.let { symbol -> placeholders[Pair(chunk, chunk.instructions.size)]=symbol }
if(it.opcode==Opcode.SYSCALL) { // if(it.opcode==Opcode.SYSCALL) {
// convert IR Syscall to VM Syscall // // convert IR Syscall to VM Syscall
val vmSyscall = when(it.value!!) { // val vmSyscall = when(it.value!!) {
IMSyscall.SORT_UBYTE.ordinal -> Syscall.SORT_UBYTE // IMSyscall.SORT_UBYTE.ordinal -> Syscall.SORT_UBYTE
IMSyscall.SORT_BYTE.ordinal -> Syscall.SORT_BYTE // IMSyscall.SORT_BYTE.ordinal -> Syscall.SORT_BYTE
IMSyscall.SORT_UWORD.ordinal -> Syscall.SORT_UWORD // IMSyscall.SORT_UWORD.ordinal -> Syscall.SORT_UWORD
IMSyscall.SORT_WORD.ordinal -> Syscall.SORT_WORD // IMSyscall.SORT_WORD.ordinal -> Syscall.SORT_WORD
IMSyscall.ANY_BYTE.ordinal -> Syscall.ANY_BYTE // IMSyscall.ANY_BYTE.ordinal -> Syscall.ANY_BYTE
IMSyscall.ANY_WORD.ordinal -> Syscall.ANY_WORD // IMSyscall.ANY_WORD.ordinal -> Syscall.ANY_WORD
IMSyscall.ANY_FLOAT.ordinal -> Syscall.ANY_FLOAT // IMSyscall.ANY_FLOAT.ordinal -> Syscall.ANY_FLOAT
IMSyscall.ALL_BYTE.ordinal -> Syscall.ALL_BYTE // IMSyscall.ALL_BYTE.ordinal -> Syscall.ALL_BYTE
IMSyscall.ALL_WORD.ordinal -> Syscall.ALL_WORD // IMSyscall.ALL_WORD.ordinal -> Syscall.ALL_WORD
IMSyscall.ALL_FLOAT.ordinal -> Syscall.ALL_FLOAT // IMSyscall.ALL_FLOAT.ordinal -> Syscall.ALL_FLOAT
IMSyscall.REVERSE_BYTES.ordinal -> Syscall.REVERSE_BYTES // IMSyscall.REVERSE_BYTES.ordinal -> Syscall.REVERSE_BYTES
IMSyscall.REVERSE_WORDS.ordinal -> Syscall.REVERSE_WORDS // IMSyscall.REVERSE_WORDS.ordinal -> Syscall.REVERSE_WORDS
IMSyscall.REVERSE_FLOATS.ordinal -> Syscall.REVERSE_FLOATS // IMSyscall.REVERSE_FLOATS.ordinal -> Syscall.REVERSE_FLOATS
else -> throw IRParseException("invalid IM syscall number $it") // else -> throw IRParseException("invalid IM syscall number $it")
} // }
chunk += it.copy(value=vmSyscall.ordinal) // chunk += it.copy(value=vmSyscall.ordinal)
} else { // } else {
chunk += it // chunk += it
} // }
} // }
program += chunk // program += chunk
} // }
private fun varsToMemory( private fun varsToMemory(
program: IRProgram, program: IRProgram,