From e139aea277bbf0f6228fa53afe88534d2bcd6105 Mon Sep 17 00:00:00 2001 From: eigmax Date: Thu, 10 Sep 2026 01:02:35 +0000 Subject: [PATCH] [Mips] Do not move a load/store across a call in adjustForDelaySlot adjustForDelaySlot walks forward from a load/store looking for the `ADDiu base, base, imm` that lets the memory op sink into the branch delay slot with an adjusted offset. canSwapLoadStoreWith only compared register operands and mayLoadOrStore(), so a store was carried across `jal` instructions: a call clobbers through its RegMask operand, which the operand scan never sees. A store whose value register is caller-saved ($1/$at in the reported case) then wrote whatever the callees left there. Observed in an LTO'd Rust guest (arkworks Miller loop): the iterator pointer `sw $1, 0x4c($17)` was moved past `jal memcpy`, `jal ell` and `addiu $17, $17, 0x58`, becoming `sw $1, -0xc($17)` in the `bnez` delay slot, so the next iteration copied its line coefficients from address 1 and the pairing returned zero. Every EIP-2537 pairing block failed in production. Refuse to swap with calls, terminators, branches, side-effecting or inline-asm instructions, and with any RegMask that clobbers a register the load/store reads. See https://github.com/ProjectZKM/Ziren/issues/531. --- llvm/lib/Target/Mips/MipsDelaySlotFiller.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/llvm/lib/Target/Mips/MipsDelaySlotFiller.cpp b/llvm/lib/Target/Mips/MipsDelaySlotFiller.cpp index 4c5755efd1404..9acfba5a9fc92 100644 --- a/llvm/lib/Target/Mips/MipsDelaySlotFiller.cpp +++ b/llvm/lib/Target/Mips/MipsDelaySlotFiller.cpp @@ -597,6 +597,22 @@ static int getEquivalentCallShort(int Opcode) { static bool canSwapLoadStoreWith(const MachineInstr &I, const MachineInstr &N) { if (N.mayLoadOrStore()) return false; + // Never move a load/store across a call, a terminator, or anything with + // unmodeled side effects: a call's clobbers are a register mask, not + // register operands, so the operand scan below cannot see that the + // caller-saved register the store reads (e.g. $1/$at) dies at the call. + if (N.isCall() || N.isTerminator() || N.isBranch() || + N.hasUnmodeledSideEffects() || N.isInlineAsm()) + return false; + for (const auto &MO_N : N.operands()) { + if (!MO_N.isRegMask()) + continue; + for (const auto &MO_I : I.operands()) { + if (MO_I.isReg() && MO_I.getReg().isPhysical() && + MO_N.clobbersPhysReg(MO_I.getReg())) + return false; + } + } bool ImayLoad = I.mayLoad(); auto *Fn = I.getParent()->getParent();