From 8c2bc15b41aa70b306ffccc6f355bc2ede1bbe7d Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Sat, 3 Oct 2026 13:56:15 +0200 Subject: [PATCH] Fix IR peephole crash on lsigb.l + ext.b with mismatched register types regression was introduced in commit 2ce7a63c1e --- .../intermediate/IRPeepholeOptimizer.kt | 13 ++++-- codeGenIntermediate/test/TestIRPeepholeOpt.kt | 45 +++++++++++++++++++ 2 files changed, 54 insertions(+), 4 deletions(-) diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/IRPeepholeOptimizer.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/IRPeepholeOptimizer.kt index 411bc1c96..25d30b2e1 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/IRPeepholeOptimizer.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/IRPeepholeOptimizer.kt @@ -1158,13 +1158,18 @@ jump p8_label_gen_2 mods.add(Mod(i+1, IRInstructions.binaryImmediate(Opcode.AND, IRDataType.LONG, outer.requireDest().registerNumber, 0xffff))) mods.add(Mod(i, IRInstructions.move(IRDataType.LONG, outer.requireDest().registerNumber, inner.requireSrcA().registerNumber))) } - // truncate-long/word-to-byte followed by zero-extend byte-to-word == mask with $ff + // truncate-long/word-to-byte followed by zero-extend byte-to-word == mask with $ff. + // This is only valid when the source and destination have the same type, because + // the rewritten loadr would otherwise access the source register with a different + // type than its definition (e.g. long source to word dest). inner.opcode==Opcode.LSIGB && inner.type in setOf(IRDataType.WORD, IRDataType.LONG) && outer.opcode==Opcode.EXT && outer.type==IRDataType.BYTE && mid!=null && mid==outer.srcA?.register && inner.srcA!=null && outer.dest!=null - && readCounts.getValue(mid)==1 -> { - mods.add(Mod(i+1, IRInstructions.binaryImmediate(Opcode.AND, inner.type!!, outer.requireDest().registerNumber, 0xff))) - mods.add(Mod(i, IRInstructions.move(inner.type!!, outer.requireDest().registerNumber, inner.requireSrcA().registerNumber))) + && readCounts.getValue(mid)==1 + && inner.type == outer.requireDest().type -> { + val resultType = outer.requireDest().type + mods.add(Mod(i+1, IRInstructions.binaryImmediate(Opcode.AND, resultType, outer.requireDest().registerNumber, 0xff))) + mods.add(Mod(i, IRInstructions.move(resultType, outer.requireDest().registerNumber, inner.requireSrcA().registerNumber))) } else -> continue } diff --git a/codeGenIntermediate/test/TestIRPeepholeOpt.kt b/codeGenIntermediate/test/TestIRPeepholeOpt.kt index a097af4d6..322d97654 100644 --- a/codeGenIntermediate/test/TestIRPeepholeOpt.kt +++ b/codeGenIntermediate/test/TestIRPeepholeOpt.kt @@ -514,6 +514,51 @@ class TestIRPeepholeOpt: FunSpec({ instr[2].requireImmediateInt() shouldBe 42 } + test("collapse lsigb.w + ext.b into loadr.w + and.w") { + val irProg = makeIRProgram(listOf( + IRInstructions.loadMemory(Opcode.LOADM, IRDataType.WORD, 7, IRMemory.direct("src")), + IRInstructions.binary(Opcode.LSIGB, IRDataType.WORD, 8, 7), + IRInstructions.binary(Opcode.EXT, IRDataType.BYTE, 9, 8), + IRInstructions.binary(Opcode.ADDR, IRDataType.WORD, 2, 9), + IRInstructions.returnVoid() + )) + val opt = IRPeepholeOptimizer(irProg, false) + opt.optimize(true, ErrorReporterForTests()) + val instr = irProg.chunks().single().instructions + instr.any { it.opcode == Opcode.LSIGB } shouldBe false + instr.any { it.opcode == Opcode.EXT && it.requireDest().register == VirtualRegister.int(9) } shouldBe false + instr.any { + it.opcode == Opcode.LOADR && + it.type == IRDataType.WORD && + it.requireDest().register == VirtualRegister.int(9) && + it.requireSrcA().register == VirtualRegister.int(7) + } shouldBe true + instr.any { + it.opcode == Opcode.AND && + it.type == IRDataType.WORD && + it.requireDest().register == VirtualRegister.int(9) && + it.requireImmediateInt() == 0xff + } shouldBe true + } + + test("do not collapse lsigb.l + ext.b when source and dest types differ") { + // lsigb.l produces a byte from a long; ext.b widens it to word. Rewriting this as + // loadr + and would require accessing the long source as word, which conflicts with + // the register type system. The optimizer must leave the original sequence intact. + val irProg = makeIRProgram(listOf( + IRInstructions.loadMemory(Opcode.LOADM, IRDataType.LONG, 7, IRMemory.direct("src")), + IRInstructions.binary(Opcode.LSIGB, IRDataType.LONG, 8, 7), + IRInstructions.binary(Opcode.EXT, IRDataType.BYTE, 9, 8), + IRInstructions.binary(Opcode.ADDR, IRDataType.WORD, 2, 9), + IRInstructions.returnVoid() + )) + val opt = IRPeepholeOptimizer(irProg, false) + opt.optimize(true, ErrorReporterForTests()) + val instr = irProg.chunks().single().instructions + instr.any { it.opcode == Opcode.LSIGB } shouldBe true + instr.any { it.opcode == Opcode.EXT && it.requireDest().register == VirtualRegister.int(9) } shouldBe true + } + test("coalesce redundant LOADX/STOREX to same index is removed") { val irProg = makeIRProgram(listOf( IRInstructions.loadMemory(Opcode.LOADX, IRDataType.BYTE, 1, IRMemory.indexed("myArray", 10, indexRegType)),