added a few more vm optimizations and unit tests

This commit is contained in:
Irmen de Jong
2022-07-12 12:42:37 +02:00
parent 6181b12ab8
commit 840331347b
10 changed files with 235 additions and 28 deletions
+1 -1
View File
@@ -24,9 +24,9 @@ compileTestKotlin {
}
dependencies {
implementation project(':virtualmachine')
implementation project(':codeAst')
implementation project(':codeCore')
implementation project(':virtualmachine')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.16"
@@ -13,8 +13,7 @@ import kotlin.io.path.bufferedWriter
import kotlin.io.path.div
internal class AssemblyProgram(override val name: String,
private val allocations: VariableAllocator
class AssemblyProgram(override val name: String, private val allocations: VariableAllocator
) : IAssemblyProgram {
private val globalInits = mutableListOf<VmCodeLine>()
@@ -71,9 +70,9 @@ internal class AssemblyProgram(override val name: String,
fun getBlocks(): List<VmCodeChunk> = blocks
}
internal sealed class VmCodeLine
sealed class VmCodeLine
internal class VmCodeInstruction(
class VmCodeInstruction(
opcode: Opcode,
type: VmDataType?=null,
reg1: Int?=null, // 0-$ffff
@@ -112,10 +111,10 @@ internal class VmCodeInstruction(
}
}
internal class VmCodeLabel(val name: List<String>): VmCodeLine()
class VmCodeLabel(val name: List<String>): VmCodeLine()
internal class VmCodeComment(val comment: String): VmCodeLine()
internal class VmCodeChunk(initial: VmCodeLine? = null) {
class VmCodeChunk(initial: VmCodeLine? = null) {
val lines = mutableListOf<VmCodeLine>()
init {
@@ -40,7 +40,7 @@ class CodeGen(internal val program: PtProgram,
internal val errors: IErrorReporter
): IAssemblyGenerator {
internal val allocations = VariableAllocator(symbolTable, program, errors)
internal val allocations = VariableAllocator(symbolTable, program)
private val expressionEval = ExpressionGen(this)
private val builtinFuncGen = BuiltinFuncGen(this, expressionEval)
private val assignmentGen = AssignmentGen(this, expressionEval)
@@ -72,7 +72,7 @@ class CodeGen(internal val program: PtProgram,
optimizer.optimize()
}
println("Vm codegen: amount of vm registers=${vmRegisters.peekNext()}")
println("Vm codegen: virtual registers=${vmRegisters.peekNext()} memory usage=${allocations.freeMem}")
return vmprog
}
@@ -4,7 +4,7 @@ import prog8.code.SymbolTable
import prog8.code.ast.PtProgram
import prog8.code.core.*
class VariableAllocator(private val st: SymbolTable, private val program: PtProgram, errors: IErrorReporter) {
class VariableAllocator(private val st: SymbolTable, private val program: PtProgram) {
private val allocations = mutableMapOf<List<String>, Int>()
private var freeMemoryStart: Int
@@ -3,30 +3,138 @@ package prog8.codegen.virtual
import prog8.vm.Instruction
import prog8.vm.Opcode
internal class VmPeepholeOptimizer(private val vmprog: AssemblyProgram, private val allocations: VariableAllocator) {
internal class VmOptimizerException(msg: String): Exception(msg)
class VmPeepholeOptimizer(private val vmprog: AssemblyProgram, private val allocations: VariableAllocator) {
fun optimize() {
vmprog.getBlocks().forEach { block ->
do {
val indexedInstructions = block.lines.withIndex()
.filter { it.value is VmCodeInstruction }
.map { IndexedValue(it.index, (it.value as VmCodeInstruction).ins)}
val changed = optimizeRemoveNops(block, indexedInstructions)
|| optimizeDoubleLoadsAndStores(block, indexedInstructions)
// TODO other optimizations:
// useless arithmethic (div/mul by 1, add/sub 0, ...)
// useless logical (bitwise (x)or 0, bitwise and by ffff, shl followed by shr or vice versa (no carry)... )
// jump/branch to label immediately below
// branch instructions with reg1==reg2
// conditional set instructions with reg1==reg2
// push followed by pop to same target, or different target replace with load
// double sec, clc
// sec+clc or clc+sec
// move complex optimizations such as unused registers, ...
.map { IndexedValue(it.index, (it.value as VmCodeInstruction).ins) }
val changed = removeNops(block, indexedInstructions)
|| removeDoubleLoadsAndStores(block, indexedInstructions)
// || removeUselessArithmetic(block, indexedInstructions) // TODO enable
|| removeWeirdBranches(block, indexedInstructions)
|| removeDoubleSecClc(block, indexedInstructions)
|| cleanupPushPop(block, indexedInstructions)
// TODO other optimizations:
// other useless logical?
// conditional set instructions with reg1==reg2
// move complex optimizations such as unused registers, ...
} while(changed)
}
}
private fun optimizeRemoveNops(block: VmCodeChunk, indexedInstructions: List<IndexedValue<Instruction>>): Boolean {
private fun cleanupPushPop(block: VmCodeChunk, indexedInstructions: List<IndexedValue<Instruction>>): Boolean {
// push followed by pop to same target, or different target->replace with load
var changed = false
indexedInstructions.reversed().forEach { (idx, ins) ->
if(ins.opcode==Opcode.PUSH) {
if(idx < block.lines.size-1) {
val insAfter = block.lines[idx+1] as? VmCodeInstruction
if(insAfter!=null && insAfter.ins.opcode ==Opcode.POP) {
if(ins.reg1==insAfter.ins.reg1) {
block.lines.removeAt(idx)
block.lines.removeAt(idx)
} else {
block.lines[idx] = VmCodeInstruction(Opcode.LOADR, ins.type, reg1=insAfter.ins.reg1, reg2=ins.reg1)
block.lines.removeAt(idx+1)
}
changed = true
}
}
}
}
return changed
}
private fun removeDoubleSecClc(block: VmCodeChunk, indexedInstructions: List<IndexedValue<Instruction>>): Boolean {
// double sec, clc
// sec+clc or clc+sec
var changed = false
indexedInstructions.reversed().forEach { (idx, ins) ->
if(ins.opcode==Opcode.SEC || ins.opcode==Opcode.CLC) {
if(idx < block.lines.size-1) {
val insAfter = block.lines[idx+1] as? VmCodeInstruction
if(insAfter?.ins?.opcode == ins.opcode) {
block.lines.removeAt(idx)
changed = true
}
else if(ins.opcode==Opcode.SEC && insAfter?.ins?.opcode==Opcode.CLC) {
block.lines.removeAt(idx)
changed = true
}
else if(ins.opcode==Opcode.CLC && insAfter?.ins?.opcode==Opcode.SEC) {
block.lines.removeAt(idx)
changed = true
}
}
}
}
return changed
}
private fun removeWeirdBranches(block: VmCodeChunk, indexedInstructions: List<IndexedValue<Instruction>>): Boolean {
// jump/branch to label immediately below
// branch instructions with reg1==reg2
var changed = false
indexedInstructions.reversed().forEach { (idx, ins) ->
if(ins.opcode==Opcode.JUMP && ins.labelSymbol!=null) {
// if jumping to label immediately following this
if(idx < block.lines.size-1) {
val label = block.lines[idx+1] as? VmCodeLabel
if(label?.name == ins.labelSymbol) {
block.lines.removeAt(idx)
changed = true
}
}
}
/*
beq reg1, reg2, location - jump to location in program given by location, if reg1 == reg2
bne reg1, reg2, location - jump to location in program given by location, if reg1 != reg2
blt reg1, reg2, location - jump to location in program given by location, if reg1 < reg2 (unsigned)
blts reg1, reg2, location - jump to location in program given by location, if reg1 < reg2 (signed)
ble reg1, reg2, location - jump to location in program given by location, if reg1 <= reg2 (unsigned)
bles reg1, reg2, location - jump to location in program given by location, if reg1 <= reg2 (signed)
bgt reg1, reg2, location - jump to location in program given by location, if reg1 > reg2 (unsigned)
bgts reg1, reg2, location - jump to location in program given by location, if reg1 > reg2 (signed)
bge reg1, reg2, location - jump to location in program given by location, if reg1 >= reg2 (unsigned)
bges reg1, reg2, location - jump to location in program given by location, if reg1 >= reg2 (signed)
*/
}
return changed
}
private fun removeUselessArithmetic(block: VmCodeChunk, indexedInstructions: List<IndexedValue<Instruction>>): Boolean {
// TODO this is hard to solve atm because the values are loaded into registers first
var changed = false
indexedInstructions.reversed().forEach { (idx, ins) ->
when (ins.opcode) {
Opcode.DIV, Opcode.DIVS, Opcode.MUL, Opcode.MOD -> {
TODO("remove div/mul by 1")
}
Opcode.ADD, Opcode.SUB -> {
TODO("remove add/sub by 1 -> inc/dec, by 0->remove")
}
Opcode.AND -> {
TODO("and 0 -> 0, and ffff -> remove")
}
Opcode.OR -> {
TODO("or 0 -> remove, of ffff -> ffff")
}
Opcode.XOR -> {
TODO("xor 0 -> remove")
}
else -> {}
}
}
return changed
}
private fun removeNops(block: VmCodeChunk, indexedInstructions: List<IndexedValue<Instruction>>): Boolean {
var changed = false
indexedInstructions.reversed().forEach { (idx, ins) ->
if (ins.opcode == Opcode.NOP) {
@@ -37,7 +145,7 @@ internal class VmPeepholeOptimizer(private val vmprog: AssemblyProgram, private
return changed
}
private fun optimizeDoubleLoadsAndStores(block: VmCodeChunk, indexedInstructions: List<IndexedValue<Instruction>>): Boolean {
private fun removeDoubleLoadsAndStores(block: VmCodeChunk, indexedInstructions: List<IndexedValue<Instruction>>): Boolean {
var changed = false
indexedInstructions.forEach { (idx, ins) ->