diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt index 4a3a5a54d..0e19c77c4 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt @@ -47,6 +47,7 @@ class IRCodeGen( ensureFirstChunkLabels(irProg) irProg.linkChunks() irProg.convertAsmChunks() + irProg.splitSSAchunks() // the optimizer also does 1 essential step regardless of optimizations: joining adjacent chunks. val optimizer = IRPeepholeOptimizer(irProg) diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/IRPeepholeOptimizer.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/IRPeepholeOptimizer.kt index e62a380ac..32e65150d 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/IRPeepholeOptimizer.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/IRPeepholeOptimizer.kt @@ -161,10 +161,10 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) { if(chunk.label!=null) return false if(previous is IRCodeChunk && chunk is IRCodeChunk) { - // if the previous chunk doesn't end in a jump or a return, flow continues into the next chunk + // if the previous chunk doesn't end in a SSA branching instruction, flow continues into the next chunk, so they may be joined val lastInstruction = previous.instructions.lastOrNull() if(lastInstruction!=null) - return lastInstruction.opcode !in OpcodesThatJump + return lastInstruction.opcode !in OpcodesThatEndSSAblock return true } return false @@ -300,16 +300,16 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) { // remove useless RETURN if(idx>0 && (ins.opcode == Opcode.RETURN || ins.opcode==Opcode.RETURNR || ins.opcode==Opcode.RETURNI)) { val previous = chunk.instructions[idx-1] - if(previous.opcode in OpcodesThatJump) { + if(previous.opcode in OpcodesThatBranchUnconditionally) { chunk.instructions.removeAt(idx) changed = true } } // replace subsequent opcodes that jump by just the first - if(idx>0 && (ins.opcode in OpcodesThatJump)) { + if(idx>0 && (ins.opcode in OpcodesThatBranchUnconditionally)) { val previous = chunk.instructions[idx-1] - if(previous.opcode in OpcodesThatJump) { + if(previous.opcode in OpcodesThatBranchUnconditionally) { chunk.instructions.removeAt(idx) changed = true } @@ -362,7 +362,7 @@ jump p8_label_gen_2 */ var changed=false indexedInstructions.reversed().forEach { (idx, ins) -> - if(idx>=2 && ins.opcode in OpcodesThatJump) { + if(idx>=2 && ins.opcode in OpcodesThatBranchUnconditionally) { val previous = indexedInstructions[idx-1].value val previous2 = indexedInstructions[idx-2].value if(previous.opcode==Opcode.LOADR && previous2.opcode in OpcodesThatLoad) { diff --git a/codeGenIntermediate/test/TestVmCodeGen.kt b/codeGenIntermediate/test/TestVmCodeGen.kt index d8f7bf4c5..58664344b 100644 --- a/codeGenIntermediate/test/TestVmCodeGen.kt +++ b/codeGenIntermediate/test/TestVmCodeGen.kt @@ -341,7 +341,7 @@ class TestVmCodeGen: FunSpec({ val errors = ErrorReporterForTests() val result = codegen.generate(program, st, options, errors) as VmAssemblyProgram val irChunks = (result.irProgram.blocks.first().children.single() as IRSubroutine).chunks - irChunks.size shouldBe 1 + irChunks.size shouldBe 2 } test("integer comparison expressions against zero") { @@ -537,7 +537,7 @@ class TestVmCodeGen: FunSpec({ val errors = ErrorReporterForTests() val result = codegen.generate(program, st, options, errors) as VmAssemblyProgram val irChunks = (result.irProgram.blocks.first().children.single() as IRSubroutine).chunks - irChunks.size shouldBe 1 + irChunks.size shouldBe 2 } test("extsub allowed in ir-codegen") { diff --git a/compiler/test/TestPointers.kt b/compiler/test/TestPointers.kt index c71b2fdf2..37c4e9885 100644 --- a/compiler/test/TestPointers.kt +++ b/compiler/test/TestPointers.kt @@ -763,7 +763,6 @@ main { test("address-of struct fields") { val src=""" %import floats -%import textio main { struct List { @@ -1458,7 +1457,6 @@ other { test("passing arrays to subroutines via typed pointer parameters") { val src=""" %import floats -%import textio main { struct Node { diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 033b69ecf..617452e86 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -1,6 +1,9 @@ TODO ==== +IR: rename to , put the code lines in a new sub node for improved xml structure. +TestVMCodegen: add tests for SSA block split/joins/check last instruction to be a valid SSE block ender + STRUCTS and TYPED POINTERS -------------------------- @@ -80,7 +83,6 @@ IR/VM - implement more TODOs in AssignmentGen - do something with the 'split' tag on split word arrays - add more optimizations in IRPeepholeOptimizer -- apparently for SSA form, the IRCodeChunk is not a proper "basic block" yet because the last operation should be a branch or return, and no other branches - reduce register usage via linear-scan algorithm (based on live intervals) https://anoopsarkar.github.io/compilers-class/assets/lectures/opt3-regalloc-linearscan.pdf don't forget to take into account the data type of the register when it's going to be reused! - 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) diff --git a/examples/test.p8 b/examples/test.p8 index cdcc502f9..1250ef5ad 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -1,15 +1,30 @@ -main { +%import textio +%zeropage basicsafe + +main { sub start() { - uword @shared value + cx16.r0 = 200 + cx16.r1 = 0 + plot_particles() + txt.print_uw(cx16.r1) + txt.print(" expected 0\n") - if msb(value)>0 - cx16.r0++ + cx16.r0 = 500 + cx16.r1 = 0 + plot_particles() + txt.print_uw(cx16.r1) + txt.print(" expected 1\n") - if lsb(value)>0 - cx16.r0++ + cx16.r0 = 1 + cx16.r1 = 0 + plot_particles() + txt.print_uw(cx16.r1) + txt.print(" expected 1\n") + } - value = mkword(cx16.r0L, cx16.r1L) - if_z - cx16.r0++ + sub plot_particles() { + if cx16.r0<10 or cx16.r0>319 { + cx16.r1++ + } } } diff --git a/intermediate/src/prog8/intermediate/IRInstructions.kt b/intermediate/src/prog8/intermediate/IRInstructions.kt index e64abc55b..a7ce488d3 100644 --- a/intermediate/src/prog8/intermediate/IRInstructions.kt +++ b/intermediate/src/prog8/intermediate/IRInstructions.kt @@ -427,7 +427,7 @@ enum class Opcode { ALIGN } -val OpcodesThatJump = arrayOf( +val OpcodesThatBranchUnconditionally = arrayOf( Opcode.JUMP, Opcode.JUMPI, Opcode.RETURN, @@ -435,12 +435,7 @@ val OpcodesThatJump = arrayOf( Opcode.RETURNI ) -val OpcodesThatBranch = arrayOf( - Opcode.JUMP, - Opcode.JUMPI, - Opcode.RETURN, - Opcode.RETURNR, - Opcode.RETURNI, +val OpcodesThatBranch = OpcodesThatBranchUnconditionally + arrayOf( Opcode.CALLI, Opcode.CALL, Opcode.CALLFAR, @@ -468,6 +463,29 @@ val OpcodesThatBranch = arrayOf( Opcode.BLES ) +val OpcodesThatEndSSAblock = OpcodesThatBranchUnconditionally + arrayOf( + Opcode.BSTCC, + Opcode.BSTCS, + Opcode.BSTEQ, + Opcode.BSTNE, + Opcode.BSTNEG, + Opcode.BSTPOS, + Opcode.BSTVC, + Opcode.BSTVS, + Opcode.BGTR, + Opcode.BGT, + Opcode.BLT, + Opcode.BGTSR, + Opcode.BGTS, + Opcode.BLTS, + Opcode.BGER, + Opcode.BGE, + Opcode.BLE, + Opcode.BGESR, + Opcode.BGES, + Opcode.BLES +) + val OpcodesThatSetStatusbitsIncludingCarry = arrayOf( Opcode.BIT, Opcode.CMP, diff --git a/intermediate/src/prog8/intermediate/IRProgram.kt b/intermediate/src/prog8/intermediate/IRProgram.kt index 6145c37f3..9c318f788 100644 --- a/intermediate/src/prog8/intermediate/IRProgram.kt +++ b/intermediate/src/prog8/intermediate/IRProgram.kt @@ -144,11 +144,11 @@ class IRProgram(val name: String, fun linkCodeChunk(chunk: IRCodeChunk, next: IRCodeChunkBase?) { // link sequential chunks val jump = chunk.instructions.lastOrNull()?.opcode - if (jump == null || jump !in OpcodesThatJump) { + if (jump == null || jump !in OpcodesThatBranchUnconditionally) { // no jump at the end, so link to next chunk (if it exists) if(next!=null) { when (next) { - is IRCodeChunk if chunk.instructions.lastOrNull()?.opcode !in OpcodesThatJump -> chunk.next = next + is IRCodeChunk if chunk.instructions.lastOrNull()?.opcode !in OpcodesThatBranchUnconditionally -> chunk.next = next is IRInlineAsmChunk -> chunk.next = next is IRInlineBinaryChunk -> chunk.next =next else -> throw AssemblyError("code chunk followed by invalid chunk type $next") @@ -181,7 +181,7 @@ class IRProgram(val name: String, is IRInlineAsmChunk -> { if(next!=null) { val lastInstr = chunk.instructions.lastOrNull() - if(lastInstr==null || lastInstr.opcode !in OpcodesThatJump) + if(lastInstr==null || lastInstr.opcode !in OpcodesThatBranchUnconditionally) chunk.next = next } } @@ -210,7 +210,7 @@ class IRProgram(val name: String, if (chunk is IRCodeChunk) { if(!emptyChunkIsAllowed) require(chunk.instructions.isNotEmpty() || chunk.label != null) - if(chunk.instructions.lastOrNull()?.opcode in OpcodesThatJump) + if(chunk.instructions.lastOrNull()?.opcode in OpcodesThatBranchUnconditionally) require(chunk.next == null) { "chunk ending with a jump or return shouldn't be linked to next" } else if (sub!=null) { // if chunk is NOT the last in the block, it needs to link to next. @@ -316,7 +316,7 @@ class IRProgram(val name: String, chunks += chunk chunk = IRCodeChunk(label, null) val lastInstr = lastChunk.instructions.lastOrNull() - if(lastInstr==null || lastInstr.opcode !in OpcodesThatJump) + if(lastInstr==null || lastInstr.opcode !in OpcodesThatBranchUnconditionally) lastChunk.next = chunk } ) @@ -325,7 +325,7 @@ class IRProgram(val name: String, chunks += chunk chunks.lastOrNull()?.let { val lastInstr = it.instructions.lastOrNull() - if(lastInstr==null || lastInstr.opcode !in OpcodesThatJump) + if(lastInstr==null || lastInstr.opcode !in OpcodesThatBranchUnconditionally) it.next = asmChunk.next } return chunks @@ -361,6 +361,78 @@ class IRProgram(val name: String, } } + fun splitSSAchunks() { + + class SplitInfo(val chunk: IRCodeChunkBase, val splitAt: Int, val blockParent: IRBlock?, val subParent: IRSubroutine?, val chunkIndex: Int) + + val tosplit = mutableListOf() + + fun split(chunk: IRCodeChunk, parent: IRBlock, chunkIndex: Int) { + chunk.instructions.withIndex().forEach { (index, instr) -> + if(instr.opcode in OpcodesThatEndSSAblock) { + if(instr !== chunk.instructions.last()) { + // to be a proper SSA basic block, this instruction has to be the last one in the block. + // split the current chunk and link both halves together using the next pointer + tosplit += SplitInfo(chunk, index, parent, null, chunkIndex) + } + } + } + } + + fun split(chunk: IRCodeChunk, parent: IRSubroutine, chunkIndex: Int) { + chunk.instructions.withIndex().forEach { (index, instr) -> + if(instr.opcode in OpcodesThatEndSSAblock) { + if(instr !== chunk.instructions.last()) { + // to be a proper SSA basic block, this instruction has to be the last one in the block. + // split the current chunk and link both halves together using the next pointer + tosplit += SplitInfo(chunk, index, null, parent, chunkIndex) + } + } + } + } + + this.blocks.forEach { block -> + block.children.withIndex().forEach { (index, child) -> + when(child) { + is IRCodeChunk -> split(child, block, index) + is IRSubroutine -> child.chunks.withIndex().forEach { (index2, chunk) -> + if(chunk is IRCodeChunk) + split(chunk, child, index2) + } + else -> {} + } + } + } + + for(split in tosplit.reversed()) { + val chunk = split.chunk + if(split.blockParent!=null) + require(chunk===split.blockParent.children[split.chunkIndex]) + else + require(chunk===split.subParent!!.chunks[split.chunkIndex]) + val totalSize = chunk.instructions.size + val first = chunk.instructions.dropLast(totalSize-split.splitAt-1) + val second = chunk.instructions.drop(split.splitAt+1) + chunk.instructions.clear() + chunk.instructions.addAll(first) + val secondChunk = IRCodeChunk(null, chunk.next) + secondChunk.instructions.addAll(second) + require(chunk.instructions.last().opcode in OpcodesThatEndSSAblock) + require(chunk.instructions.size + secondChunk.instructions.size == totalSize) + if(chunk.instructions.last().opcode !in OpcodesThatBranchUnconditionally) { + chunk.next = secondChunk + if(split.blockParent!=null) split.blockParent.children.add(split.chunkIndex+1, secondChunk) + else split.subParent!!.chunks.add(split.chunkIndex+1, secondChunk) + // println("split chunk ${chunk.label} at ${split.splitAt}: ${chunk.instructions[split.splitAt]} ${totalSize} = ${chunk.instructions.size}+${secondChunk.instructions.size}") + } else { + // shouldn't occur , unreachable code in second chunk? + chunk.next = null + // println("REMOVED UNREACHABLE CODE") + } + } + } + + fun verifyRegisterTypes(registerTypes: Map) { for(block in blocks) { for(bc in block.children) {