212 changes: 141 additions & 71 deletions llvm/lib/Transforms/IPO/AttributorAttributes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3428,22 +3428,18 @@ template <typename ToTy> struct ReachabilityQueryInfo {
/// Constructor replacement to ensure unique and stable sets are used for the
/// cache.
ReachabilityQueryInfo(Attributor &A, const Instruction &From, const ToTy &To,
const AA::InstExclusionSetTy *ES)
const AA::InstExclusionSetTy *ES, bool MakeUnique)
: From(&From), To(&To), ExclusionSet(ES) {

if (ExclusionSet && !ExclusionSet->empty()) {
ExclusionSet =
A.getInfoCache().getOrCreateUniqueBlockExecutionSet(ExclusionSet);
} else {
if (!ES || ES->empty()) {
ExclusionSet = nullptr;
} else if (MakeUnique) {
ExclusionSet = A.getInfoCache().getOrCreateUniqueBlockExecutionSet(ES);
}
}

ReachabilityQueryInfo(const ReachabilityQueryInfo &RQI)
: From(RQI.From), To(RQI.To), ExclusionSet(RQI.ExclusionSet) {
assert(RQI.Result == Reachable::No &&
"Didn't expect to copy an explored RQI!");
}
: From(RQI.From), To(RQI.To), ExclusionSet(RQI.ExclusionSet) {}
};

namespace llvm {
Expand Down Expand Up @@ -3506,7 +3502,8 @@ struct CachedReachabilityAA : public BaseTy {
ChangeStatus updateImpl(Attributor &A) override {
ChangeStatus Changed = ChangeStatus::UNCHANGED;
InUpdate = true;
for (RQITy *RQI : QueryVector) {
for (unsigned u = 0, e = QueryVector.size(); u < e; ++u) {
RQITy *RQI = QueryVector[u];
if (RQI->Result == RQITy::Reachable::No && isReachableImpl(A, *RQI))
Changed = ChangeStatus::CHANGED;
}
Expand All @@ -3517,39 +3514,77 @@ struct CachedReachabilityAA : public BaseTy {
virtual bool isReachableImpl(Attributor &A, RQITy &RQI) = 0;

bool rememberResult(Attributor &A, typename RQITy::Reachable Result,
RQITy &RQI) {
if (Result == RQITy::Reachable::No) {
if (!InUpdate)
A.registerForUpdate(*this);
return false;
}
assert(RQI.Result == RQITy::Reachable::No && "Already reachable?");
RQITy &RQI, bool UsedExclusionSet) {
RQI.Result = Result;
return true;

// Remove the temporary RQI from the cache.
if (!InUpdate)
QueryCache.erase(&RQI);

// Insert a plain RQI (w/o exclusion set) if that makes sense.
if (RQI.ExclusionSet &&
(!UsedExclusionSet || Result == RQITy::Reachable::Yes)) {
RQITy PlainRQI(RQI.From, RQI.To);
if (!QueryCache.count(&PlainRQI)) {
RQITy *RQIPtr = new (A.Allocator) RQITy(RQI.From, RQI.To);
RQIPtr->Result = Result;
QueryVector.push_back(RQIPtr);
QueryCache.insert(RQIPtr);
}
}

// Check if we need to insert a new permanent RQI.
if (!InUpdate && (UsedExclusionSet ||
(Result == RQITy::Reachable::Yes && RQI.ExclusionSet))) {
assert((!RQI.ExclusionSet || !RQI.ExclusionSet->empty()) &&
"Did not expect empty set!");
RQITy *RQIPtr = new (A.Allocator)
RQITy(A, *RQI.From, *RQI.To, RQI.ExclusionSet, true);
assert(RQIPtr->Result == RQITy::Reachable::No && "Already reachable?");
RQIPtr->Result = Result;
assert(!QueryCache.count(RQIPtr));
QueryVector.push_back(RQIPtr);
QueryCache.insert(RQIPtr);
}

if (Result == RQITy::Reachable::No && !InUpdate)
A.registerForUpdate(*this);
return Result == RQITy::Reachable::Yes;
}

const std::string getAsStr() const override {
// TODO: Return the number of reachable queries.
return "#queries(" + std::to_string(QueryVector.size()) + ")";
}

RQITy *checkQueryCache(Attributor &A, RQITy &StackRQI,
typename RQITy::Reachable &Result) {
bool checkQueryCache(Attributor &A, RQITy &StackRQI,
typename RQITy::Reachable &Result) {
if (!this->getState().isValidState()) {
Result = RQITy::Reachable::Yes;
return nullptr;
return true;
}

// If we have an exclusion set we might be able to find our answer by
// ignoring it first.
if (StackRQI.ExclusionSet) {
RQITy PlainRQI(StackRQI.From, StackRQI.To);
auto It = QueryCache.find(&PlainRQI);
if (It != QueryCache.end() && (*It)->Result == RQITy::Reachable::No) {
Result = RQITy::Reachable::No;
return true;
}
}

auto It = QueryCache.find(&StackRQI);
if (It != QueryCache.end()) {
Result = (*It)->Result;
return nullptr;
return true;
}

RQITy *RQIPtr = new (A.Allocator) RQITy(StackRQI);
QueryVector.push_back(RQIPtr);
QueryCache.insert(RQIPtr);
return RQIPtr;
// Insert a temporary for recursive queries. We will replace it with a
// permanent entry later.
QueryCache.insert(&StackRQI);
return false;
}

private:
Expand All @@ -3560,8 +3595,9 @@ struct CachedReachabilityAA : public BaseTy {

struct AAIntraFnReachabilityFunction final
: public CachedReachabilityAA<AAIntraFnReachability, Instruction> {
using Base = CachedReachabilityAA<AAIntraFnReachability, Instruction>;
AAIntraFnReachabilityFunction(const IRPosition &IRP, Attributor &A)
: CachedReachabilityAA<AAIntraFnReachability, Instruction>(IRP, A) {}
: Base(IRP, A) {}

bool isAssumedReachable(
Attributor &A, const Instruction &From, const Instruction &To,
Expand All @@ -3570,23 +3606,39 @@ struct AAIntraFnReachabilityFunction final
if (&From == &To)
return true;

RQITy StackRQI(A, From, To, ExclusionSet);
RQITy StackRQI(A, From, To, ExclusionSet, false);
typename RQITy::Reachable Result;
if (RQITy *RQIPtr = NonConstThis->checkQueryCache(A, StackRQI, Result)) {
return NonConstThis->isReachableImpl(A, *RQIPtr);
}
if (!NonConstThis->checkQueryCache(A, StackRQI, Result))
return NonConstThis->isReachableImpl(A, StackRQI);
return Result == RQITy::Reachable::Yes;
}

ChangeStatus updateImpl(Attributor &A) override {
// We only depend on liveness. DeadEdges is all we care about, check if any
// of them changed.
auto &LivenessAA =
A.getAAFor<AAIsDead>(*this, getIRPosition(), DepClassTy::OPTIONAL);
if (llvm::all_of(DeadEdges, [&](const auto &DeadEdge) {
return LivenessAA.isEdgeDead(DeadEdge.first, DeadEdge.second);
})) {
return ChangeStatus::UNCHANGED;
}
DeadEdges.clear();
return Base::updateImpl(A);
}

bool isReachableImpl(Attributor &A, RQITy &RQI) override {
const Instruction *Origin = RQI.From;
bool UsedExclusionSet = false;

auto WillReachInBlock = [=](const Instruction &From, const Instruction &To,
auto WillReachInBlock = [&](const Instruction &From, const Instruction &To,
const AA::InstExclusionSetTy *ExclusionSet) {
const Instruction *IP = &From;
while (IP && IP != &To) {
if (ExclusionSet && IP != Origin && ExclusionSet->count(IP))
if (ExclusionSet && IP != Origin && ExclusionSet->count(IP)) {
UsedExclusionSet = true;
break;
}
IP = IP->getNextNode();
}
return IP == &To;
Expand All @@ -3601,7 +3653,12 @@ struct AAIntraFnReachabilityFunction final
// possible.
if (FromBB == ToBB &&
WillReachInBlock(*RQI.From, *RQI.To, RQI.ExclusionSet))
return rememberResult(A, RQITy::Reachable::Yes, RQI);
return rememberResult(A, RQITy::Reachable::Yes, RQI, UsedExclusionSet);

// Check if reaching the ToBB block is sufficient or if even that would not
// ensure reaching the target. In the latter case we are done.
if (!WillReachInBlock(ToBB->front(), *RQI.To, RQI.ExclusionSet))
return rememberResult(A, RQITy::Reachable::No, RQI, UsedExclusionSet);

SmallPtrSet<const BasicBlock *, 16> ExclusionBlocks;
if (RQI.ExclusionSet)
Expand All @@ -3612,35 +3669,47 @@ struct AAIntraFnReachabilityFunction final
if (ExclusionBlocks.count(FromBB) &&
!WillReachInBlock(*RQI.From, *FromBB->getTerminator(),
RQI.ExclusionSet))
return rememberResult(A, RQITy::Reachable::No, RQI);
return rememberResult(A, RQITy::Reachable::No, RQI, UsedExclusionSet);

SmallPtrSet<const BasicBlock *, 16> Visited;
SmallVector<const BasicBlock *, 16> Worklist;
Worklist.push_back(FromBB);

DenseSet<std::pair<const BasicBlock *, const BasicBlock *>> LocalDeadEdges;
auto &LivenessAA =
A.getAAFor<AAIsDead>(*this, getIRPosition(), DepClassTy::OPTIONAL);
while (!Worklist.empty()) {
const BasicBlock *BB = Worklist.pop_back_val();
if (!Visited.insert(BB).second)
continue;
for (const BasicBlock *SuccBB : successors(BB)) {
if (LivenessAA.isEdgeDead(BB, SuccBB))
if (LivenessAA.isEdgeDead(BB, SuccBB)) {
LocalDeadEdges.insert({BB, SuccBB});
continue;
if (SuccBB == ToBB &&
WillReachInBlock(SuccBB->front(), *RQI.To, RQI.ExclusionSet))
return rememberResult(A, RQITy::Reachable::Yes, RQI);
if (ExclusionBlocks.count(SuccBB))
}
// We checked before if we just need to reach the ToBB block.
if (SuccBB == ToBB)
return rememberResult(A, RQITy::Reachable::Yes, RQI,
UsedExclusionSet);
if (ExclusionBlocks.count(SuccBB)) {
UsedExclusionSet = true;
continue;
}
Worklist.push_back(SuccBB);
}
}

return rememberResult(A, RQITy::Reachable::No, RQI);
DeadEdges.insert(LocalDeadEdges.begin(), LocalDeadEdges.end());
return rememberResult(A, RQITy::Reachable::No, RQI, UsedExclusionSet);
}

/// See AbstractAttribute::trackStatistics()
void trackStatistics() const override {}

private:
// Set of assumed dead edges we used in the last query. If any changes we
// update the state.
DenseSet<std::pair<const BasicBlock *, const BasicBlock *>> DeadEdges;
};
} // namespace

Expand Down Expand Up @@ -10303,10 +10372,10 @@ struct AAInterFnReachabilityFunction
assert(From.getFunction() == getAnchorScope() && "Queried the wrong AA!");
auto *NonConstThis = const_cast<AAInterFnReachabilityFunction *>(this);

RQITy StackRQI(A, From, To, ExclusionSet);
RQITy StackRQI(A, From, To, ExclusionSet, false);
typename RQITy::Reachable Result;
if (RQITy *RQIPtr = NonConstThis->checkQueryCache(A, StackRQI, Result))
return NonConstThis->isReachableImpl(A, *RQIPtr);
if (!NonConstThis->checkQueryCache(A, StackRQI, Result))
return NonConstThis->isReachableImpl(A, StackRQI);
return Result == RQITy::Reachable::Yes;
}

Expand All @@ -10321,44 +10390,25 @@ struct AAInterFnReachabilityFunction
if (!Visited)
Visited = &LocalVisited;

const auto &IntraFnReachability = A.getAAFor<AAIntraFnReachability>(
*this, IRPosition::function(*RQI.From->getFunction()),
DepClassTy::OPTIONAL);

// Determine call like instructions that we can reach from the inst.
SmallVector<CallBase *> ReachableCallBases;
auto CheckCallBase = [&](Instruction &CBInst) {
if (IntraFnReachability.isAssumedReachable(A, *RQI.From, CBInst,
RQI.ExclusionSet))
ReachableCallBases.push_back(cast<CallBase>(&CBInst));
return true;
};

bool UsedAssumedInformation = false;
if (!A.checkForAllCallLikeInstructions(CheckCallBase, *this,
UsedAssumedInformation,
/* CheckBBLivenessOnly */ true))
return rememberResult(A, RQITy::Reachable::Yes, RQI);

for (CallBase *CB : ReachableCallBases) {
auto CheckReachableCallBase = [&](CallBase *CB) {
auto &CBEdges = A.getAAFor<AACallEdges>(
*this, IRPosition::callsite_function(*CB), DepClassTy::OPTIONAL);
if (!CBEdges.getState().isValidState())
return rememberResult(A, RQITy::Reachable::Yes, RQI);
return false;
// TODO Check To backwards in this case.
if (CBEdges.hasUnknownCallee())
return rememberResult(A, RQITy::Reachable::Yes, RQI);
return false;

for (Function *Fn : CBEdges.getOptimisticEdges()) {
if (Fn == RQI.To)
return rememberResult(A, RQITy::Reachable::Yes, RQI);
return false;
if (!Visited->insert(Fn).second)
continue;
if (Fn->isDeclaration()) {
if (Fn->hasFnAttribute(Attribute::NoCallback))
continue;
// TODO Check To backwards in this case.
return rememberResult(A, RQITy::Reachable::Yes, RQI);
return false;
}

const AAInterFnReachability *InterFnReachability = this;
Expand All @@ -10369,11 +10419,31 @@ struct AAInterFnReachabilityFunction
const Instruction &FnFirstInst = Fn->getEntryBlock().front();
if (InterFnReachability->instructionCanReach(A, FnFirstInst, *RQI.To,
RQI.ExclusionSet, Visited))
return rememberResult(A, RQITy::Reachable::Yes, RQI);
return false;
}
}
return true;
};

const auto &IntraFnReachability = A.getAAFor<AAIntraFnReachability>(
*this, IRPosition::function(*RQI.From->getFunction()),
DepClassTy::OPTIONAL);

// Determine call like instructions that we can reach from the inst.
auto CheckCallBase = [&](Instruction &CBInst) {
if (!IntraFnReachability.isAssumedReachable(A, *RQI.From, CBInst,
RQI.ExclusionSet))
return true;
return CheckReachableCallBase(cast<CallBase>(&CBInst));
};

bool UsedExclusionSet = /* conservative */ true;
bool UsedAssumedInformation = false;
if (!A.checkForAllCallLikeInstructions(CheckCallBase, *this,
UsedAssumedInformation,
/* CheckBBLivenessOnly */ true))
return rememberResult(A, RQITy::Reachable::Yes, RQI, UsedExclusionSet);

return rememberResult(A, RQITy::Reachable::No, RQI);
return rememberResult(A, RQITy::Reachable::No, RQI, UsedExclusionSet);
}

void trackStatistics() const override {}
Expand Down
41 changes: 4 additions & 37 deletions llvm/test/Transforms/Attributor/depgraph.ll

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion llvm/test/Transforms/Attributor/heap_to_stack.ll
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function-signature --check-attributes --check-globals
; RUN: opt -opaque-pointers=0 -aa-pipeline=basic-aa -passes=attributor -attributor-manifest-internal -attributor-max-iterations-verify -attributor-annotate-decl-cs -attributor-max-iterations=8 -S < %s | FileCheck %s --check-prefixes=CHECK,TUNIT
; RUN: opt -opaque-pointers=0 -aa-pipeline=basic-aa -passes=attributor -attributor-manifest-internal -attributor-max-iterations-verify -attributor-annotate-decl-cs -attributor-max-iterations=9 -S < %s | FileCheck %s --check-prefixes=CHECK,TUNIT
; RUN: opt -opaque-pointers=0 -aa-pipeline=basic-aa -passes=attributor-cgscc -attributor-manifest-internal -attributor-annotate-decl-cs -S < %s | FileCheck %s --check-prefixes=CHECK,CGSCC

declare noalias i8* @malloc(i64) allockind("alloc,uninitialized") allocsize(0)
Expand Down
2 changes: 1 addition & 1 deletion llvm/test/Transforms/Attributor/noalias.ll
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function-signature --check-attributes --check-globals
; RUN: opt -aa-pipeline=basic-aa -passes=attributor -attributor-manifest-internal -attributor-max-iterations-verify -attributor-annotate-decl-cs -attributor-max-iterations=2 -S < %s | FileCheck %s --check-prefixes=CHECK,TUNIT
; 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,TUNIT
; RUN: opt -aa-pipeline=basic-aa -passes=attributor-cgscc -attributor-manifest-internal -attributor-annotate-decl-cs -S < %s | FileCheck %s --check-prefixes=CHECK,CGSCC

; TEST 1 - negative.
Expand Down
2 changes: 1 addition & 1 deletion llvm/test/Transforms/Attributor/nonnull.ll
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function-signature --check-attributes --check-globals
; RUN: opt -aa-pipeline=basic-aa -passes=attributor -attributor-manifest-internal -attributor-max-iterations-verify -attributor-annotate-decl-cs -attributor-max-iterations=19 -S < %s | FileCheck %s --check-prefixes=CHECK,TUNIT
; RUN: opt -aa-pipeline=basic-aa -passes=attributor -attributor-manifest-internal -attributor-max-iterations-verify -attributor-annotate-decl-cs -attributor-max-iterations=18 -S < %s | FileCheck %s --check-prefixes=CHECK,TUNIT
; RUN: opt -aa-pipeline=basic-aa -passes=attributor-cgscc -attributor-manifest-internal -attributor-annotate-decl-cs -S < %s | FileCheck %s --check-prefixes=CHECK,CGSCC


Expand Down
2 changes: 1 addition & 1 deletion llvm/test/Transforms/Attributor/openmp_parallel.ll
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function-signature --check-attributes --check-globals
; RUN: opt -aa-pipeline=basic-aa -passes=attributor -attributor-manifest-internal -attributor-max-iterations-verify -attributor-annotate-decl-cs -attributor-max-iterations=13 -S < %s | FileCheck %s --check-prefixes=CHECK,TUNIT
; RUN: opt -aa-pipeline=basic-aa -passes=attributor -attributor-manifest-internal -attributor-max-iterations-verify -attributor-annotate-decl-cs -attributor-max-iterations=12 -S < %s | FileCheck %s --check-prefixes=CHECK,TUNIT
; RUN: opt -aa-pipeline=basic-aa -passes=attributor-cgscc -attributor-manifest-internal -attributor-annotate-decl-cs -S < %s | FileCheck %s --check-prefixes=CHECK,CGSCC

target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
Expand Down
2 changes: 1 addition & 1 deletion llvm/test/Transforms/Attributor/value-simplify-gpu.ll
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function-signature --check-attributes --check-globals
; RUN: opt -aa-pipeline=basic-aa -passes=attributor -attributor-manifest-internal -attributor-max-iterations-verify -attributor-annotate-decl-cs -attributor-max-iterations=9 -S < %s | FileCheck %s --check-prefixes=CHECK,TUNIT
; RUN: opt -aa-pipeline=basic-aa -passes=attributor -attributor-manifest-internal -attributor-max-iterations-verify -attributor-annotate-decl-cs -attributor-max-iterations=8 -S < %s | FileCheck %s --check-prefixes=CHECK,TUNIT
; RUN: opt -aa-pipeline=basic-aa -passes=attributor-cgscc -attributor-manifest-internal -attributor-annotate-decl-cs -S < %s | FileCheck %s --check-prefixes=CHECK,CGSCC

target triple = "amdgcn-amd-amdhsa"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function-signature --check-attributes --check-globals
; RUN: opt -aa-pipeline=basic-aa -passes=attributor -attributor-manifest-internal -attributor-max-iterations-verify -attributor-annotate-decl-cs -attributor-max-iterations=17 -S < %s | FileCheck %s --check-prefixes=CHECK,TUNIT
; RUN: opt -aa-pipeline=basic-aa -passes=attributor -attributor-manifest-internal -attributor-max-iterations-verify -attributor-annotate-decl-cs -attributor-max-iterations=16 -S < %s | FileCheck %s --check-prefixes=CHECK,TUNIT
; RUN: opt -aa-pipeline=basic-aa -passes=attributor-cgscc -attributor-manifest-internal -attributor-annotate-decl-cs -S < %s | FileCheck %s --check-prefixes=CHECK,CGSCC

target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
Expand Down