[llvm] [CodeGen] Fix stale LiveIntervals regmask tables after MachineBasicBl… (PR #209610)

via llvm-commits llvm-commits at lists.llvm.org
Thu Jul 23 10:54:34 PDT 2026


https://github.com/dlei6g updated https://github.com/llvm/llvm-project/pull/209610

>From 3b20ac80c60c055fcb4fd579c855095c7717e4e5 Mon Sep 17 00:00:00 2001
From: David Lei <dalei at nvidia.com>
Date: Tue, 7 Jul 2026 21:19:12 +0000
Subject: [PATCH] [CodeGen] Fix stale LiveIntervals regmask tables after
 MachineBasicBlock::splitAt

MachineBasicBlock::splitAt() moves the tail of a block -- including any call
instructions carrying register-mask operands -- into a newly created block.
When LiveIntervals is attached it calls LIS->insertMBBInMaps() for the new
block, which records the block with zero regmask slots. That is only correct
for a fresh, empty block: because the tail (and its regmasks) was *moved* out
of the original block, the per-block RegMaskBlocks index for both the original
and the split block is left stale.

checkRegMaskInterference() consults RegMaskBlocks to find the regmask slots
that apply to a live range. With the stale index a value that is live across a
moved call regmask is no longer seen to interfere with the call clobber, so the
register allocator can assign it to a clobbered physical register, which then
trips the machine verifier (-verify-machineinstrs).

Add a LiveIntervals::splitAt(Orig, SplitBB) entry point that inserts SplitBB
into the SlotIndexes/regmask maps and then re-slices the moved regmask slots out
of Orig's RegMaskBlocks entry into SplitBB's (RegMaskSlots is sorted, so the
slots that moved are those at/after SplitBB's start index).
MachineBasicBlock::splitAt() now calls it in place of insertMBBInMaps().

The re-slicing helper (reassignRegMaskSlots) and the shared map-insertion helper
are private; insertMBBInMaps() is documented as expecting an empty block and,
under EXPENSIVE_CHECKS, asserts the block has no regmask operands so that misuse
is caught.

Adds a LiveIntervals unit test (SplitAtMovesRegMaskInterference) that splits a
block so a value becomes block-local yet still crosses a call regmask, and
checks that checkRegMaskInterference() still reports the clobber.
---
 llvm/include/llvm/CodeGen/LiveIntervals.h | 26 +++++++++++---
 llvm/lib/CodeGen/LiveIntervals.cpp        | 43 +++++++++++++++++++++++
 llvm/lib/CodeGen/MachineBasicBlock.cpp    |  5 ++-
 llvm/unittests/MI/LiveIntervalTest.cpp    | 33 +++++++++++++++++
 4 files changed, 101 insertions(+), 6 deletions(-)

diff --git a/llvm/include/llvm/CodeGen/LiveIntervals.h b/llvm/include/llvm/CodeGen/LiveIntervals.h
index 99a0122265d3b..5d2e1702ed0b4 100644
--- a/llvm/include/llvm/CodeGen/LiveIntervals.h
+++ b/llvm/include/llvm/CodeGen/LiveIntervals.h
@@ -281,11 +281,20 @@ class LiveIntervals {
     return Indexes->getMBBFromIndex(index);
   }
 
-  void insertMBBInMaps(MachineBasicBlock *MBB) {
-    Indexes->insertMBBInMaps(MBB);
-    assert(unsigned(MBB->getNumber()) == RegMaskBlocks.size() &&
-           "Blocks must be added in order.");
-    RegMaskBlocks.push_back(std::make_pair(RegMaskSlots.size(), 0));
+  /// Add \p MBB to the SlotIndexes and regmask maps. \p MBB is normally a
+  /// fresh, empty block and is recorded as having no regmask slots. A caller
+  /// that inserts a block already populated with regmask-bearing instructions
+  /// should call splitAt() instead (which also fixes up the regmask table), or
+  /// pass \p AssumeRegMaskEmpty = false and fix up the table itself.
+  void insertMBBInMaps(MachineBasicBlock *MBB, bool AssumeRegMaskEmpty = true);
+
+  /// Update the SlotIndexes and regmask maps after the tail of \p Orig --
+  /// including any regmask-bearing instructions -- has been moved into the new
+  /// block \p SplitBB: insert \p SplitBB into the maps and re-slice \p Orig's
+  /// regmask table across the two blocks.
+  void splitAt(MachineBasicBlock &Orig, MachineBasicBlock &SplitBB) {
+    insertMBBInMaps(&SplitBB, /*AssumeRegMaskEmpty=*/false);
+    reassignRegMaskSlots(Orig, SplitBB);
   }
 
   SlotIndex InsertMachineInstrInMaps(MachineInstr &MI) {
@@ -482,6 +491,13 @@ class LiveIntervals {
   /// Compute RegMaskSlots and RegMaskBits.
   void computeRegMasks();
 
+  /// Move the per-block regmask table (RegMaskBlocks) entries for the
+  /// instructions splitAt() moved from \p Orig into the new block \p SplitBB,
+  /// so that checkRegMaskInterference does not miss call clobbers for ranges
+  /// local to either block.
+  void reassignRegMaskSlots(MachineBasicBlock &Orig,
+                            MachineBasicBlock &SplitBB);
+
   /// Walk the values in \p LI and check for dead values:
   /// - Dead PHIDef values are marked as unused.
   /// - Dead operands are marked as such.
diff --git a/llvm/lib/CodeGen/LiveIntervals.cpp b/llvm/lib/CodeGen/LiveIntervals.cpp
index 3da53566ae0c2..e7af3aa00a999 100644
--- a/llvm/lib/CodeGen/LiveIntervals.cpp
+++ b/llvm/lib/CodeGen/LiveIntervals.cpp
@@ -294,6 +294,49 @@ void LiveIntervals::computeRegMasks() {
   }
 }
 
+void LiveIntervals::reassignRegMaskSlots(MachineBasicBlock &Orig,
+                                         MachineBasicBlock &SplitBB) {
+  assert(&Orig != &SplitBB && "expected distinct blocks");
+  std::pair<unsigned, unsigned> &OrigRMB = RegMaskBlocks[Orig.getNumber()];
+  std::pair<unsigned, unsigned> &SplitRMB = RegMaskBlocks[SplitBB.getNumber()];
+
+  // splitAt moved the tail of Orig into SplitBB. RegMaskSlots is sorted, so the
+  // slots now belonging to SplitBB are those in Orig's slice at/after SplitBB's
+  // start index.
+  ArrayRef<SlotIndex> OrigSlots =
+      getRegMaskSlots().slice(OrigRMB.first, OrigRMB.second);
+  unsigned KeptCount = llvm::lower_bound(OrigSlots, getMBBStartIdx(&SplitBB)) -
+                       OrigSlots.begin();
+  if (KeptCount == OrigRMB.second)
+    return; // No regmask slots moved into SplitBB.
+
+  SplitRMB.first = OrigRMB.first + KeptCount;
+  SplitRMB.second = OrigRMB.second - KeptCount;
+  OrigRMB.second = KeptCount;
+}
+
+void LiveIntervals::insertMBBInMaps(MachineBasicBlock *MBB,
+                                    [[maybe_unused]] bool AssumeRegMaskEmpty) {
+#ifdef EXPENSIVE_CHECKS
+  // A block inserted here is recorded as having no regmask slots, so unless the
+  // caller opts out it must not contain any.
+  assert((!AssumeRegMaskEmpty ||
+          none_of(*MBB,
+                  [](const MachineInstr &MI) {
+                    return any_of(MI.operands(), [](const MachineOperand &MO) {
+                      return MO.isRegMask();
+                    });
+                  })) &&
+         "insertMBBInMaps expects a block with no regmask operands; pass "
+         "AssumeRegMaskEmpty=false (e.g. via LiveIntervals::splitAt) for a "
+         "block containing calls");
+#endif
+  Indexes->insertMBBInMaps(MBB);
+  assert(unsigned(MBB->getNumber()) == RegMaskBlocks.size() &&
+         "Blocks must be added in order.");
+  RegMaskBlocks.push_back(std::make_pair(RegMaskSlots.size(), 0));
+}
+
 //===----------------------------------------------------------------------===//
 //                           Register Unit Liveness
 //===----------------------------------------------------------------------===//
diff --git a/llvm/lib/CodeGen/MachineBasicBlock.cpp b/llvm/lib/CodeGen/MachineBasicBlock.cpp
index 2870bf404644c..d91cbcb104d4b 100644
--- a/llvm/lib/CodeGen/MachineBasicBlock.cpp
+++ b/llvm/lib/CodeGen/MachineBasicBlock.cpp
@@ -1087,8 +1087,11 @@ MachineBasicBlock *MachineBasicBlock::splitAt(MachineInstr &MI,
   if (UpdateLiveIns)
     addLiveIns(*SplitBB, LiveRegs);
 
+  // splitAt() may have moved regmask-bearing instructions (e.g. calls) into
+  // SplitBB; LIS::splitAt() inserts it into the maps and fixes up the per-block
+  // regmask table so the allocator still sees call clobbers across the split.
   if (LIS)
-    LIS->insertMBBInMaps(SplitBB);
+    LIS->splitAt(*this, *SplitBB);
 
   return SplitBB;
 }
diff --git a/llvm/unittests/MI/LiveIntervalTest.cpp b/llvm/unittests/MI/LiveIntervalTest.cpp
index 0581d84fd0709..0a1851529e4f0 100644
--- a/llvm/unittests/MI/LiveIntervalTest.cpp
+++ b/llvm/unittests/MI/LiveIntervalTest.cpp
@@ -790,6 +790,39 @@ TEST(LiveIntervalTest, SplitAtMultiInstruction) {
       });
 }
 
+TEST(LiveIntervalTest, SplitAtMovesRegMaskInterference) {
+  // %0 is defined before and used after a call-clobber regmask, then split
+  // (with the regmask) into a new block, so %0 becomes block-local and still
+  // crosses the regmask. checkRegMaskInterference takes the single-MBB fast
+  // path over the per-block regmask slice, so a stale slice after splitAt
+  // makes it miss the clobber.
+  liveIntervalTest(
+      R"MIR(
+    successors: %bb.1
+    S_NOP 0
+    %0 = IMPLICIT_DEF
+    S_NOP 0, csr_amdgpu
+    S_NOP 0, implicit %0
+    S_BRANCH %bb.1
+  bb.1:
+    S_NOP 0
+)MIR",
+      [](MachineFunction &MF, LiveIntervalsWrapperPass &LISWrapper) {
+        LiveIntervals &LIS = LISWrapper.getLIS();
+        // Split before the def, moving the def, regmask, and use into a new
+        // block; %0 is then block-local and still crosses the regmask.
+        testSplitAt(MF, LIS, 0, 0);
+        LiveInterval &LI = LIS.getInterval(Register::index2VirtReg(0));
+        MachineBasicBlock *MBB = LIS.intervalIsInOneMBB(LI);
+        ASSERT_TRUE(MBB);
+        // The block %0 now lives in must own the moved regmask slot...
+        EXPECT_FALSE(LIS.getRegMaskSlotsInBlock(MBB->getNumber()).empty());
+        // ...so the allocator still sees the call-clobber interference.
+        BitVector UsableRegs(MF.getSubtarget().getRegisterInfo()->getNumRegs());
+        EXPECT_TRUE(LIS.checkRegMaskInterference(LI, UsableRegs));
+      });
+}
+
 TEST(LiveIntervalTest, RepairIntervals) {
   liveIntervalTest(
       R"MIR(



More information about the llvm-commits mailing list