https://github.com/lucas-rami updated https://github.com/llvm/llvm-project/pull/202673
>From 905b4eec2c70ca39fcd8e24e024401a25117ae2e Mon Sep 17 00:00:00 2001 From: Lucas Ramirez <[email protected]> Date: Sun, 26 Apr 2026 11:52:17 +0000 Subject: [PATCH 1/3] [CodeGen] Fine-grained LIS updates on remat and dead-def handling This replaces the rematerializer's manual bulk LIS update paradigm in favor of an automated fine-grained one that 1. performs LIS updates as rematerializations happen and 2. handles the removal of dead-definitions properly (this replaces the prior partial handling of live interval splitting). The new approach should be less error-prone (clients do not have to periodically update the LIS, which is now up-to-date at all times from the clients's perspective) and faster in general (live intervals aren't fully re-created every time a def or use of a register changes). Handling dead-definitions (through a `LiveRangeEditor`) adds some complexity to the rematerializer since unrematerializable MIs can now also be deleted. This is exposed to listeners through a new event. Furthermore, rematerializable registers can now become "permanently dead" if all their users were unrematerializable MIs that became dead as a result of other rematerializations. The combination of these two improvements makes handling live-interval splitting unnecessary. Rematerializable registers have a single-def by construction so cannot ever have multiple disconnected components. On the other hand, if we remove dead definitions as they appear, unreamaterializable registers's live interval cannot become made up of multiple disconnected components purely as a result of rematerializations. It is the rematerializer's client responsibility to ensure that the LIS is in a valid state before the rematerializer analyses the function. --- llvm/include/llvm/CodeGen/Rematerializer.h | 88 +++- llvm/lib/CodeGen/Rematerializer.cpp | 407 +++++++++++++----- .../machine-scheduler-sink-trivial-remats.mir | 2 - llvm/unittests/CodeGen/RematerializerTest.cpp | 185 +++++--- 4 files changed, 508 insertions(+), 174 deletions(-) diff --git a/llvm/include/llvm/CodeGen/Rematerializer.h b/llvm/include/llvm/CodeGen/Rematerializer.h index 2ceb6907236fd..62195e8eec8d1 100644 --- a/llvm/include/llvm/CodeGen/Rematerializer.h +++ b/llvm/include/llvm/CodeGen/Rematerializer.h @@ -15,6 +15,7 @@ #define LLVM_CODEGEN_REMATERIALIZER_H #include "llvm/CodeGen/LiveIntervals.h" +#include "llvm/CodeGen/LiveRangeEdit.h" #include "llvm/CodeGen/MachineBasicBlock.h" #include "llvm/CodeGen/MachineRegisterInfo.h" #include "llvm/CodeGen/TargetInstrInfo.h" @@ -77,11 +78,12 @@ namespace llvm { /// /// Throughout its lifetime, the rematerializer tracks new registers it creates /// (which are rematerializable by construction) and their relations to other -/// registers. It performs DAG updates immediately on rematerialization but -/// defers/batches all necessary live interval updates to reduce the number of -/// expensive LIS queries when successively rematerializing many registers. \ref -/// Rematerializer::updateLiveIntervals performs all currently batched live -/// interval updates. +/// registers. It performs DAG and live interval updates immediately on +/// rematerialization and/or user transfer. Importantly, missing dead flags on +/// partial definitions of unrematerializable registers can yield dead +/// definitions when rematerializing their users. They are deleted to preserve +/// live interval validity. These deletions can cascade to other +/// (un)rematerializable registers that also become dead as a result. /// /// In its nomenclature, the rematerializer differentiates between "original /// registers" (registers that were present when it analyzed the function) and @@ -176,6 +178,12 @@ class Rematerializer { rematerializerNoteRegWillBeDeleted(const Rematerializer &Remater, RegisterIdx RegIdx) {} + /// Called just before unrematerializable instruction \p MI is deleted from + /// the MIR because it has become a dead definition. + virtual void + rematerializerNoteMIWillBeDeleted(const Rematerializer &Remater, + MachineInstr &MI) {} + virtual ~Listener() = default; private: @@ -225,6 +233,14 @@ class Rematerializer { ArrayRef<Reg> getRegs() const { return Regs; }; unsigned getNumRegs() const { return Regs.size(); }; + /// Determines whether register \p RegIdx fully disappeared from the MIR. This + /// may happen when it was only used by instructions which became dead during + /// the rematerializer's lifetime. + bool isPermanentlyDead(RegisterIdx RegIdx) const { + RegisterIdx OrigIdx = getOriginOrSelf(RegIdx); + return !getReg(OrigIdx).isAlive() && !Rematerializations.contains(OrigIdx); + } + const RegionBoundaries &getRegion(RegisterIdx RegionIdx) const { assert(RegionIdx < Regions.size() && "out of bounds"); return Regions[RegionIdx]; @@ -386,10 +402,6 @@ class Rematerializer { /// ToRegIdx. LLVM_ABI void transferAllUsers(RegisterIdx FromRegIdx, RegisterIdx ToRegIdx); - /// Recomputes all live intervals that have changed as a result of previous - /// rematerializations. - LLVM_ABI void updateLiveIntervals(); - /// Determines whether (sub-)register operand \p MO has the same value at /// all \p Uses as at \p MO. This implies that it is also available at all \p /// Uses according to its current live interval. @@ -418,6 +430,12 @@ class Rematerializer { std::optional<unsigned> UseRegion = std::nullopt) const; private: + struct DeadDefDelegate : LiveRangeEdit::Delegate { + Rematerializer &Remater; + DeadDefDelegate(Rematerializer &Remater) : Remater(Remater) {} + void LRE_WillEraseInstruction(MachineInstr *MI) override; + }; + SmallVectorImpl<RegionBoundaries> &Regions; MachineRegisterInfo &MRI; LiveIntervals &LIS; @@ -435,6 +453,11 @@ class Rematerializer { Listen->rematerializerNoteRegWillBeDeleted(*this, RegIdx); } + void noteMIWillBeDeleted(MachineInstr &MI) const { + for (Listener *Listen : Listeners) + Listen->rematerializerNoteMIWillBeDeleted(*this, MI); + } + /// Rematerializable registers identified since the rematerializer's creation, /// both dead and alive, originals and rematerializations. No register is ever /// deleted. Indices inside this vector serve as handles for rematerializable @@ -461,14 +484,36 @@ class Rematerializer { DenseMap<Register, RegisterIdx> RegToIdx; /// Parent block of each region, in order. SmallVector<MachineBasicBlock *> RegionMBB; - /// Set of registers whose live-range may have changed during past - /// rematerializations. - DenseSet<RegisterIdx> LISUpdates; /// Common post-processing step after creating a new register \p RematRegIdx /// based on register \p ModelRegIdx. void postRematerialization(RegisterIdx ModelRegIdx, RegisterIdx RematRegIdx); + /// Common pre-processing step before deleting a register \p DeleteRegIdx. The + /// register's defining instruction must still be alive. + void preDeletion(RegisterIdx DeleteRegIdx); + + /// Extends \p LI over \p Mask to be live at \p UdeIdx. + void extendInterval(LiveInterval &LI, LaneBitmask Mask, + SlotIndex UseIdx) const; + + /// Extends the live interval of rematerializable register \p RegIdx to be + /// live at the register slot of all MIs in \p NewUsers. Creates and/or + /// refines the interval's sub-ranges as needed. Updates the register's + /// defining instruction's dead flag as needed. + void extendToNewUsers(RegisterIdx RegIdx, + ArrayRef<MachineInstr *> NewUsers) const; + + /// Shrinks the live interval of rematerializable register \p RegIdx to its + /// current uses. If the register has no users, deletes it along with + /// registers in its dependency DAG that no longer have users as a result. + void shrinkToUses(RegisterIdx RegIdx); + + /// Shrinks the live interval of unrematerializable register \p Reg to its + /// current uses. The interval is split if necessary, creating new + /// unrematerializable registers and updating register dependencies as needed. + void shrinkToUsesUnremat(Register Reg); + /// During the analysis phase, creates a \ref Rematerializer::Reg object for /// virtual register \p VirtRegIdx if it is rematerializable. \p MIRegion maps /// all MIs to their parent region. Set bits in \p SeenRegs indicate virtual @@ -489,16 +534,12 @@ class Rematerializer { void transferUserImpl(RegisterIdx FromRegIdx, RegisterIdx ToRegIdx, MachineInstr &UserMI); - /// Deletes register \p RootIdx if it no longer has any user. If the register - /// is deleted, recursively deletes any of its transitive rematerializable - /// dependencies that no longer have users as a result. In case of recursive - /// deletion, all of a register's users are always deleted before the register - /// itself. - void deleteRegIfUnused(RegisterIdx RootIdx); - - /// Deletes rematerializable register \p RegIdx from the DAG and relevant - /// internal state. - void deleteReg(RegisterIdx RegIdx); + /// Deletes register \p RootIdx, which must not have any users left. If the + /// register is deleted, recursively deletes any of its transitive + /// rematerializable dependencies that no longer have users as a result. In + /// case of recursive deletion, all of a register's users are always deleted + /// before the register itself. + void deleteReg(RegisterIdx RootIdx); }; /// Rematerializer listener with the ability to re-create deleted registers and @@ -524,6 +565,9 @@ class LLVM_ABI Rollbacker : public Rematerializer::Listener { void rematerializerNoteRegWillBeDeleted(const Rematerializer &Remater, RegisterIdx RegIdx) override; + void rematerializerNoteMIWillBeDeleted(const Rematerializer &Remater, + MachineInstr &MI) override; + private: struct DeadReg { /// Register index. diff --git a/llvm/lib/CodeGen/Rematerializer.cpp b/llvm/lib/CodeGen/Rematerializer.cpp index 05544d7dcb895..0ae8be7b99adf 100644 --- a/llvm/lib/CodeGen/Rematerializer.cpp +++ b/llvm/lib/CodeGen/Rematerializer.cpp @@ -119,37 +119,51 @@ Rematerializer::rematerializeToPos(RegisterIdx RootIdx, unsigned UseRegion, void Rematerializer::transferUser(RegisterIdx FromRegIdx, RegisterIdx ToRegIdx, unsigned UserRegion, MachineInstr &UserMI) { transferUserImpl(FromRegIdx, ToRegIdx, UserMI); - Regs[FromRegIdx].eraseUser(&UserMI, UserRegion); + Regs[ToRegIdx].addUser(&UserMI, UserRegion); - deleteRegIfUnused(FromRegIdx); + extendToNewUsers(ToRegIdx, &UserMI); + + Regs[FromRegIdx].eraseUser(&UserMI, UserRegion); + shrinkToUses(FromRegIdx); } void Rematerializer::transferRegionUsers(RegisterIdx FromRegIdx, RegisterIdx ToRegIdx, unsigned UseRegion) { - auto &FromRegUsers = Regs[FromRegIdx].Uses; - auto UsesIt = FromRegUsers.find(UseRegion); - if (UsesIt == FromRegUsers.end()) + Reg &FromReg = Regs[FromRegIdx]; + auto UsesIt = FromReg.Uses.find(UseRegion); + if (UsesIt == FromReg.Uses.end()) return; const SmallDenseSet<MachineInstr *, 4> &RegionUsers = UsesIt->getSecond(); - for (MachineInstr *UserMI : RegionUsers) + SmallVector<MachineInstr *, 4> NewUsers; + for (MachineInstr *UserMI : RegionUsers) { transferUserImpl(FromRegIdx, ToRegIdx, *UserMI); + NewUsers.push_back(UserMI); + } + + extendToNewUsers(ToRegIdx, NewUsers); Regs[ToRegIdx].addUsers(RegionUsers, UseRegion); - FromRegUsers.erase(UseRegion); - deleteRegIfUnused(FromRegIdx); + + FromReg.Uses.erase(UseRegion); + shrinkToUses(FromRegIdx); } void Rematerializer::transferAllUsers(RegisterIdx FromRegIdx, RegisterIdx ToRegIdx) { - Reg &FromReg = Regs[FromRegIdx], &ToReg = Regs[ToRegIdx]; + Reg &FromReg = Regs[FromRegIdx]; + SmallVector<MachineInstr *, 4> NewUsers; for (const auto &[UseRegion, RegionUsers] : FromReg.Uses) { - for (MachineInstr *UserMI : RegionUsers) + for (MachineInstr *UserMI : RegionUsers) { transferUserImpl(FromRegIdx, ToRegIdx, *UserMI); - ToReg.addUsers(RegionUsers, UseRegion); + NewUsers.push_back(UserMI); + } + Regs[ToRegIdx].addUsers(RegionUsers, UseRegion); } + extendToNewUsers(ToRegIdx, NewUsers); + FromReg.Uses.clear(); - deleteRegIfUnused(FromRegIdx); + deleteReg(FromRegIdx); } void Rematerializer::transferUserImpl(RegisterIdx FromRegIdx, @@ -164,8 +178,6 @@ void Rematerializer::transferUserImpl(RegisterIdx FromRegIdx, UserMI.substituteRegister(getReg(FromRegIdx).getDefReg(), getReg(ToRegIdx).getDefReg(), 0, TRI); - LISUpdates.insert(FromRegIdx); - LISUpdates.insert(ToRegIdx); // If the user is rematerializable, we must change its dependency to the // new register. @@ -181,53 +193,6 @@ void Rematerializer::transferUserImpl(RegisterIdx FromRegIdx, } } -void Rematerializer::updateLiveIntervals() { - DenseSet<Register> SeenUnrematRegs; - for (RegisterIdx RegIdx : LISUpdates) { - const Reg &UpdateReg = getReg(RegIdx); - assert(UpdateReg.isAlive() && "dead register"); - - Register DefReg = UpdateReg.getDefReg(); - if (LIS.hasInterval(DefReg)) - LIS.removeInterval(DefReg); - // Rematerializable registers have a single definition by construction so - // re-creating their interval cannot yield a live interval with multiple - // connected components. - LIS.createAndComputeVirtRegInterval(DefReg); - - LLVM_DEBUG({ - dbgs() << "Re-computed interval for " << printID(RegIdx) << ": "; - LIS.getInterval(DefReg).print(dbgs()); - dbgs() << '\n' << printRegUsers(RegIdx); - }); - - // Update intervals for unrematerializable operands. - for (const auto &[UnrematReg, Mask] : getUnrematableDeps(RegIdx)) { - if (!SeenUnrematRegs.insert(UnrematReg).second) - continue; - LIS.removeInterval(UnrematReg); - bool NeedSplit = false; - - // Unrematerializable registers may end up with multiple connected - // components in their live interval after it is re-created. It needs to - // be split in such cases. We don't track unrematerializable registers by - // their actual register index (just by operand index) so we do not need - // to update any state in the rematerializer. - LiveInterval &LI = - LIS.createAndComputeVirtRegInterval(UnrematReg, NeedSplit); - if (NeedSplit) { - SmallVector<LiveInterval *> SplitLIs; - LIS.splitSeparateComponents(LI, SplitLIs); - } - LLVM_DEBUG( - dbgs() << " Re-computed interval for unrematerializable register " - << printReg(UnrematReg, &TRI, 0, &MRI) << " with lanemask " - << Mask << '\n'); - } - } - LISUpdates.clear(); -} - bool Rematerializer::isMOIdenticalAtUses(MachineOperand &MO, ArrayRef<SlotIndex> Uses) const { unsigned SubIdx = MO.getSubReg(); @@ -277,9 +242,8 @@ RegisterIdx Rematerializer::findRematInRegion(RegisterIdx RegIdx, return BestRegIdx; } -void Rematerializer::deleteRegIfUnused(RegisterIdx RootIdx) { - if (!getReg(RootIdx).Uses.empty()) - return; +void Rematerializer::deleteReg(RegisterIdx RootIdx) { + assert(getReg(RootIdx).Uses.empty() && "register still has uses"); // Traverse the root's dependency DAG depth-first to find the set of registers // we can delete and a legal order to delete them in. @@ -300,42 +264,132 @@ void Rematerializer::deleteRegIfUnused(RegisterIdx RootIdx) { } while (!DepDAG.empty()); for (RegisterIdx RegIdx : DeleteOrder) { + preDeletion(RegIdx); Reg &DeleteReg = Regs[RegIdx]; - - // It is possible that the defined register we are deleting doesn't have an - // interval yet if the LIS hasn't been updated since it was created. Register DefReg = DeleteReg.getDefReg(); - if (LIS.hasInterval(DefReg)) - LIS.removeInterval(DefReg); - LISUpdates.erase(RegIdx); + LIS.RemoveMachineInstrFromMaps(*DeleteReg.DefMI); + DeleteReg.DefMI->eraseFromParent(); + DeleteReg.DefMI = nullptr; + LIS.removeInterval(DefReg); + } - deleteReg(RegIdx); - if (isRematerializedRegister(RegIdx)) { - // Delete rematerialized register from its origin's rematerializations. - const RegisterIdx OriginIdx = getOriginOf(RegIdx); - RematsOf &OriginRemats = Rematerializations.at(OriginIdx); - assert(OriginRemats.contains(RegIdx) && "broken remat<->origin link"); - OriginRemats.erase(RegIdx); - if (OriginRemats.empty()) - Rematerializations.erase(OriginIdx); + SmallSet<RegisterIdx, 8> ShrinkRematRegs; + SmallSet<Register, 8> ShrinkUnrematRegs; + + // All dependencies lose a user; their live interval could be shrunk. + for (RegisterIdx DeletedRegIdx : DeleteOrder) { + for (RegisterIdx DepRegIdx : getReg(DeletedRegIdx).Dependencies) { + const Reg &DepReg = getReg(DepRegIdx); + if (DepReg.isAlive() && ShrinkRematRegs.insert(DepRegIdx).second) { + assert(!DepReg.Uses.empty() && "dep should have uses"); + shrinkToUses(DepRegIdx); + } + } + for (const auto [Reg, Mask] : getUnrematableDeps(DeletedRegIdx)) { + if (ShrinkUnrematRegs.insert(Reg).second) + shrinkToUsesUnremat(Reg); } - LLVM_DEBUG(dbgs() << "** Deleted " << printID(RegIdx) << "\n"); } } -void Rematerializer::deleteReg(RegisterIdx RegIdx) { - noteRegWillBeDeleted(RegIdx); +void Rematerializer::DeadDefDelegate::LRE_WillEraseInstruction( + MachineInstr *MI) { + RegisterIdx RegIdx = Remater.getDefRegIdx(*MI); + if (RegIdx == Rematerializer::NoReg) { + // This is an unrematerializable register. + Remater.noteMIWillBeDeleted(*MI); + LLVM_DEBUG(dbgs() << "** About to delete dead definition: " << *MI); + + // Do a linear scan through regions to figure out which one the about to be + // deleted unrematerializable MI is a part of. This is expensive but should + // happen extremely rarely. + // + // FIXME: the rematerializer should stop tracking regions and operate on a + // machine basic block-basis. This would simplify this and a lot of the + // tracking elsewhere. + MachineBasicBlock::iterator It = MI->getIterator(); + const LiveIntervals &LIS = Remater.LIS; + SlotIndex MISlot = LIS.getInstructionIndex(*MI); + unsigned MIRegion = ~0U; + for (auto [RegionIdx, Bounds] : enumerate(Remater.Regions)) { + auto &[RegionBegin, RegionEnd] = Bounds; + MachineBasicBlock::iterator FirstMI = + skipDebugInstructionsForward(RegionBegin, RegionEnd); + if (FirstMI == RegionEnd) { + // The MI cannot be in an empty region. + continue; + } + + if (LIS.getInstructionIndex(*FirstMI) <= MISlot) { + // FistMI exists inside the region so this is guaranteed to point to a + // non-debug MI. + MachineBasicBlock::iterator LastMI = + skipDebugInstructionsBackward(std::prev(RegionEnd), RegionBegin); + if (LIS.getInstructionIndex(*LastMI) < MISlot) + continue; + + // We have found the region the MI is a part of. + MIRegion = RegionIdx; + if (RegionBegin == It) + ++RegionBegin; + break; + } + } + + // All rematerializable registers that this MI uses must be notified. + SmallDenseSet<Register, 2> UsedRegs; + for (const MachineOperand &MO : MI->all_uses()) { + Register Reg = MO.getReg(); + if (Reg.isVirtual() && !UsedRegs.insert(Reg).second) + continue; + auto RematRegUse = Remater.RegToIdx.find(Reg); + if (RematRegUse == Remater.RegToIdx.end()) + continue; + assert(MIRegion != ~0U && "remat user cannot be outside regions"); + Remater.Regs[RematRegUse->second].eraseUser(MI, MIRegion); + } + return; + } + // This is a rematerializable register. - Reg &DeleteReg = Regs[RegIdx]; - assert(DeleteReg.DefMI && "register was already deleted"); - // It is not possible for the deleted instruction to be the upper region - // boundary since we don't ever consider them rematerializable. + // All rematerializable dependencies must be notified. + Reg &DeleteReg = Remater.Regs[RegIdx]; + for (RegisterIdx DepRegIdx : DeleteReg.Dependencies) + Remater.Regs[DepRegIdx].eraseUser(MI, DeleteReg.DefRegion); + + assert(DeleteReg.isAlive() && "register must be alive"); + assert(DeleteReg.Uses.empty() && "register should no longer have uses"); + + // The live-range editor will delete the defining instruction from the MIR + // as well as the register's live-range, so we just need to nullify the def + // internally. + Remater.preDeletion(RegIdx); + DeleteReg.DefMI = nullptr; +} + +void Rematerializer::preDeletion(RegisterIdx DeleteRegIdx) { + Reg &DeleteReg = Regs[DeleteRegIdx]; + assert(DeleteReg.isAlive() && "register must still be alive"); + noteRegWillBeDeleted(DeleteRegIdx); + LLVM_DEBUG(dbgs() << "** About to delete " << printID(DeleteRegIdx) << "\n"); + + // Update region boundary if necessary. It is not possible for the deleted + // instruction to be the upper region boundary since we don't ever consider + // them rematerializable. MachineBasicBlock::iterator &RegionBegin = Regions[DeleteReg.DefRegion].first; if (RegionBegin == DeleteReg.DefMI) - RegionBegin = std::next(MachineBasicBlock::iterator(DeleteReg.DefMI)); - LIS.RemoveMachineInstrFromMaps(*DeleteReg.DefMI); - DeleteReg.DefMI->eraseFromParent(); - DeleteReg.DefMI = nullptr; + ++RegionBegin; + + if (isOriginalRegister(DeleteRegIdx)) + return; + + // Delete rematerialized register from its origin's rematerializations. + const RegisterIdx OriginIdx = getOriginOf(DeleteRegIdx); + RematsOf &OriginRemats = Rematerializations.at(OriginIdx); + assert(OriginRemats.contains(DeleteRegIdx) && "broken remat<->origin link"); + OriginRemats.erase(DeleteRegIdx); + if (OriginRemats.empty()) + Rematerializations.erase(OriginIdx); } Rematerializer::Rematerializer(MachineFunction &MF, @@ -368,7 +422,6 @@ bool Rematerializer::analyze() { Rematerializations.clear(); RegionMBB.clear(); RegToIdx.clear(); - LISUpdates.clear(); if (Regions.empty()) return false; @@ -580,10 +633,14 @@ void Rematerializer::recreateReg(RegisterIdx RegIdx, void Rematerializer::postRematerialization(RegisterIdx ModelRegIdx, RegisterIdx RematRegIdx) { + Reg &ModelReg = Regs[ModelRegIdx], &RematReg = Regs[RematRegIdx]; + + // The rematerialization has no user at this point so its interval will + // initially be empty. + SlotIndex UseIdx = LIS.InsertMachineInstrInMaps(*RematReg.DefMI).getRegSlot(); + LIS.createAndComputeVirtRegInterval(RematReg.getDefReg()); // The start of the new register's region may have changed. - Reg &ModelReg = Regs[ModelRegIdx], &RematReg = Regs[RematRegIdx]; - LIS.InsertMachineInstrInMaps(*RematReg.DefMI); MachineBasicBlock::iterator &RegionBegin = Regions[RematReg.DefRegion].first; if (RegionBegin == std::next(MachineBasicBlock::iterator(RematReg.DefMI))) RegionBegin = RematReg.DefMI; @@ -600,11 +657,140 @@ void Rematerializer::postRematerialization(RegisterIdx ModelRegIdx, Reg &OldDepReg = Regs[OldDepRegIdx]; RematReg.DefMI->substituteRegister(OldDepReg.getDefReg(), NewDepReg.getDefReg(), 0, TRI); - LISUpdates.insert(OldDepRegIdx); } NewDepReg.addUser(RematReg.DefMI, RematReg.DefRegion); - LISUpdates.insert(NewDepRegIdx); + extendToNewUsers(NewDepRegIdx, RematReg.DefMI); + } + + // Unrematerializable dependencies always gain a new user after a + // rematerialization; their live range may need to be extended. + for (const auto &[Reg, Mask] : getUnrematableDeps(ModelRegIdx)) + extendInterval(LIS.getInterval(Reg), Mask, UseIdx); +} + +void Rematerializer::extendToNewUsers(RegisterIdx RegIdx, + ArrayRef<MachineInstr *> NewUsers) const { + if (NewUsers.empty()) + return; + const Reg &ExtendReg = getReg(RegIdx); + assert(ExtendReg.isAlive() && "register must be alive"); + + Register DefReg = ExtendReg.getDefReg(); + LiveInterval &LI = LIS.getInterval(DefReg); + const LaneBitmask FullLaneMask = MRI.getMaxLaneMaskForVReg(DefReg); + const bool ShouldTrackSubReg = MRI.shouldTrackSubRegLiveness(DefReg); + + // Extend all ranges in the register's live interval so that they reach the + // new users. + for (MachineInstr *UserMI : NewUsers) { + SlotIndex UseIdx = LIS.getInstructionIndex(*UserMI).getRegSlot(); + + // Derive register lanes read by that user. + LaneBitmask RegMask; + for (MachineOperand &MO : UserMI->all_uses()) { + if (MO.getReg() == DefReg) { + unsigned SubIdx = MO.getSubReg(); + if (SubIdx == 0) { + RegMask = FullLaneMask; + break; + } + RegMask |= TRI.getSubRegIndexLaneMask(SubIdx); + } + } + + if (RegMask != FullLaneMask) { + // When subreg liveness tracking is required but no subrange exists yet + // (e.g., the interval was computed with only a def of the entire + // register), initialize subranges from the main range so subreg uses are + // tracked. + if (!LI.hasSubRanges() && ShouldTrackSubReg) + LI.createSubRangeFrom(LIS.getVNInfoAllocator(), FullLaneMask, LI); + + // Refine sub-ranges to be able to track the mask for that user. + LI.refineSubRanges( + LIS.getVNInfoAllocator(), RegMask, [](LiveInterval::SubRange &SR) {}, + *LIS.getSlotIndexes(), TRI); + } + extendInterval(LI, RegMask, UseIdx); + } + + LLVM_DEBUG({ + if (ExtendReg.DefMI->getOperand(0).isDead()) + dbgs() << "Clearing dead flag for " + << printRematReg(RegIdx, /*SkipRegions=*/false) << '\n'; + }); + ExtendReg.DefMI->getOperand(0).setIsDead(false); +} + +void Rematerializer::extendInterval(LiveInterval &LI, LaneBitmask Mask, + SlotIndex UseIdx) const { + if (!LI.hasSubRanges()) { + if (!LI.liveAt(UseIdx)) + LLVM_DEBUG(dbgs() << "Extending interval of register " + << llvm::printReg(LI.reg(), &TRI, 0, &MRI) << " to " + << UseIdx << '\n'); + LIS.extendToIndices(LI, UseIdx); + return; } + + bool SubRangeExtended = false; + for (LiveInterval::SubRange &SR : LI.subranges()) { + if ((SR.LaneMask & Mask).any() && !SR.liveAt(UseIdx)) { + SubRangeExtended = true; + LLVM_DEBUG(dbgs() << "Extending subrange " << SR << " of register " + << llvm::printReg(LI.reg(), &TRI, 0, &MRI) << " to " + << UseIdx << '\n'); + LIS.extendToIndices(SR, UseIdx); + } + } + if (!SubRangeExtended) + return; + + // FIXME: this fully reconstructs the main live range from scratch, but + // there may be a more targeted way to make the update. + LI.clear(); + LIS.constructMainRangeFromSubranges(LI); +} + +void Rematerializer::shrinkToUses(RegisterIdx RegIdx) { + Reg &ShrinkReg = Regs[RegIdx]; + assert(ShrinkReg.isAlive() && "register must be alive"); + if (ShrinkReg.Uses.empty()) { + deleteReg(RegIdx); + return; + } + + // By construction, registers should never end up with multiple disconnected + // components or dead definitions. + LiveInterval &LI = LIS.getInterval(ShrinkReg.getDefReg()); + LLVM_DEBUG(dbgs() << "Shrinking interval of " << printID(RegIdx) << ": " << LI + << '\n'); + LIS.shrinkToUses(&LI); +} + +void Rematerializer::shrinkToUsesUnremat(Register Reg) { + LiveInterval &LI = LIS.getInterval(Reg); + LLVM_DEBUG(dbgs() << "Shrinking interval of unrematerializable register " + << LI << '\n'); + + SmallVector<MachineInstr *, 2> DeadDefs; + if (!LIS.shrinkToUses(&LI, &DeadDefs)) { + assert(DeadDefs.empty() && "expected no dead def"); + return; + } + + // This should be a very rare occurence, but shrinking an unrematerializable + // register could create dead defs. + if (DeadDefs.empty()) + return; + + // The live-range editor delegate will take care of reflecting the + // elimination of all dead definitions in the rematerializer. + SmallVector<Register, 4> NewRegs; + DeadDefDelegate DeadDefDeleg(*this); + MachineFunction &MF = *DeadDefs.front()->getParent()->getParent(); + LiveRangeEdit(nullptr, NewRegs, MF, LIS, nullptr, &DeadDefDeleg) + .eliminateDeadDefs(DeadDefs); } std::pair<MachineInstr *, MachineInstr *> @@ -817,6 +1003,20 @@ void Rollbacker::rematerializerNoteRegWillBeDeleted( Positions.push_back(InsertPos); } +void Rollbacker::rematerializerNoteMIWillBeDeleted( + const Rematerializer &Remater, MachineInstr &MI) { + if (RollingBack) + return; + + // Previously deleted registers that reference this MI as their re-creation + // position should instead be re-created at a valid position after it. + MachineBasicBlock *ParentMBB = MI.getParent(); + MachineBasicBlock::iterator ValidPos = std::next(MI.getIterator()); + while (ValidPos != ParentMBB->end() && isRollbackableMI(*ValidPos, Remater)) + ValidPos = std::next(ValidPos); + invalidatePosition(&MI, ValidPos); +} + void Rollbacker::rollback(Rematerializer &Remater) { RollingBack = true; @@ -831,6 +1031,14 @@ void Rollbacker::rollback(Rematerializer &Remater) { // in def-use order. This also ensures that re-creation positions that became // invalid due to later MI deletions can be corrected as we go. for (const DeadReg &Reg : reverse(DeadRegs)) { + if (Remater.isPermanentlyDead(Reg.Idx)) { + // It is possible the register was permanently deleted as a consequence of + // dead-def elimination. + Rematerializations.erase(Reg.Idx); + --PositionIndex; + continue; + } + assert(!Remater.getReg(Reg.Idx).isAlive() && "register should be dead"); // Determine re-creation position for the register's definition. @@ -860,7 +1068,6 @@ void Rollbacker::rollback(Rematerializer &Remater) { } } - Remater.updateLiveIntervals(); DeadRegs.clear(); Positions.clear(); PosToIdx.clear(); @@ -890,10 +1097,10 @@ void Rollbacker::invalidatePosition(MachineInstr *MI, auto MIIndices = PosToIdx.find(MIPos); if (MIIndices == PosToIdx.end()) return; - assert(!MIIndices->getSecond().empty() && "no index hold position"); - for (unsigned I : MIIndices->getSecond()) + const SmallDenseSet<unsigned, 1> &InvalIndices = MIIndices->getSecond(); + assert(!InvalIndices.empty() && "no index hold position"); + for (unsigned I : InvalIndices) Positions[I] = NewPos; - PosToIdx.try_emplace(NewPos).first->getSecond().insert_range( - MIIndices->getSecond()); + PosToIdx.try_emplace(NewPos).first->getSecond().insert_range(InvalIndices); PosToIdx.erase(MIPos); } diff --git a/llvm/test/CodeGen/AMDGPU/machine-scheduler-sink-trivial-remats.mir b/llvm/test/CodeGen/AMDGPU/machine-scheduler-sink-trivial-remats.mir index b421f07b05877..32f5d28c296c3 100644 --- a/llvm/test/CodeGen/AMDGPU/machine-scheduler-sink-trivial-remats.mir +++ b/llvm/test/CodeGen/AMDGPU/machine-scheduler-sink-trivial-remats.mir @@ -13007,8 +13007,6 @@ body: | ; GFX908-GCNTRACKERS-NEXT: [[V_CVT_I32_F32_e32_25:%[0-9]+]]:vgpr_32 = nofpexcept V_CVT_I32_F32_e32 [[DEF26]], implicit $exec, implicit $mode ; GFX908-GCNTRACKERS-NEXT: [[V_CVT_I32_F32_e32_26:%[0-9]+]]:vgpr_32 = nofpexcept V_CVT_I32_F32_e32 [[DEF27]], implicit $exec, implicit $mode ; GFX908-GCNTRACKERS-NEXT: undef [[S_MOV_B32_:%[0-9]+]].sub1:sreg_64 = S_MOV_B32 0 - ; GFX908-GCNTRACKERS-NEXT: [[DEF23:%[0-9]+]].sub1:vreg_512 = IMPLICIT_DEF - ; GFX908-GCNTRACKERS-NEXT: [[DEF23:%[0-9]+]].sub3:vreg_512 = IMPLICIT_DEF ; GFX908-GCNTRACKERS-NEXT: dead [[DEF28:%[0-9]+]]:vgpr_32 = IMPLICIT_DEF ; GFX908-GCNTRACKERS-NEXT: dead undef [[S_MOV_B32_:%[0-9]+]].sub0:sreg_64 = COPY [[S_LOAD_DWORDX2_IMM]].sub1 ; GFX908-GCNTRACKERS-NEXT: [[DEF23:%[0-9]+]].sub2:vreg_512 = IMPLICIT_DEF diff --git a/llvm/unittests/CodeGen/RematerializerTest.cpp b/llvm/unittests/CodeGen/RematerializerTest.cpp index 462f17547483b..85e13ea19d578 100644 --- a/llvm/unittests/CodeGen/RematerializerTest.cpp +++ b/llvm/unittests/CodeGen/RematerializerTest.cpp @@ -106,8 +106,13 @@ class RematerializerTest : public CodeGenTestBase { void SetUp() override { setUpImpl("amdgpu9.50--", "", ""); } using RematerializerTestFn = std::function<void(RematerializerWrapper &RW)>; + using ProcessMIRFn = + std::function<void(MachineFunction &MF, LiveIntervals &LIS)>; - void rematerializerTest(StringRef MIRBody, RematerializerTestFn Test) { + static void doNothing(MachineFunction &MF, LiveIntervals &LIS) {}; + + void rematerializerTest(StringRef MIRBody, RematerializerTestFn Test, + ProcessMIRFn PreRemat = doNothing) { SmallString<512> S; StringRef MIRString = (Twine(R"MIR( --- @@ -122,6 +127,8 @@ body: | MachineFunction &MF = getMF("func"); LiveIntervals &LIS = MFAM.getResult<LiveIntervalsAnalysis>(MF); + PreRemat(MF, LIS); + SmallVector<Rematerializer::RegionBoundaries> Regions; MachineInstr *FirstMI = nullptr; for (MachineBasicBlock &MBB : MF) { @@ -143,9 +150,26 @@ body: | RematerializerWrapper RW(MF, Regions, LIS); Test(RW); - RW->updateLiveIntervals(); EXPECT_TRUE(MF.verify()); } + + /// Replicates the scheduler's effect on \p LIS on an intra-block move of \p + /// MI right before \p MoveBefore, which must be in the same block as \p MI. + void moveMIAndAdjustLiveness(MachineBasicBlock::iterator MoveBefore, + MachineInstr &MI, LiveIntervals &LIS) { + MachineBasicBlock &MBB = *MI.getParent(); + const MachineFunction &MF = *MBB.getParent(); + const MachineRegisterInfo &MRI = MF.getRegInfo(); + const TargetRegisterInfo &TRI = *MF.getSubtarget().getRegisterInfo(); + + MBB.splice(MoveBefore, &MBB, MI.getIterator()); + LIS.handleMove(MI); + + RegisterOperands RegOpers; + RegOpers.collect(MI, TRI, MRI, true, /*IgnoreDead=*/false); + SlotIndex Slot = LIS.getInstructionIndex(MI).getRegSlot(); + RegOpers.adjustLaneLiveness(LIS, MRI, Slot, &MI); + }; }; } // namespace @@ -207,7 +231,6 @@ TEST_F(RematerializerTest, TreeRematRollback) { // Rematerialize Add23 with all transitive dependencies. RW->rematerializeToRegion(Add23, MBB1, DRI); - RW->updateLiveIntervals(); EXPECT_NO_USERS(Cst0); EXPECT_NO_USERS(Cst1); @@ -227,7 +250,6 @@ TEST_F(RematerializerTest, TreeRematRollback) { // Rematerialize Add23 only with its direct dependencies, reuse the rest. DRI.clear().reuse(Cst0).reuse(Cst1); RW->rematerializeToRegion(Add23, MBB1, DRI); - RW->updateLiveIntervals(); EXPECT_NUM_USERS(Cst0, 1); EXPECT_NUM_USERS(Cst1, 1); @@ -391,10 +413,6 @@ TEST_F(RematerializerTest, MultiStep) { EXPECT_REMAT(RematCst0, Cst0, MBB1, 1); EXPECT_REMAT(RematAdd01, Add01, MBB1, 1); - // We are going to re-rematerialize a register so the LIS need to be - // up-to-date. - RW->updateLiveIntervals(); - // Rematerialize Add22 from the second to the third block, which will also // indirectly rematerialize RematAdd01; make sure the latter's // rematerialization's origin is the original register, not RematAdd01. @@ -509,12 +527,11 @@ TEST_F(RematerializerTest, SubRegRematSupport) { } /// The rematerializer had a bug where re-creating the interval of a -/// non-rematerializable super-register defined over multiple MIs, some of which -/// defining entirely dead subregisters, could cause a crash when changing the -/// order of sub-definitions (for example during scheduling) because the -/// re-created interval could end up with multiple connected components, which -/// is illegal. The solution is to split separate components of the interval in -/// such cases. +/// super-register defined over multiple MIs, some of which defining entirely +/// dead subregisters, could cause a crash when changing the order of +/// sub-definitions (for example during scheduling) because the re-created +/// interval could end up with multiple connected components, which is illegal. +/// The solution is to elimimate dead definitions in such cases. TEST_F(RematerializerTest, SplitSubRegDeadDef) { StringRef MIRBody = R"MIR( bb.0: @@ -526,48 +543,116 @@ TEST_F(RematerializerTest, SplitSubRegDeadDef) { S_NOP 0, implicit %1 S_ENDPGM 0 )MIR"; - rematerializerTest(MIRBody, [](RematerializerWrapper &RW) { - // Replicates the scheduler's effect on LIS on an intra-block move of MI. - auto MoveMIAndAdjustLiveness = [&](MachineInstr &MI) { - RW.LIS.handleMove(MI); - const MachineRegisterInfo &MRI = RW.MF.getRegInfo(); - const TargetRegisterInfo &TRI = *RW.MF.getSubtarget().getRegisterInfo(); - RegisterOperands RegOpers; - RegOpers.collect(MI, TRI, MRI, true, /*IgnoreDead=*/false); - SlotIndex Sub1Slot = RW.LIS.getInstructionIndex(MI).getRegSlot(); - RegOpers.adjustLaneLiveness(RW.LIS, MRI, Sub1Slot, &MI); - }; - - MachineBasicBlock &MBB0 = *RW.MF.getBlockNumbered(0); + ProcessMIRFn PreRemat = [this](MachineFunction &MF, LiveIntervals &LIS) { + MachineBasicBlock &MBB0 = *MF.getBlockNumbered(0); MachineInstr &Sub0Def = *MBB0.begin(); - MachineInstr &Sub1Def = *MBB0.begin()->getNextNode(); + MachineInstr &Sub1Def = *std::next(Sub0Def.getIterator()); - // Flip %0's subdefinition order. After the move, the definitions look like: + // Flip %0's subdefinition order. After the move, the definitions look + // like: // undef %0.sub1:vreg_64 = IMPLICIT_DEF // undef %0.sub0:vreg_64 = IMPLICIT_DEF - MBB0.splice(Sub0Def.getIterator(), &MBB0, Sub1Def.getIterator()); - MoveMIAndAdjustLiveness(Sub1Def); + moveMIAndAdjustLiveness(Sub0Def.getIterator(), Sub1Def, LIS); + }; + + rematerializerTest( + MIRBody, + [](RematerializerWrapper &RW) { + // Only %1 should be rematerializable. + ASSERT_EQ(RW->getNumRegs(), 1U); + + // Rematerialize %1 to bb.1. This triggers a live-interval update of %0, + // during which the sub1 def is identified as dead and sub-sequently + // removed. + Rematerializer::DependencyReuseInfo DRI; + const unsigned MBB0 = 0, MBB1 = 1; + const RegisterIdx Add = 0; + RW->rematerializeToRegion(Add, MBB1, DRI); + + // The add is moved to another region. + RW.moveMIs(MBB0, MBB1, 1); + // The sub1 def is dead and deleted. + RW.removeMIs(MBB0, 1); + ASSERT_REGION_SIZES(); + }, + PreRemat); +} - // Rematerialize %1 to bb.1. This triggers a live-interval update of %0 when - // calling Remater.updateLiveIntervals(), during which its interval is - // split. - Rematerializer::DependencyReuseInfo DRI; - const unsigned MBB1 = 1; - const RegisterIdx Add = 0; - RW->rematerializeToRegion(Add, MBB1, DRI); - RW->updateLiveIntervals(); +/// Checks that dead-def elimination successfully deletes all unrematerializable +/// MIs and rematerializable registers that become dead after shrinking the +/// interval of an unrematerializable register reveals a dead definition. +TEST_F(RematerializerTest, DeadDefCascadeDeletion) { + StringRef MIRBody = R"MIR( + bb.0: + %cst0Die:vgpr_32 = nofpexcept V_CVT_I32_F64_e32 0, implicit $exec, implicit $mode + %cst1Die:vgpr_32 = nofpexcept V_CVT_I32_F64_e32 1, implicit $exec, implicit $mode + %addDie:vgpr_32 = V_ADD_U32_e32 %cst0Die, %cst1Die, implicit $exec - // If we didn't split %0 before, its definitions would now look like: - // dead undef %0.sub1:vreg_64 = IMPLICIT_DEF - // undef %0.sub0:vreg_64 = IMPLICIT_DEF - // - // Trying to flip back %0's definition order then triggers an - // error in LIS.handleMove because its live interval is made up of multiple - // connected components. - ASSERT_NE(Sub0Def.getOperand(0).getReg(), Sub1Def.getOperand(0).getReg()); - MBB0.splice(MBB0.end(), &MBB0, Sub1Def.getIterator()); - MoveMIAndAdjustLiveness(Sub1Def); - }); + undef %multidefDontDie.sub0:vreg_64 = IMPLICIT_DEF + %multidefDontDie.sub1:vreg_64 = IMPLICIT_DEF + + bb.1: + %cst2:vgpr_32 = nofpexcept V_CVT_I32_F64_e32 2, implicit $exec, implicit $mode + undef %multidef.sub0:vreg_64 = IMPLICIT_DEF + %multidef.sub1:vreg_64 = V_ADD_U32_e32 %addDie, %multidefDontDie.sub1, implicit $exec + %add:vgpr_32 = V_ADD_U32_e32 %multidef.sub0, %multidefDontDie.sub0, implicit $exec + + bb.2: + S_NOP 0, implicit %cst2, implicit %add + S_ENDPGM 0 +)MIR"; + ProcessMIRFn PreRemat = [this](MachineFunction &MF, LiveIntervals &LIS) { + MachineBasicBlock &MBB0 = *MF.getBlockNumbered(1); + MachineInstr &Sub0Def = *std::next(MBB0.begin()); + MachineInstr &Sub1Def = *std::next(Sub0Def.getIterator()); + + // Flip %multidef's subdefinition order. After the move, the definitions + // look like: + // undef %multidef.sub1:vreg_64 = ... + // undef %multidef.sub0:vreg_64 = ... + moveMIAndAdjustLiveness(Sub0Def.getIterator(), Sub1Def, LIS); + }; + + rematerializerTest( + MIRBody, + [](RematerializerWrapper &RW) { + Rollbacker Rollback; + RW->addListener(&Rollback); + + Rematerializer::DependencyReuseInfo DRI; + const unsigned MBB0 = 0, MBB1 = 1, MBB2 = 2; + const RegisterIdx Cst1Die = 1, AddDie = 2, Cst2 = 3, Add = 4; + ASSERT_EQ(RW->getNumRegs(), 5U); + + // Rematerialize %addDie along with %cst0Die right after %cst2. + RW->rematerializeToRegion(AddDie, MBB1, DRI.reuse(Cst1Die)); + RW.moveMIs(MBB0, MBB1, 2); + + // %cst2 and %add are moved to their using region. + RW->rematerializeToRegion(Cst2, MBB2, DRI.clear()); + RW->rematerializeToRegion(Add, MBB2, DRI.clear()); + RW.moveMIs(MBB1, MBB2, 2); + + // The rematerialization of %add makes %multidef.sub1 become a dead def. + // It is deleted along with %addDie, %cst1Die, and %cst0Die, which in + // turn no longer have any uses. These are rematerializable registers + // that become "permanently dead" in the rematerializer's nomenclature. + RW.removeMIs(MBB1, 3); + RW.removeMIs(MBB0, 1); + ASSERT_REGION_SIZES(); + + // We are mostly interested in %cst2 being re-created correctly. When + // it was rematerialized it was followed by rematerializations that have + // now been permanently deleted (which cannot therefore be rolled back), + // and by an unrematerializable MI that has also been permanently + // deleted. It should be re-created at the beginning of its block, as it + // was initially. + Rollback.rollback(*RW); + EXPECT_EQ(RW->getReg(Cst2).DefMI, &*RW.MF.getBlockNumbered(1)->begin()); + RW.moveMIs(MBB2, MBB1, 2); + ASSERT_REGION_SIZES(); + }, + PreRemat); } /// Checks that rollback works as expected when the rollback listener is added >From 12114b0b857b3387564103e479b9c494469fb0fb Mon Sep 17 00:00:00 2001 From: Lucas Ramirez <[email protected]> Date: Mon, 13 Jul 2026 13:20:11 +0000 Subject: [PATCH 2/3] Add missing includes --- llvm/include/llvm/CodeGen/Rematerializer.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/llvm/include/llvm/CodeGen/Rematerializer.h b/llvm/include/llvm/CodeGen/Rematerializer.h index 62195e8eec8d1..257f5caa51441 100644 --- a/llvm/include/llvm/CodeGen/Rematerializer.h +++ b/llvm/include/llvm/CodeGen/Rematerializer.h @@ -14,6 +14,8 @@ #ifndef LLVM_CODEGEN_REMATERIALIZER_H #define LLVM_CODEGEN_REMATERIALIZER_H +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/DenseSet.h" #include "llvm/CodeGen/LiveIntervals.h" #include "llvm/CodeGen/LiveRangeEdit.h" #include "llvm/CodeGen/MachineBasicBlock.h" >From d81ce6d75d74517e4104aae1c917aafc5765451e Mon Sep 17 00:00:00 2001 From: Lucas Ramirez <[email protected]> Date: Mon, 13 Jul 2026 14:21:58 +0000 Subject: [PATCH 3/3] Move includes to .cpp --- llvm/include/llvm/CodeGen/Rematerializer.h | 2 -- llvm/lib/CodeGen/Rematerializer.cpp | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/llvm/include/llvm/CodeGen/Rematerializer.h b/llvm/include/llvm/CodeGen/Rematerializer.h index 257f5caa51441..62195e8eec8d1 100644 --- a/llvm/include/llvm/CodeGen/Rematerializer.h +++ b/llvm/include/llvm/CodeGen/Rematerializer.h @@ -14,8 +14,6 @@ #ifndef LLVM_CODEGEN_REMATERIALIZER_H #define LLVM_CODEGEN_REMATERIALIZER_H -#include "llvm/ADT/DenseMap.h" -#include "llvm/ADT/DenseSet.h" #include "llvm/CodeGen/LiveIntervals.h" #include "llvm/CodeGen/LiveRangeEdit.h" #include "llvm/CodeGen/MachineBasicBlock.h" diff --git a/llvm/lib/CodeGen/Rematerializer.cpp b/llvm/lib/CodeGen/Rematerializer.cpp index 0ae8be7b99adf..048987214ebf6 100644 --- a/llvm/lib/CodeGen/Rematerializer.cpp +++ b/llvm/lib/CodeGen/Rematerializer.cpp @@ -13,6 +13,8 @@ //===----------------------------------------------------------------------===// #include "llvm/CodeGen/Rematerializer.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/DenseSet.h" #include "llvm/ADT/MapVector.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SetVector.h" _______________________________________________ llvm-branch-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits
