import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldBe import prog8.code.core.* import prog8.code.target.VMTarget import prog8.codegen.intermediate.IRPeepholeOptimizer import prog8.intermediate.* class TestIRPeepholeOpt: FunSpec({ fun makeIRProgram(chunks: List): IRProgram { require(chunks.first().label=="main.start") val block = IRBlock("main", false, IRBlock.Options(), Position.DUMMY) val sub = IRSubroutine("main.start", emptyList(), null, Position.DUMMY) chunks.forEach { sub += it } block += sub val target = VMTarget() val options = CompilationOptions( OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.DONTUSE, emptyList(), CompilationOptions.AllZeropageAllowed, floats = false, noSysInit = true, compTarget = target, loadAddress = target.machine.PROGRAM_LOAD_ADDRESS ) val prog = IRProgram("test", IRSymbolTable(), options, target) prog.addBlock(block) prog.linkChunks() prog.validate() return prog } fun makeIRProgram(instructions: List): IRProgram { val chunk = IRCodeChunk("main.start", null) instructions.forEach { chunk += it } return makeIRProgram(listOf(chunk)) } fun IRProgram.chunks(): List = this.allSubs().flatMap { it.chunks }.toList() test("remove nops") { val irProg = makeIRProgram(listOf( IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=1, immediate=42), IRInstruction(Opcode.NOP), IRInstruction(Opcode.NOP) )) irProg.chunks().single().instructions.size shouldBe 3 val opt = IRPeepholeOptimizer(irProg) opt.optimize(true, ErrorReporterForTests()) irProg.chunks().single().instructions.size shouldBe 1 } test("remove jmp to label below") { val c1 = IRCodeChunk("main.start", null) c1 += IRInstruction(Opcode.JUMP, labelSymbol = "label") val c2 = IRCodeChunk("label", null) c2 += IRInstruction(Opcode.JUMP, labelSymbol = "label2") c2 += IRInstruction(Opcode.NOP) // removed val c3 = IRCodeChunk("label2", null) c3 += IRInstruction(Opcode.JUMP, labelSymbol = "label3") c3 += IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=1) val c4 = IRCodeChunk("label3", null) val irProg = makeIRProgram(listOf(c1, c2, c3, c4)) irProg.chunks().size shouldBe 4 irProg.chunks().flatMap { it.instructions }.size shouldBe 5 val opt = IRPeepholeOptimizer(irProg) opt.optimize(true, ErrorReporterForTests()) irProg.chunks().size shouldBe 3 irProg.chunks()[0].label shouldBe "main.start" irProg.chunks()[1].label shouldBe "label2" irProg.chunks()[2].label shouldBe "label3" irProg.chunks()[0].isEmpty() shouldBe true irProg.chunks()[1].isEmpty() shouldBe false irProg.chunks()[2].isEmpty() shouldBe true val instr = irProg.chunks().flatMap { it.instructions } instr.size shouldBe 2 instr[0].opcode shouldBe Opcode.JUMP instr[1].opcode shouldBe Opcode.INC } test("remove double sec/clc/sei/cli") { val irProg = makeIRProgram(listOf( IRInstruction(Opcode.SEC), IRInstruction(Opcode.SEC), IRInstruction(Opcode.SEC), IRInstruction(Opcode.CLC), IRInstruction(Opcode.CLC), IRInstruction(Opcode.CLC), IRInstruction(Opcode.SEI), IRInstruction(Opcode.SEI), IRInstruction(Opcode.SEI), IRInstruction(Opcode.CLI), IRInstruction(Opcode.CLI), IRInstruction(Opcode.CLI), )) irProg.chunks().single().instructions.size shouldBe 12 val opt = IRPeepholeOptimizer(irProg) opt.optimize(true, ErrorReporterForTests()) val instr = irProg.chunks().single().instructions instr.size shouldBe 2 instr[0].opcode shouldBe Opcode.CLC instr[1].opcode shouldBe Opcode.CLI } test("remove double sec/clc/sei/cli reversed") { val irProg = makeIRProgram(listOf( IRInstruction(Opcode.CLC), IRInstruction(Opcode.CLC), IRInstruction(Opcode.CLC), IRInstruction(Opcode.SEC), IRInstruction(Opcode.SEC), IRInstruction(Opcode.SEC), IRInstruction(Opcode.CLI), IRInstruction(Opcode.CLI), IRInstruction(Opcode.CLI), IRInstruction(Opcode.SEI), IRInstruction(Opcode.SEI), IRInstruction(Opcode.SEI), )) irProg.chunks().single().instructions.size shouldBe 12 val opt = IRPeepholeOptimizer(irProg) opt.optimize(true, ErrorReporterForTests()) val instr = irProg.chunks().single().instructions instr.size shouldBe 2 instr[0].opcode shouldBe Opcode.SEC instr[1].opcode shouldBe Opcode.SEI } test("push followed by pop") { val irProg = makeIRProgram(listOf( IRInstruction(Opcode.PUSH, IRDataType.BYTE, reg1=42), IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=42), IRInstruction(Opcode.PUSH, IRDataType.BYTE, reg1=99), IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=222) )) irProg.chunks().single().instructions.size shouldBe 4 val opt = IRPeepholeOptimizer(irProg) opt.optimize(true, ErrorReporterForTests()) val instr = irProg.chunks().single().instructions instr.size shouldBe 1 instr[0].opcode shouldBe Opcode.LOADR instr[0].reg1 shouldBe 222 instr[0].reg2 shouldBe 99 } test("remove useless div/mul, add/sub") { val irProg = makeIRProgram(listOf( IRInstruction(Opcode.DIV, IRDataType.BYTE, reg1=42, immediate = 1), IRInstruction(Opcode.DIVS, IRDataType.BYTE, reg1=42, immediate = 1), IRInstruction(Opcode.MUL, IRDataType.BYTE, reg1=42, immediate = 1), IRInstruction(Opcode.MOD, IRDataType.BYTE, reg1=42, immediate = 1), IRInstruction(Opcode.DIV, IRDataType.BYTE, reg1=42, immediate = 2), IRInstruction(Opcode.DIVS, IRDataType.BYTE, reg1=42, immediate = 2), IRInstruction(Opcode.MUL, IRDataType.BYTE, reg1=42, immediate = 2), IRInstruction(Opcode.MOD, IRDataType.BYTE, reg1=42, immediate = 2), IRInstruction(Opcode.ADD, IRDataType.BYTE, reg1=42, immediate = 0), IRInstruction(Opcode.SUB, IRDataType.BYTE, reg1=42, immediate = 0) )) irProg.chunks().single().instructions.size shouldBe 10 val opt = IRPeepholeOptimizer(irProg) opt.optimize(true, ErrorReporterForTests()) irProg.chunks().single().instructions.size shouldBe 4 } test("replace add/sub 1 by inc/dec") { val irProg = makeIRProgram(listOf( IRInstruction(Opcode.ADD, IRDataType.BYTE, reg1=42, immediate = 1), IRInstruction(Opcode.SUB, IRDataType.BYTE, reg1=42, immediate = 1) )) irProg.chunks().single().instructions.size shouldBe 2 val opt = IRPeepholeOptimizer(irProg) opt.optimize(true, ErrorReporterForTests()) val instr = irProg.chunks().single().instructions instr.size shouldBe 2 instr[0].opcode shouldBe Opcode.INC instr[1].opcode shouldBe Opcode.DEC } test("remove useless and/or/xor") { val irProg = makeIRProgram(listOf( IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=42, immediate = 255), IRInstruction(Opcode.AND, IRDataType.WORD, reg1=42, immediate = 65535), IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=42, immediate = 0), IRInstruction(Opcode.XOR, IRDataType.BYTE, reg1=42, immediate = 0), IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=42, immediate = 200), IRInstruction(Opcode.AND, IRDataType.WORD, reg1=42, immediate = 60000), IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=42, immediate = 1), IRInstruction(Opcode.XOR, IRDataType.BYTE, reg1=42, immediate = 1) )) irProg.chunks().single().instructions.size shouldBe 8 val opt = IRPeepholeOptimizer(irProg) opt.optimize(true, ErrorReporterForTests()) irProg.chunks().single().instructions.size shouldBe 4 } test("replace and/or/xor by constant number") { val irProg = makeIRProgram(listOf( IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=42, immediate = 0), IRInstruction(Opcode.AND, IRDataType.WORD, reg1=42, immediate = 0), IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=42, immediate = 255), IRInstruction(Opcode.OR, IRDataType.WORD, reg1=42, immediate = 65535) )) irProg.chunks().single().instructions.size shouldBe 4 val opt = IRPeepholeOptimizer(irProg) opt.optimize(true, ErrorReporterForTests()) val instr = irProg.chunks().single().instructions instr.size shouldBe 4 instr[0].opcode shouldBe Opcode.LOAD instr[1].opcode shouldBe Opcode.LOAD instr[2].opcode shouldBe Opcode.LOAD instr[3].opcode shouldBe Opcode.LOAD instr[0].immediate shouldBe 0 instr[1].immediate shouldBe 0 instr[2].immediate shouldBe 255 instr[3].immediate shouldBe 65535 } })