diff options
author | Dimitry Andric <dim@FreeBSD.org> | 2023-02-11 12:38:04 +0000 |
---|---|---|
committer | Dimitry Andric <dim@FreeBSD.org> | 2023-02-11 12:38:11 +0000 |
commit | e3b557809604d036af6e00c60f012c2025b59a5e (patch) | |
tree | 8a11ba2269a3b669601e2fd41145b174008f4da8 /llvm/lib/Transforms/Scalar/RewriteStatepointsForGC.cpp | |
parent | 08e8dd7b9db7bb4a9de26d44c1cbfd24e869c014 (diff) |
Diffstat (limited to 'llvm/lib/Transforms/Scalar/RewriteStatepointsForGC.cpp')
-rw-r--r-- | llvm/lib/Transforms/Scalar/RewriteStatepointsForGC.cpp | 284 |
1 files changed, 206 insertions, 78 deletions
diff --git a/llvm/lib/Transforms/Scalar/RewriteStatepointsForGC.cpp b/llvm/lib/Transforms/Scalar/RewriteStatepointsForGC.cpp index baf407c5037b..bcb012b79c2e 100644 --- a/llvm/lib/Transforms/Scalar/RewriteStatepointsForGC.cpp +++ b/llvm/lib/Transforms/Scalar/RewriteStatepointsForGC.cpp @@ -17,8 +17,6 @@ #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/DenseSet.h" #include "llvm/ADT/MapVector.h" -#include "llvm/ADT/None.h" -#include "llvm/ADT/Optional.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SetVector.h" #include "llvm/ADT/SmallSet.h" @@ -71,6 +69,7 @@ #include <cstddef> #include <cstdint> #include <iterator> +#include <optional> #include <set> #include <string> #include <utility> @@ -110,6 +109,9 @@ static cl::opt<bool> AllowStatepointWithNoDeoptInfo("rs4gc-allow-statepoint-with-no-deopt-info", cl::Hidden, cl::init(true)); +static cl::opt<bool> RematDerivedAtUses("rs4gc-remat-derived-at-uses", + cl::Hidden, cl::init(true)); + /// The IR fed into RewriteStatepointsForGC may have had attributes and /// metadata implying dereferenceability that are no longer valid/correct after /// RewriteStatepointsForGC has run. This is because semantically, after @@ -295,13 +297,13 @@ using RematCandTy = MapVector<Value *, RematerizlizationCandidateRecord>; } // end anonymous namespace static ArrayRef<Use> GetDeoptBundleOperands(const CallBase *Call) { - Optional<OperandBundleUse> DeoptBundle = + std::optional<OperandBundleUse> DeoptBundle = Call->getOperandBundle(LLVMContext::OB_deopt); if (!DeoptBundle) { assert(AllowStatepointWithNoDeoptInfo && "Found non-leaf call without deopt info!"); - return None; + return std::nullopt; } return DeoptBundle->Inputs; @@ -317,7 +319,7 @@ static void findLiveSetAtInst(Instruction *inst, GCPtrLivenessData &Data, StatepointLiveSetTy &out); // TODO: Once we can get to the GCStrategy, this becomes -// Optional<bool> isGCManagedPointer(const Type *Ty) const override { +// std::optional<bool> isGCManagedPointer(const Type *Ty) const override { static bool isGCPointerType(Type *T) { if (auto *PT = dyn_cast<PointerType>(T)) @@ -1400,6 +1402,61 @@ static void recomputeLiveInValues( } } +// Utility function which clones all instructions from "ChainToBase" +// and inserts them before "InsertBefore". Returns rematerialized value +// which should be used after statepoint. +static Instruction *rematerializeChain(ArrayRef<Instruction *> ChainToBase, + Instruction *InsertBefore, + Value *RootOfChain, + Value *AlternateLiveBase) { + Instruction *LastClonedValue = nullptr; + Instruction *LastValue = nullptr; + // Walk backwards to visit top-most instructions first. + for (Instruction *Instr : + make_range(ChainToBase.rbegin(), ChainToBase.rend())) { + // Only GEP's and casts are supported as we need to be careful to not + // introduce any new uses of pointers not in the liveset. + // Note that it's fine to introduce new uses of pointers which were + // otherwise not used after this statepoint. + assert(isa<GetElementPtrInst>(Instr) || isa<CastInst>(Instr)); + + Instruction *ClonedValue = Instr->clone(); + ClonedValue->insertBefore(InsertBefore); + ClonedValue->setName(Instr->getName() + ".remat"); + + // If it is not first instruction in the chain then it uses previously + // cloned value. We should update it to use cloned value. + if (LastClonedValue) { + assert(LastValue); + ClonedValue->replaceUsesOfWith(LastValue, LastClonedValue); +#ifndef NDEBUG + for (auto *OpValue : ClonedValue->operand_values()) { + // Assert that cloned instruction does not use any instructions from + // this chain other than LastClonedValue + assert(!is_contained(ChainToBase, OpValue) && + "incorrect use in rematerialization chain"); + // Assert that the cloned instruction does not use the RootOfChain + // or the AlternateLiveBase. + assert(OpValue != RootOfChain && OpValue != AlternateLiveBase); + } +#endif + } else { + // For the first instruction, replace the use of unrelocated base i.e. + // RootOfChain/OrigRootPhi, with the corresponding PHI present in the + // live set. They have been proved to be the same PHI nodes. Note + // that the *only* use of the RootOfChain in the ChainToBase list is + // the first Value in the list. + if (RootOfChain != AlternateLiveBase) + ClonedValue->replaceUsesOfWith(RootOfChain, AlternateLiveBase); + } + + LastClonedValue = ClonedValue; + LastValue = Instr; + } + assert(LastClonedValue); + return LastClonedValue; +} + // When inserting gc.relocate and gc.result calls, we need to ensure there are // no uses of the original value / return value between the gc.statepoint and // the gc.relocate / gc.result call. One case which can arise is a phi node @@ -1430,10 +1487,7 @@ normalizeForInvokeSafepoint(BasicBlock *BB, BasicBlock *InvokeParent, // machine model for purposes of optimization. We have to strip these on // both function declarations and call sites. static constexpr Attribute::AttrKind FnAttrsToStrip[] = - {Attribute::ReadNone, Attribute::ReadOnly, Attribute::WriteOnly, - Attribute::ArgMemOnly, Attribute::InaccessibleMemOnly, - Attribute::InaccessibleMemOrArgMemOnly, - Attribute::NoSync, Attribute::NoFree}; + {Attribute::Memory, Attribute::NoSync, Attribute::NoFree}; // Create new attribute set containing only attributes which can be transferred // from original call to the safepoint. @@ -1629,10 +1683,10 @@ makeStatepointExplicitImpl(CallBase *Call, /* to replace */ uint32_t Flags = uint32_t(StatepointFlags::None); SmallVector<Value *, 8> CallArgs(Call->args()); - Optional<ArrayRef<Use>> DeoptArgs; + std::optional<ArrayRef<Use>> DeoptArgs; if (auto Bundle = Call->getOperandBundle(LLVMContext::OB_deopt)) DeoptArgs = Bundle->Inputs; - Optional<ArrayRef<Use>> TransitionArgs; + std::optional<ArrayRef<Use>> TransitionArgs; if (auto Bundle = Call->getOperandBundle(LLVMContext::OB_gc_transition)) { TransitionArgs = Bundle->Inputs; // TODO: This flag no longer serves a purpose and can be removed later @@ -2082,8 +2136,12 @@ static void relocationViaAlloca( auto InsertClobbersAt = [&](Instruction *IP) { for (auto *AI : ToClobber) { - auto PT = cast<PointerType>(AI->getAllocatedType()); - Constant *CPN = ConstantPointerNull::get(PT); + auto AT = AI->getAllocatedType(); + Constant *CPN; + if (AT->isVectorTy()) + CPN = ConstantAggregateZero::get(AT); + else + CPN = ConstantPointerNull::get(cast<PointerType>(AT)); new StoreInst(CPN, AI, IP); } }; @@ -2379,6 +2437,126 @@ findRematerializationCandidates(PointerToBaseTy PointerToBase, } } +// Try to rematerialize derived pointers immediately before their uses +// (instead of rematerializing after every statepoint it is live through). +// This can be beneficial when derived pointer is live across many +// statepoints, but uses are rare. +static void rematerializeLiveValuesAtUses( + RematCandTy &RematerizationCandidates, + MutableArrayRef<PartiallyConstructedSafepointRecord> Records, + PointerToBaseTy &PointerToBase) { + if (!RematDerivedAtUses) + return; + + SmallVector<Instruction *, 32> LiveValuesToBeDeleted; + + LLVM_DEBUG(dbgs() << "Rematerialize derived pointers at uses, " + << "Num statepoints: " << Records.size() << '\n'); + + for (auto &It : RematerizationCandidates) { + Instruction *Cand = cast<Instruction>(It.first); + auto &Record = It.second; + + if (Record.Cost >= RematerializationThreshold) + continue; + + if (Cand->user_empty()) + continue; + + if (Cand->hasOneUse()) + if (auto *U = dyn_cast<Instruction>(Cand->getUniqueUndroppableUser())) + if (U->getParent() == Cand->getParent()) + continue; + + // Rematerialization before PHI nodes is not implemented. + if (llvm::any_of(Cand->users(), + [](const auto *U) { return isa<PHINode>(U); })) + continue; + + LLVM_DEBUG(dbgs() << "Trying cand " << *Cand << " ... "); + + // Count of rematerialization instructions we introduce is equal to number + // of candidate uses. + // Count of rematerialization instructions we eliminate is equal to number + // of statepoints it is live through. + // Consider transformation profitable if latter is greater than former + // (in other words, we create less than eliminate). + unsigned NumLiveStatepoints = llvm::count_if( + Records, [Cand](const auto &R) { return R.LiveSet.contains(Cand); }); + unsigned NumUses = Cand->getNumUses(); + + LLVM_DEBUG(dbgs() << "Num uses: " << NumUses << " Num live statepoints: " + << NumLiveStatepoints << " "); + + if (NumLiveStatepoints < NumUses) { + LLVM_DEBUG(dbgs() << "not profitable\n"); + continue; + } + + // If rematerialization is 'free', then favor rematerialization at + // uses as it generally shortens live ranges. + // TODO: Short (size ==1) chains only? + if (NumLiveStatepoints == NumUses && Record.Cost > 0) { + LLVM_DEBUG(dbgs() << "not profitable\n"); + continue; + } + + LLVM_DEBUG(dbgs() << "looks profitable\n"); + + // ChainToBase may contain another remat candidate (as a sub chain) which + // has been rewritten by now. Need to recollect chain to have up to date + // value. + // TODO: sort records in findRematerializationCandidates() in + // decreasing chain size order? + if (Record.ChainToBase.size() > 1) { + Record.ChainToBase.clear(); + findRematerializableChainToBasePointer(Record.ChainToBase, Cand); + } + + // Current rematerialization algorithm is very simple: we rematerialize + // immediately before EVERY use, even if there are several uses in same + // block or if use is local to Cand Def. The reason is that this allows + // us to avoid recomputing liveness without complicated analysis: + // - If we did not eliminate all uses of original Candidate, we do not + // know exaclty in what BBs it is still live. + // - If we rematerialize once per BB, we need to find proper insertion + // place (first use in block, but after Def) and analyze if there is + // statepoint between uses in the block. + while (!Cand->user_empty()) { + Instruction *UserI = cast<Instruction>(*Cand->user_begin()); + Instruction *RematChain = rematerializeChain( + Record.ChainToBase, UserI, Record.RootOfChain, PointerToBase[Cand]); + UserI->replaceUsesOfWith(Cand, RematChain); + PointerToBase[RematChain] = PointerToBase[Cand]; + } + LiveValuesToBeDeleted.push_back(Cand); + } + + LLVM_DEBUG(dbgs() << "Rematerialized " << LiveValuesToBeDeleted.size() + << " derived pointers\n"); + for (auto *Cand : LiveValuesToBeDeleted) { + assert(Cand->use_empty() && "Unexpected user remain"); + RematerizationCandidates.erase(Cand); + for (auto &R : Records) { + assert(!R.LiveSet.contains(Cand) || + R.LiveSet.contains(PointerToBase[Cand])); + R.LiveSet.remove(Cand); + } + } + + // Recollect not rematerialized chains - we might have rewritten + // their sub-chains. + if (!LiveValuesToBeDeleted.empty()) { + for (auto &P : RematerizationCandidates) { + auto &R = P.second; + if (R.ChainToBase.size() > 1) { + R.ChainToBase.clear(); + findRematerializableChainToBasePointer(R.ChainToBase, P.first); + } + } + } +} + // From the statepoint live set pick values that are cheaper to recompute then // to relocate. Remove this values from the live set, rematerialize them after // statepoint and record them in "Info" structure. Note that similar to @@ -2414,69 +2592,14 @@ static void rematerializeLiveValues(CallBase *Call, // Clone instructions and record them inside "Info" structure. - // For each live pointer find get its defining chain. - SmallVector<Instruction *, 3> ChainToBase = Record.ChainToBase; - // Walk backwards to visit top-most instructions first. - std::reverse(ChainToBase.begin(), ChainToBase.end()); - - // Utility function which clones all instructions from "ChainToBase" - // and inserts them before "InsertBefore". Returns rematerialized value - // which should be used after statepoint. - auto rematerializeChain = [&ChainToBase]( - Instruction *InsertBefore, Value *RootOfChain, Value *AlternateLiveBase) { - Instruction *LastClonedValue = nullptr; - Instruction *LastValue = nullptr; - for (Instruction *Instr: ChainToBase) { - // Only GEP's and casts are supported as we need to be careful to not - // introduce any new uses of pointers not in the liveset. - // Note that it's fine to introduce new uses of pointers which were - // otherwise not used after this statepoint. - assert(isa<GetElementPtrInst>(Instr) || isa<CastInst>(Instr)); - - Instruction *ClonedValue = Instr->clone(); - ClonedValue->insertBefore(InsertBefore); - ClonedValue->setName(Instr->getName() + ".remat"); - - // If it is not first instruction in the chain then it uses previously - // cloned value. We should update it to use cloned value. - if (LastClonedValue) { - assert(LastValue); - ClonedValue->replaceUsesOfWith(LastValue, LastClonedValue); -#ifndef NDEBUG - for (auto OpValue : ClonedValue->operand_values()) { - // Assert that cloned instruction does not use any instructions from - // this chain other than LastClonedValue - assert(!is_contained(ChainToBase, OpValue) && - "incorrect use in rematerialization chain"); - // Assert that the cloned instruction does not use the RootOfChain - // or the AlternateLiveBase. - assert(OpValue != RootOfChain && OpValue != AlternateLiveBase); - } -#endif - } else { - // For the first instruction, replace the use of unrelocated base i.e. - // RootOfChain/OrigRootPhi, with the corresponding PHI present in the - // live set. They have been proved to be the same PHI nodes. Note - // that the *only* use of the RootOfChain in the ChainToBase list is - // the first Value in the list. - if (RootOfChain != AlternateLiveBase) - ClonedValue->replaceUsesOfWith(RootOfChain, AlternateLiveBase); - } - - LastClonedValue = ClonedValue; - LastValue = Instr; - } - assert(LastClonedValue); - return LastClonedValue; - }; - // Different cases for calls and invokes. For invokes we need to clone // instructions both on normal and unwind path. if (isa<CallInst>(Call)) { Instruction *InsertBefore = Call->getNextNode(); assert(InsertBefore); - Instruction *RematerializedValue = rematerializeChain( - InsertBefore, Record.RootOfChain, PointerToBase[LiveValue]); + Instruction *RematerializedValue = + rematerializeChain(Record.ChainToBase, InsertBefore, + Record.RootOfChain, PointerToBase[LiveValue]); Info.RematerializedValues[RematerializedValue] = LiveValue; } else { auto *Invoke = cast<InvokeInst>(Call); @@ -2486,18 +2609,20 @@ static void rematerializeLiveValues(CallBase *Call, Instruction *UnwindInsertBefore = &*Invoke->getUnwindDest()->getFirstInsertionPt(); - Instruction *NormalRematerializedValue = rematerializeChain( - NormalInsertBefore, Record.RootOfChain, PointerToBase[LiveValue]); - Instruction *UnwindRematerializedValue = rematerializeChain( - UnwindInsertBefore, Record.RootOfChain, PointerToBase[LiveValue]); + Instruction *NormalRematerializedValue = + rematerializeChain(Record.ChainToBase, NormalInsertBefore, + Record.RootOfChain, PointerToBase[LiveValue]); + Instruction *UnwindRematerializedValue = + rematerializeChain(Record.ChainToBase, UnwindInsertBefore, + Record.RootOfChain, PointerToBase[LiveValue]); Info.RematerializedValues[NormalRematerializedValue] = LiveValue; Info.RematerializedValues[UnwindRematerializedValue] = LiveValue; } } - // Remove rematerializaed values from the live set - for (auto LiveValue: LiveValuesToBeDeleted) { + // Remove rematerialized values from the live set. + for (auto *LiveValue: LiveValuesToBeDeleted) { Info.LiveSet.remove(LiveValue); } } @@ -2697,6 +2822,9 @@ static bool insertParsePoints(Function &F, DominatorTree &DT, // In order to reduce live set of statepoint we might choose to rematerialize // some values instead of relocating them. This is purely an optimization and // does not influence correctness. + // First try rematerialization at uses, then after statepoints. + rematerializeLiveValuesAtUses(RematerizationCandidates, Records, + PointerToBase); for (size_t i = 0; i < Records.size(); i++) rematerializeLiveValues(ToUpdate[i], Records[i], PointerToBase, RematerizationCandidates, TTI); @@ -3266,7 +3394,7 @@ static void recomputeLiveInValues(GCPtrLivenessData &RevisedLivenessData, // We may have base pointers which are now live that weren't before. We need // to update the PointerToBase structure to reflect this. - for (auto V : Updated) + for (auto *V : Updated) PointerToBase.insert({ V, V }); Info.LiveSet = Updated; |