[llvm] [RegAllocFast] Reload virtual registers parked on a landing pad's EH live-ins (PR #216478)

via llvm-commits llvm-commits at lists.llvm.org
Sat Aug 15 02:54:25 PDT 2026


https://github.com/yhirose created https://github.com/llvm/llvm-project/pull/216478

RegAllocFast drops the reload for a virtual register whose assigned physical
register is a live-in of the block. That's sound for the entry block, where
live-ins are incoming ABI argument registers, but not for a landing pad: the
exception pointer and selector live-ins are written by the unwinder at run
time, not by a predecessor, so a virtual register parked on one of them never
receives its value. The MachineVerifier accepts the result, so this is
silent wrong code.

`allocateBasicBlock` already handles this via the EH-aware `MBB.liveouts()`
iterator, added by c1dc267258e0 for this pass and refined by #154325;
`reloadAtBegin` still walks the raw `MBB.liveins()`. This patch skips the
exception pointer/selector there too, computed the same way MachineLICM does
in #122446. If the pad reads those registers itself, the reverse walk
already evicts and reloads the virtual register before that read, so the
skip is safe.

`clang -O0` itself doesn't hit this, since it keeps values in `alloca`s
across EH edges and pads carry no PHIs. The practical trigger is a tiered
JIT: `setCodeGenOptLevel(CodeGenOptLevel::None)` on IR that already went
through mem2reg/SROA. Reproduces on LLVM 12 through main.

Adds `llvm/test/CodeGen/AArch64/fast-regalloc-eh-pad-livein.mir`.

Fixes: #216477

*Investigated and drafted with AI-assistant help; reviewed and verified
myself.*


>From 0e0aefa80db2d9710725d5b4bbffe1b6bb868de8 Mon Sep 17 00:00:00 2001
From: yhirose <yuji.hirose.bug at gmail.com>
Date: Sat, 15 Aug 2026 05:52:54 -0400
Subject: [PATCH] [RegAllocFast] Reload virtual registers parked on a landing
 pad's EH live-ins

RegAllocFast drops the reload for a virtual register whose assigned physical
register is a live-in of the block. That's sound for the entry block, where
live-ins are incoming ABI argument registers, but not for a landing pad: the
exception pointer and selector live-ins are written by the unwinder at run
time, not by a predecessor, so a virtual register parked on one of them never
receives its value. The MachineVerifier accepts the result, so this is
silent wrong code.

`allocateBasicBlock` already handles this via the EH-aware `MBB.liveouts()`
iterator, added by c1dc267258e0 for this pass and refined by #154325;
`reloadAtBegin` still walks the raw `MBB.liveins()`. This patch skips the
exception pointer/selector there too, computed the same way MachineLICM does
in #122446. If the pad reads those registers itself, the reverse walk
already evicts and reloads the virtual register before that read, so the
skip is safe.

`clang -O0` itself doesn't hit this, since it keeps values in `alloca`s
across EH edges and pads carry no PHIs. The practical trigger is a tiered
JIT: `setCodeGenOptLevel(CodeGenOptLevel::None)` on IR that already went
through mem2reg/SROA. Reproduces on LLVM 12 through main.

Adds llvm/test/CodeGen/AArch64/fast-regalloc-eh-pad-livein.mir.

Fixes: #216477
---
 llvm/lib/CodeGen/RegAllocFast.cpp             | 21 +++++
 .../AArch64/fast-regalloc-eh-pad-livein.mir   | 87 +++++++++++++++++++
 2 files changed, 108 insertions(+)
 create mode 100644 llvm/test/CodeGen/AArch64/fast-regalloc-eh-pad-livein.mir

diff --git a/llvm/lib/CodeGen/RegAllocFast.cpp b/llvm/lib/CodeGen/RegAllocFast.cpp
index f10283272f1d9..8bf87a1c3512c 100644
--- a/llvm/lib/CodeGen/RegAllocFast.cpp
+++ b/llvm/lib/CodeGen/RegAllocFast.cpp
@@ -33,6 +33,7 @@
 #include "llvm/CodeGen/RegAllocRegistry.h"
 #include "llvm/CodeGen/RegisterClassInfo.h"
 #include "llvm/CodeGen/TargetInstrInfo.h"
+#include "llvm/CodeGen/TargetLowering.h"
 #include "llvm/CodeGen/TargetOpcodes.h"
 #include "llvm/CodeGen/TargetRegisterInfo.h"
 #include "llvm/CodeGen/TargetSubtargetInfo.h"
@@ -42,6 +43,7 @@
 #include "llvm/Support/Debug.h"
 #include "llvm/Support/ErrorHandling.h"
 #include "llvm/Support/raw_ostream.h"
+#include "llvm/Target/TargetMachine.h"
 #include <cassert>
 #include <tuple>
 #include <vector>
@@ -704,8 +706,27 @@ void RegAllocFastImpl::reloadAtBegin(MachineBasicBlock &MBB) {
   if (LiveVirtRegs.empty())
     return;
 
+  // The exception pointer and selector live-ins of a landing pad are written by
+  // the unwinder at run time rather than by a predecessor, so the value a
+  // virtual register assigned to one of them is expected to hold does not
+  // actually arrive in that register. Such a register still needs its reload.
+  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;
     // Set state to live-in. This possibly overrides mappings to virtual
     // registers but we don't care anymore at this point.
     setPhysRegState(Reg, regLiveIn);
diff --git a/llvm/test/CodeGen/AArch64/fast-regalloc-eh-pad-livein.mir b/llvm/test/CodeGen/AArch64/fast-regalloc-eh-pad-livein.mir
new file mode 100644
index 0000000000000..e7869c5cf6363
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/fast-regalloc-eh-pad-livein.mir
@@ -0,0 +1,87 @@
+# RUN: llc -mtriple=aarch64-unknown-linux-gnu -run-pass=regallocfast -o - %s | FileCheck %s
+# RUN: llc -mtriple=aarch64-unknown-linux-gnu -passes=regallocfast -o - %s | FileCheck %s
+
+# The exception pointer and selector live-ins of a landing pad are written by
+# the unwinder at run time, not by a predecessor, so a virtual register that the
+# fast allocator parked on one of them does not actually arrive there and still
+# needs to be reloaded at the start of the pad.
+#
+# Here 13 values are live into the pad, which is one more than the 11 registers
+# the allocator reaches before it gets to $x0 and $x1. Without the reloads, the
+# two values allocated to $x0/$x1 silently read the unwinder's exception pointer
+# and selector instead.
+
+--- |
+  declare void @may_throw()
+  declare i32 @__gxx_personality_v0(...)
+
+  define void @eh_pad_livein_needs_reload(i64 %x) personality ptr @__gxx_personality_v0 {
+  entry:
+    invoke void @may_throw()
+            to label %done unwind label %pad
+
+  done:
+    ret void
+
+  pad:
+    %lp = landingpad { ptr, i32 }
+            cleanup
+    ret void
+  }
+...
+---
+name:            eh_pad_livein_needs_reload
+alignment:       4
+tracksRegLiveness: true
+stack:
+  - { id: 0, size: 8, alignment: 8 }
+body:             |
+  ; CHECK-LABEL: bb.2.pad
+  ; CHECK:       EH_LABEL
+  ; CHECK-DAG:   $x0 = LDRXui
+  ; CHECK-DAG:   $x1 = LDRXui
+  bb.0.entry:
+    successors: %bb.1(0x7ffff800), %bb.2(0x00000800)
+    liveins: $x0
+
+    %0:gpr64 = COPY $x0
+    %1:gpr64 = COPY $x0
+    %2:gpr64 = COPY $x0
+    %3:gpr64 = COPY $x0
+    %4:gpr64 = COPY $x0
+    %5:gpr64 = COPY $x0
+    %6:gpr64 = COPY $x0
+    %7:gpr64 = COPY $x0
+    %8:gpr64 = COPY $x0
+    %9:gpr64 = COPY $x0
+    %10:gpr64 = COPY $x0
+    %11:gpr64 = COPY $x0
+    %12:gpr64 = COPY $x0
+    EH_LABEL <mcsymbol .Ltmp0>
+    BL @may_throw, csr_aarch64_aapcs, implicit-def dead $lr, implicit $sp
+    EH_LABEL <mcsymbol .Ltmp1>
+    B %bb.1
+
+  bb.1.done:
+    RET_ReallyLR
+
+  bb.2.pad (landing-pad):
+    liveins: $x0, $x1
+
+    EH_LABEL <mcsymbol .Ltmp2>
+    STRXui %0, %stack.0, 0 :: (store (s64) into %stack.0)
+    STRXui %1, %stack.0, 0 :: (store (s64) into %stack.0)
+    STRXui %2, %stack.0, 0 :: (store (s64) into %stack.0)
+    STRXui %3, %stack.0, 0 :: (store (s64) into %stack.0)
+    STRXui %4, %stack.0, 0 :: (store (s64) into %stack.0)
+    STRXui %5, %stack.0, 0 :: (store (s64) into %stack.0)
+    STRXui %6, %stack.0, 0 :: (store (s64) into %stack.0)
+    STRXui %7, %stack.0, 0 :: (store (s64) into %stack.0)
+    STRXui %8, %stack.0, 0 :: (store (s64) into %stack.0)
+    STRXui %9, %stack.0, 0 :: (store (s64) into %stack.0)
+    STRXui %10, %stack.0, 0 :: (store (s64) into %stack.0)
+    STRXui %11, %stack.0, 0 :: (store (s64) into %stack.0)
+    STRXui %12, %stack.0, 0 :: (store (s64) into %stack.0)
+    RET_ReallyLR
+
+...



More information about the llvm-commits mailing list