Files
Dhruva Chakrabarti 66abc2ebf1 [AMDGPU] Fix overlapping insert crash during rewrite-agpr-copy-mfma (#205962)
Fixes https://github.com/llvm/llvm-project/issues/204224

Guard against a possibly wrong interference result for a discontiguous
stack slot interval by using the entire range.

A spilled stack slot can have a discontiguous live interval, e.g. a
single value live across several disjoint segments:

  [a, b)  [c, d)  ........gap........  [e, f)

with gaps where the slot is dead. The interference check previously only
considered the covered segments, so it could pick a PhysReg that is free
within them but busy inside a gap. Unspilling replaces the slot with a
vreg whose recomputed interval is continuous over [a, f) (it fills the
gaps), so assigning that PhysReg could overlap the value live in the gap
and trip the "Overlapping insert" assertion in LiveRegMatrix::assign.
Checking interference over the whole [a, f) hull avoids this.

Rather than constructing a temporary "hull" LiveInterval and calling the
LiveInterval-based checkInterference overload -- which risks a
query-cache
identity hazard when that temporary interval is stack-allocated and
reused
across the AllocOrder loop (see the FIXME comment in
LiveRegMatrix::checkInterference(SlotIndex, SlotIndex, MCRegister)) --
this
patch switches the unspilling code to call the slot-index-based
checkInterference(SlotIndex, SlotIndex, MCRegister) overload directly
with
the hull's [beginIndex, endIndex).

Doing so exposed a real bug in that overload: LiveRegMatrix.h documents
that returning false means "PhysReg is free at [Start, End)", but the
implementation only checked the matrix of already-assigned virtual
registers. It never checked fixed register-unit interference (e.g.
PhysReg
is defined directly by some instruction in [Start, End)) or regmask
interference (e.g. PhysReg is clobbered by a call in [Start, End)) --
both
of which the LiveInterval-based overload does check. This patch adds
both
checks to the slot-index-based overload, in the same priority order
(regmask -> regunit -> matrix) as the LiveInterval-based one, bringing
the
two implementations in line.

(Note: checkInterferenceLanes(SlotIndex, SlotIndex, MCRegister) has the
same matrix-only shape, but is out of scope for this patch -- it has a
single caller with different semantics.)

Prior to this patch, the only callers of the slot-index-based
checkInterference were two call sites in InlineSpiller
(hoistSpillInsideBB, hasPhysRegAvailable). Both check narrow windows by
construction (a block's PHI/label/debug prologue, or a single
instruction
boundary), which is likely why missing regunit/regmask coverage there
hasn't caused any existing LIT test to regress.

Three new tests:
- rewrite-vgpr-mfma-to-agpr-spill-discontiguous-interval.mir: adapted
  from #204224. Verifies we no
  longer crash when there is interference
  inside the gap between a discontiguous stack slot's live segments.
- rewrite-vgpr-mfma-to-agpr-unspill-regunit-interference.mir: a stack
  slot value is live across a block of instructions that directly define
every VGPR. Without the new regunit check, the slot is wrongly unspilled
  into a clobbered register, and the CHECK lines fail.
- rewrite-vgpr-mfma-to-agpr-unspill-regmask-interference.mir: same
  scenario, but the value is live across a call whose regmask
(amdgpu_allagprs, a synthetic mask that preserves only AGPRs, used here
  to reliably clobber every VGPR) clobbers the register. Without the new
regmask check, the unspill proceeds, and either the CHECK lines fail or
  the machine verifier reports a use of an undefined physical register.

Assisted-by: Cursor/Claude Opus
2026-10-08 13:17:05 -07:00

417 lines
15 KiB
C++

//===- LiveRegMatrix.cpp - Track register interference --------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file defines the LiveRegMatrix analysis pass.
//
//===----------------------------------------------------------------------===//
#include "llvm/CodeGen/LiveRegMatrix.h"
#include "RegisterCoalescer.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/Statistic.h"
#include "llvm/CodeGen/LiveInterval.h"
#include "llvm/CodeGen/LiveIntervalUnion.h"
#include "llvm/CodeGen/LiveIntervals.h"
#include "llvm/CodeGen/MachineFunction.h"
#include "llvm/CodeGen/MachineOperand.h"
#include "llvm/CodeGen/MachineRegisterInfo.h"
#include "llvm/CodeGen/TargetRegisterInfo.h"
#include "llvm/CodeGen/TargetSubtargetInfo.h"
#include "llvm/CodeGen/VirtRegMap.h"
#include "llvm/InitializePasses.h"
#include "llvm/MC/LaneBitmask.h"
#include "llvm/MC/MCRegisterInfo.h"
#include "llvm/Pass.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/raw_ostream.h"
#include <cassert>
using namespace llvm;
#define DEBUG_TYPE "regalloc"
STATISTIC(NumAssigned , "Number of registers assigned");
STATISTIC(NumUnassigned , "Number of registers unassigned");
char LiveRegMatrixWrapperLegacy::ID = 0;
INITIALIZE_PASS_BEGIN(LiveRegMatrixWrapperLegacy, "liveregmatrix",
"Live Register Matrix", false, false)
INITIALIZE_PASS_DEPENDENCY(LiveIntervalsWrapperPass)
INITIALIZE_PASS_DEPENDENCY(VirtRegMapWrapperLegacy)
INITIALIZE_PASS_END(LiveRegMatrixWrapperLegacy, "liveregmatrix",
"Live Register Matrix", false, true)
void LiveRegMatrixWrapperLegacy::getAnalysisUsage(AnalysisUsage &AU) const {
AU.setPreservesAll();
AU.addRequiredTransitive<LiveIntervalsWrapperPass>();
AU.addRequiredTransitive<VirtRegMapWrapperLegacy>();
MachineFunctionPass::getAnalysisUsage(AU);
}
bool LiveRegMatrixWrapperLegacy::runOnMachineFunction(MachineFunction &MF) {
auto &LIS = getAnalysis<LiveIntervalsWrapperPass>().getLIS();
auto &VRM = getAnalysis<VirtRegMapWrapperLegacy>().getVRM();
LRM.init(MF, LIS, VRM);
return false;
}
void LiveRegMatrix::init(MachineFunction &MF, LiveIntervals &pLIS,
VirtRegMap &pVRM) {
TRI = MF.getSubtarget().getRegisterInfo();
LIS = &pLIS;
VRM = &pVRM;
unsigned NumRegUnits = TRI->getNumRegUnits();
if (NumRegUnits != Matrix.size())
Queries.reset(new LiveIntervalUnion::Query[NumRegUnits]);
Matrix.init(*LIUAlloc, NumRegUnits);
// Make sure no stale queries get reused.
invalidateVirtRegs();
}
void LiveRegMatrixWrapperLegacy::releaseMemory() { LRM.releaseMemory(); }
void LiveRegMatrix::releaseMemory() {
for (unsigned i = 0, e = Matrix.size(); i != e; ++i) {
Matrix[static_cast<MCRegUnit>(i)].clear();
// No need to clear Queries here, since LiveIntervalUnion::Query doesn't
// have anything important to clear and LiveRegMatrix's runOnFunction()
// does a std::unique_ptr::reset anyways.
}
}
template <typename Callable>
static bool foreachUnit(const TargetRegisterInfo *TRI,
const LiveInterval &VRegInterval, MCRegister PhysReg,
Callable Func) {
if (VRegInterval.hasSubRanges()) {
for (MCRegUnitMaskIterator Units(PhysReg, TRI); Units.isValid(); ++Units) {
MCRegUnit Unit = (*Units).first;
LaneBitmask Mask = (*Units).second;
for (const LiveInterval::SubRange &S : VRegInterval.subranges()) {
if ((S.LaneMask & Mask).any()) {
if (Func(Unit, S))
return true;
break;
}
}
}
} else {
for (MCRegUnit Unit : TRI->regunits(PhysReg)) {
if (Func(Unit, VRegInterval))
return true;
}
}
return false;
}
void LiveRegMatrix::assign(const LiveInterval &VirtReg, MCRegister PhysReg) {
LLVM_DEBUG(dbgs() << "assigning " << printReg(VirtReg.reg(), TRI) << " to "
<< printReg(PhysReg, TRI) << ':');
assert(!VRM->hasPhys(VirtReg.reg()) && "Duplicate VirtReg assignment");
VRM->assignVirt2Phys(VirtReg.reg(), PhysReg);
foreachUnit(
TRI, VirtReg, PhysReg, [&](MCRegUnit Unit, const LiveRange &Range) {
LLVM_DEBUG(dbgs() << ' ' << printRegUnit(Unit, TRI) << ' ' << Range);
Matrix[Unit].unify(VirtReg, Range);
return false;
});
++NumAssigned;
LLVM_DEBUG(dbgs() << '\n');
}
void LiveRegMatrix::unassign(const LiveInterval &VirtReg,
bool ClearAllReferencingSegments) {
Register PhysReg = VRM->getPhys(VirtReg.reg());
LLVM_DEBUG(dbgs() << "unassigning " << printReg(VirtReg.reg(), TRI)
<< " from " << printReg(PhysReg, TRI) << ':');
VRM->clearVirt(VirtReg.reg());
if (!ClearAllReferencingSegments) {
foreachUnit(TRI, VirtReg, PhysReg,
[&](MCRegUnit Unit, const LiveRange &Range) {
LLVM_DEBUG(dbgs() << ' ' << printRegUnit(Unit, TRI));
Matrix[Unit].extract(VirtReg, Range);
return false;
});
} else {
for (MCRegUnit Unit : TRI->regunits(PhysReg)) {
Matrix[Unit].clearAllSegmentsReferencing(VirtReg);
}
}
++NumUnassigned;
LLVM_DEBUG(dbgs() << '\n');
}
bool LiveRegMatrix::isPhysRegUsed(MCRegister PhysReg) const {
for (MCRegUnit Unit : TRI->regunits(PhysReg)) {
if (!Matrix[Unit].empty())
return true;
}
return false;
}
bool LiveRegMatrix::checkRegMaskInterference(const LiveInterval &VirtReg,
MCRegister PhysReg) {
// Check if the cached information is valid.
// The same BitVector can be reused for all PhysRegs.
// We could cache multiple VirtRegs if it becomes necessary.
if (RegMaskVirtReg != VirtReg.reg() || RegMaskTag != UserTag) {
RegMaskVirtReg = VirtReg.reg();
RegMaskTag = UserTag;
RegMaskUsable.clear();
LIS->checkRegMaskInterference(VirtReg, RegMaskUsable);
}
// The BitVector is indexed by PhysReg, not register unit.
// Regmask interference is more fine grained than regunits.
// For example, a Win64 call can clobber %ymm8 yet preserve %xmm8.
return !RegMaskUsable.empty() &&
(!PhysReg || !RegMaskUsable.test(PhysReg.id()));
}
bool LiveRegMatrix::checkRegUnitInterference(const LiveInterval &VirtReg,
MCRegister PhysReg) {
if (VirtReg.empty())
return false;
CoalescerPair CP(VirtReg.reg(), PhysReg, *TRI);
bool Result = foreachUnit(
TRI, VirtReg, PhysReg, [&](MCRegUnit Unit, const LiveRange &Range) {
const LiveRange &UnitRange = LIS->getRegUnit(Unit);
return Range.overlaps(UnitRange, CP, *LIS->getSlotIndexes());
});
return Result;
}
bool LiveRegMatrix::checkRegMaskInterference(SlotIndex Start, SlotIndex End,
MCRegister PhysReg) {
ArrayRef<SlotIndex> Slots = LIS->getRegMaskSlots();
ArrayRef<const uint32_t *> Bits = LIS->getRegMaskBits();
// Find the first regmask slot that is not before Start.
auto SlotI = llvm::lower_bound(Slots, Start);
for (; SlotI != Slots.end() && *SlotI < End; ++SlotI) {
if (MachineOperand::clobbersPhysReg(Bits[SlotI - Slots.begin()], PhysReg))
return true;
}
return false;
}
bool LiveRegMatrix::checkRegUnitInterference(SlotIndex Start, SlotIndex End,
MCRegister PhysReg) {
for (MCRegUnit Unit : TRI->regunits(PhysReg)) {
if (LIS->getRegUnit(Unit).overlaps(Start, End))
return true;
}
return false;
}
LiveIntervalUnion::Query &LiveRegMatrix::query(const LiveRange &LR,
MCRegUnit RegUnit) {
LiveIntervalUnion::Query &Q = Queries[static_cast<unsigned>(RegUnit)];
Q.init(UserTag, LR, Matrix[RegUnit]);
return Q;
}
LiveRegMatrix::InterferenceKind
LiveRegMatrix::checkInterference(const LiveInterval &VirtReg,
MCRegister PhysReg) {
if (VirtReg.empty())
return IK_Free;
// Regmask interference is the fastest check.
if (checkRegMaskInterference(VirtReg, PhysReg))
return IK_RegMask;
// Check for fixed interference.
if (checkRegUnitInterference(VirtReg, PhysReg))
return IK_RegUnit;
// Check the matrix for virtual register interference.
bool Interference = foreachUnit(TRI, VirtReg, PhysReg,
[&](MCRegUnit Unit, const LiveRange &LR) {
return query(LR, Unit).checkInterference();
});
if (Interference)
return IK_VirtReg;
return IK_Free;
}
bool LiveRegMatrix::checkInterference(SlotIndex Start, SlotIndex End,
MCRegister PhysReg) {
// Regmask interference is the fastest check.
if (checkRegMaskInterference(Start, End, PhysReg))
return true;
// Check for fixed interference.
if (checkRegUnitInterference(Start, End, PhysReg))
return true;
// Construct artificial live range containing only one segment [Start, End).
VNInfo valno(0, Start);
LiveRange::Segment Seg(Start, End, &valno);
LiveRange LR;
LR.addSegment(Seg);
// Check the matrix for virtual register interference with that segment.
for (MCRegUnit Unit : TRI->regunits(PhysReg)) {
// LR is stack-allocated. LiveRegMatrix caches queries by a key that
// includes the address of the live range. If (for the same reg unit) this
// checkInterference overload is called twice, without any other query()
// calls in between (on heap-allocated LiveRanges) - which would invalidate
// the cached query - the LR address seen the second time may well be the
// same as that seen the first time, while the Start/End/valno may not - yet
// the same cached result would be fetched. To avoid that, we don't cache
// this query.
//
// FIXME: the usability of the Query API needs to be improved to avoid
// subtle bugs due to query identity. Avoiding caching, for example, would
// greatly simplify things.
LiveIntervalUnion::Query Q;
Q.reset(UserTag, LR, Matrix[Unit]);
if (Q.checkInterference())
return true;
}
return false;
}
LaneBitmask LiveRegMatrix::checkInterferenceLanes(SlotIndex Start,
SlotIndex End,
MCRegister PhysReg) {
// Construct artificial live range containing only one segment [Start, End).
VNInfo valno(0, Start);
LiveRange::Segment Seg(Start, End, &valno);
LiveRange LR;
LR.addSegment(Seg);
LaneBitmask InterferingLanes;
// Check for interference with that segment
for (MCRegUnitMaskIterator MCRU(PhysReg, TRI); MCRU.isValid(); ++MCRU) {
auto [Unit, Lanes] = *MCRU;
// LR is stack-allocated. LiveRegMatrix caches queries by a key that
// includes the address of the live range. If (for the same reg unit) this
// checkInterference overload is called twice, without any other query()
// calls in between (on heap-allocated LiveRanges) - which would invalidate
// the cached query - the LR address seen the second time may well be the
// same as that seen the first time, while the Start/End/valno may not - yet
// the same cached result would be fetched. To avoid that, we don't cache
// this query.
//
// FIXME: the usability of the Query API needs to be improved to avoid
// subtle bugs due to query identity. Avoiding caching, for example, would
// greatly simplify things.
LiveIntervalUnion::Query Q;
Q.reset(UserTag, LR, Matrix[Unit]);
if (Q.checkInterference())
InterferingLanes |= Lanes;
}
return InterferingLanes;
}
Register LiveRegMatrix::getOneVReg(unsigned PhysReg) const {
const LiveInterval *VRegInterval = nullptr;
for (MCRegUnit Unit : TRI->regunits(PhysReg)) {
if ((VRegInterval = Matrix[Unit].getOneVReg()))
return VRegInterval->reg();
}
return MCRegister::NoRegister;
}
#ifndef NDEBUG
bool LiveRegMatrix::isValid() const {
// Build set of all valid LiveInterval pointers from LiveIntervals.
DenseSet<const LiveInterval *> ValidIntervals;
for (unsigned RegIdx = 0, NumRegs = VRM->getRegInfo().getNumVirtRegs();
RegIdx < NumRegs; ++RegIdx) {
Register VReg = Register::index2VirtReg(RegIdx);
// Only track assigned registers since unassigned ones won't be in Matrix
if (VRM->hasPhys(VReg) && LIS->hasInterval(VReg))
ValidIntervals.insert(&LIS->getInterval(VReg));
}
// Now scan all LiveIntervalUnions in the matrix and verify each pointer
unsigned NumDanglingPointers = 0;
for (unsigned I = 0, Size = Matrix.size(); I < Size; ++I) {
MCRegUnit Unit = static_cast<MCRegUnit>(I);
for (const LiveInterval *LI : Matrix[Unit]) {
if (!ValidIntervals.contains(LI)) {
++NumDanglingPointers;
dbgs() << "ERROR: LiveInterval pointer is not found in LiveIntervals:\n"
<< " Register Unit: " << printRegUnit(Unit, TRI) << '\n'
<< " LiveInterval pointer: " << LI << '\n';
}
}
}
// Reverse check: every VRM-assigned vreg with a non-empty live interval
// must have its segments present in the Matrix for its assigned phys reg.
unsigned NumMissing = 0;
for (unsigned RegIdx = 0, NumRegs = VRM->getRegInfo().getNumVirtRegs();
RegIdx < NumRegs; ++RegIdx) {
Register VReg = Register::index2VirtReg(RegIdx);
if (!VRM->hasPhys(VReg) || !LIS->hasInterval(VReg))
continue;
const LiveInterval &LI = LIS->getInterval(VReg);
if (LI.empty())
continue;
MCRegister PhysReg = VRM->getPhys(VReg);
// Check that the first segment of LI is present in the LiveUnion for
// at least one reg unit of PhysReg.
SlotIndex FirstStart = LI.beginIndex();
bool Found = false;
for (MCRegUnit Unit : TRI->regunits(PhysReg)) {
auto It = Matrix[Unit].find(FirstStart);
if (It.valid() && It.start() == FirstStart && It.value() == &LI) {
Found = true;
break;
}
}
if (!Found) {
++NumMissing;
dbgs() << "ERROR: VirtReg " << printReg(VReg, TRI) << " assigned to "
<< printReg(PhysReg, TRI)
<< " in VirtRegMap but not found in LiveRegMatrix\n";
dbgs() << " LiveInterval: " << LI << "\n";
dbgs() << " FirstStart: " << FirstStart << "\n";
for (MCRegUnit Unit : TRI->regunits(PhysReg)) {
dbgs() << " RegUnit " << printRegUnit(Unit, TRI) << " segments: ";
auto It = Matrix[Unit].find(FirstStart);
if (It.valid())
dbgs() << "[" << It.start() << "," << It.stop() << ") -> "
<< printReg(It.value()->reg(), TRI);
else
dbgs() << "(none found)";
dbgs() << "\n";
}
}
}
return NumDanglingPointers == 0 && NumMissing == 0;
}
#endif
AnalysisKey LiveRegMatrixAnalysis::Key;
LiveRegMatrix LiveRegMatrixAnalysis::run(MachineFunction &MF,
MachineFunctionAnalysisManager &MFAM) {
auto &LIS = MFAM.getResult<LiveIntervalsAnalysis>(MF);
auto &VRM = MFAM.getResult<VirtRegMapAnalysis>(MF);
LiveRegMatrix LRM;
LRM.init(MF, LIS, VRM);
return LRM;
}