[llvm] [CodeGen] Fix stale LiveIntervals regmask tables after MachineBasicBl… (PR #209610)
via llvm-commits
llvm-commits at lists.llvm.org
Wed Aug 12 10:01:28 PDT 2026
https://github.com/dlei6g updated https://github.com/llvm/llvm-project/pull/209610
>From b70ff8acad656674a3eb5ae17535bf44700ce580 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 blocks is
left stale, so checkRegMaskInterference() can miss a call clobber and the
register allocator can assign a live value to a clobbered physical register.
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 | 23 ++++++++++---
llvm/lib/CodeGen/LiveIntervals.cpp | 39 +++++++++++++++++++++++
llvm/lib/CodeGen/MachineBasicBlock.cpp | 2 +-
llvm/unittests/MI/LiveIntervalTest.cpp | 33 +++++++++++++++++++
4 files changed, 92 insertions(+), 5 deletions(-)
diff --git a/llvm/include/llvm/CodeGen/LiveIntervals.h b/llvm/include/llvm/CodeGen/LiveIntervals.h
index 99a0122265d3b..e343819097f8f 100644
--- a/llvm/include/llvm/CodeGen/LiveIntervals.h
+++ b/llvm/include/llvm/CodeGen/LiveIntervals.h
@@ -281,11 +281,17 @@ class LiveIntervals {
return Indexes->getMBBFromIndex(index);
}
+ /// Adds an empty block \p MBB to the SlotIndexes and regmask maps.
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));
+ insertMBBInMapsImpl(MBB, /*AssumeRegMaskEmpty=*/true);
+ }
+
+ /// After the tail of \p Orig has been sliced into \p SplitBB, updates the
+ /// SlotIndexes and regmask maps and re-slices \p Orig's regmask table across
+ /// the two blocks.
+ void splitAt(MachineBasicBlock &Orig, MachineBasicBlock &SplitBB) {
+ insertMBBInMapsImpl(&SplitBB, /*AssumeRegMaskEmpty=*/false);
+ reassignRegMaskSlots(Orig, SplitBB);
}
SlotIndex InsertMachineInstrInMaps(MachineInstr &MI) {
@@ -482,6 +488,15 @@ class LiveIntervals {
/// Compute RegMaskSlots and RegMaskBits.
void computeRegMasks();
+ /// Implementation of insertMBBInMaps(). \p MBB must contain no regmask
+ /// operands when \p AssumeRegMaskEmpty is true.
+ void insertMBBInMapsImpl(MachineBasicBlock *MBB, bool AssumeRegMaskEmpty);
+
+ /// Updates the regmask table for \p Orig's instructions that are moved into
+ /// \p SplitBB, so that the table is sliced across both blocks.
+ 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..26db1afda20aa 100644
--- a/llvm/lib/CodeGen/LiveIntervals.cpp
+++ b/llvm/lib/CodeGen/LiveIntervals.cpp
@@ -294,6 +294,45 @@ 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()];
+
+ // RegMaskSlots is sorted, so the slots that moved are those at or 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::insertMBBInMapsImpl(
+ MachineBasicBlock *MBB, [[maybe_unused]] bool AssumeRegMaskEmpty) {
+#ifdef EXPENSIVE_CHECKS
+ 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; use "
+ "LiveIntervals::splitAt() to split 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 f4fe9116c3a15..8870f9a8cfbdd 100644
--- a/llvm/lib/CodeGen/MachineBasicBlock.cpp
+++ b/llvm/lib/CodeGen/MachineBasicBlock.cpp
@@ -1088,7 +1088,7 @@ MachineBasicBlock *MachineBasicBlock::splitAt(MachineInstr &MI,
addLiveIns(*SplitBB, LiveRegs);
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