2022-09-19 16:09:56 +00:00
|
|
|
import io.kotest.core.spec.style.FunSpec
|
|
|
|
import io.kotest.matchers.shouldBe
|
|
|
|
import prog8.code.core.*
|
|
|
|
import prog8.code.target.VMTarget
|
2022-09-19 17:41:43 +00:00
|
|
|
import prog8.codegen.intermediate.IRPeepholeOptimizer
|
2022-09-19 16:09:56 +00:00
|
|
|
import prog8.intermediate.*
|
|
|
|
|
|
|
|
class TestIRPeepholeOpt: FunSpec({
|
2022-10-06 22:34:56 +00:00
|
|
|
fun makeIRProgram(chunks: List<IRCodeChunkBase>): IRProgram {
|
2022-10-25 21:51:22 +00:00
|
|
|
require(chunks.first().label=="main.start")
|
2023-12-26 21:01:49 +00:00
|
|
|
val block = IRBlock("main", false, IRBlock.Options(), Position.DUMMY)
|
2022-09-19 16:09:56 +00:00
|
|
|
val sub = IRSubroutine("main.start", emptyList(), null, Position.DUMMY)
|
2022-10-06 22:34:56 +00:00
|
|
|
chunks.forEach { sub += it }
|
2022-09-19 16:09:56 +00:00
|
|
|
block += sub
|
|
|
|
val target = VMTarget()
|
|
|
|
val options = CompilationOptions(
|
|
|
|
OutputType.RAW,
|
|
|
|
CbmPrgLauncherType.NONE,
|
|
|
|
ZeropageType.DONTUSE,
|
|
|
|
emptyList(),
|
2023-11-02 22:45:10 +00:00
|
|
|
CompilationOptions.AllZeropageAllowed,
|
2022-09-19 16:09:56 +00:00
|
|
|
floats = false,
|
|
|
|
noSysInit = true,
|
|
|
|
compTarget = target,
|
|
|
|
loadAddress = target.machine.PROGRAM_LOAD_ADDRESS
|
|
|
|
)
|
2023-12-11 21:51:33 +00:00
|
|
|
val prog = IRProgram("test", IRSymbolTable(), options, target)
|
2022-09-19 16:09:56 +00:00
|
|
|
prog.addBlock(block)
|
2022-10-23 15:29:03 +00:00
|
|
|
prog.linkChunks()
|
2022-10-16 16:30:14 +00:00
|
|
|
prog.validate()
|
2022-09-19 16:09:56 +00:00
|
|
|
return prog
|
|
|
|
}
|
|
|
|
|
2022-10-06 22:34:56 +00:00
|
|
|
fun makeIRProgram(instructions: List<IRInstruction>): IRProgram {
|
2022-11-02 21:12:42 +00:00
|
|
|
val chunk = IRCodeChunk("main.start", null)
|
2022-10-06 22:34:56 +00:00
|
|
|
instructions.forEach { chunk += it }
|
|
|
|
return makeIRProgram(listOf(chunk))
|
|
|
|
}
|
|
|
|
|
2023-05-18 09:32:20 +00:00
|
|
|
fun IRProgram.chunks(): List<IRCodeChunkBase> = this.allSubs().flatMap { it.chunks }.toList()
|
2022-09-19 16:09:56 +00:00
|
|
|
|
|
|
|
test("remove nops") {
|
|
|
|
val irProg = makeIRProgram(listOf(
|
2023-04-09 13:06:40 +00:00
|
|
|
IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=1, immediate=42),
|
2022-09-26 17:46:44 +00:00
|
|
|
IRInstruction(Opcode.NOP),
|
|
|
|
IRInstruction(Opcode.NOP)
|
2022-09-19 16:09:56 +00:00
|
|
|
))
|
2022-10-06 22:34:56 +00:00
|
|
|
irProg.chunks().single().instructions.size shouldBe 3
|
2022-09-19 16:09:56 +00:00
|
|
|
val opt = IRPeepholeOptimizer(irProg)
|
2023-05-01 21:00:51 +00:00
|
|
|
opt.optimize(true, ErrorReporterForTests())
|
2022-10-06 22:34:56 +00:00
|
|
|
irProg.chunks().single().instructions.size shouldBe 1
|
2022-09-19 16:09:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
test("remove jmp to label below") {
|
2022-11-02 21:12:42 +00:00
|
|
|
val c1 = IRCodeChunk("main.start", null)
|
2023-11-25 00:08:26 +00:00
|
|
|
c1 += IRInstruction(Opcode.JUMP, labelSymbol = "label")
|
2022-11-02 21:12:42 +00:00
|
|
|
val c2 = IRCodeChunk("label", null)
|
2023-11-25 00:08:26 +00:00
|
|
|
c2 += IRInstruction(Opcode.JUMP, labelSymbol = "label2")
|
2022-10-06 22:34:56 +00:00
|
|
|
c2 += IRInstruction(Opcode.NOP) // removed
|
2022-11-02 21:12:42 +00:00
|
|
|
val c3 = IRCodeChunk("label2", null)
|
2022-10-06 22:34:56 +00:00
|
|
|
c3 += IRInstruction(Opcode.JUMP, labelSymbol = "label3")
|
|
|
|
c3 += IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=1)
|
2022-11-02 21:12:42 +00:00
|
|
|
val c4 = IRCodeChunk("label3", null)
|
2022-10-06 22:34:56 +00:00
|
|
|
val irProg = makeIRProgram(listOf(c1, c2, c3, c4))
|
|
|
|
|
|
|
|
irProg.chunks().size shouldBe 4
|
|
|
|
irProg.chunks().flatMap { it.instructions }.size shouldBe 5
|
2022-09-19 16:09:56 +00:00
|
|
|
val opt = IRPeepholeOptimizer(irProg)
|
2023-05-01 21:00:51 +00:00
|
|
|
opt.optimize(true, ErrorReporterForTests())
|
2023-11-25 00:08:26 +00:00
|
|
|
irProg.chunks().size shouldBe 3
|
2022-10-25 21:51:22 +00:00
|
|
|
irProg.chunks()[0].label shouldBe "main.start"
|
2023-11-25 00:08:26 +00:00
|
|
|
irProg.chunks()[1].label shouldBe "label2"
|
|
|
|
irProg.chunks()[2].label shouldBe "label3"
|
2022-10-25 21:51:22 +00:00
|
|
|
irProg.chunks()[0].isEmpty() shouldBe true
|
2023-11-25 00:08:26 +00:00
|
|
|
irProg.chunks()[1].isEmpty() shouldBe false
|
|
|
|
irProg.chunks()[2].isEmpty() shouldBe true
|
2022-10-06 22:34:56 +00:00
|
|
|
val instr = irProg.chunks().flatMap { it.instructions }
|
|
|
|
instr.size shouldBe 2
|
|
|
|
instr[0].opcode shouldBe Opcode.JUMP
|
|
|
|
instr[1].opcode shouldBe Opcode.INC
|
2022-09-19 16:09:56 +00:00
|
|
|
}
|
|
|
|
|
2024-07-14 19:01:19 +00:00
|
|
|
test("remove double sec/clc/sei/cli") {
|
2022-09-19 16:09:56 +00:00
|
|
|
val irProg = makeIRProgram(listOf(
|
2022-09-26 17:46:44 +00:00
|
|
|
IRInstruction(Opcode.SEC),
|
|
|
|
IRInstruction(Opcode.SEC),
|
|
|
|
IRInstruction(Opcode.SEC),
|
|
|
|
IRInstruction(Opcode.CLC),
|
|
|
|
IRInstruction(Opcode.CLC),
|
2024-07-14 19:01:19 +00:00
|
|
|
IRInstruction(Opcode.CLC),
|
|
|
|
IRInstruction(Opcode.SEI),
|
|
|
|
IRInstruction(Opcode.SEI),
|
|
|
|
IRInstruction(Opcode.SEI),
|
|
|
|
IRInstruction(Opcode.CLI),
|
|
|
|
IRInstruction(Opcode.CLI),
|
|
|
|
IRInstruction(Opcode.CLI),
|
2022-09-19 16:09:56 +00:00
|
|
|
))
|
2024-07-14 19:01:19 +00:00
|
|
|
irProg.chunks().single().instructions.size shouldBe 12
|
2022-09-19 16:09:56 +00:00
|
|
|
val opt = IRPeepholeOptimizer(irProg)
|
2023-05-01 21:00:51 +00:00
|
|
|
opt.optimize(true, ErrorReporterForTests())
|
2022-10-06 22:34:56 +00:00
|
|
|
val instr = irProg.chunks().single().instructions
|
2024-07-14 19:01:19 +00:00
|
|
|
instr.size shouldBe 2
|
2022-10-06 22:34:56 +00:00
|
|
|
instr[0].opcode shouldBe Opcode.CLC
|
2024-07-14 19:01:19 +00:00
|
|
|
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
|
2022-09-19 16:09:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
test("push followed by pop") {
|
|
|
|
val irProg = makeIRProgram(listOf(
|
2022-09-30 13:27:03 +00:00
|
|
|
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)
|
2022-09-19 16:09:56 +00:00
|
|
|
))
|
2022-10-06 22:34:56 +00:00
|
|
|
irProg.chunks().single().instructions.size shouldBe 4
|
2022-09-19 16:09:56 +00:00
|
|
|
val opt = IRPeepholeOptimizer(irProg)
|
2023-05-01 21:00:51 +00:00
|
|
|
opt.optimize(true, ErrorReporterForTests())
|
2022-10-06 22:34:56 +00:00
|
|
|
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
|
2022-09-19 16:09:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
test("remove useless div/mul, add/sub") {
|
|
|
|
val irProg = makeIRProgram(listOf(
|
2023-04-09 13:06:40 +00:00
|
|
|
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)
|
2022-09-19 16:09:56 +00:00
|
|
|
))
|
2022-10-06 22:34:56 +00:00
|
|
|
irProg.chunks().single().instructions.size shouldBe 10
|
2022-09-19 16:09:56 +00:00
|
|
|
val opt = IRPeepholeOptimizer(irProg)
|
2023-05-01 21:00:51 +00:00
|
|
|
opt.optimize(true, ErrorReporterForTests())
|
2022-10-06 22:34:56 +00:00
|
|
|
irProg.chunks().single().instructions.size shouldBe 4
|
2022-09-19 16:09:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
test("replace add/sub 1 by inc/dec") {
|
|
|
|
val irProg = makeIRProgram(listOf(
|
2023-04-09 13:06:40 +00:00
|
|
|
IRInstruction(Opcode.ADD, IRDataType.BYTE, reg1=42, immediate = 1),
|
|
|
|
IRInstruction(Opcode.SUB, IRDataType.BYTE, reg1=42, immediate = 1)
|
2022-09-19 16:09:56 +00:00
|
|
|
))
|
2022-10-06 22:34:56 +00:00
|
|
|
irProg.chunks().single().instructions.size shouldBe 2
|
2022-09-19 16:09:56 +00:00
|
|
|
val opt = IRPeepholeOptimizer(irProg)
|
2023-05-01 21:00:51 +00:00
|
|
|
opt.optimize(true, ErrorReporterForTests())
|
2022-10-06 22:34:56 +00:00
|
|
|
val instr = irProg.chunks().single().instructions
|
|
|
|
instr.size shouldBe 2
|
|
|
|
instr[0].opcode shouldBe Opcode.INC
|
|
|
|
instr[1].opcode shouldBe Opcode.DEC
|
2022-09-19 16:09:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
test("remove useless and/or/xor") {
|
|
|
|
val irProg = makeIRProgram(listOf(
|
2023-04-09 13:06:40 +00:00
|
|
|
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)
|
2022-09-19 16:09:56 +00:00
|
|
|
))
|
2022-10-06 22:34:56 +00:00
|
|
|
irProg.chunks().single().instructions.size shouldBe 8
|
2022-09-19 16:09:56 +00:00
|
|
|
val opt = IRPeepholeOptimizer(irProg)
|
2023-05-01 21:00:51 +00:00
|
|
|
opt.optimize(true, ErrorReporterForTests())
|
2022-10-06 22:34:56 +00:00
|
|
|
irProg.chunks().single().instructions.size shouldBe 4
|
2022-09-19 16:09:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
test("replace and/or/xor by constant number") {
|
|
|
|
val irProg = makeIRProgram(listOf(
|
2023-04-09 13:06:40 +00:00
|
|
|
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)
|
2022-09-19 16:09:56 +00:00
|
|
|
))
|
2022-10-06 22:34:56 +00:00
|
|
|
irProg.chunks().single().instructions.size shouldBe 4
|
2022-09-19 16:09:56 +00:00
|
|
|
val opt = IRPeepholeOptimizer(irProg)
|
2023-05-01 21:00:51 +00:00
|
|
|
opt.optimize(true, ErrorReporterForTests())
|
2022-10-06 22:34:56 +00:00
|
|
|
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
|
2023-04-09 13:06:40 +00:00
|
|
|
instr[0].immediate shouldBe 0
|
|
|
|
instr[1].immediate shouldBe 0
|
|
|
|
instr[2].immediate shouldBe 255
|
|
|
|
instr[3].immediate shouldBe 65535
|
2022-09-19 16:09:56 +00:00
|
|
|
}
|
|
|
|
})
|