This commit is contained in:
Irmen de Jong 2022-10-04 22:54:14 +02:00
parent 39d6d2857e
commit 2340760f53
8 changed files with 77 additions and 76 deletions

View File

@ -63,9 +63,9 @@ class IRCodeGen(
// for instance when a piece of inlined assembly references them.
val replacements = mutableListOf<Triple<IRCodeChunkBase, Int, UInt>>()
irProg.blocks.asSequence().flatMap { it.subroutines }.flatMap { it.chunks }.forEach { chunk ->
chunk.lines.withIndex().forEach {
(lineIndex, line) -> if(line is IRInstruction) {
val symbolExpr = line.labelSymbol
chunk.instructions.withIndex().forEach {
(idx, instr) -> if(instr is IRInstruction) {
val symbolExpr = instr.labelSymbol
if(symbolExpr!=null) {
val symbol: String
val index: UInt
@ -79,7 +79,7 @@ class IRCodeGen(
}
val target = symbolTable.flat[symbol.split('.')]
if (target is StMemVar) {
replacements.add(Triple(chunk, lineIndex, target.address+index))
replacements.add(Triple(chunk, idx, target.address+index))
}
}
}
@ -87,8 +87,8 @@ class IRCodeGen(
}
replacements.forEach {
val old = it.first.lines[it.second] as IRInstruction
it.first.lines[it.second] = IRInstruction(
val old = it.first.instructions[it.second] as IRInstruction
it.first.instructions[it.second] = IRInstruction(
old.opcode,
old.type,
old.reg1,

View File

@ -10,7 +10,7 @@ internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
// we don't optimize Inline Asm chunks here.
if(chunk is IRCodeChunk) {
do {
val indexedInstructions = chunk.lines.withIndex()
val indexedInstructions = chunk.instructions.withIndex()
.filter { it.value is IRInstruction }
.map { IndexedValue(it.index, it.value as IRInstruction) }
val changed = removeNops(chunk, indexedInstructions)
@ -51,7 +51,7 @@ internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
chunks += sub.chunks[0]
for(ix in 1 until sub.chunks.size) {
if(mayJoin(chunks.last(), sub.chunks[ix]))
chunks.last().lines += sub.chunks[ix].lines
chunks.last().instructions += sub.chunks[ix].instructions
else
chunks += sub.chunks[ix]
}
@ -64,15 +64,15 @@ internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
var changed = false
indexedInstructions.reversed().forEach { (idx, ins) ->
if(ins.opcode== Opcode.PUSH) {
if(idx < chunk.lines.size-1) {
val insAfter = chunk.lines[idx+1] as? IRInstruction
if(idx < chunk.instructions.size-1) {
val insAfter = chunk.instructions[idx+1] as? IRInstruction
if(insAfter!=null && insAfter.opcode == Opcode.POP) {
if(ins.reg1==insAfter.reg1) {
chunk.lines.removeAt(idx)
chunk.lines.removeAt(idx)
chunk.instructions.removeAt(idx)
chunk.instructions.removeAt(idx)
} else {
chunk.lines[idx] = IRInstruction(Opcode.LOADR, ins.type, reg1=insAfter.reg1, reg2=ins.reg1)
chunk.lines.removeAt(idx+1)
chunk.instructions[idx] = IRInstruction(Opcode.LOADR, ins.type, reg1=insAfter.reg1, reg2=ins.reg1)
chunk.instructions.removeAt(idx+1)
}
changed = true
}
@ -88,18 +88,18 @@ internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
var changed = false
indexedInstructions.reversed().forEach { (idx, ins) ->
if(ins.opcode== Opcode.SEC || ins.opcode== Opcode.CLC) {
if(idx < chunk.lines.size-1) {
val insAfter = chunk.lines[idx+1] as? IRInstruction
if(idx < chunk.instructions.size-1) {
val insAfter = chunk.instructions[idx+1] as? IRInstruction
if(insAfter?.opcode == ins.opcode) {
chunk.lines.removeAt(idx)
chunk.instructions.removeAt(idx)
changed = true
}
else if(ins.opcode== Opcode.SEC && insAfter?.opcode== Opcode.CLC) {
chunk.lines.removeAt(idx)
chunk.instructions.removeAt(idx)
changed = true
}
else if(ins.opcode== Opcode.CLC && insAfter?.opcode== Opcode.SEC) {
chunk.lines.removeAt(idx)
chunk.instructions.removeAt(idx)
changed = true
}
}
@ -114,19 +114,19 @@ internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
val labelSymbol = ins.labelSymbol
if(ins.opcode== Opcode.JUMP && labelSymbol!=null) {
// remove jump/branch to label immediately below
if(idx < chunk.lines.size-1) {
val label = chunk.lines[idx+1] as? IRCodeLabel
if(idx < chunk.instructions.size-1) {
val label = chunk.instructions[idx+1] as? IRCodeLabel
if(label?.name == labelSymbol) {
chunk.lines.removeAt(idx)
chunk.instructions.removeAt(idx)
changed = true
}
}
}
// remove useless RETURN
if(ins.opcode == Opcode.RETURN && idx>0) {
val previous = chunk.lines[idx-1] as? IRInstruction
val previous = chunk.instructions[idx-1] as? IRInstruction
if(previous?.opcode in setOf(Opcode.JUMP, Opcode.JUMPA, Opcode.RETURN)) {
chunk.lines.removeAt(idx)
chunk.instructions.removeAt(idx)
changed = true
}
}
@ -141,47 +141,47 @@ internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
when (ins.opcode) {
Opcode.DIV, Opcode.DIVS, Opcode.MUL, Opcode.MOD -> {
if (ins.value == 1) {
chunk.lines.removeAt(idx)
chunk.instructions.removeAt(idx)
changed = true
}
}
Opcode.ADD, Opcode.SUB -> {
if (ins.value == 1) {
chunk.lines[idx] = IRInstruction(
chunk.instructions[idx] = IRInstruction(
if (ins.opcode == Opcode.ADD) Opcode.INC else Opcode.DEC,
ins.type,
ins.reg1
)
changed = true
} else if (ins.value == 0) {
chunk.lines.removeAt(idx)
chunk.instructions.removeAt(idx)
changed = true
}
}
Opcode.AND -> {
if (ins.value == 0) {
chunk.lines[idx] = IRInstruction(Opcode.LOAD, ins.type, reg1 = ins.reg1, value = 0)
chunk.instructions[idx] = IRInstruction(Opcode.LOAD, ins.type, reg1 = ins.reg1, value = 0)
changed = true
} else if (ins.value == 255 && ins.type == IRDataType.BYTE) {
chunk.lines.removeAt(idx)
chunk.instructions.removeAt(idx)
changed = true
} else if (ins.value == 65535 && ins.type == IRDataType.WORD) {
chunk.lines.removeAt(idx)
chunk.instructions.removeAt(idx)
changed = true
}
}
Opcode.OR -> {
if (ins.value == 0) {
chunk.lines.removeAt(idx)
chunk.instructions.removeAt(idx)
changed = true
} else if ((ins.value == 255 && ins.type == IRDataType.BYTE) || (ins.value == 65535 && ins.type == IRDataType.WORD)) {
chunk.lines[idx] = IRInstruction(Opcode.LOAD, ins.type, reg1 = ins.reg1, value = ins.value)
chunk.instructions[idx] = IRInstruction(Opcode.LOAD, ins.type, reg1 = ins.reg1, value = ins.value)
changed = true
}
}
Opcode.XOR -> {
if (ins.value == 0) {
chunk.lines.removeAt(idx)
chunk.instructions.removeAt(idx)
changed = true
}
}
@ -196,7 +196,7 @@ internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
indexedInstructions.reversed().forEach { (idx, ins) ->
if (ins.opcode == Opcode.NOP) {
changed = true
chunk.lines.removeAt(idx)
chunk.instructions.removeAt(idx)
}
}
return changed

View File

@ -6,12 +6,12 @@ import prog8.codegen.intermediate.IRPeepholeOptimizer
import prog8.intermediate.*
class TestIRPeepholeOpt: FunSpec({
fun makeIRProgram(lines: List<IRCodeLine>): IRProgram {
fun makeIRProgram(instructions: List<IRCodeLine>): IRProgram {
val block = IRBlock("main", null, IRBlock.BlockAlignment.NONE, Position.DUMMY)
val sub = IRSubroutine("main.start", emptyList(), null, Position.DUMMY)
val chunk = IRCodeChunk(Position.DUMMY)
for(line in lines)
chunk += line
for(instr in instructions)
chunk += instr
sub += chunk
block += sub
val target = VMTarget()
@ -30,7 +30,7 @@ class TestIRPeepholeOpt: FunSpec({
return prog
}
fun IRProgram.lines(): List<IRCodeLine> = this.blocks.flatMap { it.subroutines }.flatMap { it.chunks }.flatMap { it.lines }
fun IRProgram.instructions(): List<IRCodeLine> = this.blocks.flatMap { it.subroutines }.flatMap { it.chunks }.flatMap { it.instructions }
test("remove nops") {
val irProg = makeIRProgram(listOf(
@ -38,10 +38,10 @@ class TestIRPeepholeOpt: FunSpec({
IRInstruction(Opcode.NOP),
IRInstruction(Opcode.NOP)
))
irProg.lines().size shouldBe 3
irProg.instructions().size shouldBe 3
val opt = IRPeepholeOptimizer(irProg)
opt.optimize()
irProg.lines().size shouldBe 1
irProg.instructions().size shouldBe 1
}
test("remove jmp to label below") {
@ -55,10 +55,10 @@ class TestIRPeepholeOpt: FunSpec({
IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=1),
IRCodeLabel("label3")
))
irProg.lines().size shouldBe 8
irProg.instructions().size shouldBe 8
val opt = IRPeepholeOptimizer(irProg)
opt.optimize()
val lines = irProg.lines()
val lines = irProg.instructions()
lines.size shouldBe 5
(lines[0] as IRCodeLabel).name shouldBe "label"
(lines[1] as IRCodeLabel).name shouldBe "label2"
@ -76,10 +76,10 @@ class TestIRPeepholeOpt: FunSpec({
IRInstruction(Opcode.CLC),
IRInstruction(Opcode.CLC)
))
irProg.lines().size shouldBe 6
irProg.instructions().size shouldBe 6
val opt = IRPeepholeOptimizer(irProg)
opt.optimize()
val lines = irProg.lines()
val lines = irProg.instructions()
lines.size shouldBe 1
(lines[0] as IRInstruction).opcode shouldBe Opcode.CLC
}
@ -91,10 +91,10 @@ class TestIRPeepholeOpt: FunSpec({
IRInstruction(Opcode.PUSH, IRDataType.BYTE, reg1=99),
IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=222)
))
irProg.lines().size shouldBe 4
irProg.instructions().size shouldBe 4
val opt = IRPeepholeOptimizer(irProg)
opt.optimize()
val lines = irProg.lines()
val lines = irProg.instructions()
lines.size shouldBe 1
(lines[0] as IRInstruction).opcode shouldBe Opcode.LOADR
(lines[0] as IRInstruction).reg1 shouldBe 222
@ -114,10 +114,10 @@ class TestIRPeepholeOpt: FunSpec({
IRInstruction(Opcode.ADD, IRDataType.BYTE, reg1=42, value = 0),
IRInstruction(Opcode.SUB, IRDataType.BYTE, reg1=42, value = 0)
))
irProg.lines().size shouldBe 10
irProg.instructions().size shouldBe 10
val opt = IRPeepholeOptimizer(irProg)
opt.optimize()
val lines = irProg.lines()
val lines = irProg.instructions()
lines.size shouldBe 4
}
@ -126,10 +126,10 @@ class TestIRPeepholeOpt: FunSpec({
IRInstruction(Opcode.ADD, IRDataType.BYTE, reg1=42, value = 1),
IRInstruction(Opcode.SUB, IRDataType.BYTE, reg1=42, value = 1)
))
irProg.lines().size shouldBe 2
irProg.instructions().size shouldBe 2
val opt = IRPeepholeOptimizer(irProg)
opt.optimize()
val lines = irProg.lines()
val lines = irProg.instructions()
lines.size shouldBe 2
(lines[0] as IRInstruction).opcode shouldBe Opcode.INC
(lines[1] as IRInstruction).opcode shouldBe Opcode.DEC
@ -146,10 +146,10 @@ class TestIRPeepholeOpt: FunSpec({
IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=42, value = 1),
IRInstruction(Opcode.XOR, IRDataType.BYTE, reg1=42, value = 1)
))
irProg.lines().size shouldBe 8
irProg.instructions().size shouldBe 8
val opt = IRPeepholeOptimizer(irProg)
opt.optimize()
val lines = irProg.lines()
val lines = irProg.instructions()
lines.size shouldBe 4
}
@ -160,10 +160,10 @@ class TestIRPeepholeOpt: FunSpec({
IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=42, value = 255),
IRInstruction(Opcode.OR, IRDataType.WORD, reg1=42, value = 65535)
))
irProg.lines().size shouldBe 4
irProg.instructions().size shouldBe 4
val opt = IRPeepholeOptimizer(irProg)
opt.optimize()
val lines = irProg.lines()
val lines = irProg.instructions()
lines.size shouldBe 4
(lines[0] as IRInstruction).opcode shouldBe Opcode.LOAD
(lines[1] as IRInstruction).opcode shouldBe Opcode.LOAD

View File

@ -3,6 +3,9 @@ TODO
For next release
^^^^^^^^^^^^^^^^
- ir: get rid of IRCodeLabel, make every label start a new code chunk, give those a 'label' property.
- ir: fix joinChunks() in the IR optimizer ?
...
@ -19,8 +22,6 @@ Compiler:
- create BSS section in output program and put StStaticVariables in there with bss=true. Don't forget to add init code to zero out everything that was put in bss. If array in bss->only zero ONCE! So requires self-modifying code
- ir: Jumps go to a code block rather than a specific address(label) -> also helps future dead code elimination?
- ir: the above means that every label introduces a new code block. This eliminates the use of actual labels altogether during execution/translation.
- ir: joinChunks() in the IR optimizer should be changed accordingly
- ir: add more optimizations in IRPeepholeOptimizer
- ir: how to remove all unused subroutines? (the 6502 assembly codegen relies on 64tass solve this for us)
- see if we can let for loops skip the loop if end<start, like other programming languages. Without adding a lot of code size/duplicating the loop condition.

View File

@ -11,7 +11,7 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
private val outfile = outfileOverride ?: (irProgram.options.outputDir / ("${irProgram.name}.p8ir"))
private val out = outfile.bufferedWriter()
private var numChunks = 0
private var numLines = 0
private var numInstr = 0
fun write(): Path {
@ -35,7 +35,7 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
val used = irProgram.registersUsed()
val numberUsed = (used.inputRegs.keys + used.outputRegs.keys).size + (used.inputFpRegs.keys + used.outputFpRegs.keys).size
println("($numLines lines in $numChunks code chunks, $numberUsed registers)")
println("($numInstr instructions in $numChunks chunks, $numberUsed registers)")
return outfile
}
@ -63,11 +63,11 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
is IRInlineBinaryChunk -> writeInlineBytes(chunk)
else -> {
out.write("<C>\n")
if (chunk.lines.isEmpty())
if (chunk.instructions.isEmpty())
throw InternalCompilerException("empty code chunk in ${it.name} ${it.position}")
chunk.lines.forEach { line ->
numLines++
out.writeLine(line)
chunk.instructions.forEach { instr ->
numInstr++
out.writeLine(instr)
}
out.write("</C>\n")
}

View File

@ -55,7 +55,7 @@ class IRProgram(val name: String,
val globalInits = mutableListOf<IRCodeLine>()
val blocks = mutableListOf<IRBlock>()
fun addGlobalInits(chunk: IRCodeChunk) = globalInits.addAll(chunk.lines)
fun addGlobalInits(chunk: IRCodeChunk) = globalInits.addAll(chunk.instructions)
fun addBlock(block: IRBlock) {
require(blocks.all { it.name != block.name}) { "duplicate block ${block.name} ${block.position}" }
blocks.add(block)
@ -68,11 +68,11 @@ class IRProgram(val name: String,
fun validate() {
blocks.forEach {
it.inlineAssembly.forEach { chunk ->
require(chunk.lines.isEmpty())
require(chunk.instructions.isEmpty())
}
it.subroutines.forEach { sub ->
sub.chunks.forEach { chunk ->
if (chunk is IRInlineAsmChunk) { require(chunk.lines.isEmpty()) }
if (chunk is IRInlineAsmChunk) { require(chunk.instructions.isEmpty()) }
}
}
}
@ -176,7 +176,7 @@ sealed class IRCodeLine
class IRCodeLabel(val name: String): IRCodeLine()
abstract class IRCodeChunkBase(val position: Position) {
val lines = mutableListOf<IRCodeLine>()
val instructions = mutableListOf<IRCodeLine>()
abstract fun isEmpty(): Boolean
abstract fun isNotEmpty(): Boolean
@ -185,14 +185,14 @@ abstract class IRCodeChunkBase(val position: Position) {
class IRCodeChunk(position: Position): IRCodeChunkBase(position) {
override fun isEmpty() = lines.isEmpty()
override fun isNotEmpty() = lines.isNotEmpty()
override fun isEmpty() = instructions.isEmpty()
override fun isNotEmpty() = instructions.isNotEmpty()
override fun usedRegisters(): RegistersUsed {
val inputRegs = mutableMapOf<Int, Int>().withDefault { 0 }
val inputFpRegs = mutableMapOf<Int, Int>().withDefault { 0 }
val outputRegs = mutableMapOf<Int, Int>().withDefault { 0 }
val outputFpRegs = mutableMapOf<Int, Int>().withDefault { 0 }
lines.forEach {
instructions.forEach {
if(it is IRInstruction)
it.addUsedRegistersCounts(inputRegs, outputRegs, inputFpRegs, outputFpRegs)
}
@ -200,11 +200,11 @@ class IRCodeChunk(position: Position): IRCodeChunkBase(position) {
}
operator fun plusAssign(line: IRCodeLine) {
lines.add(line)
instructions.add(line)
}
operator fun plusAssign(chunk: IRCodeChunkBase) {
lines.addAll(chunk.lines)
instructions.addAll(chunk.instructions)
}
}

View File

@ -39,7 +39,7 @@ class VmProgramLoader {
when (chunk) {
is IRInlineAsmChunk -> addAssemblyToProgram(chunk, program, symbolAddresses)
is IRInlineBinaryChunk -> program += IRInstruction(Opcode.BINARYDATA, binaryData = chunk.data)
else -> addToProgram(chunk.lines, program, symbolAddresses)
else -> addToProgram(chunk.instructions, program, symbolAddresses)
}
}
}
@ -187,11 +187,11 @@ class VmProgramLoader {
}
private fun addToProgram(
lines: Iterable<IRCodeLine>,
instructions: Iterable<IRCodeLine>,
program: MutableList<IRInstruction>,
symbolAddresses: MutableMap<String, Int>
) {
lines.map {
instructions.map {
when(it) {
is IRInstruction -> {
it.labelSymbol?.let { symbol -> placeholders[program.size]=symbol }

View File

@ -61,8 +61,8 @@ class TestVm: FunSpec( {
vm.memory.getUW(1000) shouldBe 12345u
vm.callStack.shouldBeEmpty()
vm.valueStack.shouldBeEmpty()
vm.pc shouldBe code.lines.size-1
vm.stepCount shouldBe code.lines.size
vm.pc shouldBe code.instructions.size-1
vm.stepCount shouldBe code.instructions.size
}
test("vm asmsub not supported") {