IR: wrong attempt at optimizing register usage by reusing registers inside different code chunks

This commit is contained in:
Irmen de Jong 2023-05-18 13:51:13 +02:00
parent 91e1643627
commit 300e2fe9f8
6 changed files with 149 additions and 6 deletions

View File

@ -58,6 +58,10 @@ class IRCodeGen(
val optimizer = IRPeepholeOptimizer(irProg)
optimizer.optimize(options.optimize, errors)
irProg.validate()
val regOptimizer = IRRegisterOptimizer(irProg)
regOptimizer.optimize()
return irProg
}

View File

@ -36,6 +36,9 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
joinChunks(sub)
removeEmptyChunks(sub)
joinChunks(sub)
// TODO also do register optimization step here?
sub.chunks.withIndex().forEach { (index, chunk1) ->
// we don't optimize Inline Asm chunks here.
val chunk2 = if(index<sub.chunks.size-1) sub.chunks[index+1] else null

View File

@ -0,0 +1,103 @@
package prog8.codegen.intermediate
import prog8.intermediate.IRProgram
class IRRegisterOptimizer(private val irProg: IRProgram) {
fun optimize() {
// reuseRegisters()
}
/*
TODO: this register re-use renumbering isn't going to work like this,
because subroutines will be clobbering the registers that the subroutine
which is calling them might be using...
private fun reuseRegisters() {
fun addToUsage(usage: MutableMap<Pair<Int, IRDataType>, MutableSet<IRCodeChunkBase>>,
regnum: Int,
dt: IRDataType,
chunk: IRCodeChunkBase) {
val key = regnum to dt
val chunks = usage[key] ?: mutableSetOf()
chunks.add(chunk)
usage[key] = chunks
}
val usage: MutableMap<Pair<Int, IRDataType>, MutableSet<IRCodeChunkBase>> = mutableMapOf()
irProg.foreachCodeChunk { chunk ->
chunk.usedRegisters().regsTypes.forEach { (regNum, types) ->
types.forEach { dt ->
addToUsage(usage, regNum, dt, chunk)
}
}
}
val registerReplacements = usage.asSequence()
.filter { it.value.size==1 }
.map { it.key to it.value.iterator().next() }
.groupBy({ it.second }, {it.first})
.asSequence()
.associate { (chunk, registers) ->
chunk to registers.withIndex().associate { (index, reg) -> reg to 50000+index }
}
registerReplacements.forEach { replaceRegisters(it.key, it.value) }
}
private fun replaceRegisters(chunk: IRCodeChunkBase, replacements: Map<Pair<Int, IRDataType>, Int>) {
val (rF, rI) = replacements.asSequence().partition { it.key.second==IRDataType.FLOAT }
val replacementsInt = rI.associate { it.key.first to it.value }
val replacementsFloat = rF.associate { it.key.first to it.value }
fun replaceRegs(fcallArgs: FunctionCallArgs?): FunctionCallArgs? {
if(fcallArgs==null)
return null
val args = if(fcallArgs.arguments.isEmpty()) fcallArgs.arguments else {
fcallArgs.arguments.map {
FunctionCallArgs.ArgumentSpec(
it.name,
it.address,
FunctionCallArgs.RegSpec(
it.reg.dt,
if(it.reg.dt==IRDataType.FLOAT)
replacementsFloat.getOrDefault(it.reg.registerNum, it.reg.registerNum)
else
replacementsInt.getOrDefault(it.reg.registerNum, it.reg.registerNum),
it.reg.cpuRegister
)
)
}
}
val rt = fcallArgs.returns
val returns = if(rt==null) null else {
FunctionCallArgs.RegSpec(
rt.dt,
if(rt.dt==IRDataType.FLOAT)
replacementsFloat.getOrDefault(rt.registerNum, rt.registerNum)
else
replacementsInt.getOrDefault(rt.registerNum, rt.registerNum),
rt.cpuRegister
)
}
return FunctionCallArgs(args, returns)
}
fun replaceRegs(instruction: IRInstruction): IRInstruction {
val reg1 = replacementsInt.getOrDefault(instruction.reg1, instruction.reg1)
val reg2 = replacementsInt.getOrDefault(instruction.reg2, instruction.reg2)
val fpReg1 = replacementsFloat.getOrDefault(instruction.fpReg1, instruction.fpReg1)
val fpReg2 = replacementsFloat.getOrDefault(instruction.fpReg2, instruction.fpReg2)
return instruction.copy(reg1 = reg1, reg2 = reg2, fpReg1 = fpReg1, fpReg2 = fpReg2, fcallArgs = replaceRegs(instruction.fcallArgs))
}
val newInstructions = chunk.instructions.map {
replaceRegs(it)
}
chunk.instructions.clear()
chunk.instructions.addAll(newInstructions)
}
*/
}

View File

@ -253,7 +253,7 @@ main {
main {
sub start() {
%ir {{
loadr.b r1,r2
incm.b $2000
return
}}
}
@ -262,7 +262,7 @@ main {
val result = compileText(target, false, src, writeAssembly = true)!!
val virtfile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".p8ir")
val irSrc = virtfile.readText()
irSrc.shouldContain("loadr.b r1,r2")
irSrc.shouldContain("incm.b $2000")
irSrc.shouldNotContain("INLINEASM")
VmRunner().runProgram(irSrc)
}

View File

@ -1,6 +1,12 @@
TODO
====
Fix register renumber issue in vm/sincos, vm/bouncegfx examples
IS THE RENUMBERING EVEN GOING TO WORK AT ALL?
Subroutines calling each other will start to overwrite the same registers that they now share?
For 9.0 major changes
^^^^^^^^^^^^^^^^^^^^^
- DONE: added 'cbm' block in the syslib module that now contains all CBM compatible kernal routines and variables
@ -41,10 +47,6 @@ Compiler:
- ir: idea: (but LLVM IR simply keeps the variables, so not a good idea then?...): replace all scalar variables by an allocated register. Keep a table of the variable to register mapping (including the datatype)
global initialization values are simply a list of LOAD instructions.
Variables replaced include all subroutine parameters! So the only variables that remain as variables are arrays and strings.
- 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: peephole opt: (maybe just integrate this in the variable/register allocator though?) renumber registers in chunks to start with 1 again every time (but keep entry values in mind!)
- ir: peephole opt: (maybe just integrate this in the variable/register allocator though?) reuse registers in chunks (but keep result registers in mind that pass values out! and don't renumber registers above SyscallRegisterBase!)
- ir: add more optimizations in IRPeepholeOptimizer
- ir: for expressions with array indexes that occur multiple times, can we avoid loading them into new virtualregs everytime and just reuse a single virtualreg as indexer? (simple form of common subexpression elimination)
- PtAst/IR: more complex common subexpression eliminations

View File

@ -806,6 +806,37 @@ data class IRInstruction(
OperandDirection.READ -> readFpRegsCounts[this.fpReg2!!] = readFpRegsCounts.getValue(this.fpReg2)+1
else -> throw IllegalArgumentException("fpReg2 can only be read")
}
if(fcallArgs!=null) {
fcallArgs.returns?.let {
if(it.dt==IRDataType.FLOAT)
writeFpRegsCounts[it.registerNum] = writeFpRegsCounts.getValue(it.registerNum)+1
else {
writeRegsCounts[it.registerNum] = writeRegsCounts.getValue(it.registerNum) + 1
val types = regsTypes[it.registerNum]
if(types==null) {
regsTypes[it.registerNum] = mutableSetOf(it.dt)
} else {
types += it.dt
regsTypes[it.registerNum] = types
}
}
}
fcallArgs.arguments.forEach {
if(it.reg.dt==IRDataType.FLOAT)
readFpRegsCounts[it.reg.registerNum] = readFpRegsCounts.getValue(it.reg.registerNum)+1
else {
readRegsCounts[it.reg.registerNum] = readRegsCounts.getValue(it.reg.registerNum) + 1
val types = regsTypes[it.reg.registerNum]
if(types==null) {
regsTypes[it.reg.registerNum] = mutableSetOf(it.reg.dt)
} else {
types += it.reg.dt
regsTypes[it.reg.registerNum] = types
}
}
}
}
}
override fun toString(): String {