Skip to content

Commit a265cf2

Browse files
committed
[Attributor] Introduce the AA::isPotentiallyReachable helper APIs
To make usage easier (compared to the many reachability related AAs), this patch introduces a helper API, `AA::isPotentiallyReachable`, which performs all the necessary steps. It also does the "backwards" reachability (see D106720) as that simplifies the AA a lot (backwards queries were somewhat different from the other query resolvers), and ensures we use cached values in every stage. To test inter-procedural reachability in a reasonable way this patch includes an extension to `AAPointerInfo::forallInterferingWrites`. Basically, we can exclude writes if they cannot reach a load "during the lifetime" of the allocation. That is, we need to go up the call graph to determine reachability until we can determine the allocation would be dead in the caller. This leads to new constant propagations (through memory) in `value-simplify-pointer-info-gpu.ll`. Note: The new code contains plenty debug output to determine how reachability queries are resolved. Parts extracted from D110078. Differential Revision: https://reviews.llvm.org/D118673
1 parent b51b83f commit a265cf2

File tree

8 files changed

+386
-259
lines changed

8 files changed

+386
-259
lines changed

llvm/include/llvm/Transforms/IPO/Attributor.h

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,24 @@ bool isAssumedReadOnly(Attributor &A, const IRPosition &IRP,
217217
bool isAssumedReadNone(Attributor &A, const IRPosition &IRP,
218218
const AbstractAttribute &QueryingAA, bool &IsKnown);
219219

220+
/// Return true if \p ToI is potentially reachable from \p FromI. The two
221+
/// instructions do not need to be in the same function. \p GoBackwardsCB
222+
/// can be provided to convey domain knowledge about the "lifespan" the user is
223+
/// interested in. By default, the callers of \p FromI are checked as well to
224+
/// determine if \p ToI can be reached. If the query is not interested in
225+
/// callers beyond a certain point, e.g., a GPU kernel entry or the function
226+
/// containing an alloca, the \p GoBackwardsCB should return false.
227+
bool isPotentiallyReachable(
228+
Attributor &A, const Instruction &FromI, const Instruction &ToI,
229+
const AbstractAttribute &QueryingAA,
230+
std::function<bool(const Function &F)> GoBackwardsCB = nullptr);
231+
232+
/// Same as above but it is sufficient to reach any instruction in \p ToFn.
233+
bool isPotentiallyReachable(
234+
Attributor &A, const Instruction &FromI, const Function &ToFn,
235+
const AbstractAttribute &QueryingAA,
236+
std::function<bool(const Function &F)> GoBackwardsCB);
237+
220238
} // namespace AA
221239

222240
/// The value passed to the line option that defines the maximal initialization
@@ -4636,11 +4654,12 @@ struct AAFunctionReachability
46364654
/// If the function represented by this possition can reach \p Fn.
46374655
virtual bool canReach(Attributor &A, const Function &Fn) const = 0;
46384656

4639-
/// Can \p CB reach \p Fn
4657+
/// Can \p CB reach \p Fn.
46404658
virtual bool canReach(Attributor &A, CallBase &CB,
46414659
const Function &Fn) const = 0;
46424660

4643-
/// Can \p Inst reach \p Fn
4661+
/// Can \p Inst reach \p Fn.
4662+
/// See also AA::isPotentiallyReachable.
46444663
virtual bool instructionCanReach(Attributor &A, const Instruction &Inst,
46454664
const Function &Fn,
46464665
bool UseBackwards = true) const = 0;

llvm/lib/Transforms/IPO/Attributor.cpp

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,121 @@ bool AA::isAssumedReadNone(Attributor &A, const IRPosition &IRP,
436436
/* RequireReadNone */ true, IsKnown);
437437
}
438438

439+
static bool
440+
isPotentiallyReachable(Attributor &A, const Instruction &FromI,
441+
const Instruction *ToI, const Function &ToFn,
442+
const AbstractAttribute &QueryingAA,
443+
std::function<bool(const Function &F)> GoBackwardsCB) {
444+
LLVM_DEBUG(dbgs() << "[AA] isPotentiallyReachable @" << ToFn.getName()
445+
<< " from " << FromI << " [GBCB: " << bool(GoBackwardsCB)
446+
<< "]\n");
447+
448+
SmallPtrSet<const Instruction *, 8> Visited;
449+
SmallVector<const Instruction *> Worklist;
450+
Worklist.push_back(&FromI);
451+
452+
while (!Worklist.empty()) {
453+
const Instruction *CurFromI = Worklist.pop_back_val();
454+
if (!Visited.insert(CurFromI).second)
455+
continue;
456+
457+
const Function *FromFn = CurFromI->getFunction();
458+
if (FromFn == &ToFn) {
459+
if (!ToI)
460+
return true;
461+
LLVM_DEBUG(dbgs() << "[AA] check " << *ToI << " from " << *CurFromI
462+
<< " intraprocedurally\n");
463+
const auto &ReachabilityAA = A.getAAFor<AAReachability>(
464+
QueryingAA, IRPosition::function(ToFn), DepClassTy::OPTIONAL);
465+
bool Result = ReachabilityAA.isAssumedReachable(A, *CurFromI, *ToI);
466+
LLVM_DEBUG(dbgs() << "[AA] " << *CurFromI << " "
467+
<< (Result ? "can potentially " : "cannot ") << "reach "
468+
<< *ToI << " [Intra]\n");
469+
if (Result)
470+
return true;
471+
continue;
472+
}
473+
474+
// TODO: If we can go arbitrarily backwards we will eventually reach an
475+
// entry point that can reach ToI. Only once this takes a set of blocks
476+
// through which we cannot go, or once we track internal functions not
477+
// accessible from the outside, it makes sense to perform backwards analysis
478+
// in the absence of a GoBackwardsCB.
479+
if (!GoBackwardsCB) {
480+
LLVM_DEBUG(dbgs() << "[AA] check @" << ToFn.getName() << " from "
481+
<< *CurFromI << " is not checked backwards, abort\n");
482+
return true;
483+
}
484+
485+
// Check if the current instruction is already known to reach the ToFn.
486+
const auto &FnReachabilityAA = A.getAAFor<AAFunctionReachability>(
487+
QueryingAA, IRPosition::function(*FromFn), DepClassTy::OPTIONAL);
488+
bool Result = FnReachabilityAA.instructionCanReach(
489+
A, *CurFromI, ToFn, /* UseBackwards */ false);
490+
LLVM_DEBUG(dbgs() << "[AA] " << *CurFromI << " in @" << FromFn->getName()
491+
<< " " << (Result ? "can potentially " : "cannot ")
492+
<< "reach @" << ToFn.getName() << " [FromFn]\n");
493+
if (Result)
494+
return true;
495+
496+
// If we do not go backwards from the FromFn we are done here and so far we
497+
// could not find a way to reach ToFn/ToI.
498+
if (!GoBackwardsCB(*FromFn))
499+
continue;
500+
501+
LLVM_DEBUG(dbgs() << "Stepping backwards to the call sites of @"
502+
<< FromFn->getName() << "\n");
503+
504+
auto CheckCallSite = [&](AbstractCallSite ACS) {
505+
CallBase *CB = ACS.getInstruction();
506+
if (!CB)
507+
return false;
508+
509+
if (isa<InvokeInst>(CB))
510+
return false;
511+
512+
Instruction *Inst = CB->getNextNonDebugInstruction();
513+
Worklist.push_back(Inst);
514+
return true;
515+
};
516+
517+
bool AllCallSitesKnown;
518+
Result = !A.checkForAllCallSites(CheckCallSite, *FromFn,
519+
/* RequireAllCallSites */ true,
520+
&QueryingAA, AllCallSitesKnown);
521+
if (Result) {
522+
LLVM_DEBUG(dbgs() << "[AA] stepping back to call sites from " << *CurFromI
523+
<< " in @" << FromFn->getName()
524+
<< " failed, give up\n");
525+
return true;
526+
}
527+
528+
LLVM_DEBUG(dbgs() << "[AA] stepped back to call sites from " << *CurFromI
529+
<< " in @" << FromFn->getName()
530+
<< " worklist size is: " << Worklist.size() << "\n");
531+
}
532+
return false;
533+
}
534+
535+
bool AA::isPotentiallyReachable(
536+
Attributor &A, const Instruction &FromI, const Instruction &ToI,
537+
const AbstractAttribute &QueryingAA,
538+
std::function<bool(const Function &F)> GoBackwardsCB) {
539+
LLVM_DEBUG(dbgs() << "[AA] isPotentiallyReachable " << ToI << " from "
540+
<< FromI << " [GBCB: " << bool(GoBackwardsCB) << "]\n");
541+
const Function *ToFn = ToI.getFunction();
542+
return ::isPotentiallyReachable(A, FromI, &ToI, *ToFn, QueryingAA,
543+
GoBackwardsCB);
544+
}
545+
546+
bool AA::isPotentiallyReachable(
547+
Attributor &A, const Instruction &FromI, const Function &ToFn,
548+
const AbstractAttribute &QueryingAA,
549+
std::function<bool(const Function &F)> GoBackwardsCB) {
550+
return ::isPotentiallyReachable(A, FromI, /* ToI */ nullptr, ToFn, QueryingAA,
551+
GoBackwardsCB);
552+
}
553+
439554
/// Return true if \p New is equal or worse than \p Old.
440555
static bool isEqualOrWorse(const Attribute &New, const Attribute &Old) {
441556
if (!Old.isIntAttribute())
@@ -1464,9 +1579,11 @@ void Attributor::runTillFixpoint() {
14641579
InvalidAA->Deps.pop_back();
14651580
AbstractAttribute *DepAA = cast<AbstractAttribute>(Dep.getPointer());
14661581
if (Dep.getInt() == unsigned(DepClassTy::OPTIONAL)) {
1582+
LLVM_DEBUG(dbgs() << " - recompute: " << *DepAA);
14671583
Worklist.insert(DepAA);
14681584
continue;
14691585
}
1586+
LLVM_DEBUG(dbgs() << " - invalidate: " << *DepAA);
14701587
DepAA->getState().indicatePessimisticFixpoint();
14711588
assert(DepAA->getState().isAtFixpoint() && "Expected fixpoint state!");
14721589
if (!DepAA->getState().isValidState())

llvm/lib/Transforms/IPO/AttributorAttributes.cpp

Lines changed: 66 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1130,19 +1130,63 @@ struct AAPointerInfoImpl
11301130
QueryingAA, IRPosition::function(*LI.getFunction()),
11311131
DepClassTy::OPTIONAL);
11321132

1133-
// Helper to determine if the instruction may reach the load.
1134-
auto IsReachableFrom = [&](const Instruction &I) {
1135-
const auto &ReachabilityAA = A.getAAFor<AAReachability>(
1136-
QueryingAA, IRPosition::function(*I.getFunction()),
1137-
DepClassTy::OPTIONAL);
1138-
return ReachabilityAA.isAssumedReachable(A, I, LI);
1139-
};
1140-
1141-
const bool CanUseCFGResoning =
1142-
NoRecurseAA.isKnownNoRecurse() && CanIgnoreThreading(LI);
1133+
const bool CanUseCFGResoning = CanIgnoreThreading(LI);
11431134
InformationCache &InfoCache = A.getInfoCache();
11441135
const DominatorTree *DT =
1145-
InfoCache.getAnalysisResultForFunction<DominatorTreeAnalysis>(Scope);
1136+
NoRecurseAA.isKnownNoRecurse()
1137+
? InfoCache.getAnalysisResultForFunction<DominatorTreeAnalysis>(
1138+
Scope)
1139+
: nullptr;
1140+
1141+
enum GPUAddressSpace : unsigned {
1142+
Generic = 0,
1143+
Global = 1,
1144+
Shared = 3,
1145+
Constant = 4,
1146+
Local = 5,
1147+
};
1148+
1149+
// Helper to check if a value has "kernel lifetime", that is it will not
1150+
// outlive a GPU kernel. This is true for shared, constant, and local
1151+
// globals on AMD and NVIDIA GPUs.
1152+
auto HasKernelLifetime = [&](Value *V, Module &M) {
1153+
Triple T(M.getTargetTriple());
1154+
if (!(T.isAMDGPU() || T.isNVPTX()))
1155+
return false;
1156+
switch (V->getType()->getPointerAddressSpace()) {
1157+
case GPUAddressSpace::Shared:
1158+
case GPUAddressSpace::Constant:
1159+
case GPUAddressSpace::Local:
1160+
return true;
1161+
default:
1162+
return false;
1163+
};
1164+
};
1165+
1166+
// The IsLiveInCalleeCB will be used by the AA::isPotentiallyReachable query
1167+
// to determine if we should look at reachability from the callee. For
1168+
// certain pointers we know the lifetime and we do not have to step into the
1169+
// callee to determine reachability as the pointer would be dead in the
1170+
// callee. See the conditional initialization below.
1171+
std::function<bool(const Function &)> IsLiveInCalleeCB;
1172+
1173+
if (auto *AI = dyn_cast<AllocaInst>(&getAssociatedValue())) {
1174+
// If the alloca containing function is not recursive the alloca
1175+
// must be dead in the callee.
1176+
const Function *AIFn = AI->getFunction();
1177+
const auto &NoRecurseAA = A.getAAFor<AANoRecurse>(
1178+
*this, IRPosition::function(*AIFn), DepClassTy::OPTIONAL);
1179+
if (NoRecurseAA.isAssumedNoRecurse()) {
1180+
IsLiveInCalleeCB = [AIFn](const Function &Fn) { return AIFn != &Fn; };
1181+
}
1182+
} else if (auto *GV = dyn_cast<GlobalValue>(&getAssociatedValue())) {
1183+
// If the global has kernel lifetime we can stop if we reach a kernel
1184+
// as it is "dead" in the (unknown) callees.
1185+
if (HasKernelLifetime(GV, *GV->getParent()))
1186+
IsLiveInCalleeCB = [](const Function &Fn) {
1187+
return !Fn.hasFnAttribute("kernel");
1188+
};
1189+
}
11461190

11471191
auto AccessCB = [&](const Access &Acc, bool Exact) {
11481192
if (!Acc.isWrite())
@@ -1151,7 +1195,8 @@ struct AAPointerInfoImpl
11511195
// For now we only filter accesses based on CFG reasoning which does not
11521196
// work yet if we have threading effects, or the access is complicated.
11531197
if (CanUseCFGResoning) {
1154-
if (!IsReachableFrom(*Acc.getLocalInst()))
1198+
if (!AA::isPotentiallyReachable(A, *Acc.getLocalInst(), LI, QueryingAA,
1199+
IsLiveInCalleeCB))
11551200
return true;
11561201
if (DT && Exact &&
11571202
(Acc.getLocalInst()->getFunction() == LI.getFunction()) &&
@@ -9674,7 +9719,7 @@ struct AAFunctionReachabilityFunction : public AAFunctionReachability {
96749719
if (!Reachability.isAssumedReachable(A, Inst, CBInst))
96759720
return true;
96769721

9677-
const auto &CB = cast<CallBase>(CBInst);
9722+
auto &CB = cast<CallBase>(CBInst);
96789723
const AACallEdges &AAEdges = A.getAAFor<AACallEdges>(
96799724
*this, IRPosition::callsite_function(CB), DepClassTy::REQUIRED);
96809725

@@ -9684,47 +9729,8 @@ struct AAFunctionReachabilityFunction : public AAFunctionReachability {
96849729

96859730
bool UsedAssumedInformation = false;
96869731
return A.checkForAllCallLikeInstructions(CheckCallBase, *this,
9687-
UsedAssumedInformation);
9688-
}
9689-
9690-
ChangeStatus checkReachableBackwards(Attributor &A, QuerySet &Set) {
9691-
ChangeStatus Change = ChangeStatus::UNCHANGED;
9692-
9693-
// For all remaining instruction queries, check
9694-
// callers. A call inside that function might satisfy the query.
9695-
auto CheckCallSite = [&](AbstractCallSite CallSite) {
9696-
CallBase *CB = CallSite.getInstruction();
9697-
if (!CB)
9698-
return false;
9699-
9700-
if (isa<InvokeInst>(CB))
9701-
return false;
9702-
9703-
Instruction *Inst = CB->getNextNonDebugInstruction();
9704-
const AAFunctionReachability &AA = A.getAAFor<AAFunctionReachability>(
9705-
*this, IRPosition::function(*Inst->getFunction()),
9706-
DepClassTy::REQUIRED);
9707-
for (const Function *Fn : make_early_inc_range(Set.Unreachable)) {
9708-
if (AA.instructionCanReach(A, *Inst, *Fn, /* UseBackwards */ false)) {
9709-
Set.markReachable(*Fn);
9710-
Change = ChangeStatus::CHANGED;
9711-
}
9712-
}
9713-
return true;
9714-
};
9715-
9716-
bool NoUnknownCall = true;
9717-
if (A.checkForAllCallSites(CheckCallSite, *this, true, NoUnknownCall))
9718-
return Change;
9719-
9720-
// If we don't know all callsites we have to assume that we can reach fn.
9721-
for (auto &QSet : InstQueriesBackwards) {
9722-
if (!QSet.second.CanReachUnknownCallee)
9723-
Change = ChangeStatus::CHANGED;
9724-
QSet.second.CanReachUnknownCallee = true;
9725-
}
9726-
9727-
return Change;
9732+
UsedAssumedInformation,
9733+
/* CheckBBLivenessOnly */ true);
97289734
}
97299735

97309736
public:
@@ -9776,12 +9782,15 @@ struct AAFunctionReachabilityFunction : public AAFunctionReachability {
97769782
if (!isValidState())
97779783
return true;
97789784

9779-
const auto &Reachability = &A.getAAFor<AAReachability>(
9785+
if (UseBackwards)
9786+
return AA::isPotentiallyReachable(A, Inst, Fn, *this, nullptr);
9787+
9788+
const auto &Reachability = A.getAAFor<AAReachability>(
97809789
*this, IRPosition::function(*getAssociatedFunction()),
97819790
DepClassTy::REQUIRED);
97829791

97839792
SmallVector<const AACallEdges *> CallEdges;
9784-
bool AllKnown = getReachableCallEdges(A, *Reachability, Inst, CallEdges);
9793+
bool AllKnown = getReachableCallEdges(A, Reachability, Inst, CallEdges);
97859794
// Attributor returns attributes as const, so this function has to be
97869795
// const for users of this attribute to use it without having to do
97879796
// a const_cast.
@@ -9791,25 +9800,7 @@ struct AAFunctionReachabilityFunction : public AAFunctionReachability {
97919800
if (!AllKnown)
97929801
InstQSet.CanReachUnknownCallee = true;
97939802

9794-
bool ForwardsResult = InstQSet.isReachable(A, *NonConstThis, CallEdges, Fn);
9795-
if (ForwardsResult)
9796-
return true;
9797-
// We are done.
9798-
if (!UseBackwards)
9799-
return false;
9800-
9801-
QuerySet &InstBackwardsQSet = NonConstThis->InstQueriesBackwards[&Inst];
9802-
9803-
Optional<bool> BackwardsCached = InstBackwardsQSet.isCachedReachable(Fn);
9804-
if (BackwardsCached.hasValue())
9805-
return BackwardsCached.getValue();
9806-
9807-
// Assume unreachable, to prevent problems.
9808-
InstBackwardsQSet.Unreachable.insert(&Fn);
9809-
9810-
// Check backwards reachability.
9811-
NonConstThis->checkReachableBackwards(A, InstBackwardsQSet);
9812-
return InstBackwardsQSet.isCachedReachable(Fn).getValue();
9803+
return InstQSet.isReachable(A, *NonConstThis, CallEdges, Fn);
98139804
}
98149805

98159806
/// See AbstractAttribute::updateImpl(...).
@@ -9847,10 +9838,6 @@ struct AAFunctionReachabilityFunction : public AAFunctionReachability {
98479838
Change |= InstPair.second.update(A, *this, CallEdges);
98489839
}
98499840

9850-
// Update backwards queries.
9851-
for (auto &QueryPair : InstQueriesBackwards)
9852-
Change |= checkReachableBackwards(A, QueryPair.second);
9853-
98549841
return Change;
98559842
}
98569843

@@ -9879,9 +9866,6 @@ struct AAFunctionReachabilityFunction : public AAFunctionReachability {
98799866

98809867
/// This is for instruction queries than scan "forward".
98819868
DenseMap<const Instruction *, QueryResolver> InstQueries;
9882-
9883-
/// This is for instruction queries than scan "backward".
9884-
DenseMap<const Instruction *, QuerySet> InstQueriesBackwards;
98859869
};
98869870

98879871
/// ---------------------- Assumption Propagation ------------------------------

llvm/test/Transforms/Attributor/ArgumentPromotion/X86/min-legal-vector-width.ll

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function-signature --check-attributes --check-globals
2-
; RUN: opt -attributor -enable-new-pm=0 -attributor-manifest-internal -attributor-max-iterations-verify -attributor-annotate-decl-cs -attributor-max-iterations=3 -S < %s | FileCheck %s --check-prefixes=CHECK,NOT_CGSCC_NPM,NOT_CGSCC_OPM,NOT_TUNIT_NPM,IS__TUNIT____,IS________OPM,IS__TUNIT_OPM
3-
; RUN: opt -aa-pipeline=basic-aa -passes=attributor -attributor-manifest-internal -attributor-max-iterations-verify -attributor-annotate-decl-cs -attributor-max-iterations=3 -S < %s | FileCheck %s --check-prefixes=CHECK,NOT_CGSCC_OPM,NOT_CGSCC_NPM,NOT_TUNIT_OPM,IS__TUNIT____,IS________NPM,IS__TUNIT_NPM
2+
; RUN: opt -attributor -enable-new-pm=0 -attributor-manifest-internal -attributor-max-iterations-verify -attributor-annotate-decl-cs -attributor-max-iterations=7 -S < %s | FileCheck %s --check-prefixes=CHECK,NOT_CGSCC_NPM,NOT_CGSCC_OPM,NOT_TUNIT_NPM,IS__TUNIT____,IS________OPM,IS__TUNIT_OPM
3+
; RUN: opt -aa-pipeline=basic-aa -passes=attributor -attributor-manifest-internal -attributor-max-iterations-verify -attributor-annotate-decl-cs -attributor-max-iterations=7 -S < %s | FileCheck %s --check-prefixes=CHECK,NOT_CGSCC_OPM,NOT_CGSCC_NPM,NOT_TUNIT_OPM,IS__TUNIT____,IS________NPM,IS__TUNIT_NPM
44
; RUN: opt -attributor-cgscc -enable-new-pm=0 -attributor-manifest-internal -attributor-annotate-decl-cs -S < %s | FileCheck %s --check-prefixes=CHECK,NOT_TUNIT_NPM,NOT_TUNIT_OPM,NOT_CGSCC_NPM,IS__CGSCC____,IS________OPM,IS__CGSCC_OPM
55
; RUN: opt -aa-pipeline=basic-aa -passes=attributor-cgscc -attributor-manifest-internal -attributor-annotate-decl-cs -S < %s | FileCheck %s --check-prefixes=CHECK,NOT_TUNIT_NPM,NOT_TUNIT_OPM,NOT_CGSCC_OPM,IS__CGSCC____,IS________NPM,IS__CGSCC_NPM
66
; Test that we only promote arguments when the caller/callee have compatible

0 commit comments

Comments
 (0)