diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt index 72f590f64..4660ed21e 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt @@ -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 } diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/IRPeepholeOptimizer.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/IRPeepholeOptimizer.kt index e4cfcef26..0f38f1748 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/IRPeepholeOptimizer.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/IRPeepholeOptimizer.kt @@ -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, MutableSet>, + 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, MutableSet> = 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, 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) + } +*/ +} \ No newline at end of file diff --git a/compiler/test/vm/TestCompilerVirtual.kt b/compiler/test/vm/TestCompilerVirtual.kt index b92242121..a9c6869c8 100644 --- a/compiler/test/vm/TestCompilerVirtual.kt +++ b/compiler/test/vm/TestCompilerVirtual.kt @@ -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) } diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 638bc0861..8de4d2255 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -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 diff --git a/intermediate/src/prog8/intermediate/IRInstructions.kt b/intermediate/src/prog8/intermediate/IRInstructions.kt index eb69c8704..cbbd8b451 100644 --- a/intermediate/src/prog8/intermediate/IRInstructions.kt +++ b/intermediate/src/prog8/intermediate/IRInstructions.kt @@ -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 {