mirror of
https://github.com/irmen/prog8.git
synced 2024-09-07 03:54:27 +00:00
ir: ensure that block and sub labels are also on the first chunk in said block/sub
This commit is contained in:
parent
76428b16f0
commit
30ee65fd14
@ -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 {
|
||||
var namedParent: PtNode = this.parent
|
||||
if(namedParent is PtProgram)
|
||||
|
@ -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...
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -49,19 +49,59 @@ class IRCodeGen(
|
||||
irProg.addBlock(translate(block))
|
||||
|
||||
replaceMemoryMappedVars(irProg)
|
||||
IRChunkLinker(irProg).link()
|
||||
ensureFirstChunkLabels(irProg)
|
||||
irProg.linkChunks()
|
||||
|
||||
if(options.optimize) {
|
||||
val optimizer = IRPeepholeOptimizer(irProg)
|
||||
optimizer.optimize()
|
||||
IRChunkLinker(irProg).link() // re-link
|
||||
irProg.linkChunks() // re-link
|
||||
}
|
||||
|
||||
irProg.validate()
|
||||
|
||||
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) {
|
||||
// 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
|
||||
@ -309,7 +349,7 @@ class IRCodeGen(
|
||||
val labeledFirstChunk = when(val first=chunks[0]) {
|
||||
is IRCodeChunk -> {
|
||||
val newChunk = IRCodeChunk(label, first.position, null)
|
||||
first.instructions.forEach { newChunk += it }
|
||||
newChunk.instructions += first.instructions
|
||||
newChunk
|
||||
}
|
||||
is IRInlineAsmChunk -> {
|
||||
|
@ -2,7 +2,6 @@ import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.VMTarget
|
||||
import prog8.codegen.intermediate.IRChunkLinker
|
||||
import prog8.codegen.intermediate.IRPeepholeOptimizer
|
||||
import prog8.intermediate.*
|
||||
|
||||
@ -25,6 +24,7 @@ class TestIRPeepholeOpt: FunSpec({
|
||||
)
|
||||
val prog = IRProgram("test", IRSymbolTable(null), options, target)
|
||||
prog.addBlock(block)
|
||||
prog.linkChunks()
|
||||
prog.validate()
|
||||
return prog
|
||||
}
|
||||
@ -44,7 +44,6 @@ class TestIRPeepholeOpt: FunSpec({
|
||||
IRInstruction(Opcode.NOP)
|
||||
))
|
||||
irProg.chunks().single().instructions.size shouldBe 3
|
||||
IRChunkLinker(irProg).link()
|
||||
val opt = IRPeepholeOptimizer(irProg)
|
||||
opt.optimize()
|
||||
irProg.chunks().single().instructions.size shouldBe 1
|
||||
@ -64,7 +63,6 @@ class TestIRPeepholeOpt: FunSpec({
|
||||
|
||||
irProg.chunks().size shouldBe 4
|
||||
irProg.chunks().flatMap { it.instructions }.size shouldBe 5
|
||||
IRChunkLinker(irProg).link()
|
||||
val opt = IRPeepholeOptimizer(irProg)
|
||||
opt.optimize()
|
||||
irProg.chunks().size shouldBe 3
|
||||
@ -87,7 +85,6 @@ class TestIRPeepholeOpt: FunSpec({
|
||||
IRInstruction(Opcode.CLC)
|
||||
))
|
||||
irProg.chunks().single().instructions.size shouldBe 6
|
||||
IRChunkLinker(irProg).link()
|
||||
val opt = IRPeepholeOptimizer(irProg)
|
||||
opt.optimize()
|
||||
val instr = irProg.chunks().single().instructions
|
||||
@ -103,7 +100,6 @@ class TestIRPeepholeOpt: FunSpec({
|
||||
IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=222)
|
||||
))
|
||||
irProg.chunks().single().instructions.size shouldBe 4
|
||||
IRChunkLinker(irProg).link()
|
||||
val opt = IRPeepholeOptimizer(irProg)
|
||||
opt.optimize()
|
||||
val instr = irProg.chunks().single().instructions
|
||||
@ -127,7 +123,6 @@ class TestIRPeepholeOpt: FunSpec({
|
||||
IRInstruction(Opcode.SUB, IRDataType.BYTE, reg1=42, value = 0)
|
||||
))
|
||||
irProg.chunks().single().instructions.size shouldBe 10
|
||||
IRChunkLinker(irProg).link()
|
||||
val opt = IRPeepholeOptimizer(irProg)
|
||||
opt.optimize()
|
||||
irProg.chunks().single().instructions.size shouldBe 4
|
||||
@ -139,7 +134,6 @@ class TestIRPeepholeOpt: FunSpec({
|
||||
IRInstruction(Opcode.SUB, IRDataType.BYTE, reg1=42, value = 1)
|
||||
))
|
||||
irProg.chunks().single().instructions.size shouldBe 2
|
||||
IRChunkLinker(irProg).link()
|
||||
val opt = IRPeepholeOptimizer(irProg)
|
||||
opt.optimize()
|
||||
val instr = irProg.chunks().single().instructions
|
||||
@ -160,7 +154,6 @@ class TestIRPeepholeOpt: FunSpec({
|
||||
IRInstruction(Opcode.XOR, IRDataType.BYTE, reg1=42, value = 1)
|
||||
))
|
||||
irProg.chunks().single().instructions.size shouldBe 8
|
||||
IRChunkLinker(irProg).link()
|
||||
val opt = IRPeepholeOptimizer(irProg)
|
||||
opt.optimize()
|
||||
irProg.chunks().single().instructions.size shouldBe 4
|
||||
@ -174,7 +167,6 @@ class TestIRPeepholeOpt: FunSpec({
|
||||
IRInstruction(Opcode.OR, IRDataType.WORD, reg1=42, value = 65535)
|
||||
))
|
||||
irProg.chunks().single().instructions.size shouldBe 4
|
||||
IRChunkLinker(irProg).link()
|
||||
val opt = IRPeepholeOptimizer(irProg)
|
||||
opt.optimize()
|
||||
val instr = irProg.chunks().single().instructions
|
||||
|
@ -3,13 +3,13 @@ TODO
|
||||
|
||||
For next release
|
||||
^^^^^^^^^^^^^^^^
|
||||
- ir: check that block and sub labels are also on the first chunk in said block/sub
|
||||
- ir: link all sequential chunks to another (exiting one chunk 'calls' the next chunk)
|
||||
- vm: program is list of chunks, fix dispatcher
|
||||
- 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 removeWeirdBranches in IR optimizer
|
||||
- ir: fix unit tests
|
||||
- ir: fix joinChunks() in the IR optimizer
|
||||
- vm: program is list of chunks, fix dispatcher
|
||||
- ir: fix joinChunks() in the IR optimizer - there are WAY too many chunks with 1 instruction in them only
|
||||
- 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
|
||||
|
||||
...
|
||||
|
@ -44,6 +44,7 @@ class IRFileReader {
|
||||
program.addGlobalInits(initGlobals)
|
||||
blocks.forEach{ program.addBlock(it) }
|
||||
|
||||
program.linkChunks()
|
||||
program.validate()
|
||||
return program
|
||||
}
|
||||
|
@ -25,10 +25,8 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
|
||||
|
||||
out.write("\n<INITGLOBALS>\n")
|
||||
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.
|
||||
irProgram.globalInits.forEach { out.write(it.toString()) }
|
||||
out.write("</C>\n")
|
||||
writeCodeChunk(irProgram.globalInits)
|
||||
}
|
||||
out.write("</INITGLOBALS>\n")
|
||||
writeBlocks()
|
||||
@ -63,18 +61,8 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
|
||||
when (chunk) {
|
||||
is IRInlineAsmChunk -> writeInlineAsm(chunk)
|
||||
is IRInlineBinaryChunk -> writeInlineBytes(chunk)
|
||||
else -> {
|
||||
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")
|
||||
}
|
||||
is IRCodeChunk -> writeCodeChunk(chunk)
|
||||
else -> throw InternalCompilerException("invalid chunk")
|
||||
}
|
||||
}
|
||||
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) {
|
||||
out.write("<BYTES LABEL=${chunk.label ?: ""} POS=${chunk.position}>\n")
|
||||
chunk.data.withIndex().forEach {(index, byte) ->
|
||||
|
@ -353,60 +353,6 @@ enum class Opcode {
|
||||
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(
|
||||
Opcode.JUMP,
|
||||
Opcode.JUMPA,
|
||||
@ -681,7 +627,7 @@ data class IRInstruction(
|
||||
require(reg2==null || reg2 in 0..65536) {"reg2 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"}
|
||||
if(value!=null && opcode !in OpcodesWithAddress) {
|
||||
if(value!=null) {
|
||||
when (type) {
|
||||
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"}
|
||||
|
@ -52,10 +52,13 @@ class IRProgram(val name: String,
|
||||
val encoding: IStringEncoding) {
|
||||
|
||||
val asmSymbols = mutableMapOf<String, String>()
|
||||
val globalInits = mutableListOf<IRInstruction>()
|
||||
val globalInits = IRCodeChunk(null, Position.DUMMY, null)
|
||||
val blocks = mutableListOf<IRBlock>()
|
||||
|
||||
fun addGlobalInits(chunk: IRCodeChunk) = globalInits.addAll(chunk.instructions)
|
||||
fun addGlobalInits(chunk: IRCodeChunk) {
|
||||
globalInits += chunk
|
||||
}
|
||||
|
||||
fun addBlock(block: IRBlock) {
|
||||
require(blocks.all { it.name != block.name}) { "duplicate block ${block.name} ${block.position}" }
|
||||
blocks.add(block)
|
||||
@ -65,15 +68,84 @@ class IRProgram(val name: String,
|
||||
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() {
|
||||
|
||||
// TODO: check that block and sub labels are also on the first chunk in said block/sub
|
||||
|
||||
blocks.forEach {
|
||||
it.inlineAssembly.forEach { chunk ->
|
||||
blocks.forEach { block ->
|
||||
if(block.inlineAssembly.isNotEmpty()) {
|
||||
require(block.inlineAssembly.first().label == block.name) { "first block chunk should have block name as its label" }
|
||||
}
|
||||
block.inlineAssembly.forEach { chunk ->
|
||||
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 ->
|
||||
if (chunk is IRCodeChunk) require(chunk.instructions.isNotEmpty() || chunk.label!=null)
|
||||
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 }
|
||||
}
|
||||
|
||||
globalInits.forEach { it.addUsedRegistersCounts(inputRegs, outputRegs, inputFpRegs, outputFpRegs) }
|
||||
globalInits.instructions.forEach { it.addUsedRegistersCounts(inputRegs, outputRegs, inputFpRegs, outputFpRegs) }
|
||||
blocks.forEach {
|
||||
it.inlineAssembly.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 position: Position
|
||||
) {
|
||||
val inlineAssembly = mutableListOf<IRInlineAsmChunk>()
|
||||
val subroutines = mutableListOf<IRSubroutine>()
|
||||
val asmSubroutines = mutableListOf<IRAsmSubroutine>()
|
||||
val inlineAssembly = mutableListOf<IRInlineAsmChunk>()
|
||||
|
||||
enum class BlockAlignment {
|
||||
NONE,
|
||||
@ -177,7 +249,7 @@ class IRAsmSubroutine(
|
||||
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>()
|
||||
|
||||
abstract fun isEmpty(): Boolean
|
||||
@ -187,7 +259,7 @@ abstract class IRCodeChunkBase(val label: String?, val position: Position) {
|
||||
|
||||
class IRCodeChunk(label: String?,
|
||||
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 isNotEmpty() = instructions.isNotEmpty()
|
||||
|
@ -206,7 +206,7 @@ fun parseIRCodeLine(line: String, location: Pair<IRCodeChunk, Int>?, placeholder
|
||||
throw IRParseException("invalid reg1 for $line")
|
||||
if(format.reg2==OperandDirection.UNUSED && reg2!=null)
|
||||
throw IRParseException("invalid reg2 for $line")
|
||||
if(value!=null && opcode !in OpcodesWithAddress) {
|
||||
if(value!=null) {
|
||||
when (type) {
|
||||
IRDataType.BYTE -> {
|
||||
if (value < -128 || value > 255)
|
||||
|
@ -75,7 +75,7 @@ load.b r1,42
|
||||
<SUB NAME=main.start RETURNTYPE=null POS=[examples/test.p8: line 4 col 6-8]>
|
||||
<PARAMS>
|
||||
</PARAMS>
|
||||
<C>
|
||||
<C LABEL=main.start>
|
||||
return
|
||||
</C>
|
||||
</SUB>
|
||||
@ -86,7 +86,7 @@ return
|
||||
<PARAMS>
|
||||
uword sys.wait.jiffies
|
||||
</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
|
||||
syscall 13
|
||||
</INLINEASM>
|
||||
|
@ -117,10 +117,12 @@ class VirtualMachine(irProgram: IRProgram) {
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun nextPc() {
|
||||
private fun nextPc() {
|
||||
pcIndex ++
|
||||
if(pcIndex>=pcChunk.instructions.size) {
|
||||
pcIndex = 0
|
||||
if(pcChunk.next==null)
|
||||
TODO("huh no next chunk in $pcChunk")
|
||||
pcChunk = pcChunk.next!!
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package prog8.vm
|
||||
|
||||
import prog8.code.core.AssemblyError
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.Position
|
||||
import prog8.intermediate.*
|
||||
@ -11,14 +12,14 @@ class VmProgramLoader {
|
||||
placeholders.clear()
|
||||
irProgram.validate()
|
||||
val allocations = VmVariableAllocator(irProgram.st, irProgram.encoding, irProgram.options.compTarget)
|
||||
val symbolAddresses = allocations.allocations.toMutableMap()
|
||||
val variableAddresses = allocations.allocations.toMutableMap()
|
||||
val programChunks = mutableListOf<IRCodeChunk>()
|
||||
|
||||
varsToMemory(irProgram, allocations, symbolAddresses, memory)
|
||||
varsToMemory(irProgram, allocations, variableAddresses, memory)
|
||||
|
||||
if(!irProgram.options.dontReinitGlobals) {
|
||||
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
|
||||
@ -31,18 +32,19 @@ class VmProgramLoader {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO load rest of the program
|
||||
// load rest of the program into the list
|
||||
irProgram.blocks.forEach { block ->
|
||||
if(block.address!=null)
|
||||
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 {
|
||||
it.chunks.forEach { 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")
|
||||
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}")
|
||||
}
|
||||
|
||||
pass2replaceLabelsByProgIndex(programChunks, symbolAddresses)
|
||||
|
||||
programChunks.asSequence().flatMap { it.instructions }.forEach {
|
||||
if(it.opcode in OpcodesWithAddress && it.value==null) {
|
||||
throw IRParseException("instruction missing numeric value, label not replaced? $it")
|
||||
}
|
||||
}
|
||||
pass2replaceLabelsByProgIndex(programChunks, variableAddresses)
|
||||
|
||||
return programChunks
|
||||
}
|
||||
|
||||
private fun pass2replaceLabelsByProgIndex(
|
||||
chunks: MutableList<IRCodeChunk>,
|
||||
symbolAddresses: MutableMap<String, Int>
|
||||
variableAddresses: MutableMap<String, Int>
|
||||
) {
|
||||
for((ref, label) in placeholders) {
|
||||
val (chunk, line) = ref
|
||||
val replacement = symbolAddresses[label]
|
||||
val replacement = variableAddresses[label]
|
||||
if(replacement==null) {
|
||||
// it could be an address + index: symbol+42
|
||||
if('+' in label) {
|
||||
val (symbol, indexStr) = label.split('+')
|
||||
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)
|
||||
} 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 {
|
||||
chunk.instructions[line] = chunk.instructions[line].copy(value = replacement)
|
||||
@ -84,39 +87,39 @@ class VmProgramLoader {
|
||||
}
|
||||
}
|
||||
|
||||
private fun addToProgram(
|
||||
instructions: Iterable<IRInstruction>,
|
||||
program: MutableList<IRCodeChunk>,
|
||||
symbolAddresses: MutableMap<String, Int>
|
||||
) {
|
||||
val chunk = IRCodeChunk(null, Position.DUMMY, null)
|
||||
instructions.map {
|
||||
it.labelSymbol?.let { symbol -> placeholders[Pair(chunk, chunk.instructions.size)]=symbol }
|
||||
if(it.opcode==Opcode.SYSCALL) {
|
||||
// convert IR Syscall to VM Syscall
|
||||
val vmSyscall = when(it.value!!) {
|
||||
IMSyscall.SORT_UBYTE.ordinal -> Syscall.SORT_UBYTE
|
||||
IMSyscall.SORT_BYTE.ordinal -> Syscall.SORT_BYTE
|
||||
IMSyscall.SORT_UWORD.ordinal -> Syscall.SORT_UWORD
|
||||
IMSyscall.SORT_WORD.ordinal -> Syscall.SORT_WORD
|
||||
IMSyscall.ANY_BYTE.ordinal -> Syscall.ANY_BYTE
|
||||
IMSyscall.ANY_WORD.ordinal -> Syscall.ANY_WORD
|
||||
IMSyscall.ANY_FLOAT.ordinal -> Syscall.ANY_FLOAT
|
||||
IMSyscall.ALL_BYTE.ordinal -> Syscall.ALL_BYTE
|
||||
IMSyscall.ALL_WORD.ordinal -> Syscall.ALL_WORD
|
||||
IMSyscall.ALL_FLOAT.ordinal -> Syscall.ALL_FLOAT
|
||||
IMSyscall.REVERSE_BYTES.ordinal -> Syscall.REVERSE_BYTES
|
||||
IMSyscall.REVERSE_WORDS.ordinal -> Syscall.REVERSE_WORDS
|
||||
IMSyscall.REVERSE_FLOATS.ordinal -> Syscall.REVERSE_FLOATS
|
||||
else -> throw IRParseException("invalid IM syscall number $it")
|
||||
}
|
||||
chunk += it.copy(value=vmSyscall.ordinal)
|
||||
} else {
|
||||
chunk += it
|
||||
}
|
||||
}
|
||||
program += chunk
|
||||
}
|
||||
// TODO replace IM syscalls by their VM Syscall equivalent
|
||||
// private fun addToProgram(
|
||||
// instructions: Iterable<IRInstruction>,
|
||||
// program: MutableList<IRCodeChunk>
|
||||
// ) {
|
||||
// val chunk = IRCodeChunk(null, Position.DUMMY, null)
|
||||
// instructions.map {
|
||||
// it.labelSymbol?.let { symbol -> placeholders[Pair(chunk, chunk.instructions.size)]=symbol }
|
||||
// if(it.opcode==Opcode.SYSCALL) {
|
||||
// // convert IR Syscall to VM Syscall
|
||||
// val vmSyscall = when(it.value!!) {
|
||||
// IMSyscall.SORT_UBYTE.ordinal -> Syscall.SORT_UBYTE
|
||||
// IMSyscall.SORT_BYTE.ordinal -> Syscall.SORT_BYTE
|
||||
// IMSyscall.SORT_UWORD.ordinal -> Syscall.SORT_UWORD
|
||||
// IMSyscall.SORT_WORD.ordinal -> Syscall.SORT_WORD
|
||||
// IMSyscall.ANY_BYTE.ordinal -> Syscall.ANY_BYTE
|
||||
// IMSyscall.ANY_WORD.ordinal -> Syscall.ANY_WORD
|
||||
// IMSyscall.ANY_FLOAT.ordinal -> Syscall.ANY_FLOAT
|
||||
// IMSyscall.ALL_BYTE.ordinal -> Syscall.ALL_BYTE
|
||||
// IMSyscall.ALL_WORD.ordinal -> Syscall.ALL_WORD
|
||||
// IMSyscall.ALL_FLOAT.ordinal -> Syscall.ALL_FLOAT
|
||||
// IMSyscall.REVERSE_BYTES.ordinal -> Syscall.REVERSE_BYTES
|
||||
// IMSyscall.REVERSE_WORDS.ordinal -> Syscall.REVERSE_WORDS
|
||||
// IMSyscall.REVERSE_FLOATS.ordinal -> Syscall.REVERSE_FLOATS
|
||||
// else -> throw IRParseException("invalid IM syscall number $it")
|
||||
// }
|
||||
// chunk += it.copy(value=vmSyscall.ordinal)
|
||||
// } else {
|
||||
// chunk += it
|
||||
// }
|
||||
// }
|
||||
// program += chunk
|
||||
// }
|
||||
|
||||
private fun varsToMemory(
|
||||
program: IRProgram,
|
||||
|
Loading…
Reference in New Issue
Block a user