| //=====-- Rematerializer.cpp - MIR rematerialization support ----*- C++ -*-===// |
| // |
| // 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 |
| // |
| //==-----------------------------------------------------------------------===// |
| // |
| /// \file |
| /// Implements helpers for target-independent rematerialization at the MIR |
| /// level. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "llvm/CodeGen/Rematerializer.h" |
| #include "llvm/ADT/MapVector.h" |
| #include "llvm/ADT/STLExtras.h" |
| #include "llvm/ADT/SetVector.h" |
| #include "llvm/ADT/SmallSet.h" |
| #include "llvm/CodeGen/LiveIntervals.h" |
| #include "llvm/CodeGen/LiveRangeEdit.h" |
| #include "llvm/CodeGen/MachineBasicBlock.h" |
| #include "llvm/CodeGen/MachineOperand.h" |
| #include "llvm/CodeGen/MachineRegisterInfo.h" |
| #include "llvm/CodeGen/Register.h" |
| #include "llvm/CodeGen/TargetRegisterInfo.h" |
| #include "llvm/MC/LaneBitmask.h" |
| #include "llvm/Support/Debug.h" |
| #include <optional> |
| |
| #define DEBUG_TYPE "rematerializer" |
| |
| using namespace llvm; |
| using RegisterIdx = Rematerializer::RegisterIdx; |
| |
| // Pin the vtable to this file. |
| void Rematerializer::Listener::anchor() {} |
| |
| /// Checks whether the value in \p LI at \p UseIdx is identical to \p OVNI (this |
| /// implies it is also live there). When \p LI has sub-ranges, checks that |
| /// all sub-ranges intersecting with \p Mask are also live at \p UseIdx. |
| static bool isIdenticalAtUse(const VNInfo &OVNI, LaneBitmask Mask, |
| SlotIndex UseIdx, const LiveInterval &LI) { |
| if (&OVNI != LI.getVNInfoAt(UseIdx)) |
| return false; |
| |
| if (LI.hasSubRanges()) { |
| // Check that intersecting subranges are live at user. |
| for (const LiveInterval::SubRange &SR : LI.subranges()) { |
| if ((SR.LaneMask & Mask).none()) |
| continue; |
| if (!SR.liveAt(UseIdx)) |
| return false; |
| |
| // Early exit if all used lanes are checked. No need to continue. |
| Mask &= ~SR.LaneMask; |
| if (Mask.none()) |
| break; |
| } |
| } |
| return true; |
| } |
| |
| /// If \p MO is a virtual read register, returns it. Otherwise returns the |
| /// sentinel register. |
| static Register getRegDependency(const MachineOperand &MO) { |
| if (!MO.isReg() || !MO.readsReg()) |
| return Register(); |
| Register Reg = MO.getReg(); |
| if (Reg.isPhysical()) { |
| // By the requirements on trivially rematerializable instructions, a |
| // physical register use is either constant or ignorable. |
| return Register(); |
| } |
| return Reg; |
| } |
| |
| RegisterIdx Rematerializer::rematerializeToRegion(RegisterIdx RootIdx, |
| unsigned UseRegion, |
| DependencyReuseInfo &DRI) { |
| MachineInstr *FirstMI = |
| getReg(RootIdx).getRegionUseBounds(UseRegion, LIS).first; |
| // If there are no users in the region, rematerialize the register at the very |
| // end of the region. |
| MachineBasicBlock::iterator InsertPos = |
| FirstMI ? FirstMI : Regions[UseRegion].second; |
| RegisterIdx NewRegIdx = |
| rematerializeToPos(RootIdx, UseRegion, InsertPos, DRI); |
| transferRegionUsers(RootIdx, NewRegIdx, UseRegion); |
| return NewRegIdx; |
| } |
| |
| RegisterIdx |
| Rematerializer::rematerializeToPos(RegisterIdx RootIdx, unsigned UseRegion, |
| MachineBasicBlock::iterator InsertPos, |
| DependencyReuseInfo &DRI) { |
| assert(!DRI.DependencyMap.contains(RootIdx)); |
| LLVM_DEBUG(dbgs() << "Rematerializing " << printID(RootIdx) << '\n'); |
| |
| SmallVector<RegisterIdx, 2> NewDeps; |
| // Copy all dependencies because recursive rematerialization of dependencies |
| // may invalidate references to the backing vector of registers. |
| SmallVector<RegisterIdx, 2> OldDeps(getReg(RootIdx).Dependencies); |
| for (RegisterIdx DepRegIdx : OldDeps) { |
| // Recursively rematerialize required dependencies at the same position as |
| // the root. Registers form a DAG so the recursion is guaranteed to |
| // terminate. |
| auto RematIdx = DRI.DependencyMap.find(DepRegIdx); |
| RegisterIdx NewDepRegIdx; |
| if (RematIdx == DRI.DependencyMap.end()) |
| NewDepRegIdx = rematerializeToPos(DepRegIdx, UseRegion, InsertPos, DRI); |
| else |
| NewDepRegIdx = RematIdx->second; |
| NewDeps.push_back(NewDepRegIdx); |
| } |
| RegisterIdx NewIdx = |
| rematerializeReg(RootIdx, UseRegion, InsertPos, std::move(NewDeps)); |
| DRI.DependencyMap.insert({RootIdx, NewIdx}); |
| return NewIdx; |
| } |
| |
| void Rematerializer::transferUser(RegisterIdx FromRegIdx, RegisterIdx ToRegIdx, |
| unsigned UserRegion, MachineInstr &UserMI) { |
| transferUserImpl(FromRegIdx, ToRegIdx, UserMI); |
| |
| Regs[ToRegIdx].addUser(&UserMI, UserRegion); |
| extendToNewUsers(ToRegIdx, &UserMI); |
| |
| Regs[FromRegIdx].eraseUser(&UserMI, UserRegion); |
| shrinkToUses(FromRegIdx); |
| } |
| |
| void Rematerializer::transferRegionUsers(RegisterIdx FromRegIdx, |
| RegisterIdx ToRegIdx, |
| unsigned UseRegion) { |
| Reg &FromReg = Regs[FromRegIdx]; |
| auto UsesIt = FromReg.Uses.find(UseRegion); |
| if (UsesIt == FromReg.Uses.end()) |
| return; |
| |
| const SmallDenseSet<MachineInstr *, 4> &RegionUsers = UsesIt->getSecond(); |
| SmallVector<MachineInstr *, 4> NewUsers; |
| for (MachineInstr *UserMI : RegionUsers) { |
| transferUserImpl(FromRegIdx, ToRegIdx, *UserMI); |
| NewUsers.push_back(UserMI); |
| } |
| |
| extendToNewUsers(ToRegIdx, NewUsers); |
| Regs[ToRegIdx].addUsers(RegionUsers, UseRegion); |
| |
| FromReg.Uses.erase(UseRegion); |
| shrinkToUses(FromRegIdx); |
| } |
| |
| void Rematerializer::transferAllUsers(RegisterIdx FromRegIdx, |
| RegisterIdx ToRegIdx) { |
| Reg &FromReg = Regs[FromRegIdx]; |
| SmallVector<MachineInstr *, 4> NewUsers; |
| for (const auto &[UseRegion, RegionUsers] : FromReg.Uses) { |
| for (MachineInstr *UserMI : RegionUsers) { |
| transferUserImpl(FromRegIdx, ToRegIdx, *UserMI); |
| NewUsers.push_back(UserMI); |
| } |
| Regs[ToRegIdx].addUsers(RegionUsers, UseRegion); |
| } |
| extendToNewUsers(ToRegIdx, NewUsers); |
| |
| FromReg.Uses.clear(); |
| deleteReg(FromRegIdx); |
| } |
| |
| void Rematerializer::transferUserImpl(RegisterIdx FromRegIdx, |
| RegisterIdx ToRegIdx, |
| MachineInstr &UserMI) { |
| assert(FromRegIdx != ToRegIdx && "identical registers"); |
| assert(getOriginOrSelf(FromRegIdx) == getOriginOrSelf(ToRegIdx) && |
| "unrelated registers"); |
| |
| LLVM_DEBUG(dbgs() << "User transfer from " << printID(FromRegIdx) << " to " |
| << printID(ToRegIdx) << ": " << printUser(&UserMI) << '\n'); |
| |
| Register FromReg = getReg(FromRegIdx).getDefReg(); |
| UserMI.substituteRegister(FromReg, getReg(ToRegIdx).getDefReg(), 0, TRI); |
| |
| RegisterIdx UserRegIdx = getDefRegIdx(UserMI); |
| if (UserRegIdx == NoReg) |
| return; |
| |
| // When the user is rematerializable, we must reflect the change in its |
| // dependencies. |
| Reg &UserReg = Regs[UserRegIdx]; |
| SmallVectorImpl<RegisterIdx> &UserDeps = Regs[UserRegIdx].Dependencies; |
| bool IsNewDep = true; |
| if (UserReg.Defs.size() > 1) { |
| // Other defining MIs might already be using the new register. |
| IsNewDep = !is_contained(UserDeps, ToRegIdx); |
| |
| // If any other defining instruction of the rematerializable user still uses |
| // the original register, we should not remove it from dependencies and may |
| // need to add a new dependency if it is the first time the new register is |
| // used by defining instructions. |
| for (MachineInstr *DefMI : UserReg.Defs) { |
| if (DefMI == &UserMI) |
| continue; |
| for (const MachineOperand &MO : DefMI->all_uses()) { |
| if (MO.getReg() == FromReg) { |
| if (IsNewDep) |
| UserDeps.push_back(ToRegIdx); |
| return; |
| } |
| } |
| } |
| } |
| |
| // No other defining instruction has the original register as user. This |
| // either removes a dependency if the new register was previously used, or is |
| // a simple replacement if not. |
| unsigned *FindFromReg = find(UserDeps, FromRegIdx); |
| assert(FindFromReg != UserDeps.end() && "broken dependency"); |
| if (IsNewDep) |
| *FindFromReg = ToRegIdx; |
| else |
| UserReg.Dependencies.erase(FindFromReg); |
| } |
| |
| bool Rematerializer::isMOIdenticalAtUses(MachineOperand &MO, |
| ArrayRef<SlotIndex> Uses) const { |
| unsigned SubIdx = MO.getSubReg(); |
| LaneBitmask Mask = SubIdx ? TRI.getSubRegIndexLaneMask(SubIdx) |
| : MRI.getMaxLaneMaskForVReg(MO.getReg()); |
| return isRegIdenticalAtUses( |
| MO.getReg(), Mask, |
| LIS.getInstructionIndex(*MO.getParent()).getRegSlot(true), Uses); |
| } |
| |
| bool Rematerializer::isRegIdenticalAtUses(Register Reg, LaneBitmask Mask, |
| SlotIndex RefSlot, |
| ArrayRef<SlotIndex> Uses) const { |
| if (Uses.empty()) |
| return true; |
| const LiveInterval &LI = LIS.getInterval(Reg); |
| const VNInfo *DefVN = LI.getVNInfoAt(RefSlot); |
| if (!DefVN) |
| return false; |
| for (SlotIndex Use : Uses) { |
| if (!isIdenticalAtUse(*DefVN, Mask, Use, LI)) |
| return false; |
| } |
| return true; |
| } |
| |
| RegisterIdx Rematerializer::findRematInRegion(RegisterIdx RegIdx, |
| unsigned Region, |
| SlotIndex Before) const { |
| auto It = Rematerializations.find(getOriginOrSelf(RegIdx)); |
| if (It == Rematerializations.end()) |
| return NoReg; |
| const RematsOf &Remats = It->getSecond(); |
| |
| SlotIndex BestSlot; |
| RegisterIdx BestRegIdx = NoReg; |
| for (RegisterIdx RematRegIdx : Remats) { |
| const Reg &RematReg = getReg(RematRegIdx); |
| if (RematReg.DefRegion != Region || RematReg.Uses.empty()) |
| continue; |
| SlotIndex RematRegSlot = |
| LIS.getInstructionIndex(*RematReg.getLastDef()).getRegSlot(); |
| if (RematRegSlot < Before && |
| (BestRegIdx == NoReg || RematRegSlot > BestSlot)) { |
| BestSlot = RematRegSlot; |
| BestRegIdx = RematRegIdx; |
| } |
| } |
| return BestRegIdx; |
| } |
| |
| 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. |
| SmallVector<RegisterIdx, 4> DepDAG{RootIdx}; |
| SmallVector<RegisterIdx, 8> DeleteOrder{RootIdx}; |
| do { |
| // A deleted register's dependencies may be deletable too. |
| const Reg &DeleteReg = getReg(DepDAG.pop_back_val()); |
| for (RegisterIdx DepRegIdx : DeleteReg.Dependencies) { |
| // All dependencies lose a user (the deleted register). |
| Reg &DepReg = Regs[DepRegIdx]; |
| for (MachineInstr *DefMI : DeleteReg.Defs) { |
| if (DepReg.tryEraseUser(DefMI, DeleteReg.DefRegion) && |
| DepReg.Uses.empty()) { |
| // The if condition will only be true at most once for any given |
| // register because, once the dependency no longer has any user, |
| // tryEraseUser will always produce false. We can therefore safely use |
| // vectors instead of sets for determining deletable registers. |
| DeleteOrder.push_back(DepRegIdx); |
| DepDAG.push_back(DepRegIdx); |
| break; |
| } |
| } |
| } |
| } while (!DepDAG.empty()); |
| |
| for (RegisterIdx RegIdx : DeleteOrder) { |
| preDeletion(RegIdx); |
| Reg &DeleteReg = Regs[RegIdx]; |
| Register DefReg = DeleteReg.getDefReg(); |
| for (MachineInstr *DefMI : reverse(DeleteReg.Defs)) { |
| LIS.RemoveMachineInstrFromMaps(*DefMI); |
| DefMI->eraseFromParent(); |
| } |
| LIS.removeInterval(DefReg); |
| DeleteReg.Defs.clear(); |
| } |
| |
| 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); |
| } |
| } |
| } |
| |
| 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. |
| |
| // All rematerializable dependencies must be notified. |
| Reg &DeleteReg = Remater.Regs[RegIdx]; |
| for (RegisterIdx DepRegIdx : DeleteReg.Dependencies) |
| Remater.Regs[DepRegIdx].tryEraseUser(MI, DeleteReg.DefRegion); |
| |
| // The constraint that no other register reads any intermediate value of a |
| // register defined over multiple MI implies that the live range editor will |
| // either not touch or fully delete rematerializable registers i.e., if this |
| // is called for any defining instruction of a rematerializable register, this |
| // will be called for every definition of the register. Furthermore, def/use |
| // order between defining instructions ensures this will be called from last |
| // definition to first definition. When the last definition / first MI |
| // deletion happens, we want to reflect the deletion in our internal |
| // data-structures and notify any rematerializer listener. |
| if (!DeleteReg.isAlive()) |
| return; |
| assert(DeleteReg.getLastDef() == MI && "last def should be deleted first"); |
| assert(DeleteReg.Uses.empty() && "register should no longer have uses"); |
| |
| // The live-reange editor will delete all defining instructions from the MIR |
| // as well as the register's live-range, so we just need to clear out the defs |
| // vector. |
| Remater.preDeletion(RegIdx); |
| DeleteReg.Defs.clear(); |
| } |
| |
| 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; |
| for (MachineInstr *DefMI : DeleteReg.Defs) { |
| if (RegionBegin != DefMI) |
| break; |
| ++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, |
| SmallVectorImpl<RegionBoundaries> &Regions, |
| LiveIntervals &LIS) |
| : Regions(Regions), MRI(MF.getRegInfo()), LIS(LIS), |
| TII(*MF.getSubtarget().getInstrInfo()), TRI(TII.getRegisterInfo()) { |
| #ifdef EXPENSIVE_CHECKS |
| // Check that regions are valid. |
| DenseSet<MachineInstr *> SeenMIs; |
| for (const auto &[RegionBegin, RegionEnd] : Regions) { |
| assert(RegionBegin != RegionEnd && "empty region"); |
| for (auto MI = RegionBegin; MI != RegionEnd; ++MI) { |
| bool IsNewMI = SeenMIs.insert(&*MI).second; |
| assert(IsNewMI && "overlapping regions"); |
| assert(!MI->isTerminator() && "terminator in region"); |
| } |
| if (RegionEnd != RegionBegin->getParent()->end()) { |
| bool IsNewMI = SeenMIs.insert(&*RegionEnd).second; |
| assert(IsNewMI && "overlapping regions (upper bound)"); |
| } |
| } |
| #endif |
| } |
| |
| bool Rematerializer::analyze() { |
| Regs.clear(); |
| UnrematableDeps.clear(); |
| Origins.clear(); |
| Rematerializations.clear(); |
| RegionMBB.clear(); |
| RegToIdx.clear(); |
| if (Regions.empty()) |
| return false; |
| |
| /// Maps all MIs to their parent region. Region terminators are considered |
| /// part of the region they terminate. |
| DenseMap<MachineInstr *, unsigned> MIRegion; |
| |
| // Initialize MI to containing region mapping. |
| RegionMBB.reserve(Regions.size()); |
| for (unsigned I = 0, E = Regions.size(); I < E; ++I) { |
| RegionBoundaries Region = Regions[I]; |
| assert(Region.first != Region.second && "empty cannot be region"); |
| for (auto MI = Region.first; MI != Region.second; ++MI) { |
| assert(!MIRegion.contains(&*MI) && "regions should not intersect"); |
| MIRegion.insert({&*MI, I}); |
| } |
| MachineBasicBlock &MBB = *Region.first->getParent(); |
| RegionMBB.push_back(&MBB); |
| |
| // A terminator instruction is considered part of the region it terminates. |
| if (Region.second != MBB.end()) { |
| MachineInstr *RegionTerm = &*Region.second; |
| assert(!MIRegion.contains(RegionTerm) && "regions should not intersect"); |
| MIRegion.insert({RegionTerm, I}); |
| } |
| } |
| |
| const unsigned NumVirtRegs = MRI.getNumVirtRegs(); |
| BitVector SeenRegs(NumVirtRegs); |
| for (unsigned I = 0, E = NumVirtRegs; I != E; ++I) { |
| if (!SeenRegs[I]) |
| addRegIfRematerializable(I, MIRegion, SeenRegs); |
| } |
| assert(Regs.size() == UnrematableDeps.size()); |
| |
| LLVM_DEBUG({ |
| for (RegisterIdx I = 0, E = getNumRegs(); I < E; ++I) |
| dbgs() << printDependencyDAG(I) << '\n'; |
| }); |
| return !Regs.empty(); |
| } |
| |
| void Rematerializer::addRegIfRematerializable( |
| unsigned VirtRegIdx, const DenseMap<MachineInstr *, unsigned> &MIRegion, |
| BitVector &SeenRegs) { |
| assert(!SeenRegs[VirtRegIdx] && "register already seen"); |
| Register DefReg = Register::index2VirtReg(VirtRegIdx); |
| SeenRegs.set(VirtRegIdx); |
| Reg RematReg; |
| |
| // Check that the register's definitions can be rematerialized. |
| SmallPtrSet<MachineInstr *, 1> DefSet; |
| for (MachineInstr &DefMI : MRI.def_instructions(DefReg)) { |
| // If a single MI has multiple defs for the same register, we don't need to |
| // redo MI-based checks. |
| if (!DefSet.insert(&DefMI).second) |
| continue; |
| |
| // The defining MI must be rematerializable and in the same region as all |
| // other defining MIs. |
| if (!isMIRematerializable(DefMI)) |
| return; |
| auto DefRegion = MIRegion.find(&DefMI); |
| if (DefRegion == MIRegion.end()) |
| return; |
| if (RematReg.Defs.empty()) |
| RematReg.DefRegion = DefRegion->getSecond(); |
| else if (RematReg.DefRegion != DefRegion->getSecond()) |
| return; |
| RematReg.Defs.push_back(&DefMI); |
| } |
| if (RematReg.Defs.empty()) |
| return; |
| |
| // Order defining MIs by slot index. |
| sort(RematReg.Defs, [&](MachineInstr *LHS, MachineInstr *RHS) { |
| return LIS.getInstructionIndex(*LHS) < LIS.getInstructionIndex(*RHS); |
| }); |
| // None of the non-first register defintions can be marked undef. |
| for (const MachineInstr *DefMI : drop_begin(RematReg.Defs)) { |
| for (const MachineOperand &DefMO : DefMI->all_defs()) { |
| if (DefMO.getReg() == DefReg && DefMO.isUndef()) |
| return; |
| } |
| } |
| |
| SlotIndex LastDefSlot = LIS.getInstructionIndex(*RematReg.getLastDef()); |
| |
| // Set the register's mask to all active lanes after the last def. |
| const LiveInterval &DefLI = LIS.getInterval(DefReg); |
| SlotIndex AfterLastDef = LastDefSlot.getRegSlot(); |
| if (DefLI.hasSubRanges()) { |
| for (const LiveInterval::SubRange &SR : DefLI.subranges()) |
| if (SR.liveAt(AfterLastDef)) |
| RematReg.Mask |= SR.LaneMask; |
| } else { |
| RematReg.Mask = MRI.getMaxLaneMaskForVReg(DefReg); |
| } |
| |
| // Collect the candidate's direct users, both rematerializable and |
| // unrematerializable. |
| const bool MoreThanOneDef = RematReg.Defs.size() > 1; |
| for (MachineInstr &UseMI : MRI.use_nodbg_instructions(DefReg)) { |
| // We are only interested in users that do not define part of the register. |
| if (DefSet.contains(&UseMI)) |
| continue; |
| // MIs outside provided regions cannot be tracked so the registers they use |
| // are not safely rematerializable. |
| auto UseRegion = MIRegion.find(&UseMI); |
| if (UseRegion == MIRegion.end()) |
| return; |
| // Disallow reads before the last def. |
| if (MoreThanOneDef && RematReg.DefRegion == UseRegion->second && |
| LastDefSlot > LIS.getInstructionIndex(UseMI)) |
| return; |
| |
| RematReg.addUser(&UseMI, UseRegion->second); |
| } |
| if (RematReg.Uses.empty()) |
| return; |
| |
| // Collect the candidate's dependencies, rematerializable or not. If the same |
| // rematerializable register is used multiple times we just need to consider |
| // it once. |
| SmallSetVector<RegisterIdx, 2> RematDeps; |
| SmallMapVector<Register, LaneBitmask, 2> UnrematDeps; |
| for (const MachineInstr *DefMI : RematReg.Defs) { |
| for (const MachineOperand &MO : DefMI->all_uses()) { |
| Register DepReg = getRegDependency(MO); |
| if (!DepReg || DepReg == DefReg) |
| continue; |
| unsigned DepRegIdx = DepReg.virtRegIndex(); |
| if (!SeenRegs[DepRegIdx]) |
| addRegIfRematerializable(DepRegIdx, MIRegion, SeenRegs); |
| if (auto DepIt = RegToIdx.find(DepReg); DepIt != RegToIdx.end()) { |
| RematDeps.insert(DepIt->second); |
| } else { |
| LaneBitmask &CurrentMask = |
| UnrematDeps.try_emplace(DepReg, LaneBitmask::getNone()) |
| .first->second; |
| LaneBitmask Mask = MO.getSubReg() |
| ? TRI.getSubRegIndexLaneMask(MO.getSubReg()) |
| : MRI.getMaxLaneMaskForVReg(DepReg); |
| CurrentMask |= Mask; |
| } |
| } |
| } |
| |
| if (MoreThanOneDef) { |
| // A def of an unrematerializable dependency between the defs of the |
| // register under consideration makes the latter unrematerializable. |
| SlotIndex FirstDefSlot = LIS.getInstructionIndex(*RematReg.getFirstDef()); |
| for (const auto &[UnrematDepReg, _] : UnrematDeps) { |
| for (MachineInstr &UnrematDefMI : MRI.def_instructions(UnrematDepReg)) { |
| SlotIndex UnrematDefSlot = LIS.getInstructionIndex(UnrematDefMI); |
| if (UnrematDefSlot > FirstDefSlot || UnrematDefSlot < LastDefSlot) |
| return; |
| } |
| } |
| } |
| |
| // The register is rematerializable. |
| RematReg.Dependencies = RematDeps.takeVector(); |
| RegToIdx.insert({DefReg, Regs.size()}); |
| Regs.push_back(RematReg); |
| UnrematableDeps.push_back(UnrematDeps.takeVector()); |
| } |
| |
| bool Rematerializer::isMIRematerializable(const MachineInstr &MI) const { |
| if (!TII.isReMaterializable(MI)) |
| return false; |
| |
| assert(MI.getOperand(0).getReg().isVirtual() && "should be virtual"); |
| |
| for (const MachineOperand &MO : MI.all_uses()) { |
| // We can't remat physreg uses, unless it is a constant or an ignorable |
| // use (e.g. implicit exec use on VALU instructions) |
| if (MO.getReg().isPhysical()) { |
| if (MRI.isConstantPhysReg(MO.getReg()) || |
| TII.isIgnorableUse(MI, MI.getOperandNo(&MO))) |
| continue; |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| RegisterIdx Rematerializer::getDefRegIdx(const MachineInstr &MI) const { |
| if (!MI.getNumOperands() || !MI.getOperand(0).isReg() || |
| !MI.getOperand(0).isDef()) |
| return NoReg; |
| Register Reg = MI.getOperand(0).getReg(); |
| auto UserRegIt = RegToIdx.find(Reg); |
| if (UserRegIt == RegToIdx.end()) |
| return NoReg; |
| return UserRegIt->second; |
| } |
| |
| RegisterIdx |
| Rematerializer::rematerializeReg(RegisterIdx RegIdx, unsigned UseRegion, |
| MachineBasicBlock::iterator InsertPos, |
| SmallVectorImpl<RegisterIdx> &&Dependencies) { |
| RegisterIdx NewRegIdx = Regs.size(); |
| |
| Reg &NewReg = Regs.emplace_back(); |
| Reg &FromReg = Regs[RegIdx]; |
| NewReg.Mask = FromReg.Mask; |
| NewReg.DefRegion = UseRegion; |
| NewReg.Defs.reserve(FromReg.Defs.size()); |
| NewReg.Dependencies = std::move(Dependencies); |
| |
| // Track rematerialization link between registers. Origins are always |
| // registers that existed originally, and rematerializations are always |
| // attached to them. |
| const RegisterIdx OriginIdx = getOriginOrSelf(RegIdx); |
| Origins.push_back(OriginIdx); |
| Rematerializations[OriginIdx].insert(NewRegIdx); |
| |
| // Use the TII to rematerialize the defining instruction with a new defined |
| // register. |
| Register NewDefReg = MRI.cloneVirtualRegister(FromReg.getDefReg()); |
| for (const MachineInstr *DefMI : FromReg.Defs) { |
| TII.reMaterialize(*RegionMBB[UseRegion], InsertPos, NewDefReg, 0, *DefMI); |
| NewReg.Defs.push_back(&*std::prev(InsertPos)); |
| } |
| RegToIdx.insert({NewDefReg, NewRegIdx}); |
| postRematerialization(RegIdx, NewRegIdx); |
| |
| noteRegCreated(NewRegIdx); |
| LLVM_DEBUG(dbgs() << "** Rematerialized " << printID(RegIdx) << " as " |
| << printRematReg(NewRegIdx) << '\n'); |
| return NewRegIdx; |
| } |
| |
| void Rematerializer::recreateReg( |
| RegisterIdx RegIdx, ArrayRef<MachineBasicBlock::iterator> Positions, |
| Register DefReg) { |
| assert(RegToIdx.contains(DefReg) && "unknown defined register"); |
| assert(RegToIdx.at(DefReg) == RegIdx && "incorrect defined register"); |
| assert(!getReg(RegIdx).isAlive() && "register is still alive"); |
| Reg &OriginReg = Regs[RegIdx]; |
| |
| // Re-establish the link between origin and rematerialization if necessary. |
| const bool RecreateOriginalReg = isOriginalRegister(RegIdx); |
| if (!RecreateOriginalReg) |
| Rematerializations[getOriginOf(RegIdx)].insert(RegIdx); |
| |
| // Rematerialize from one of the existing rematerializations or from the |
| // origin. We expect at least one to exist, otherwise it would mean the value |
| // held by the original register is no longer available anywhere in the MF. |
| RegisterIdx ModelRegIdx; |
| if (RecreateOriginalReg) { |
| assert(Rematerializations.contains(RegIdx) && "expected remats"); |
| ModelRegIdx = *Rematerializations.at(RegIdx).begin(); |
| } else { |
| assert(getReg(getOriginOf(RegIdx)).isAlive() && "expected alive origin"); |
| ModelRegIdx = getOriginOf(RegIdx); |
| } |
| const Reg &ModelReg = getReg(ModelRegIdx); |
| |
| for (auto [DefMI, InsertPos] : zip_equal(ModelReg.Defs, Positions)) { |
| TII.reMaterialize(*RegionMBB[OriginReg.DefRegion], InsertPos, DefReg, 0, |
| *DefMI); |
| OriginReg.Defs.push_back(&*std::prev(InsertPos)); |
| } |
| postRematerialization(ModelRegIdx, RegIdx); |
| LLVM_DEBUG(dbgs() << "** Recreated " << printID(RegIdx) << " as " |
| << printRematReg(RegIdx) << '\n'); |
| } |
| |
| void Rematerializer::postRematerialization(RegisterIdx ModelRegIdx, |
| RegisterIdx RematRegIdx) { |
| Reg &ModelReg = Regs[ModelRegIdx], &RematReg = Regs[RematRegIdx]; |
| |
| SlotIndex UseIdx; |
| for (MachineInstr *DefMI : RematReg.Defs) |
| UseIdx = LIS.InsertMachineInstrInMaps(*DefMI); |
| UseIdx = UseIdx.getRegSlot(); |
| |
| // The rematerialization has no user at this point so its interval will |
| // initially be empty. |
| LIS.createAndComputeVirtRegInterval(RematReg.getDefReg()); |
| |
| // The start of the new register's region may have changed. |
| MachineInstr &FirstDefMI = *RematReg.getFirstDef(); |
| auto &[RegionBegin, RegionEnd] = Regions[RematReg.DefRegion]; |
| if (RegionBegin == RegionEnd || |
| (!RegionBegin->isDebugInstr() && LIS.getInstructionIndex(*RegionBegin) > |
| LIS.getInstructionIndex(FirstDefMI))) |
| RegionBegin = FirstDefMI.getIterator(); |
| |
| // Replace dependencies as needed in the rematerialized MI. All dependencies |
| // of the latter gain a new user. |
| auto ZipedDeps = zip_equal(ModelReg.Dependencies, RematReg.Dependencies); |
| for (const auto &[OldDepRegIdx, NewDepRegIdx] : ZipedDeps) { |
| LLVM_DEBUG(dbgs() << " Dependency: " << printID(OldDepRegIdx) << " -> " |
| << printID(NewDepRegIdx) << '\n'); |
| Register OldReg = getReg(OldDepRegIdx).getDefReg(); |
| Register NewReg = getReg(NewDepRegIdx).getDefReg(); |
| |
| SmallVector<MachineInstr *, 2> DefsUsingNewDep; |
| for (MachineInstr *DefMI : RematReg.Defs) { |
| bool NewDefHasReg = false; |
| for (MachineOperand &MO : DefMI->operands()) { |
| if (!MO.isReg() || MO.getReg() != OldReg) |
| continue; |
| NewDefHasReg = true; |
| DefsUsingNewDep.push_back(DefMI); |
| if (OldDepRegIdx != NewDepRegIdx) |
| MO.substVirtReg(NewReg, 0, TRI); |
| } |
| if (NewDefHasReg) |
| Regs[NewDepRegIdx].addUser(DefMI, RematReg.DefRegion); |
| } |
| assert(!DefsUsingNewDep.empty() && "no user of dependency"); |
| extendToNewUsers(NewDepRegIdx, DefsUsingNewDep); |
| } |
| |
| // 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); |
| |
| // Seed subranges from the main range when subreg liveness is tracked but no |
| // subrange exists yet. VirtRegRewriter later requires subranges even when a |
| // new user reads the full mask, because other users may read subregs. |
| if (!LI.hasSubRanges() && ShouldTrackSubReg) |
| LI.createSubRangeFrom(LIS.getVNInfoAllocator(), FullLaneMask, LI); |
| |
| // 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) { |
| // Refine sub-ranges to be able to track the mask for that user. |
| LI.refineSubRanges( |
| LIS.getVNInfoAllocator(), RegMask, [](LiveInterval::SubRange &SR) {}, |
| *LIS.getSlotIndexes(), TRI); |
| // Refining may have introduced empty sub-ranges, which are illegal. |
| LI.removeEmptySubRanges(); |
| } |
| extendInterval(LI, RegMask, UseIdx); |
| } |
| |
| // Rematerializable registers are never read by instructions not defining them |
| // until after their last def, so adding a user to them ensures their last |
| // definition is alive. All potential other definitions are read by the last |
| // definition and are therefore already alive by construction. |
| LLVM_DEBUG({ |
| if (ExtendReg.getLastDef()->getOperand(0).isDead()) |
| dbgs() << "Clearing dead flag for " |
| << printRematReg(RegIdx, /*SkipRegions=*/false, |
| /*DefIdx=*/ExtendReg.Defs.size() - 1) |
| << '\n'; |
| }); |
| ExtendReg.getLastDef()->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 " |
| << 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 " |
| << 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 *> |
| Rematerializer::Reg::getRegionUseBounds(unsigned UseRegion, |
| const LiveIntervals &LIS) const { |
| auto It = Uses.find(UseRegion); |
| if (It == Uses.end()) |
| return {nullptr, nullptr}; |
| const RegionUsers &RegionUsers = It->getSecond(); |
| assert(!RegionUsers.empty() && "empty userset in region"); |
| |
| auto User = RegionUsers.begin(), UserEnd = RegionUsers.end(); |
| MachineInstr *FirstMI = *User, *LastMI = FirstMI; |
| SlotIndex FirstIndex = LIS.getInstructionIndex(*FirstMI), |
| LastIndex = FirstIndex; |
| |
| while (++User != UserEnd) { |
| SlotIndex UserIndex = LIS.getInstructionIndex(**User); |
| if (UserIndex < FirstIndex) { |
| FirstIndex = UserIndex; |
| FirstMI = *User; |
| } else if (UserIndex > LastIndex) { |
| LastIndex = UserIndex; |
| LastMI = *User; |
| } |
| } |
| |
| return {FirstMI, LastMI}; |
| } |
| |
| void Rematerializer::Reg::addUser(MachineInstr *MI, unsigned Region) { |
| Uses[Region].insert(MI); |
| } |
| |
| void Rematerializer::Reg::addUsers(const RegionUsers &NewUsers, |
| unsigned Region) { |
| Uses[Region].insert_range(NewUsers); |
| } |
| |
| void Rematerializer::Reg::eraseUser(MachineInstr *MI, unsigned Region) { |
| RegionUsers &RUsers = Uses.at(Region); |
| assert(RUsers.contains(MI) && "user not in region"); |
| if (RUsers.size() == 1) |
| Uses.erase(Region); |
| else |
| RUsers.erase(MI); |
| } |
| |
| bool Rematerializer::Reg::tryEraseUser(MachineInstr *MI, unsigned Region) { |
| auto RegionUsers = Uses.find(Region); |
| if (RegionUsers == Uses.end() || !RegionUsers->getSecond().erase(MI)) |
| return false; |
| if (RegionUsers->getSecond().empty()) |
| Uses.erase(Region); |
| return true; |
| } |
| |
| Printable Rematerializer::printDependencyDAG(RegisterIdx RootIdx) const { |
| return Printable([&, RootIdx](raw_ostream &OS) { |
| DenseMap<RegisterIdx, unsigned> RegDepths; |
| std::function<void(RegisterIdx, unsigned)> WalkTree = |
| [&](RegisterIdx RegIdx, unsigned Depth) -> void { |
| unsigned MaxDepth = std::max(RegDepths.lookup_or(RegIdx, Depth), Depth); |
| RegDepths.emplace_or_assign(RegIdx, MaxDepth); |
| for (RegisterIdx DepRegIdx : getReg(RegIdx).Dependencies) |
| WalkTree(DepRegIdx, Depth + 1); |
| }; |
| WalkTree(RootIdx, 0); |
| |
| // Sort in decreasing depth order to print root at the bottom. |
| SmallVector<std::pair<RegisterIdx, unsigned>> Regs(RegDepths.begin(), |
| RegDepths.end()); |
| sort(Regs, [](const auto &LHS, const auto &RHS) { |
| return LHS.second > RHS.second; |
| }); |
| |
| OS << printID(RootIdx) << " has " << Regs.size() - 1 << " dependencies\n"; |
| for (const auto &[RegIdx, Depth] : Regs) { |
| OS << indent(Depth, 2) << (Depth ? '|' : '*') << ' ' |
| << printRematReg(RegIdx, /*SkipRegions=*/Depth) << '\n'; |
| } |
| OS << printRegUsers(RootIdx); |
| }); |
| } |
| |
| Printable Rematerializer::printID(RegisterIdx RegIdx) const { |
| return Printable([&, RegIdx](raw_ostream &OS) { |
| const Reg &PrintReg = getReg(RegIdx); |
| OS << '(' << RegIdx << '/'; |
| if (!PrintReg.isAlive()) |
| OS << "<dead>"; |
| else |
| OS << printReg(PrintReg.getDefReg(), &TRI, 0, &MRI); |
| OS << ")[" << PrintReg.DefRegion << "]"; |
| }); |
| } |
| |
| Printable Rematerializer::printRematReg(RegisterIdx RegIdx, bool SkipRegions, |
| unsigned DefIdx) const { |
| return Printable([&, RegIdx, SkipRegions, DefIdx](raw_ostream &OS) { |
| const Reg &PrintReg = getReg(RegIdx); |
| OS << printID(RegIdx); |
| if (!SkipRegions) { |
| OS << " [" << PrintReg.DefRegion; |
| if (!PrintReg.Uses.empty()) { |
| assert(PrintReg.isAlive() && "dead register cannot have uses"); |
| const LiveInterval &LI = LIS.getInterval(PrintReg.getDefReg()); |
| // First display all regions in which the register is live-through and |
| // not used. |
| bool First = true; |
| for (const auto &[I, Bounds] : enumerate(Regions)) { |
| if (PrintReg.Uses.contains(I)) |
| continue; |
| // The register must be live at the live-ins and live-outs of the |
| // region. |
| MachineBasicBlock::iterator LiveIn = |
| skipDebugInstructionsForward(Bounds.first, Bounds.second); |
| if (LiveIn == Bounds.second) { |
| // The region has no non-debug instructions, it's hard to assess |
| // whether the register is live across it without an index. |
| continue; |
| } |
| // LiveIn is inside the range and a non-debug instruction so we know |
| // this will also point to a non-debug instruction within the region. |
| MachineBasicBlock::iterator LiveOut = skipDebugInstructionsBackward( |
| std::prev(Bounds.second), Bounds.first); |
| if (LI.liveAt(LIS.getInstructionIndex(*LiveIn)) && |
| LI.liveAt(LIS.getInstructionIndex(*LiveOut).getDeadSlot())) { |
| OS << (First ? " - " : ",") << I; |
| First = false; |
| } |
| } |
| OS << (First ? " --> " : " -> "); |
| |
| // Then display regions in which the register is used. |
| auto It = PrintReg.Uses.begin(); |
| OS << It->first; |
| while (++It != PrintReg.Uses.end()) |
| OS << "," << It->first; |
| } |
| OS << "] "; |
| } |
| if (PrintReg.isAlive()) { |
| assert(DefIdx < PrintReg.Defs.size() && "out-of-bound def"); |
| MachineInstr &PrintDef = *PrintReg.Defs[DefIdx]; |
| OS << "(def. " << DefIdx + 1 << " / " << PrintReg.Defs.size() << ") "; |
| PrintDef.print(OS, /*IsStandalone=*/true, /*SkipOpers=*/false, |
| /*SkipDebugLoc=*/false, /*AddNewLine=*/false); |
| OS << " @ "; |
| LIS.getInstructionIndex(PrintDef).print(OS); |
| } |
| }); |
| } |
| |
| Printable Rematerializer::printRegUsers(RegisterIdx RegIdx) const { |
| return Printable([&, RegIdx](raw_ostream &OS) { |
| for (const auto &[UseRegion, Users] : getReg(RegIdx).Uses) { |
| for (MachineInstr *MI : Users) |
| OS << " User " << printUser(MI, UseRegion) << '\n'; |
| } |
| }); |
| } |
| |
| Printable Rematerializer::printUser(const MachineInstr *MI, |
| std::optional<unsigned> UseRegion) const { |
| return Printable([&, MI, UseRegion](raw_ostream &OS) { |
| RegisterIdx RegIdx = getDefRegIdx(*MI); |
| if (RegIdx != NoReg) { |
| OS << printID(RegIdx); |
| } else { |
| OS << "(-/-)["; |
| if (UseRegion) |
| OS << *UseRegion; |
| else |
| OS << '?'; |
| OS << ']'; |
| } |
| OS << ' '; |
| MI->print(OS, /*IsStandalone=*/true, /*SkipOpers=*/false, |
| /*SkipDebugLoc=*/false, /*AddNewLine=*/false); |
| OS << " @ "; |
| LIS.getInstructionIndex(*MI).print(OS); |
| }); |
| } |
| |
| void Rollbacker::rematerializerNoteRegCreated(const Rematerializer &Remater, |
| RegisterIdx RegIdx) { |
| if (RollingBack) |
| return; |
| assert(Remater.isRematerializedRegister(RegIdx) && "only remats are created"); |
| Rematerializations[Remater.getOriginOf(RegIdx)].insert(RegIdx); |
| } |
| |
| void Rollbacker::rematerializerNoteRegWillBeDeleted( |
| const Rematerializer &Remater, RegisterIdx RegIdx) { |
| if (RollingBack) |
| return; |
| |
| const Rematerializer::Reg &Reg = Remater.getReg(RegIdx); |
| MachineBasicBlock *ParentMBB = Reg.getFirstDef()->getParent(); |
| MachineBasicBlock::iterator LastValidPos; |
| |
| auto GetNextValidPosAfterDef = |
| [&](unsigned DefIdx) -> MachineBasicBlock::iterator { |
| const MachineInstr *NextDef = |
| DefIdx + 1 < Reg.Defs.size() ? Reg.Defs[DefIdx + 1] : nullptr; |
| MachineBasicBlock::iterator ValidPos = |
| std::next(Reg.Defs[DefIdx]->getIterator()); |
| |
| while (ValidPos != ParentMBB->end()) { |
| // When there are no valid insert positions between the current and next |
| // definition of the register about to be deleted, the first valid insert |
| // position for the current definition is the same as for the next |
| // definition. |
| const MachineInstr &CandMI = *ValidPos; |
| if (NextDef && &CandMI == NextDef) |
| return LastValidPos; |
| if (!isRollbackableMI(CandMI, Remater)) |
| break; |
| |
| // Move to the next candidate position. |
| ValidPos = std::next(ValidPos); |
| } |
| |
| LastValidPos = ValidPos; |
| return ValidPos; |
| }; |
| |
| if (Remater.isRematerializedRegister(RegIdx)) { |
| // Rematerializations will not be re-created. Previously deleted registers |
| // that reference this register's defining instructions as their re-creation |
| // position should instead be re-created at a valid position after the |
| // deleted MIs. |
| for (unsigned I = Reg.Defs.size(); I > 0; --I) |
| invalidatePosition(Reg.Defs[I - 1], GetNextValidPosAfterDef(I - 1)); |
| return; |
| } |
| |
| // Original registers can be re-created. Add a re-creation position for each |
| // definition of the rematerializable register. |
| DeadRegs.push_back(DeadReg(RegIdx, Remater)); |
| for (unsigned I = Reg.Defs.size(); I > 0; --I) { |
| const InsertBeforePos InsertPos = |
| makePos(GetNextValidPosAfterDef(I - 1), ParentMBB); |
| PosToIdx[InsertPos].insert(Positions.size()); |
| 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; |
| |
| // As we re-create registers, map deleted definitions to re-created ones. This |
| // allows to replace invalid re-creation positions that reference deleted |
| // definitions to valid new positions while restoring original MI order. |
| DenseMap<MachineInstr *, MachineInstr *> Replacements; |
| unsigned PositionIndex = Positions.size(); |
| |
| // Re-create deleted registers in reverse order of deletion. Related registers |
| // are deleted in reverse def-use order so this ensures we re-create registers |
| // 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 -= Reg.Defs.size(); |
| continue; |
| } |
| assert(!Remater.getReg(Reg.Idx).isAlive() && "register should be dead"); |
| |
| // Determine re-creation positions for all the deleted register's defs. |
| SmallVector<MachineBasicBlock::iterator, 1> InsertPositions; |
| for (unsigned I = 0, E = Reg.Defs.size(); I < E; ++I) { |
| InsertBeforePos Pos = Positions[--PositionIndex]; |
| if (auto *MBB = dyn_cast<MachineBasicBlock *>(Pos)) { |
| InsertPositions.push_back(MBB->end()); |
| } else { |
| auto *MI = cast<MachineInstr *>(Pos); |
| MachineInstr *InsertBeforeMI = Replacements.lookup_or(MI, MI); |
| InsertPositions.push_back(InsertBeforeMI->getIterator()); |
| } |
| } |
| |
| Remater.recreateReg(Reg.Idx, InsertPositions, Reg.DefReg); |
| |
| const Rematerializer::Reg &RecreateReg = Remater.getReg(Reg.Idx); |
| for (const auto [OldDef, NewDef] : zip_equal(Reg.Defs, RecreateReg.Defs)) { |
| assert(!Replacements.contains(OldDef) && "duplicate deleted MI"); |
| Replacements[OldDef] = NewDef; |
| } |
| } |
| |
| // Rollback rematerializations. |
| for (const auto &[RegIdx, RematsOf] : Rematerializations) { |
| for (RegisterIdx RematRegIdx : RematsOf) { |
| // It is possible that rematerializations were deleted. Their users would |
| // have been transfered to some other rematerialization so we can safely |
| // ignore them. Original registers that were deleted were just re-created |
| // so we do not need to check for that. |
| if (Remater.getReg(RematRegIdx).isAlive()) |
| Remater.transferAllUsers(RematRegIdx, RegIdx); |
| } |
| } |
| |
| DeadRegs.clear(); |
| Positions.clear(); |
| PosToIdx.clear(); |
| Rematerializations.clear(); |
| RollingBack = false; |
| } |
| |
| bool Rollbacker::isRollbackableMI(const MachineInstr &MI, |
| const Rematerializer &Remater) const { |
| RegisterIdx RegIdx = Remater.getDefRegIdx(MI); |
| if (RegIdx == Rematerializer::NoReg || |
| !Remater.isRematerializedRegister(RegIdx)) |
| return false; |
| // It is possible that the MI defines a rematerializable register that was not |
| // recorded if the rollbacker was attached to the rematerializer after the |
| // rematerialization happened. In such cases the MI won't be rolled back. |
| auto RematsOf = Rematerializations.find(Remater.getOriginOf(RegIdx)); |
| if (RematsOf == Rematerializations.end()) |
| return false; |
| return RematsOf->getSecond().contains(RegIdx); |
| } |
| |
| void Rollbacker::invalidatePosition(MachineInstr *MI, |
| MachineBasicBlock::iterator It) { |
| const InsertBeforePos MIPos = InsertBeforePos(MI), |
| NewPos = makePos(It, MI->getParent()); |
| auto MIIndices = PosToIdx.find(MIPos); |
| if (MIIndices == PosToIdx.end()) |
| return; |
| 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(InvalIndices); |
| PosToIdx.erase(MIPos); |
| } |