Skip to content

Commit

Permalink
[Attributor] Introduce AA[Intra/Inter]Reachability
Browse files Browse the repository at this point in the history
We had two AAs for reachability but it was very cumbersome to extend
them. We also had some fallback to use LLVM-core mechanisms and cache
the result. The new design shares the query code and interface nicely
between AAIntraFnReachability and AAInterFnReachability.

As part of the rewrite we also added the ExclusionSet to the queries.
  • Loading branch information
jdoerfert committed Dec 14, 2022
1 parent dde21c1 commit fc21f2d
Show file tree
Hide file tree
Showing 24 changed files with 1,339 additions and 455 deletions.
153 changes: 83 additions & 70 deletions llvm/include/llvm/Transforms/IPO/Attributor.h
Expand Up @@ -151,6 +151,7 @@ class Function;

/// Abstract Attribute helper functions.
namespace AA {
using InstExclusionSetTy = SmallPtrSet<Instruction *, 4>;

/// Flags to distinguish intra-procedural queries from *potentially*
/// inter-procedural queries. Not that information can be valid for both and
Expand Down Expand Up @@ -352,23 +353,26 @@ bool isAssumedReadOnly(Attributor &A, const IRPosition &IRP,
bool isAssumedReadNone(Attributor &A, const IRPosition &IRP,
const AbstractAttribute &QueryingAA, bool &IsKnown);

/// Return true if \p ToI is potentially reachable from \p FromI. The two
/// instructions do not need to be in the same function. \p GoBackwardsCB
/// can be provided to convey domain knowledge about the "lifespan" the user is
/// interested in. By default, the callers of \p FromI are checked as well to
/// determine if \p ToI can be reached. If the query is not interested in
/// callers beyond a certain point, e.g., a GPU kernel entry or the function
/// containing an alloca, the \p GoBackwardsCB should return false.
/// Return true if \p ToI is potentially reachable from \p FromI without running
/// into any instruction in \p ExclusionSet The two instructions do not need to
/// be in the same function. \p GoBackwardsCB can be provided to convey domain
/// knowledge about the "lifespan" the user is interested in. By default, the
/// callers of \p FromI are checked as well to determine if \p ToI can be
/// reached. If the query is not interested in callers beyond a certain point,
/// e.g., a GPU kernel entry or the function containing an alloca, the
/// \p GoBackwardsCB should return false.
bool isPotentiallyReachable(
Attributor &A, const Instruction &FromI, const Instruction &ToI,
const AbstractAttribute &QueryingAA,
const AA::InstExclusionSetTy *ExclusionSet = nullptr,
std::function<bool(const Function &F)> GoBackwardsCB = nullptr);

/// Same as above but it is sufficient to reach any instruction in \p ToFn.
bool isPotentiallyReachable(
Attributor &A, const Instruction &FromI, const Function &ToFn,
const AbstractAttribute &QueryingAA,
std::function<bool(const Function &F)> GoBackwardsCB);
const AA::InstExclusionSetTy *ExclusionSet = nullptr,
std::function<bool(const Function &F)> GoBackwardsCB = nullptr);

} // namespace AA

Expand Down Expand Up @@ -410,6 +414,39 @@ struct DenseMapInfo<AA::ValueScope> : public DenseMapInfo<unsigned char> {
}
};

template <>
struct DenseMapInfo<const AA::InstExclusionSetTy *>
: public DenseMapInfo<void *> {
using super = DenseMapInfo<void *>;
static inline const AA::InstExclusionSetTy *getEmptyKey() {
return static_cast<const AA::InstExclusionSetTy *>(super::getEmptyKey());
}
static inline const AA::InstExclusionSetTy *getTombstoneKey() {
return static_cast<const AA::InstExclusionSetTy *>(
super::getTombstoneKey());
}
static unsigned getHashValue(const AA::InstExclusionSetTy *BES) {
unsigned H = 0;
if (BES)
for (const auto *II : *BES)
H += DenseMapInfo<const Instruction *>::getHashValue(II);
return H;
}
static bool isEqual(const AA::InstExclusionSetTy *LHS,
const AA::InstExclusionSetTy *RHS) {
if (LHS == RHS)
return true;
if (LHS == getEmptyKey() || RHS == getEmptyKey() ||
LHS == getTombstoneKey() || RHS == getTombstoneKey())
return false;
if (!LHS || !RHS)
return ((LHS && LHS->empty()) || (RHS && RHS->empty()));
if (LHS->size() != RHS->size())
return false;
return llvm::set_is_subset(*LHS, *RHS);
}
};

/// The value passed to the line option that defines the maximal initialization
/// chain length.
extern unsigned MaxInitializationChainLength;
Expand Down Expand Up @@ -1222,21 +1259,16 @@ struct InformationCache {
/// Return the map conaining all the knowledge we have from `llvm.assume`s.
const RetainedKnowledgeMap &getKnowledgeMap() const { return KnowledgeMap; }

/// Return if \p To is potentially reachable form \p From or not
/// If the same query was answered, return cached result
bool getPotentiallyReachable(const Instruction &From, const Instruction &To) {
auto KeyPair = std::make_pair(&From, &To);
auto Iter = PotentiallyReachableMap.find(KeyPair);
if (Iter != PotentiallyReachableMap.end())
return Iter->second;
const Function &F = *From.getFunction();
bool Result = true;
if (From.getFunction() == To.getFunction())
Result = isPotentiallyReachable(&From, &To, nullptr,
AG.getAnalysis<DominatorTreeAnalysis>(F),
AG.getAnalysis<LoopAnalysis>(F));
PotentiallyReachableMap.insert(std::make_pair(KeyPair, Result));
return Result;
/// Given \p BES, return a uniqued version. \p BES is destroyed in the
/// process.
const AA::InstExclusionSetTy *
getOrCreateUniqueBlockExecutionSet(const AA::InstExclusionSetTy *BES) {
auto It = BESets.find(BES);
if (It != BESets.end())
return *It;
auto *UniqueBES = new (Allocator) AA::InstExclusionSetTy(*BES);
BESets.insert(UniqueBES);
return UniqueBES;
}

/// Check whether \p F is part of module slice.
Expand Down Expand Up @@ -1305,16 +1337,15 @@ struct InformationCache {
/// A container for all instructions that are only used by `llvm.assume`.
SetVector<const Instruction *> AssumeOnlyValues;

/// Cache for block sets to allow reuse.
DenseSet<const AA::InstExclusionSetTy *> BESets;

/// Getters for analysis.
AnalysisGetter &AG;

/// Set of inlineable functions
SmallPtrSet<const Function *, 8> InlineableFunctions;

/// A map for caching results of queries for isPotentiallyReachable
DenseMap<std::pair<const Instruction *, const Instruction *>, bool>
PotentiallyReachableMap;

/// The triple describing the target machine.
Triple TargetTriple;

Expand Down Expand Up @@ -3392,42 +3423,30 @@ struct AAUndefinedBehavior
};

/// An abstract interface to determine reachability of point A to B.
struct AAReachability : public StateWrapper<BooleanState, AbstractAttribute> {
struct AAIntraFnReachability
: public StateWrapper<BooleanState, AbstractAttribute> {
using Base = StateWrapper<BooleanState, AbstractAttribute>;
AAReachability(const IRPosition &IRP, Attributor &A) : Base(IRP) {}
AAIntraFnReachability(const IRPosition &IRP, Attributor &A) : Base(IRP) {}

/// Returns true if 'From' instruction is assumed to reach, 'To' instruction.
/// Users should provide two positions they are interested in, and the class
/// determines (and caches) reachability.
bool isAssumedReachable(Attributor &A, const Instruction &From,
const Instruction &To) const {
if (!getState().isValidState())
return true;
return A.getInfoCache().getPotentiallyReachable(From, To);
}

/// Returns true if 'From' instruction is known to reach, 'To' instruction.
/// Users should provide two positions they are interested in, and the class
/// determines (and caches) reachability.
bool isKnownReachable(Attributor &A, const Instruction &From,
const Instruction &To) const {
if (!getState().isValidState())
return false;
return A.getInfoCache().getPotentiallyReachable(From, To);
}
virtual bool isAssumedReachable(
Attributor &A, const Instruction &From, const Instruction &To,
const AA::InstExclusionSetTy *ExclusionSet = nullptr) const = 0;

/// Create an abstract attribute view for the position \p IRP.
static AAReachability &createForPosition(const IRPosition &IRP,
Attributor &A);
static AAIntraFnReachability &createForPosition(const IRPosition &IRP,
Attributor &A);

/// See AbstractAttribute::getName()
const std::string getName() const override { return "AAReachability"; }
const std::string getName() const override { return "AAIntraFnReachability"; }

/// See AbstractAttribute::getIdAddr()
const char *getIdAddr() const override { return &ID; }

/// This function should return true if the type of the \p AA is
/// AAReachability
/// AAIntraFnReachability
static bool classof(const AbstractAttribute *AA) {
return (AA->getIdAddr() == &ID);
}
Expand Down Expand Up @@ -4948,35 +4967,33 @@ struct AAExecutionDomain
};

/// An abstract Attribute for computing reachability between functions.
struct AAFunctionReachability
struct AAInterFnReachability
: public StateWrapper<BooleanState, AbstractAttribute> {
using Base = StateWrapper<BooleanState, AbstractAttribute>;

AAFunctionReachability(const IRPosition &IRP, Attributor &A) : Base(IRP) {}

/// See AbstractAttribute::isQueryAA.
bool isQueryAA() const override { return true; }
AAInterFnReachability(const IRPosition &IRP, Attributor &A) : Base(IRP) {}

/// If the function represented by this possition can reach \p Fn.
virtual bool canReach(Attributor &A, const Function &Fn) const = 0;

/// Can \p CB reach \p Fn.
virtual bool canReach(Attributor &A, CallBase &CB,
const Function &Fn) const = 0;
bool canReach(Attributor &A, const Function &Fn) const {
Function *Scope = getAnchorScope();
if (!Scope || Scope->isDeclaration())
return true;
return instructionCanReach(A, Scope->getEntryBlock().front(), Fn);
}

/// Can \p Inst reach \p Fn.
/// See also AA::isPotentiallyReachable.
virtual bool instructionCanReach(Attributor &A, const Instruction &Inst,
const Function &Fn) const = 0;
virtual bool instructionCanReach(
Attributor &A, const Instruction &Inst, const Function &Fn,
const AA::InstExclusionSetTy *ExclusionSet = nullptr,
SmallPtrSet<const Function *, 16> *Visited = nullptr) const = 0;

/// Create an abstract attribute view for the position \p IRP.
static AAFunctionReachability &createForPosition(const IRPosition &IRP,
Attributor &A);
static AAInterFnReachability &createForPosition(const IRPosition &IRP,
Attributor &A);

/// See AbstractAttribute::getName()
const std::string getName() const override {
return "AAFunctionReachability";
}
const std::string getName() const override { return "AAInterFnReachability"; }

/// See AbstractAttribute::getIdAddr()
const char *getIdAddr() const override { return &ID; }
Expand All @@ -4988,10 +5005,6 @@ struct AAFunctionReachability

/// Unique ID (due to the unique address)
static const char ID;

private:
/// Can this function reach a call with unknown calee.
virtual bool canReachUnknownCallee() const = 0;
};

/// An abstract interface for struct information.
Expand Down
100 changes: 75 additions & 25 deletions llvm/lib/Transforms/IPO/Attributor.cpp
Expand Up @@ -570,19 +570,27 @@ static bool
isPotentiallyReachable(Attributor &A, const Instruction &FromI,
const Instruction *ToI, const Function &ToFn,
const AbstractAttribute &QueryingAA,
const AA::InstExclusionSetTy *ExclusionSet,
std::function<bool(const Function &F)> GoBackwardsCB) {
LLVM_DEBUG(dbgs() << "[AA] isPotentiallyReachable @" << ToFn.getName()
<< " from " << FromI << " [GBCB: " << bool(GoBackwardsCB)
<< "]\n");

// TODO: If we can go arbitrarily backwards we will eventually reach an
// entry point that can reach ToI. Only once this takes a set of blocks
// through which we cannot go, or once we track internal functions not
// accessible from the outside, it makes sense to perform backwards analysis
// in the absence of a GoBackwardsCB.
if (!GoBackwardsCB) {
LLVM_DEBUG({
dbgs() << "[AA] isPotentiallyReachable @" << ToFn.getName() << " from "
<< FromI << " [GBCB: " << bool(GoBackwardsCB) << "][#ExS: "
<< (ExclusionSet ? std::to_string(ExclusionSet->size()) : "none")
<< "]\n";
if (ExclusionSet)
for (auto *ES : *ExclusionSet)
dbgs() << *ES << "\n";
});

// If we can go arbitrarily backwards we will eventually reach an entry point
// that can reach ToI. Only if a set of blocks through which we cannot go is
// provided, or once we track internal functions not accessible from the
// outside, it makes sense to perform backwards analysis in the absence of a
// GoBackwardsCB.
if (!GoBackwardsCB && !ExclusionSet) {
LLVM_DEBUG(dbgs() << "[AA] check @" << ToFn.getName() << " from " << FromI
<< " is not checked backwards, abort\n");
<< " is not checked backwards and does not have an "
"exclusion set, abort\n");
return true;
}

Expand All @@ -601,25 +609,68 @@ isPotentiallyReachable(Attributor &A, const Instruction &FromI,
return true;
LLVM_DEBUG(dbgs() << "[AA] check " << *ToI << " from " << *CurFromI
<< " intraprocedurally\n");
const auto &ReachabilityAA = A.getAAFor<AAReachability>(
const auto &ReachabilityAA = A.getAAFor<AAIntraFnReachability>(
QueryingAA, IRPosition::function(ToFn), DepClassTy::OPTIONAL);
bool Result = ReachabilityAA.isAssumedReachable(A, *CurFromI, *ToI);
bool Result =
ReachabilityAA.isAssumedReachable(A, *CurFromI, *ToI, ExclusionSet);
LLVM_DEBUG(dbgs() << "[AA] " << *CurFromI << " "
<< (Result ? "can potentially " : "cannot ") << "reach "
<< *ToI << " [Intra]\n");
if (Result)
return true;
}

// Check if the current instruction is already known to reach the ToFn.
const auto &FnReachabilityAA = A.getAAFor<AAFunctionReachability>(
bool Result = true;
if (!ToFn.isDeclaration() && ToI) {
const auto &ToReachabilityAA = A.getAAFor<AAIntraFnReachability>(
QueryingAA, IRPosition::function(ToFn), DepClassTy::OPTIONAL);
const Instruction &EntryI = ToFn.getEntryBlock().front();
Result =
ToReachabilityAA.isAssumedReachable(A, EntryI, *ToI, ExclusionSet);
LLVM_DEBUG(dbgs() << "[AA] Entry " << EntryI << " of @" << ToFn.getName()
<< " " << (Result ? "can potentially " : "cannot ")
<< "reach @" << *ToI << " [ToFn]\n");
}

if (Result) {
// The entry of the ToFn can reach the instruction ToI. If the current
// instruction is already known to reach the ToFn.
const auto &FnReachabilityAA = A.getAAFor<AAInterFnReachability>(
QueryingAA, IRPosition::function(*FromFn), DepClassTy::OPTIONAL);
Result = FnReachabilityAA.instructionCanReach(A, *CurFromI, ToFn,
ExclusionSet);
LLVM_DEBUG(dbgs() << "[AA] " << *CurFromI << " in @" << FromFn->getName()
<< " " << (Result ? "can potentially " : "cannot ")
<< "reach @" << ToFn.getName() << " [FromFn]\n");
if (Result)
return true;
}

// TODO: Check assumed nounwind.
const auto &ReachabilityAA = A.getAAFor<AAIntraFnReachability>(
QueryingAA, IRPosition::function(*FromFn), DepClassTy::OPTIONAL);
bool Result = FnReachabilityAA.instructionCanReach(A, *CurFromI, ToFn);
LLVM_DEBUG(dbgs() << "[AA] " << *CurFromI << " in @" << FromFn->getName()
<< " " << (Result ? "can potentially " : "cannot ")
<< "reach @" << ToFn.getName() << " [FromFn]\n");
if (Result)
auto ReturnInstCB = [&](Instruction &Ret) {
bool Result =
ReachabilityAA.isAssumedReachable(A, *CurFromI, Ret, ExclusionSet);
LLVM_DEBUG(dbgs() << "[AA][Ret] " << *CurFromI << " "
<< (Result ? "can potentially " : "cannot ") << "reach "
<< Ret << " [Intra]\n");
return !Result;
};

// Check if we can reach returns.
bool UsedAssumedInformation = false;
if (A.checkForAllInstructions(ReturnInstCB, FromFn, QueryingAA,
{Instruction::Ret}, UsedAssumedInformation)) {
LLVM_DEBUG(dbgs() << "[AA] No return is reachable, done\n");
return false;
}

if (!GoBackwardsCB) {
LLVM_DEBUG(dbgs() << "[AA] check @" << ToFn.getName() << " from " << FromI
<< " is not checked backwards, abort\n");
return true;
}

// If we do not go backwards from the FromFn we are done here and so far we
// could not find a way to reach ToFn/ToI.
Expand All @@ -642,7 +693,6 @@ isPotentiallyReachable(Attributor &A, const Instruction &FromI,
return true;
};

bool UsedAssumedInformation = false;
Result = !A.checkForAllCallSites(CheckCallSite, *FromFn,
/* RequireAllCallSites */ true,
&QueryingAA, UsedAssumedInformation);
Expand All @@ -663,20 +713,20 @@ isPotentiallyReachable(Attributor &A, const Instruction &FromI,
bool AA::isPotentiallyReachable(
Attributor &A, const Instruction &FromI, const Instruction &ToI,
const AbstractAttribute &QueryingAA,
const AA::InstExclusionSetTy *ExclusionSet,
std::function<bool(const Function &F)> GoBackwardsCB) {
LLVM_DEBUG(dbgs() << "[AA] isPotentiallyReachable " << ToI << " from "
<< FromI << " [GBCB: " << bool(GoBackwardsCB) << "]\n");
const Function *ToFn = ToI.getFunction();
return ::isPotentiallyReachable(A, FromI, &ToI, *ToFn, QueryingAA,
GoBackwardsCB);
ExclusionSet, GoBackwardsCB);
}

bool AA::isPotentiallyReachable(
Attributor &A, const Instruction &FromI, const Function &ToFn,
const AbstractAttribute &QueryingAA,
const AA::InstExclusionSetTy *ExclusionSet,
std::function<bool(const Function &F)> GoBackwardsCB) {
return ::isPotentiallyReachable(A, FromI, /* ToI */ nullptr, ToFn, QueryingAA,
GoBackwardsCB);
ExclusionSet, GoBackwardsCB);
}

/// Return true if \p New is equal or worse than \p Old.
Expand Down

0 comments on commit fc21f2d

Please sign in to comment.