[RegAllocFast] Wrong codegen due to missing reload of a landing pad's EH live-ins
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
Compiling the following test case on AArch64/Darwin:
```llvm
target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-n32:64-S128-Fn32"
target triple = "arm64-apple-darwin24.6.0"
declare void @may_throw()
declare void @sink(i64)
declare ptr @__cxa_begin_catch(ptr)
declare i32 @__gxx_personality_v0(...)
define void @probe(i64 %a0, i64 %a1, i64 %a2, i64 %a3, i64 %a4, i64 %a5, i64 %a6, i64 %a7, i64 %a8, i64 %a9, i64 %a10, i64 %n) personality ptr @__gxx_personality_v0 {
entry:
%c = icmp eq i64 %n, 0
br i1 %c, label %b1, label %b2
b1:
invoke void @may_throw() to label %done unwind label %pad
b2:
invoke void @may_throw() to label %done unwind label %pad
done:
ret void
pad:
%v0 = phi i64 [ %a0, %b1 ], [ 0, %b2 ]
%v1 = phi i64 [ %a1, %b1 ], [ 0, %b2 ]
%v2 = phi i64 [ %a2, %b1 ], [ 0, %b2 ]
%v3 = phi i64 [ %a3, %b1 ], [ 0, %b2 ]
%v4 = phi i64 [ %a4, %b1 ], [ 0, %b2 ]
%v5 = phi i64 [ %a5, %b1 ], [ 0, %b2 ]
%v6 = phi i64 [ %a6, %b1 ], [ 0, %b2 ]
%v7 = phi i64 [ %a7, %b1 ], [ 0, %b2 ]
%v8 = phi i64 [ %a8, %b1 ], [ 0, %b2 ]
%v9 = phi i64 [ %a9, %b1 ], [ 0, %b2 ]
%v10 = phi i64 [ %a10, %b1 ], [ 0, %b2 ]
%e = landingpad { ptr, i32 } catch ptr null
%p = extractvalue { ptr, i32 } %e, 0
%cc = call ptr @__cxa_begin_catch(ptr %p)
call void @sink(i64 %v0)
call void @sink(i64 %v1)
call void @sink(i64 %v2)
call void @sink(i64 %v3)
call void @sink(i64 %v4)
call void @sink(i64 %v5)
call void @sink(i64 %v6)
call void @sink(i64 %v7)
call void @sink(i64 %v8)
call void @sink(i64 %v9)
call void @sink(i64 %v10)
ret void
}
```
with `llc -O0` results in the pad reloading only ten of the eleven live
values:
```asm
LBB0_4: ; %pad
ldr x8, [sp, #96] ; 8-byte Reload
... ; (x9-x17 reload the same way)
str x1, [sp, #80] ; 8-byte Spill <-- no reload
```
The eleventh value (`%v10`) is spilled straight from `x1` with no preceding
reload. But by the time the pad starts, the unwinder has already written the
exception selector into `x1` per the Itanium EH ABI, so `%v10` silently
becomes the selector value instead of the value `%a10` held.
`RegAllocFastImpl::reloadAtBegin` (`llvm/lib/CodeGen/RegAllocFast.cpp`)
marks every raw `MBB.liveins()` entry as `regLiveIn` and skips the reload
for any virtual register already parked on one. That's correct for the
entry block, where live-ins are incoming ABI argument registers, but not
for a landing pad, whose exception pointer/selector live-ins are written by
the unwinder at run time rather than by a predecessor. `allocateBasicBlock`
already excludes them on the liveout side via the EH-aware `MBB.liveouts()`
iterator, added by c1dc267258e0 for this exact pass. The livein side was
never updated to match. #122446 fixed the same class of bug in MachineLICM.
The MachineVerifier accepts the result, so this is silent wrong code.
Reproduces on LLVM 12 through current main. Linux needs twelve live values
instead of eleven to hit it, since `x18` is allocatable there.
Skipping the exception pointer/selector the same way MachineLICM computes
them in #122446 fixes it:
```cpp
MCRegister ExceptionPointer, ExceptionSelector;
if (MBB.isEHPad()) {
const MachineFunction &MF = *MBB.getParent();
if (MF.getFunction().hasPersonalityFn()) {
auto PersonalityFn = MF.getFunction().getPersonalityFn();
const TargetLowering &TLI = *MF.getSubtarget().getTargetLowering();
ExceptionPointer = TLI.getExceptionPointerRegister(
TLI.getTargetMachine().getExceptionModel(), PersonalityFn);
ExceptionSelector = TLI.getExceptionSelectorRegister(
TLI.getTargetMachine().getExceptionModel(), PersonalityFn);
}
}
for (MachineBasicBlock::RegisterMaskPair P : MBB.liveins()) {
MCRegister Reg = P.PhysReg;
if (Reg == ExceptionPointer || Reg == ExceptionSelector)
continue;
setPhysRegState(Reg, regLiveIn);
}
```
Built and tested against current main: `ninja check-llvm` is clean, and I
have a new regression test for this. Will follow up with a PR.
Contributor guide
Research direction
Start in llvm/lib/CodeGen/RegAllocFast.cpp, focusing on RegAllocFastImpl::reloadAtBegin and allocateBasicBlock, and compare the EH-aware liveout handling with the livein handling described in the issue. Reproduce with llc -O0 using the AArch64/Darwin IR case, then run the new regression test and ninja check-llvm; done means the landing pad reloads every preserved live value and the regression test passes.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 35/100