aboutsummaryrefslogtreecommitdiff
path: root/llvm/lib/Transforms/Scalar/RewriteStatepointsForGC.cpp
diff options
context:
space:
mode:
authorDimitry Andric <dim@FreeBSD.org>2023-02-11 12:38:04 +0000
committerDimitry Andric <dim@FreeBSD.org>2023-02-11 12:38:11 +0000
commite3b557809604d036af6e00c60f012c2025b59a5e (patch)
tree8a11ba2269a3b669601e2fd41145b174008f4da8 /llvm/lib/Transforms/Scalar/RewriteStatepointsForGC.cpp
parent08e8dd7b9db7bb4a9de26d44c1cbfd24e869c014 (diff)
Diffstat (limited to 'llvm/lib/Transforms/Scalar/RewriteStatepointsForGC.cpp')
-rw-r--r--llvm/lib/Transforms/Scalar/RewriteStatepointsForGC.cpp284
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;