blob: 41b66aafe7d3432d9fa346ac5c8f236003d6779e [file] [log] [blame]
//===- AttributorAttributes.cpp - Attributes for Attributor deduction -----===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// See the Attributor.h file comment and the class descriptions in that file for
// more information.
//
//===----------------------------------------------------------------------===//
#include "llvm/Transforms/IPO/Attributor.h"
#include "llvm/ADT/APInt.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/DenseMapInfo.h"
#include "llvm/ADT/MapVector.h"
#include "llvm/ADT/SCCIterator.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SetOperations.h"
#include "llvm/ADT/SetVector.h"
#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/Statistic.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/Analysis/AliasAnalysis.h"
#include "llvm/Analysis/AssumeBundleQueries.h"
#include "llvm/Analysis/AssumptionCache.h"
#include "llvm/Analysis/CaptureTracking.h"
#include "llvm/Analysis/CycleAnalysis.h"
#include "llvm/Analysis/InstructionSimplify.h"
#include "llvm/Analysis/LazyValueInfo.h"
#include "llvm/Analysis/MemoryBuiltins.h"
#include "llvm/Analysis/OptimizationRemarkEmitter.h"
#include "llvm/Analysis/ScalarEvolution.h"
#include "llvm/Analysis/TargetTransformInfo.h"
#include "llvm/Analysis/ValueTracking.h"
#include "llvm/IR/Argument.h"
#include "llvm/IR/Assumptions.h"
#include "llvm/IR/Attributes.h"
#include "llvm/IR/BasicBlock.h"
#include "llvm/IR/Constant.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/DataLayout.h"
#include "llvm/IR/DerivedTypes.h"
#include "llvm/IR/GlobalValue.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/InlineAsm.h"
#include "llvm/IR/InstrTypes.h"
#include "llvm/IR/Instruction.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/IntrinsicInst.h"
#include "llvm/IR/IntrinsicsAMDGPU.h"
#include "llvm/IR/IntrinsicsNVPTX.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/MDBuilder.h"
#include "llvm/IR/NoFolder.h"
#include "llvm/IR/Value.h"
#include "llvm/IR/ValueHandle.h"
#include "llvm/Support/Alignment.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/GraphWriter.h"
#include "llvm/Support/MathExtras.h"
#include "llvm/Support/TypeSize.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Transforms/Utils/BasicBlockUtils.h"
#include "llvm/Transforms/Utils/CallPromotionUtils.h"
#include "llvm/Transforms/Utils/Local.h"
#include "llvm/Transforms/Utils/ValueMapper.h"
#include <cassert>
#include <numeric>
#include <optional>
#include <string>
using namespace llvm;
#define DEBUG_TYPE "attributor"
static cl::opt<bool> ManifestInternal(
"attributor-manifest-internal", cl::Hidden,
cl::desc("Manifest Attributor internal string attributes."),
cl::init(false));
static cl::opt<int> MaxHeapToStackSize("max-heap-to-stack-size", cl::init(128),
cl::Hidden);
template <>
unsigned llvm::PotentialConstantIntValuesState::MaxPotentialValues = 0;
template <> unsigned llvm::PotentialLLVMValuesState::MaxPotentialValues = -1;
static cl::opt<unsigned, true> MaxPotentialValues(
"attributor-max-potential-values", cl::Hidden,
cl::desc("Maximum number of potential values to be "
"tracked for each position."),
cl::location(llvm::PotentialConstantIntValuesState::MaxPotentialValues),
cl::init(7));
static cl::opt<int> MaxPotentialValuesIterations(
"attributor-max-potential-values-iterations", cl::Hidden,
cl::desc(
"Maximum number of iterations we keep dismantling potential values."),
cl::init(64));
STATISTIC(NumAAs, "Number of abstract attributes created");
// Some helper macros to deal with statistics tracking.
//
// Usage:
// For simple IR attribute tracking overload trackStatistics in the abstract
// attribute and choose the right STATS_DECLTRACK_********* macro,
// e.g.,:
// void trackStatistics() const override {
// STATS_DECLTRACK_ARG_ATTR(returned)
// }
// If there is a single "increment" side one can use the macro
// STATS_DECLTRACK with a custom message. If there are multiple increment
// sides, STATS_DECL and STATS_TRACK can also be used separately.
//
#define BUILD_STAT_MSG_IR_ATTR(TYPE, NAME) \
("Number of " #TYPE " marked '" #NAME "'")
#define BUILD_STAT_NAME(NAME, TYPE) NumIR##TYPE##_##NAME
#define STATS_DECL_(NAME, MSG) STATISTIC(NAME, MSG);
#define STATS_DECL(NAME, TYPE, MSG) \
STATS_DECL_(BUILD_STAT_NAME(NAME, TYPE), MSG);
#define STATS_TRACK(NAME, TYPE) ++(BUILD_STAT_NAME(NAME, TYPE));
#define STATS_DECLTRACK(NAME, TYPE, MSG) \
{ \
STATS_DECL(NAME, TYPE, MSG) \
STATS_TRACK(NAME, TYPE) \
}
#define STATS_DECLTRACK_ARG_ATTR(NAME) \
STATS_DECLTRACK(NAME, Arguments, BUILD_STAT_MSG_IR_ATTR(arguments, NAME))
#define STATS_DECLTRACK_CSARG_ATTR(NAME) \
STATS_DECLTRACK(NAME, CSArguments, \
BUILD_STAT_MSG_IR_ATTR(call site arguments, NAME))
#define STATS_DECLTRACK_FN_ATTR(NAME) \
STATS_DECLTRACK(NAME, Function, BUILD_STAT_MSG_IR_ATTR(functions, NAME))
#define STATS_DECLTRACK_CS_ATTR(NAME) \
STATS_DECLTRACK(NAME, CS, BUILD_STAT_MSG_IR_ATTR(call site, NAME))
#define STATS_DECLTRACK_FNRET_ATTR(NAME) \
STATS_DECLTRACK(NAME, FunctionReturn, \
BUILD_STAT_MSG_IR_ATTR(function returns, NAME))
#define STATS_DECLTRACK_CSRET_ATTR(NAME) \
STATS_DECLTRACK(NAME, CSReturn, \
BUILD_STAT_MSG_IR_ATTR(call site returns, NAME))
#define STATS_DECLTRACK_FLOATING_ATTR(NAME) \
STATS_DECLTRACK(NAME, Floating, \
("Number of floating values known to be '" #NAME "'"))
// Specialization of the operator<< for abstract attributes subclasses. This
// disambiguates situations where multiple operators are applicable.
namespace llvm {
#define PIPE_OPERATOR(CLASS) \
raw_ostream &operator<<(raw_ostream &OS, const CLASS &AA) { \
return OS << static_cast<const AbstractAttribute &>(AA); \
}
PIPE_OPERATOR(AAIsDead)
PIPE_OPERATOR(AANoUnwind)
PIPE_OPERATOR(AANoSync)
PIPE_OPERATOR(AANoRecurse)
PIPE_OPERATOR(AANonConvergent)
PIPE_OPERATOR(AAWillReturn)
PIPE_OPERATOR(AANoReturn)
PIPE_OPERATOR(AANonNull)
PIPE_OPERATOR(AAMustProgress)
PIPE_OPERATOR(AANoAlias)
PIPE_OPERATOR(AADereferenceable)
PIPE_OPERATOR(AAAlign)
PIPE_OPERATOR(AAInstanceInfo)
PIPE_OPERATOR(AANoCapture)
PIPE_OPERATOR(AAValueSimplify)
PIPE_OPERATOR(AANoFree)
PIPE_OPERATOR(AAHeapToStack)
PIPE_OPERATOR(AAIntraFnReachability)
PIPE_OPERATOR(AAMemoryBehavior)
PIPE_OPERATOR(AAMemoryLocation)
PIPE_OPERATOR(AAValueConstantRange)
PIPE_OPERATOR(AAPrivatizablePtr)
PIPE_OPERATOR(AAUndefinedBehavior)
PIPE_OPERATOR(AAPotentialConstantValues)
PIPE_OPERATOR(AAPotentialValues)
PIPE_OPERATOR(AANoUndef)
PIPE_OPERATOR(AANoFPClass)
PIPE_OPERATOR(AACallEdges)
PIPE_OPERATOR(AAInterFnReachability)
PIPE_OPERATOR(AAPointerInfo)
PIPE_OPERATOR(AAAssumptionInfo)
PIPE_OPERATOR(AAUnderlyingObjects)
PIPE_OPERATOR(AAAddressSpace)
PIPE_OPERATOR(AAAllocationInfo)
PIPE_OPERATOR(AAIndirectCallInfo)
PIPE_OPERATOR(AAGlobalValueInfo)
PIPE_OPERATOR(AADenormalFPMath)
#undef PIPE_OPERATOR
template <>
ChangeStatus clampStateAndIndicateChange<DerefState>(DerefState &S,
const DerefState &R) {
ChangeStatus CS0 =
clampStateAndIndicateChange(S.DerefBytesState, R.DerefBytesState);
ChangeStatus CS1 = clampStateAndIndicateChange(S.GlobalState, R.GlobalState);
return CS0 | CS1;
}
} // namespace llvm
static bool mayBeInCycle(const CycleInfo *CI, const Instruction *I,
bool HeaderOnly, Cycle **CPtr = nullptr) {
if (!CI)
return true;
auto *BB = I->getParent();
auto *C = CI->getCycle(BB);
if (!C)
return false;
if (CPtr)
*CPtr = C;
return !HeaderOnly || BB == C->getHeader();
}
/// Checks if a type could have padding bytes.
static bool isDenselyPacked(Type *Ty, const DataLayout &DL) {
// There is no size information, so be conservative.
if (!Ty->isSized())
return false;
// If the alloc size is not equal to the storage size, then there are padding
// bytes. For x86_fp80 on x86-64, size: 80 alloc size: 128.
if (DL.getTypeSizeInBits(Ty) != DL.getTypeAllocSizeInBits(Ty))
return false;
// FIXME: This isn't the right way to check for padding in vectors with
// non-byte-size elements.
if (VectorType *SeqTy = dyn_cast<VectorType>(Ty))
return isDenselyPacked(SeqTy->getElementType(), DL);
// For array types, check for padding within members.
if (ArrayType *SeqTy = dyn_cast<ArrayType>(Ty))
return isDenselyPacked(SeqTy->getElementType(), DL);
if (!isa<StructType>(Ty))
return true;
// Check for padding within and between elements of a struct.
StructType *StructTy = cast<StructType>(Ty);
const StructLayout *Layout = DL.getStructLayout(StructTy);
uint64_t StartPos = 0;
for (unsigned I = 0, E = StructTy->getNumElements(); I < E; ++I) {
Type *ElTy = StructTy->getElementType(I);
if (!isDenselyPacked(ElTy, DL))
return false;
if (StartPos != Layout->getElementOffsetInBits(I))
return false;
StartPos += DL.getTypeAllocSizeInBits(ElTy);
}
return true;
}
/// Get pointer operand of memory accessing instruction. If \p I is
/// not a memory accessing instruction, return nullptr. If \p AllowVolatile,
/// is set to false and the instruction is volatile, return nullptr.
static const Value *getPointerOperand(const Instruction *I,
bool AllowVolatile) {
if (!AllowVolatile && I->isVolatile())
return nullptr;
if (auto *LI = dyn_cast<LoadInst>(I)) {
return LI->getPointerOperand();
}
if (auto *SI = dyn_cast<StoreInst>(I)) {
return SI->getPointerOperand();
}
if (auto *CXI = dyn_cast<AtomicCmpXchgInst>(I)) {
return CXI->getPointerOperand();
}
if (auto *RMWI = dyn_cast<AtomicRMWInst>(I)) {
return RMWI->getPointerOperand();
}
return nullptr;
}
/// Helper function to create a pointer based on \p Ptr, and advanced by \p
/// Offset bytes.
static Value *constructPointer(Value *Ptr, int64_t Offset,
IRBuilder<NoFolder> &IRB) {
LLVM_DEBUG(dbgs() << "Construct pointer: " << *Ptr << " + " << Offset
<< "-bytes\n");
if (Offset)
Ptr = IRB.CreatePtrAdd(Ptr, IRB.getInt64(Offset),
Ptr->getName() + ".b" + Twine(Offset));
return Ptr;
}
static const Value *
stripAndAccumulateOffsets(Attributor &A, const AbstractAttribute &QueryingAA,
const Value *Val, const DataLayout &DL, APInt &Offset,
bool GetMinOffset, bool AllowNonInbounds,
bool UseAssumed = false) {
auto AttributorAnalysis = [&](Value &V, APInt &ROffset) -> bool {
const IRPosition &Pos = IRPosition::value(V);
// Only track dependence if we are going to use the assumed info.
const AAValueConstantRange *ValueConstantRangeAA =
A.getAAFor<AAValueConstantRange>(QueryingAA, Pos,
UseAssumed ? DepClassTy::OPTIONAL
: DepClassTy::NONE);
if (!ValueConstantRangeAA)
return false;
ConstantRange Range = UseAssumed ? ValueConstantRangeAA->getAssumed()
: ValueConstantRangeAA->getKnown();
if (Range.isFullSet())
return false;
// We can only use the lower part of the range because the upper part can
// be higher than what the value can really be.
if (GetMinOffset)
ROffset = Range.getSignedMin();
else
ROffset = Range.getSignedMax();
return true;
};
return Val->stripAndAccumulateConstantOffsets(DL, Offset, AllowNonInbounds,
/* AllowInvariant */ true,
AttributorAnalysis);
}
static const Value *
getMinimalBaseOfPointer(Attributor &A, const AbstractAttribute &QueryingAA,
const Value *Ptr, int64_t &BytesOffset,
const DataLayout &DL, bool AllowNonInbounds = false) {
APInt OffsetAPInt(DL.getIndexTypeSizeInBits(Ptr->getType()), 0);
const Value *Base =
stripAndAccumulateOffsets(A, QueryingAA, Ptr, DL, OffsetAPInt,
/* GetMinOffset */ true, AllowNonInbounds);
BytesOffset = OffsetAPInt.getSExtValue();
return Base;
}
/// Clamp the information known for all returned values of a function
/// (identified by \p QueryingAA) into \p S.
template <typename AAType, typename StateType = typename AAType::StateType,
Attribute::AttrKind IRAttributeKind = AAType::IRAttributeKind,
bool RecurseForSelectAndPHI = true>
static void clampReturnedValueStates(
Attributor &A, const AAType &QueryingAA, StateType &S,
const IRPosition::CallBaseContext *CBContext = nullptr) {
LLVM_DEBUG(dbgs() << "[Attributor] Clamp return value states for "
<< QueryingAA << " into " << S << "\n");
assert((QueryingAA.getIRPosition().getPositionKind() ==
IRPosition::IRP_RETURNED ||
QueryingAA.getIRPosition().getPositionKind() ==
IRPosition::IRP_CALL_SITE_RETURNED) &&
"Can only clamp returned value states for a function returned or call "
"site returned position!");
// Use an optional state as there might not be any return values and we want
// to join (IntegerState::operator&) the state of all there are.
std::optional<StateType> T;
// Callback for each possibly returned value.
auto CheckReturnValue = [&](Value &RV) -> bool {
const IRPosition &RVPos = IRPosition::value(RV, CBContext);
// If possible, use the hasAssumedIRAttr interface.
if (Attribute::isEnumAttrKind(IRAttributeKind)) {
bool IsKnown;
return AA::hasAssumedIRAttr<IRAttributeKind>(
A, &QueryingAA, RVPos, DepClassTy::REQUIRED, IsKnown);
}
const AAType *AA =
A.getAAFor<AAType>(QueryingAA, RVPos, DepClassTy::REQUIRED);
if (!AA)
return false;
LLVM_DEBUG(dbgs() << "[Attributor] RV: " << RV
<< " AA: " << AA->getAsStr(&A) << " @ " << RVPos << "\n");
const StateType &AAS = AA->getState();
if (!T)
T = StateType::getBestState(AAS);
*T &= AAS;
LLVM_DEBUG(dbgs() << "[Attributor] AA State: " << AAS << " RV State: " << T
<< "\n");
return T->isValidState();
};
if (!A.checkForAllReturnedValues(CheckReturnValue, QueryingAA,
AA::ValueScope::Intraprocedural,
RecurseForSelectAndPHI))
S.indicatePessimisticFixpoint();
else if (T)
S ^= *T;
}
namespace {
/// Helper class for generic deduction: return value -> returned position.
template <typename AAType, typename BaseType,
typename StateType = typename BaseType::StateType,
bool PropagateCallBaseContext = false,
Attribute::AttrKind IRAttributeKind = AAType::IRAttributeKind,
bool RecurseForSelectAndPHI = true>
struct AAReturnedFromReturnedValues : public BaseType {
AAReturnedFromReturnedValues(const IRPosition &IRP, Attributor &A)
: BaseType(IRP, A) {}
/// See AbstractAttribute::updateImpl(...).
ChangeStatus updateImpl(Attributor &A) override {
StateType S(StateType::getBestState(this->getState()));
clampReturnedValueStates<AAType, StateType, IRAttributeKind, RecurseForSelectAndPHI>(
A, *this, S,
PropagateCallBaseContext ? this->getCallBaseContext() : nullptr);
// TODO: If we know we visited all returned values, thus no are assumed
// dead, we can take the known information from the state T.
return clampStateAndIndicateChange<StateType>(this->getState(), S);
}
};
/// Clamp the information known at all call sites for a given argument
/// (identified by \p QueryingAA) into \p S.
template <typename AAType, typename StateType = typename AAType::StateType,
Attribute::AttrKind IRAttributeKind = AAType::IRAttributeKind>
static void clampCallSiteArgumentStates(Attributor &A, const AAType &QueryingAA,
StateType &S) {
LLVM_DEBUG(dbgs() << "[Attributor] Clamp call site argument states for "
<< QueryingAA << " into " << S << "\n");
assert(QueryingAA.getIRPosition().getPositionKind() ==
IRPosition::IRP_ARGUMENT &&
"Can only clamp call site argument states for an argument position!");
// Use an optional state as there might not be any return values and we want
// to join (IntegerState::operator&) the state of all there are.
std::optional<StateType> T;
// The argument number which is also the call site argument number.
unsigned ArgNo = QueryingAA.getIRPosition().getCallSiteArgNo();
auto CallSiteCheck = [&](AbstractCallSite ACS) {
const IRPosition &ACSArgPos = IRPosition::callsite_argument(ACS, ArgNo);
// Check if a coresponding argument was found or if it is on not associated
// (which can happen for callback calls).
if (ACSArgPos.getPositionKind() == IRPosition::IRP_INVALID)
return false;
// If possible, use the hasAssumedIRAttr interface.
if (Attribute::isEnumAttrKind(IRAttributeKind)) {
bool IsKnown;
return AA::hasAssumedIRAttr<IRAttributeKind>(
A, &QueryingAA, ACSArgPos, DepClassTy::REQUIRED, IsKnown);
}
const AAType *AA =
A.getAAFor<AAType>(QueryingAA, ACSArgPos, DepClassTy::REQUIRED);
if (!AA)
return false;
LLVM_DEBUG(dbgs() << "[Attributor] ACS: " << *ACS.getInstruction()
<< " AA: " << AA->getAsStr(&A) << " @" << ACSArgPos
<< "\n");
const StateType &AAS = AA->getState();
if (!T)
T = StateType::getBestState(AAS);
*T &= AAS;
LLVM_DEBUG(dbgs() << "[Attributor] AA State: " << AAS << " CSA State: " << T
<< "\n");
return T->isValidState();
};
bool UsedAssumedInformation = false;
if (!A.checkForAllCallSites(CallSiteCheck, QueryingAA, true,
UsedAssumedInformation))
S.indicatePessimisticFixpoint();
else if (T)
S ^= *T;
}
/// This function is the bridge between argument position and the call base
/// context.
template <typename AAType, typename BaseType,
typename StateType = typename AAType::StateType,
Attribute::AttrKind IRAttributeKind = AAType::IRAttributeKind>
bool getArgumentStateFromCallBaseContext(Attributor &A,
BaseType &QueryingAttribute,
IRPosition &Pos, StateType &State) {
assert((Pos.getPositionKind() == IRPosition::IRP_ARGUMENT) &&
"Expected an 'argument' position !");
const CallBase *CBContext = Pos.getCallBaseContext();
if (!CBContext)
return false;
int ArgNo = Pos.getCallSiteArgNo();
assert(ArgNo >= 0 && "Invalid Arg No!");
const IRPosition CBArgPos = IRPosition::callsite_argument(*CBContext, ArgNo);
// If possible, use the hasAssumedIRAttr interface.
if (Attribute::isEnumAttrKind(IRAttributeKind)) {
bool IsKnown;
return AA::hasAssumedIRAttr<IRAttributeKind>(
A, &QueryingAttribute, CBArgPos, DepClassTy::REQUIRED, IsKnown);
}
const auto *AA =
A.getAAFor<AAType>(QueryingAttribute, CBArgPos, DepClassTy::REQUIRED);
if (!AA)
return false;
const StateType &CBArgumentState =
static_cast<const StateType &>(AA->getState());
LLVM_DEBUG(dbgs() << "[Attributor] Briding Call site context to argument"
<< "Position:" << Pos << "CB Arg state:" << CBArgumentState
<< "\n");
// NOTE: If we want to do call site grouping it should happen here.
State ^= CBArgumentState;
return true;
}
/// Helper class for generic deduction: call site argument -> argument position.
template <typename AAType, typename BaseType,
typename StateType = typename AAType::StateType,
bool BridgeCallBaseContext = false,
Attribute::AttrKind IRAttributeKind = AAType::IRAttributeKind>
struct AAArgumentFromCallSiteArguments : public BaseType {
AAArgumentFromCallSiteArguments(const IRPosition &IRP, Attributor &A)
: BaseType(IRP, A) {}
/// See AbstractAttribute::updateImpl(...).
ChangeStatus updateImpl(Attributor &A) override {
StateType S = StateType::getBestState(this->getState());
if (BridgeCallBaseContext) {
bool Success =
getArgumentStateFromCallBaseContext<AAType, BaseType, StateType,
IRAttributeKind>(
A, *this, this->getIRPosition(), S);
if (Success)
return clampStateAndIndicateChange<StateType>(this->getState(), S);
}
clampCallSiteArgumentStates<AAType, StateType, IRAttributeKind>(A, *this,
S);
// TODO: If we know we visited all incoming values, thus no are assumed
// dead, we can take the known information from the state T.
return clampStateAndIndicateChange<StateType>(this->getState(), S);
}
};
/// Helper class for generic replication: function returned -> cs returned.
template <typename AAType, typename BaseType,
typename StateType = typename BaseType::StateType,
bool IntroduceCallBaseContext = false,
Attribute::AttrKind IRAttributeKind = AAType::IRAttributeKind>
struct AACalleeToCallSite : public BaseType {
AACalleeToCallSite(const IRPosition &IRP, Attributor &A) : BaseType(IRP, A) {}
/// See AbstractAttribute::updateImpl(...).
ChangeStatus updateImpl(Attributor &A) override {
auto IRPKind = this->getIRPosition().getPositionKind();
assert((IRPKind == IRPosition::IRP_CALL_SITE_RETURNED ||
IRPKind == IRPosition::IRP_CALL_SITE) &&
"Can only wrap function returned positions for call site "
"returned positions!");
auto &S = this->getState();
CallBase &CB = cast<CallBase>(this->getAnchorValue());
if (IntroduceCallBaseContext)
LLVM_DEBUG(dbgs() << "[Attributor] Introducing call base context:" << CB
<< "\n");
ChangeStatus Changed = ChangeStatus::UNCHANGED;
auto CalleePred = [&](ArrayRef<const Function *> Callees) {
for (const Function *Callee : Callees) {
IRPosition FnPos =
IRPKind == llvm::IRPosition::IRP_CALL_SITE_RETURNED
? IRPosition::returned(*Callee,
IntroduceCallBaseContext ? &CB : nullptr)
: IRPosition::function(
*Callee, IntroduceCallBaseContext ? &CB : nullptr);
// If possible, use the hasAssumedIRAttr interface.
if (Attribute::isEnumAttrKind(IRAttributeKind)) {
bool IsKnown;
if (!AA::hasAssumedIRAttr<IRAttributeKind>(
A, this, FnPos, DepClassTy::REQUIRED, IsKnown))
return false;
continue;
}
const AAType *AA =
A.getAAFor<AAType>(*this, FnPos, DepClassTy::REQUIRED);
if (!AA)
return false;
Changed |= clampStateAndIndicateChange(S, AA->getState());
if (S.isAtFixpoint())
return S.isValidState();
}
return true;
};
if (!A.checkForAllCallees(CalleePred, *this, CB))
return S.indicatePessimisticFixpoint();
return Changed;
}
};
/// Helper function to accumulate uses.
template <class AAType, typename StateType = typename AAType::StateType>
static void followUsesInContext(AAType &AA, Attributor &A,
MustBeExecutedContextExplorer &Explorer,
const Instruction *CtxI,
SetVector<const Use *> &Uses,
StateType &State) {
auto EIt = Explorer.begin(CtxI), EEnd = Explorer.end(CtxI);
for (unsigned u = 0; u < Uses.size(); ++u) {
const Use *U = Uses[u];
if (const Instruction *UserI = dyn_cast<Instruction>(U->getUser())) {
bool Found = Explorer.findInContextOf(UserI, EIt, EEnd);
if (Found && AA.followUseInMBEC(A, U, UserI, State))
for (const Use &Us : UserI->uses())
Uses.insert(&Us);
}
}
}
/// Use the must-be-executed-context around \p I to add information into \p S.
/// The AAType class is required to have `followUseInMBEC` method with the
/// following signature and behaviour:
///
/// bool followUseInMBEC(Attributor &A, const Use *U, const Instruction *I)
/// U - Underlying use.
/// I - The user of the \p U.
/// Returns true if the value should be tracked transitively.
///
template <class AAType, typename StateType = typename AAType::StateType>
static void followUsesInMBEC(AAType &AA, Attributor &A, StateType &S,
Instruction &CtxI) {
MustBeExecutedContextExplorer *Explorer =
A.getInfoCache().getMustBeExecutedContextExplorer();
if (!Explorer)
return;
// Container for (transitive) uses of the associated value.
SetVector<const Use *> Uses;
for (const Use &U : AA.getIRPosition().getAssociatedValue().uses())
Uses.insert(&U);
followUsesInContext<AAType>(AA, A, *Explorer, &CtxI, Uses, S);
if (S.isAtFixpoint())
return;
SmallVector<const BranchInst *, 4> BrInsts;
auto Pred = [&](const Instruction *I) {
if (const BranchInst *Br = dyn_cast<BranchInst>(I))
if (Br->isConditional())
BrInsts.push_back(Br);
return true;
};
// Here, accumulate conditional branch instructions in the context. We
// explore the child paths and collect the known states. The disjunction of
// those states can be merged to its own state. Let ParentState_i be a state
// to indicate the known information for an i-th branch instruction in the
// context. ChildStates are created for its successors respectively.
//
// ParentS_1 = ChildS_{1, 1} /\ ChildS_{1, 2} /\ ... /\ ChildS_{1, n_1}
// ParentS_2 = ChildS_{2, 1} /\ ChildS_{2, 2} /\ ... /\ ChildS_{2, n_2}
// ...
// ParentS_m = ChildS_{m, 1} /\ ChildS_{m, 2} /\ ... /\ ChildS_{m, n_m}
//
// Known State |= ParentS_1 \/ ParentS_2 \/... \/ ParentS_m
//
// FIXME: Currently, recursive branches are not handled. For example, we
// can't deduce that ptr must be dereferenced in below function.
//
// void f(int a, int c, int *ptr) {
// if(a)
// if (b) {
// *ptr = 0;
// } else {
// *ptr = 1;
// }
// else {
// if (b) {
// *ptr = 0;
// } else {
// *ptr = 1;
// }
// }
// }
Explorer->checkForAllContext(&CtxI, Pred);
for (const BranchInst *Br : BrInsts) {
StateType ParentState;
// The known state of the parent state is a conjunction of children's
// known states so it is initialized with a best state.
ParentState.indicateOptimisticFixpoint();
for (const BasicBlock *BB : Br->successors()) {
StateType ChildState;
size_t BeforeSize = Uses.size();
followUsesInContext(AA, A, *Explorer, &BB->front(), Uses, ChildState);
// Erase uses which only appear in the child.
for (auto It = Uses.begin() + BeforeSize; It != Uses.end();)
It = Uses.erase(It);
ParentState &= ChildState;
}
// Use only known state.
S += ParentState;
}
}
} // namespace
/// ------------------------ PointerInfo ---------------------------------------
namespace llvm {
namespace AA {
namespace PointerInfo {
struct State;
} // namespace PointerInfo
} // namespace AA
/// Helper for AA::PointerInfo::Access DenseMap/Set usage.
template <>
struct DenseMapInfo<AAPointerInfo::Access> : DenseMapInfo<Instruction *> {
using Access = AAPointerInfo::Access;
static inline Access getEmptyKey();
static inline Access getTombstoneKey();
static unsigned getHashValue(const Access &A);
static bool isEqual(const Access &LHS, const Access &RHS);
};
/// Helper that allows RangeTy as a key in a DenseMap.
template <> struct DenseMapInfo<AA::RangeTy> {
static inline AA::RangeTy getEmptyKey() {
auto EmptyKey = DenseMapInfo<int64_t>::getEmptyKey();
return AA::RangeTy{EmptyKey, EmptyKey};
}
static inline AA::RangeTy getTombstoneKey() {
auto TombstoneKey = DenseMapInfo<int64_t>::getTombstoneKey();
return AA::RangeTy{TombstoneKey, TombstoneKey};
}
static unsigned getHashValue(const AA::RangeTy &Range) {
return detail::combineHashValue(
DenseMapInfo<int64_t>::getHashValue(Range.Offset),
DenseMapInfo<int64_t>::getHashValue(Range.Size));
}
static bool isEqual(const AA::RangeTy &A, const AA::RangeTy B) {
return A == B;
}
};
/// Helper for AA::PointerInfo::Access DenseMap/Set usage ignoring everythign
/// but the instruction
struct AccessAsInstructionInfo : DenseMapInfo<Instruction *> {
using Base = DenseMapInfo<Instruction *>;
using Access = AAPointerInfo::Access;
static inline Access getEmptyKey();
static inline Access getTombstoneKey();
static unsigned getHashValue(const Access &A);
static bool isEqual(const Access &LHS, const Access &RHS);
};
} // namespace llvm
/// A type to track pointer/struct usage and accesses for AAPointerInfo.
struct AA::PointerInfo::State : public AbstractState {
/// Return the best possible representable state.
static State getBestState(const State &SIS) { return State(); }
/// Return the worst possible representable state.
static State getWorstState(const State &SIS) {
State R;
R.indicatePessimisticFixpoint();
return R;
}
State() = default;
State(State &&SIS) = default;
const State &getAssumed() const { return *this; }
/// See AbstractState::isValidState().
bool isValidState() const override { return BS.isValidState(); }
/// See AbstractState::isAtFixpoint().
bool isAtFixpoint() const override { return BS.isAtFixpoint(); }
/// See AbstractState::indicateOptimisticFixpoint().
ChangeStatus indicateOptimisticFixpoint() override {
BS.indicateOptimisticFixpoint();
return ChangeStatus::UNCHANGED;
}
/// See AbstractState::indicatePessimisticFixpoint().
ChangeStatus indicatePessimisticFixpoint() override {
BS.indicatePessimisticFixpoint();
return ChangeStatus::CHANGED;
}
State &operator=(const State &R) {
if (this == &R)
return *this;
BS = R.BS;
AccessList = R.AccessList;
OffsetBins = R.OffsetBins;
RemoteIMap = R.RemoteIMap;
return *this;
}
State &operator=(State &&R) {
if (this == &R)
return *this;
std::swap(BS, R.BS);
std::swap(AccessList, R.AccessList);
std::swap(OffsetBins, R.OffsetBins);
std::swap(RemoteIMap, R.RemoteIMap);
return *this;
}
/// Add a new Access to the state at offset \p Offset and with size \p Size.
/// The access is associated with \p I, writes \p Content (if anything), and
/// is of kind \p Kind. If an Access already exists for the same \p I and same
/// \p RemoteI, the two are combined, potentially losing information about
/// offset and size. The resulting access must now be moved from its original
/// OffsetBin to the bin for its new offset.
///
/// \Returns CHANGED, if the state changed, UNCHANGED otherwise.
ChangeStatus addAccess(Attributor &A, const AAPointerInfo::RangeList &Ranges,
Instruction &I, std::optional<Value *> Content,
AAPointerInfo::AccessKind Kind, Type *Ty,
Instruction *RemoteI = nullptr);
AAPointerInfo::const_bin_iterator begin() const { return OffsetBins.begin(); }
AAPointerInfo::const_bin_iterator end() const { return OffsetBins.end(); }
int64_t numOffsetBins() const { return OffsetBins.size(); }
const AAPointerInfo::Access &getAccess(unsigned Index) const {
return AccessList[Index];
}
protected:
// Every memory instruction results in an Access object. We maintain a list of
// all Access objects that we own, along with the following maps:
//
// - OffsetBins: RangeTy -> { Access }
// - RemoteIMap: RemoteI x LocalI -> Access
//
// A RemoteI is any instruction that accesses memory. RemoteI is different
// from LocalI if and only if LocalI is a call; then RemoteI is some
// instruction in the callgraph starting from LocalI. Multiple paths in the
// callgraph from LocalI to RemoteI may produce multiple accesses, but these
// are all combined into a single Access object. This may result in loss of
// information in RangeTy in the Access object.
SmallVector<AAPointerInfo::Access> AccessList;
AAPointerInfo::OffsetBinsTy OffsetBins;
DenseMap<const Instruction *, SmallVector<unsigned>> RemoteIMap;
/// See AAPointerInfo::forallInterferingAccesses.
bool forallInterferingAccesses(
AA::RangeTy Range,
function_ref<bool(const AAPointerInfo::Access &, bool)> CB) const {
if (!isValidState())
return false;
for (const auto &It : OffsetBins) {
AA::RangeTy ItRange = It.getFirst();
if (!Range.mayOverlap(ItRange))
continue;
bool IsExact = Range == ItRange && !Range.offsetOrSizeAreUnknown();
for (auto Index : It.getSecond()) {
auto &Access = AccessList[Index];
if (!CB(Access, IsExact))
return false;
}
}
return true;
}
/// See AAPointerInfo::forallInterferingAccesses.
bool forallInterferingAccesses(
Instruction &I,
function_ref<bool(const AAPointerInfo::Access &, bool)> CB,
AA::RangeTy &Range) const {
if (!isValidState())
return false;
auto LocalList = RemoteIMap.find(&I);
if (LocalList == RemoteIMap.end()) {
return true;
}
for (unsigned Index : LocalList->getSecond()) {
for (auto &R : AccessList[Index]) {
Range &= R;
if (Range.offsetAndSizeAreUnknown())
break;
}
}
return forallInterferingAccesses(Range, CB);
}
private:
/// State to track fixpoint and validity.
BooleanState BS;
};
ChangeStatus AA::PointerInfo::State::addAccess(
Attributor &A, const AAPointerInfo::RangeList &Ranges, Instruction &I,
std::optional<Value *> Content, AAPointerInfo::AccessKind Kind, Type *Ty,
Instruction *RemoteI) {
RemoteI = RemoteI ? RemoteI : &I;
// Check if we have an access for this instruction, if not, simply add it.
auto &LocalList = RemoteIMap[RemoteI];
bool AccExists = false;
unsigned AccIndex = AccessList.size();
for (auto Index : LocalList) {
auto &A = AccessList[Index];
if (A.getLocalInst() == &I) {
AccExists = true;
AccIndex = Index;
break;
}
}
auto AddToBins = [&](const AAPointerInfo::RangeList &ToAdd) {
LLVM_DEBUG(if (ToAdd.size()) dbgs()
<< "[AAPointerInfo] Inserting access in new offset bins\n";);
for (auto Key : ToAdd) {
LLVM_DEBUG(dbgs() << " key " << Key << "\n");
OffsetBins[Key].insert(AccIndex);
}
};
if (!AccExists) {
AccessList.emplace_back(&I, RemoteI, Ranges, Content, Kind, Ty);
assert((AccessList.size() == AccIndex + 1) &&
"New Access should have been at AccIndex");
LocalList.push_back(AccIndex);
AddToBins(AccessList[AccIndex].getRanges());
return ChangeStatus::CHANGED;
}
// Combine the new Access with the existing Access, and then update the
// mapping in the offset bins.
AAPointerInfo::Access Acc(&I, RemoteI, Ranges, Content, Kind, Ty);
auto &Current = AccessList[AccIndex];
auto Before = Current;
Current &= Acc;
if (Current == Before)
return ChangeStatus::UNCHANGED;
auto &ExistingRanges = Before.getRanges();
auto &NewRanges = Current.getRanges();
// Ranges that are in the old access but not the new access need to be removed
// from the offset bins.
AAPointerInfo::RangeList ToRemove;
AAPointerInfo::RangeList::set_difference(ExistingRanges, NewRanges, ToRemove);
LLVM_DEBUG(if (ToRemove.size()) dbgs()
<< "[AAPointerInfo] Removing access from old offset bins\n";);
for (auto Key : ToRemove) {
LLVM_DEBUG(dbgs() << " key " << Key << "\n");
assert(OffsetBins.count(Key) && "Existing Access must be in some bin.");
auto &Bin = OffsetBins[Key];
assert(Bin.count(AccIndex) &&
"Expected bin to actually contain the Access.");
Bin.erase(AccIndex);
}
// Ranges that are in the new access but not the old access need to be added
// to the offset bins.
AAPointerInfo::RangeList ToAdd;
AAPointerInfo::RangeList::set_difference(NewRanges, ExistingRanges, ToAdd);
AddToBins(ToAdd);
return ChangeStatus::CHANGED;
}
namespace {
/// A helper containing a list of offsets computed for a Use. Ideally this
/// list should be strictly ascending, but we ensure that only when we
/// actually translate the list of offsets to a RangeList.
struct OffsetInfo {
using VecTy = SmallVector<int64_t>;
using const_iterator = VecTy::const_iterator;
VecTy Offsets;
const_iterator begin() const { return Offsets.begin(); }
const_iterator end() const { return Offsets.end(); }
bool operator==(const OffsetInfo &RHS) const {
return Offsets == RHS.Offsets;
}
bool operator!=(const OffsetInfo &RHS) const { return !(*this == RHS); }
void insert(int64_t Offset) { Offsets.push_back(Offset); }
bool isUnassigned() const { return Offsets.size() == 0; }
bool isUnknown() const {
if (isUnassigned())
return false;
if (Offsets.size() == 1)
return Offsets.front() == AA::RangeTy::Unknown;
return false;
}
void setUnknown() {
Offsets.clear();
Offsets.push_back(AA::RangeTy::Unknown);
}
void addToAll(int64_t Inc) {
for (auto &Offset : Offsets) {
Offset += Inc;
}
}
/// Copy offsets from \p R into the current list.
///
/// Ideally all lists should be strictly ascending, but we defer that to the
/// actual use of the list. So we just blindly append here.
void merge(const OffsetInfo &R) { Offsets.append(R.Offsets); }
};
#ifndef NDEBUG
static raw_ostream &operator<<(raw_ostream &OS, const OffsetInfo &OI) {
ListSeparator LS;
OS << "[";
for (auto Offset : OI) {
OS << LS << Offset;
}
OS << "]";
return OS;
}
#endif // NDEBUG
struct AAPointerInfoImpl
: public StateWrapper<AA::PointerInfo::State, AAPointerInfo> {
using BaseTy = StateWrapper<AA::PointerInfo::State, AAPointerInfo>;
AAPointerInfoImpl(const IRPosition &IRP, Attributor &A) : BaseTy(IRP) {}
/// See AbstractAttribute::getAsStr().
const std::string getAsStr(Attributor *A) const override {
return std::string("PointerInfo ") +
(isValidState() ? (std::string("#") +
std::to_string(OffsetBins.size()) + " bins")
: "<invalid>");
}
/// See AbstractAttribute::manifest(...).
ChangeStatus manifest(Attributor &A) override {
return AAPointerInfo::manifest(A);
}
virtual const_bin_iterator begin() const override { return State::begin(); }
virtual const_bin_iterator end() const override { return State::end(); }
virtual int64_t numOffsetBins() const override {
return State::numOffsetBins();
}
bool forallInterferingAccesses(
AA::RangeTy Range,
function_ref<bool(const AAPointerInfo::Access &, bool)> CB)
const override {
return State::forallInterferingAccesses(Range, CB);
}
bool forallInterferingAccesses(
Attributor &A, const AbstractAttribute &QueryingAA, Instruction &I,
bool FindInterferingWrites, bool FindInterferingReads,
function_ref<bool(const Access &, bool)> UserCB, bool &HasBeenWrittenTo,
AA::RangeTy &Range,
function_ref<bool(const Access &)> SkipCB) const override {
HasBeenWrittenTo = false;
SmallPtrSet<const Access *, 8> DominatingWrites;
SmallVector<std::pair<const Access *, bool>, 8> InterferingAccesses;
Function &Scope = *I.getFunction();
bool IsKnownNoSync;
bool IsAssumedNoSync = AA::hasAssumedIRAttr<Attribute::NoSync>(
A, &QueryingAA, IRPosition::function(Scope), DepClassTy::OPTIONAL,
IsKnownNoSync);
const auto *ExecDomainAA = A.lookupAAFor<AAExecutionDomain>(
IRPosition::function(Scope), &QueryingAA, DepClassTy::NONE);
bool AllInSameNoSyncFn = IsAssumedNoSync;
bool InstIsExecutedByInitialThreadOnly =
ExecDomainAA && ExecDomainAA->isExecutedByInitialThreadOnly(I);
// If the function is not ending in aligned barriers, we need the stores to
// be in aligned barriers. The load being in one is not sufficient since the
// store might be executed by a thread that disappears after, causing the
// aligned barrier guarding the load to unblock and the load to read a value
// that has no CFG path to the load.
bool InstIsExecutedInAlignedRegion =
FindInterferingReads && ExecDomainAA &&
ExecDomainAA->isExecutedInAlignedRegion(A, I);
if (InstIsExecutedInAlignedRegion || InstIsExecutedByInitialThreadOnly)
A.recordDependence(*ExecDomainAA, QueryingAA, DepClassTy::OPTIONAL);
InformationCache &InfoCache = A.getInfoCache();
bool IsThreadLocalObj =
AA::isAssumedThreadLocalObject(A, getAssociatedValue(), *this);
// Helper to determine if we need to consider threading, which we cannot
// right now. However, if the function is (assumed) nosync or the thread
// executing all instructions is the main thread only we can ignore
// threading. Also, thread-local objects do not require threading reasoning.
// Finally, we can ignore threading if either access is executed in an
// aligned region.
auto CanIgnoreThreadingForInst = [&](const Instruction &I) -> bool {
if (IsThreadLocalObj || AllInSameNoSyncFn)
return true;
const auto *FnExecDomainAA =
I.getFunction() == &Scope
? ExecDomainAA
: A.lookupAAFor<AAExecutionDomain>(
IRPosition::function(*I.getFunction()), &QueryingAA,
DepClassTy::NONE);
if (!FnExecDomainAA)
return false;
if (InstIsExecutedInAlignedRegion ||
(FindInterferingWrites &&
FnExecDomainAA->isExecutedInAlignedRegion(A, I))) {
A.recordDependence(*FnExecDomainAA, QueryingAA, DepClassTy::OPTIONAL);
return true;
}
if (InstIsExecutedByInitialThreadOnly &&
FnExecDomainAA->isExecutedByInitialThreadOnly(I)) {
A.recordDependence(*FnExecDomainAA, QueryingAA, DepClassTy::OPTIONAL);
return true;
}
return false;
};
// Helper to determine if the access is executed by the same thread as the
// given instruction, for now it is sufficient to avoid any potential
// threading effects as we cannot deal with them anyway.
auto CanIgnoreThreading = [&](const Access &Acc) -> bool {
return CanIgnoreThreadingForInst(*Acc.getRemoteInst()) ||
(Acc.getRemoteInst() != Acc.getLocalInst() &&
CanIgnoreThreadingForInst(*Acc.getLocalInst()));
};
// TODO: Use inter-procedural reachability and dominance.
bool IsKnownNoRecurse;
AA::hasAssumedIRAttr<Attribute::NoRecurse>(
A, this, IRPosition::function(Scope), DepClassTy::OPTIONAL,
IsKnownNoRecurse);
// TODO: Use reaching kernels from AAKernelInfo (or move it to
// AAExecutionDomain) such that we allow scopes other than kernels as long
// as the reaching kernels are disjoint.
bool InstInKernel = Scope.hasFnAttribute("kernel");
bool ObjHasKernelLifetime = false;
const bool UseDominanceReasoning =
FindInterferingWrites && IsKnownNoRecurse;
const DominatorTree *DT =
InfoCache.getAnalysisResultForFunction<DominatorTreeAnalysis>(Scope);
// Helper to check if a value has "kernel lifetime", that is it will not
// outlive a GPU kernel. This is true for shared, constant, and local
// globals on AMD and NVIDIA GPUs.
auto HasKernelLifetime = [&](Value *V, Module &M) {
if (!AA::isGPU(M))
return false;
switch (AA::GPUAddressSpace(V->getType()->getPointerAddressSpace())) {
case AA::GPUAddressSpace::Shared:
case AA::GPUAddressSpace::Constant:
case AA::GPUAddressSpace::Local:
return true;
default:
return false;
};
};
// The IsLiveInCalleeCB will be used by the AA::isPotentiallyReachable query
// to determine if we should look at reachability from the callee. For
// certain pointers we know the lifetime and we do not have to step into the
// callee to determine reachability as the pointer would be dead in the
// callee. See the conditional initialization below.
std::function<bool(const Function &)> IsLiveInCalleeCB;
if (auto *AI = dyn_cast<AllocaInst>(&getAssociatedValue())) {
// If the alloca containing function is not recursive the alloca
// must be dead in the callee.
const Function *AIFn = AI->getFunction();
ObjHasKernelLifetime = AIFn->hasFnAttribute("kernel");
bool IsKnownNoRecurse;
if (AA::hasAssumedIRAttr<Attribute::NoRecurse>(
A, this, IRPosition::function(*AIFn), DepClassTy::OPTIONAL,
IsKnownNoRecurse)) {
IsLiveInCalleeCB = [AIFn](const Function &Fn) { return AIFn != &Fn; };
}
} else if (auto *GV = dyn_cast<GlobalValue>(&getAssociatedValue())) {
// If the global has kernel lifetime we can stop if we reach a kernel
// as it is "dead" in the (unknown) callees.
ObjHasKernelLifetime = HasKernelLifetime(GV, *GV->getParent());
if (ObjHasKernelLifetime)
IsLiveInCalleeCB = [](const Function &Fn) {
return !Fn.hasFnAttribute("kernel");
};
}
// Set of accesses/instructions that will overwrite the result and are
// therefore blockers in the reachability traversal.
AA::InstExclusionSetTy ExclusionSet;
auto AccessCB = [&](const Access &Acc, bool Exact) {
Function *AccScope = Acc.getRemoteInst()->getFunction();
bool AccInSameScope = AccScope == &Scope;
// If the object has kernel lifetime we can ignore accesses only reachable
// by other kernels. For now we only skip accesses *in* other kernels.
if (InstInKernel && ObjHasKernelLifetime && !AccInSameScope &&
AccScope->hasFnAttribute("kernel"))
return true;
if (Exact && Acc.isMustAccess() && Acc.getRemoteInst() != &I) {
if (Acc.isWrite() || (isa<LoadInst>(I) && Acc.isWriteOrAssumption()))
ExclusionSet.insert(Acc.getRemoteInst());
}
if ((!FindInterferingWrites || !Acc.isWriteOrAssumption()) &&
(!FindInterferingReads || !Acc.isRead()))
return true;
bool Dominates = FindInterferingWrites && DT && Exact &&
Acc.isMustAccess() && AccInSameScope &&
DT->dominates(Acc.getRemoteInst(), &I);
if (Dominates)
DominatingWrites.insert(&Acc);
// Track if all interesting accesses are in the same `nosync` function as
// the given instruction.
AllInSameNoSyncFn &= Acc.getRemoteInst()->getFunction() == &Scope;
InterferingAccesses.push_back({&Acc, Exact});
return true;
};
if (!State::forallInterferingAccesses(I, AccessCB, Range))
return false;
HasBeenWrittenTo = !DominatingWrites.empty();
// Dominating writes form a chain, find the least/lowest member.
Instruction *LeastDominatingWriteInst = nullptr;
for (const Access *Acc : DominatingWrites) {
if (!LeastDominatingWriteInst) {
LeastDominatingWriteInst = Acc->getRemoteInst();
} else if (DT->dominates(LeastDominatingWriteInst,
Acc->getRemoteInst())) {
LeastDominatingWriteInst = Acc->getRemoteInst();
}
}
// Helper to determine if we can skip a specific write access.
auto CanSkipAccess = [&](const Access &Acc, bool Exact) {
if (SkipCB && SkipCB(Acc))
return true;
if (!CanIgnoreThreading(Acc))
return false;
// Check read (RAW) dependences and write (WAR) dependences as necessary.
// If we successfully excluded all effects we are interested in, the
// access can be skipped.
bool ReadChecked = !FindInterferingReads;
bool WriteChecked = !FindInterferingWrites;
// If the instruction cannot reach the access, the former does not
// interfere with what the access reads.
if (!ReadChecked) {
if (!AA::isPotentiallyReachable(A, I, *Acc.getRemoteInst(), QueryingAA,
&ExclusionSet, IsLiveInCalleeCB))
ReadChecked = true;
}
// If the instruction cannot be reach from the access, the latter does not
// interfere with what the instruction reads.
if (!WriteChecked) {
if (!AA::isPotentiallyReachable(A, *Acc.getRemoteInst(), I, QueryingAA,
&ExclusionSet, IsLiveInCalleeCB))
WriteChecked = true;
}
// If we still might be affected by the write of the access but there are
// dominating writes in the function of the instruction
// (HasBeenWrittenTo), we can try to reason that the access is overwritten
// by them. This would have happend above if they are all in the same
// function, so we only check the inter-procedural case. Effectively, we
// want to show that there is no call after the dominting write that might
// reach the access, and when it returns reach the instruction with the
// updated value. To this end, we iterate all call sites, check if they
// might reach the instruction without going through another access
// (ExclusionSet) and at the same time might reach the access. However,
// that is all part of AAInterFnReachability.
if (!WriteChecked && HasBeenWrittenTo &&
Acc.getRemoteInst()->getFunction() != &Scope) {
const auto *FnReachabilityAA = A.getAAFor<AAInterFnReachability>(
QueryingAA, IRPosition::function(Scope), DepClassTy::OPTIONAL);
// Without going backwards in the call tree, can we reach the access
// from the least dominating write. Do not allow to pass the instruction
// itself either.
bool Inserted = ExclusionSet.insert(&I).second;
if (!FnReachabilityAA ||
!FnReachabilityAA->instructionCanReach(
A, *LeastDominatingWriteInst,
*Acc.getRemoteInst()->getFunction(), &ExclusionSet))
WriteChecked = true;
if (Inserted)
ExclusionSet.erase(&I);
}
if (ReadChecked && WriteChecked)
return true;
if (!DT || !UseDominanceReasoning)
return false;
if (!DominatingWrites.count(&Acc))
return false;
return LeastDominatingWriteInst != Acc.getRemoteInst();
};
// Run the user callback on all accesses we cannot skip and return if
// that succeeded for all or not.
for (auto &It : InterferingAccesses) {
if ((!AllInSameNoSyncFn && !IsThreadLocalObj && !ExecDomainAA) ||
!CanSkipAccess(*It.first, It.second)) {
if (!UserCB(*It.first, It.second))
return false;
}
}
return true;
}
ChangeStatus translateAndAddStateFromCallee(Attributor &A,
const AAPointerInfo &OtherAA,
CallBase &CB) {
using namespace AA::PointerInfo;
if (!OtherAA.getState().isValidState() || !isValidState())
return indicatePessimisticFixpoint();
const auto &OtherAAImpl = static_cast<const AAPointerInfoImpl &>(OtherAA);
bool IsByval = OtherAAImpl.getAssociatedArgument()->hasByValAttr();
// Combine the accesses bin by bin.
ChangeStatus Changed = ChangeStatus::UNCHANGED;
const auto &State = OtherAAImpl.getState();
for (const auto &It : State) {
for (auto Index : It.getSecond()) {
const auto &RAcc = State.getAccess(Index);
if (IsByval && !RAcc.isRead())
continue;
bool UsedAssumedInformation = false;
AccessKind AK = RAcc.getKind();
auto Content = A.translateArgumentToCallSiteContent(
RAcc.getContent(), CB, *this, UsedAssumedInformation);
AK = AccessKind(AK & (IsByval ? AccessKind::AK_R : AccessKind::AK_RW));
AK = AccessKind(AK | (RAcc.isMayAccess() ? AK_MAY : AK_MUST));
Changed |= addAccess(A, RAcc.getRanges(), CB, Content, AK,
RAcc.getType(), RAcc.getRemoteInst());
}
}
return Changed;
}
ChangeStatus translateAndAddState(Attributor &A, const AAPointerInfo &OtherAA,
const OffsetInfo &Offsets, CallBase &CB) {
using namespace AA::PointerInfo;
if (!OtherAA.getState().isValidState() || !isValidState())
return indicatePessimisticFixpoint();
const auto &OtherAAImpl = static_cast<const AAPointerInfoImpl &>(OtherAA);
// Combine the accesses bin by bin.
ChangeStatus Changed = ChangeStatus::UNCHANGED;
const auto &State = OtherAAImpl.getState();
for (const auto &It : State) {
for (auto Index : It.getSecond()) {
const auto &RAcc = State.getAccess(Index);
for (auto Offset : Offsets) {
auto NewRanges = Offset == AA::RangeTy::Unknown
? AA::RangeTy::getUnknown()
: RAcc.getRanges();
if (!NewRanges.isUnknown()) {
NewRanges.addToAllOffsets(Offset);
}
Changed |=
addAccess(A, NewRanges, CB, RAcc.getContent(), RAcc.getKind(),
RAcc.getType(), RAcc.getRemoteInst());
}
}
}
return Changed;
}
/// Statistic tracking for all AAPointerInfo implementations.
/// See AbstractAttribute::trackStatistics().
void trackPointerInfoStatistics(const IRPosition &IRP) const {}
/// Dump the state into \p O.
void dumpState(raw_ostream &O) {
for (auto &It : OffsetBins) {
O << "[" << It.first.Offset << "-" << It.first.Offset + It.first.Size
<< "] : " << It.getSecond().size() << "\n";
for (auto AccIndex : It.getSecond()) {
auto &Acc = AccessList[AccIndex];
O << " - " << Acc.getKind() << " - " << *Acc.getLocalInst() << "\n";
if (Acc.getLocalInst() != Acc.getRemoteInst())
O << " --> " << *Acc.getRemoteInst()
<< "\n";
if (!Acc.isWrittenValueYetUndetermined()) {
if (isa_and_nonnull<Function>(Acc.getWrittenValue()))
O << " - c: func " << Acc.getWrittenValue()->getName()
<< "\n";
else if (Acc.getWrittenValue())
O << " - c: " << *Acc.getWrittenValue() << "\n";
else
O << " - c: <unknown>\n";
}
}
}
}
};
struct AAPointerInfoFloating : public AAPointerInfoImpl {
using AccessKind = AAPointerInfo::AccessKind;
AAPointerInfoFloating(const IRPosition &IRP, Attributor &A)
: AAPointerInfoImpl(IRP, A) {}
/// Deal with an access and signal if it was handled successfully.
bool handleAccess(Attributor &A, Instruction &I,
std::optional<Value *> Content, AccessKind Kind,
SmallVectorImpl<int64_t> &Offsets, ChangeStatus &Changed,
Type &Ty) {
using namespace AA::PointerInfo;
auto Size = AA::RangeTy::Unknown;
const DataLayout &DL = A.getDataLayout();
TypeSize AccessSize = DL.getTypeStoreSize(&Ty);
if (!AccessSize.isScalable())
Size = AccessSize.getFixedValue();
// Make a strictly ascending list of offsets as required by addAccess()
llvm::sort(Offsets);
auto *Last = std::unique(Offsets.begin(), Offsets.end());
Offsets.erase(Last, Offsets.end());
VectorType *VT = dyn_cast<VectorType>(&Ty);
if (!VT || VT->getElementCount().isScalable() ||
!Content.value_or(nullptr) || !isa<Constant>(*Content) ||
(*Content)->getType() != VT ||
DL.getTypeStoreSize(VT->getElementType()).isScalable()) {
Changed = Changed | addAccess(A, {Offsets, Size}, I, Content, Kind, &Ty);
} else {
// Handle vector stores with constant content element-wise.
// TODO: We could look for the elements or create instructions
// representing them.
// TODO: We need to push the Content into the range abstraction
// (AA::RangeTy) to allow different content values for different
// ranges. ranges. Hence, support vectors storing different values.
Type *ElementType = VT->getElementType();
int64_t ElementSize = DL.getTypeStoreSize(ElementType).getFixedValue();
auto *ConstContent = cast<Constant>(*Content);
Type *Int32Ty = Type::getInt32Ty(ElementType->getContext());
SmallVector<int64_t> ElementOffsets(Offsets.begin(), Offsets.end());
for (int i = 0, e = VT->getElementCount().getFixedValue(); i != e; ++i) {
Value *ElementContent = ConstantExpr::getExtractElement(
ConstContent, ConstantInt::get(Int32Ty, i));
// Add the element access.
Changed = Changed | addAccess(A, {ElementOffsets, ElementSize}, I,
ElementContent, Kind, ElementType);
// Advance the offsets for the next element.
for (auto &ElementOffset : ElementOffsets)
ElementOffset += ElementSize;
}
}
return true;
};
/// See AbstractAttribute::updateImpl(...).
ChangeStatus updateImpl(Attributor &A) override;
/// If the indices to \p GEP can be traced to constants, incorporate all
/// of these into \p UsrOI.
///
/// \return true iff \p UsrOI is updated.
bool collectConstantsForGEP(Attributor &A, const DataLayout &DL,
OffsetInfo &UsrOI, const OffsetInfo &PtrOI,
const GEPOperator *GEP);
/// See AbstractAttribute::trackStatistics()
void trackStatistics() const override {
AAPointerInfoImpl::trackPointerInfoStatistics(getIRPosition());
}
};
bool AAPointerInfoFloating::collectConstantsForGEP(Attributor &A,
const DataLayout &DL,
OffsetInfo &UsrOI,
const OffsetInfo &PtrOI,
const GEPOperator *GEP) {
unsigned BitWidth = DL.getIndexTypeSizeInBits(GEP->getType());
MapVector<Value *, APInt> VariableOffsets;
APInt ConstantOffset(BitWidth, 0);
assert(!UsrOI.isUnknown() && !PtrOI.isUnknown() &&
"Don't look for constant values if the offset has already been "
"determined to be unknown.");
if (!GEP->collectOffset(DL, BitWidth, VariableOffsets, ConstantOffset)) {
UsrOI.setUnknown();
return true;
}
LLVM_DEBUG(dbgs() << "[AAPointerInfo] GEP offset is "
<< (VariableOffsets.empty() ? "" : "not") << " constant "
<< *GEP << "\n");
auto Union = PtrOI;
Union.addToAll(ConstantOffset.getSExtValue());
// Each VI in VariableOffsets has a set of potential constant values. Every
// combination of elements, picked one each from these sets, is separately
// added to the original set of offsets, thus resulting in more offsets.
for (const auto &VI : VariableOffsets) {
auto *PotentialConstantsAA = A.getAAFor<AAPotentialConstantValues>(
*this, IRPosition::value(*VI.first), DepClassTy::OPTIONAL);
if (!PotentialConstantsAA || !PotentialConstantsAA->isValidState()) {
UsrOI.setUnknown();
return true;
}
// UndefValue is treated as a zero, which leaves Union as is.
if (PotentialConstantsAA->undefIsContained())
continue;
// We need at least one constant in every set to compute an actual offset.
// Otherwise, we end up pessimizing AAPointerInfo by respecting offsets that
// don't actually exist. In other words, the absence of constant values
// implies that the operation can be assumed dead for now.
auto &AssumedSet = PotentialConstantsAA->getAssumedSet();
if (AssumedSet.empty())
return false;
OffsetInfo Product;
for (const auto &ConstOffset : AssumedSet) {
auto CopyPerOffset = Union;
CopyPerOffset.addToAll(ConstOffset.getSExtValue() *
VI.second.getZExtValue());
Product.merge(CopyPerOffset);
}
Union = Product;
}
UsrOI = std::move(Union);
return true;
}
ChangeStatus AAPointerInfoFloating::updateImpl(Attributor &A) {
using namespace AA::PointerInfo;
ChangeStatus Changed = ChangeStatus::UNCHANGED;
const DataLayout &DL = A.getDataLayout();
Value &AssociatedValue = getAssociatedValue();
DenseMap<Value *, OffsetInfo> OffsetInfoMap;
OffsetInfoMap[&AssociatedValue].insert(0);
auto HandlePassthroughUser = [&](Value *Usr, Value *CurPtr, bool &Follow) {
// One does not simply walk into a map and assign a reference to a possibly
// new location. That can cause an invalidation before the assignment
// happens, like so:
//
// OffsetInfoMap[Usr] = OffsetInfoMap[CurPtr]; /* bad idea! */
//
// The RHS is a reference that may be invalidated by an insertion caused by
// the LHS. So we ensure that the side-effect of the LHS happens first.
auto &UsrOI = OffsetInfoMap[Usr];
auto &PtrOI = OffsetInfoMap[CurPtr];
assert(!PtrOI.isUnassigned() &&
"Cannot pass through if the input Ptr was not visited!");
UsrOI = PtrOI;
Follow = true;
return true;
};
const auto *F = getAnchorScope();
const auto *CI =
F ? A.getInfoCache().getAnalysisResultForFunction<CycleAnalysis>(*F)
: nullptr;
const auto *TLI =
F ? A.getInfoCache().getTargetLibraryInfoForFunction(*F) : nullptr;
auto UsePred = [&](const Use &U, bool &Follow) -> bool {
Value *CurPtr = U.get();
User *Usr = U.getUser();
LLVM_DEBUG(dbgs() << "[AAPointerInfo] Analyze " << *CurPtr << " in " << *Usr
<< "\n");
assert(OffsetInfoMap.count(CurPtr) &&
"The current pointer offset should have been seeded!");
if (ConstantExpr *CE = dyn_cast<ConstantExpr>(Usr)) {
if (CE->isCast())
return HandlePassthroughUser(Usr, CurPtr, Follow);
if (CE->isCompare())
return true;
if (!isa<GEPOperator>(CE)) {
LLVM_DEBUG(dbgs() << "[AAPointerInfo] Unhandled constant user " << *CE
<< "\n");
return false;
}
}
if (auto *GEP = dyn_cast<GEPOperator>(Usr)) {
// Note the order here, the Usr access might change the map, CurPtr is
// already in it though.
auto &UsrOI = OffsetInfoMap[Usr];
auto &PtrOI = OffsetInfoMap[CurPtr];
if (UsrOI.isUnknown())
return true;
if (PtrOI.isUnknown()) {
Follow = true;
UsrOI.setUnknown();
return true;
}
Follow = collectConstantsForGEP(A, DL, UsrOI, PtrOI, GEP);
return true;
}
if (isa<PtrToIntInst>(Usr))
return false;
if (isa<CastInst>(Usr) || isa<SelectInst>(Usr) || isa<ReturnInst>(Usr))
return HandlePassthroughUser(Usr, CurPtr, Follow);
// For PHIs we need to take care of the recurrence explicitly as the value
// might change while we iterate through a loop. For now, we give up if
// the PHI is not invariant.
if (isa<PHINode>(Usr)) {
// Note the order here, the Usr access might change the map, CurPtr is
// already in it though.
bool IsFirstPHIUser = !OffsetInfoMap.count(Usr);
auto &UsrOI = OffsetInfoMap[Usr];
auto &PtrOI = OffsetInfoMap[CurPtr];
// Check if the PHI operand has already an unknown offset as we can't
// improve on that anymore.
if (PtrOI.isUnknown()) {
LLVM_DEBUG(dbgs() << "[AAPointerInfo] PHI operand offset unknown "
<< *CurPtr << " in " << *Usr << "\n");
Follow = !UsrOI.isUnknown();
UsrOI.setUnknown();
return true;
}
// Check if the PHI is invariant (so far).
if (UsrOI == PtrOI) {
assert(!PtrOI.isUnassigned() &&
"Cannot assign if the current Ptr was not visited!");
LLVM_DEBUG(dbgs() << "[AAPointerInfo] PHI is invariant (so far)");
return true;
}
// Check if the PHI operand can be traced back to AssociatedValue.
APInt Offset(
DL.getIndexSizeInBits(CurPtr->getType()->getPointerAddressSpace()),
0);
Value *CurPtrBase = CurPtr->stripAndAccumulateConstantOffsets(
DL, Offset, /* AllowNonInbounds */ true);
auto It = OffsetInfoMap.find(CurPtrBase);
if (It == OffsetInfoMap.end()) {
LLVM_DEBUG(dbgs() << "[AAPointerInfo] PHI operand is too complex "
<< *CurPtr << " in " << *Usr << "\n");
UsrOI.setUnknown();
Follow = true;
return true;
}
// Check if the PHI operand is not dependent on the PHI itself. Every
// recurrence is a cyclic net of PHIs in the data flow, and has an
// equivalent Cycle in the control flow. One of those PHIs must be in the
// header of that control flow Cycle. This is independent of the choice of
// Cycles reported by CycleInfo. It is sufficient to check the PHIs in
// every Cycle header; if such a node is marked unknown, this will
// eventually propagate through the whole net of PHIs in the recurrence.
if (mayBeInCycle(CI, cast<Instruction>(Usr), /* HeaderOnly */ true)) {
auto BaseOI = It->getSecond();
BaseOI.addToAll(Offset.getZExtValue());
if (IsFirstPHIUser || BaseOI == UsrOI) {
LLVM_DEBUG(dbgs() << "[AAPointerInfo] PHI is invariant " << *CurPtr
<< " in " << *Usr << "\n");
return HandlePassthroughUser(Usr, CurPtr, Follow);
}
LLVM_DEBUG(
dbgs() << "[AAPointerInfo] PHI operand pointer offset mismatch "
<< *CurPtr << " in " << *Usr << "\n");
UsrOI.setUnknown();
Follow = true;
return true;
}
UsrOI.merge(PtrOI);
Follow = true;
return true;
}
if (auto *LoadI = dyn_cast<LoadInst>(Usr)) {
// If the access is to a pointer that may or may not be the associated
// value, e.g. due to a PHI, we cannot assume it will be read.
AccessKind AK = AccessKind::AK_R;
if (getUnderlyingObject(CurPtr) == &AssociatedValue)
AK = AccessKind(AK | AccessKind::AK_MUST);
else
AK = AccessKind(AK | AccessKind::AK_MAY);
if (!handleAccess(A, *LoadI, /* Content */ nullptr, AK,
OffsetInfoMap[CurPtr].Offsets, Changed,
*LoadI->getType()))
return false;
auto IsAssumption = [](Instruction &I) {
if (auto *II = dyn_cast<IntrinsicInst>(&I))
return II->isAssumeLikeIntrinsic();
return false;
};
auto IsImpactedInRange = [&](Instruction *FromI, Instruction *ToI) {
// Check if the assumption and the load are executed together without
// memory modification.
do {
if (FromI->mayWriteToMemory() && !IsAssumption(*FromI))
return true;
FromI = FromI->getNextNonDebugInstruction();
} while (FromI && FromI != ToI);
return false;
};
BasicBlock *BB = LoadI->getParent();
auto IsValidAssume = [&](IntrinsicInst &IntrI) {
if (IntrI.getIntrinsicID() != Intrinsic::assume)
return false;
BasicBlock *IntrBB = IntrI.getParent();
if (IntrI.getParent() == BB) {
if (IsImpactedInRange(LoadI->getNextNonDebugInstruction(), &IntrI))
return false;
} else {
auto PredIt = pred_begin(IntrBB);
if (PredIt == pred_end(IntrBB))
return false;
if ((*PredIt) != BB)
return false;
if (++PredIt != pred_end(IntrBB))
return false;
for (auto *SuccBB : successors(BB)) {
if (SuccBB == IntrBB)
continue;
if (isa<UnreachableInst>(SuccBB->getTerminator()))
continue;
return false;
}
if (IsImpactedInRange(LoadI->getNextNonDebugInstruction(),
BB->getTerminator()))
return false;
if (IsImpactedInRange(&IntrBB->front(), &IntrI))
return false;
}
return true;
};
std::pair<Value *, IntrinsicInst *> Assumption;
for (const Use &LoadU : LoadI->uses()) {
if (auto *CmpI = dyn_cast<CmpInst>(LoadU.getUser())) {
if (!CmpI->isEquality() || !CmpI->isTrueWhenEqual())
continue;
for (const Use &CmpU : CmpI->uses()) {
if (auto *IntrI = dyn_cast<IntrinsicInst>(CmpU.getUser())) {
if (!IsValidAssume(*IntrI))
continue;
int Idx = CmpI->getOperandUse(0) == LoadU;
Assumption = {CmpI->getOperand(Idx), IntrI};
break;
}
}
}
if (Assumption.first)
break;
}
// Check if we found an assumption associated with this load.
if (!Assumption.first || !Assumption.second)
return true;
LLVM_DEBUG(dbgs() << "[AAPointerInfo] Assumption found "
<< *Assumption.second << ": " << *LoadI
<< " == " << *Assumption.first << "\n");
bool UsedAssumedInformation = false;
std::optional<Value *> Content = nullptr;
if (Assumption.first)
Content =
A.getAssumedSimplified(*Assumption.first, *this,
UsedAssumedInformation, AA::Interprocedural);
return handleAccess(
A, *Assumption.second, Content, AccessKind::AK_ASSUMPTION,
OffsetInfoMap[CurPtr].Offsets, Changed, *LoadI->getType());
}
auto HandleStoreLike = [&](Instruction &I, Value *ValueOp, Type &ValueTy,
ArrayRef<Value *> OtherOps, AccessKind AK) {
for (auto *OtherOp : OtherOps) {
if (OtherOp == CurPtr) {
LLVM_DEBUG(
dbgs()
<< "[AAPointerInfo] Escaping use in store like instruction " << I
<< "\n");
return false;
}
}
// If the access is to a pointer that may or may not be the associated
// value, e.g. due to a PHI, we cannot assume it will be written.
if (getUnderlyingObject(CurPtr) == &AssociatedValue)
AK = AccessKind(AK | AccessKind::AK_MUST);
else
AK = AccessKind(AK | AccessKind::AK_MAY);
bool UsedAssumedInformation = false;
std::optional<Value *> Content = nullptr;
if (ValueOp)
Content = A.getAssumedSimplified(
*ValueOp, *this, UsedAssumedInformation, AA::Interprocedural);
return handleAccess(A, I, Content, AK, OffsetInfoMap[CurPtr].Offsets,
Changed, ValueTy);
};
if (auto *StoreI = dyn_cast<StoreInst>(Usr))
return HandleStoreLike(*StoreI, StoreI->getValueOperand(),
*StoreI->getValueOperand()->getType(),
{StoreI->getValueOperand()}, AccessKind::AK_W);
if (auto *RMWI = dyn_cast<AtomicRMWInst>(Usr))
return HandleStoreLike(*RMWI, nullptr, *RMWI->getValOperand()->getType(),
{RMWI->getValOperand()}, AccessKind::AK_RW);
if (auto *CXI = dyn_cast<AtomicCmpXchgInst>(Usr))
return HandleStoreLike(
*CXI, nullptr, *CXI->getNewValOperand()->getType(),
{CXI->getCompareOperand(), CXI->getNewValOperand()},
AccessKind::AK_RW);
if (auto *CB = dyn_cast<CallBase>(Usr)) {
if (CB->isLifetimeStartOrEnd())
return true;
if (getFreedOperand(CB, TLI) == U)
return true;
if (CB->isArgOperand(&U)) {
unsigned ArgNo = CB->getArgOperandNo(&U);
const auto *CSArgPI = A.getAAFor<AAPointerInfo>(
*this, IRPosition::callsite_argument(*CB, ArgNo),
DepClassTy::REQUIRED);
if (!CSArgPI)
return false;
Changed =
translateAndAddState(A, *CSArgPI, OffsetInfoMap[CurPtr], *CB) |
Changed;
return isValidState();
}
LLVM_DEBUG(dbgs() << "[AAPointerInfo] Call user not handled " << *CB
<< "\n");
// TODO: Allow some call uses
return false;
}
LLVM_DEBUG(dbgs() << "[AAPointerInfo] User not handled " << *Usr << "\n");
return false;
};
auto EquivalentUseCB = [&](const Use &OldU, const Use &NewU) {
assert(OffsetInfoMap.count(OldU) && "Old use should be known already!");
if (OffsetInfoMap.count(NewU)) {
LLVM_DEBUG({
if (!(OffsetInfoMap[NewU] == OffsetInfoMap[OldU])) {
dbgs() << "[AAPointerInfo] Equivalent use callback failed: "
<< OffsetInfoMap[NewU] << " vs " << OffsetInfoMap[OldU]
<< "\n";
}
});
return OffsetInfoMap[NewU] == OffsetInfoMap[OldU];
}
OffsetInfoMap[NewU] = OffsetInfoMap[OldU];
return true;
};
if (!A.checkForAllUses(UsePred, *this, AssociatedValue,
/* CheckBBLivenessOnly */ true, DepClassTy::OPTIONAL,
/* IgnoreDroppableUses */ true, EquivalentUseCB)) {
LLVM_DEBUG(dbgs() << "[AAPointerInfo] Check for all uses failed, abort!\n");
return indicatePessimisticFixpoint();
}
LLVM_DEBUG({
dbgs() << "Accesses by bin after update:\n";
dumpState(dbgs());
});
return Changed;
}
struct AAPointerInfoReturned final : AAPointerInfoImpl {
AAPointerInfoReturned(const IRPosition &IRP, Attributor &A)
: AAPointerInfoImpl(IRP, A) {}
/// See AbstractAttribute::updateImpl(...).
ChangeStatus updateImpl(Attributor &A) override {
return indicatePessimisticFixpoint();
}
/// See AbstractAttribute::trackStatistics()
void trackStatistics() const override {
AAPointerInfoImpl::trackPointerInfoStatistics(getIRPosition());
}
};
struct AAPointerInfoArgument final : AAPointerInfoFloating {
AAPointerInfoArgument(const IRPosition &IRP, Attributor &A)
: AAPointerInfoFloating(IRP, A) {}
/// See AbstractAttribute::trackStatistics()
void trackStatistics() const override {
AAPointerInfoImpl::trackPointerInfoStatistics(getIRPosition());
}
};
struct AAPointerInfoCallSiteArgument final : AAPointerInfoFloating {
AAPointerInfoCallSiteArgument(const IRPosition &IRP, Attributor &A)
: AAPointerInfoFloating(IRP, A) {}
/// See AbstractAttribute::updateImpl(...).
ChangeStatus updateImpl(Attributor &A) override {
using namespace AA::PointerInfo;
// We handle memory intrinsics explicitly, at least the first (=
// destination) and second (=source) arguments as we know how they are
// accessed.
if (auto *MI = dyn_cast_or_null<MemIntrinsic>(getCtxI())) {
ConstantInt *Length = dyn_cast<ConstantInt>(MI->getLength());
int64_t LengthVal = AA::RangeTy::Unknown;
if (Length)
LengthVal = Length->getSExtValue();
unsigned ArgNo = getIRPosition().getCallSiteArgNo();
ChangeStatus Changed = ChangeStatus::UNCHANGED;
if (ArgNo > 1) {
LLVM_DEBUG(dbgs() << "[AAPointerInfo] Unhandled memory intrinsic "
<< *MI << "\n");
return indicatePessimisticFixpoint();
} else {
auto Kind =
ArgNo == 0 ? AccessKind::AK_MUST_WRITE : AccessKind::AK_MUST_READ;
Changed =
Changed | addAccess(A, {0, LengthVal}, *MI, nullptr, Kind, nullptr);
}
LLVM_DEBUG({
dbgs() << "Accesses by bin after update:\n";
dumpState(dbgs());
});
return Changed;
}
// TODO: Once we have call site specific value information we can provide
// call site specific liveness information and then it makes
// sense to specialize attributes for call sites arguments instead of
// redirecting requests to the callee argument.
Argument *Arg = getAssociatedArgument();
if (Arg) {
const IRPosition &ArgPos = IRPosition::argument(*Arg);
auto *ArgAA =
A.getAAFor<AAPointerInfo>(*this, ArgPos, DepClassTy::REQUIRED);
if (ArgAA && ArgAA->getState().isValidState())
return translateAndAddStateFromCallee(A, *ArgAA,
*cast<CallBase>(getCtxI()));
if (!Arg->getParent()->isDeclaration())
return indicatePessimisticFixpoint();
}
bool IsKnownNoCapture;
if (!AA::hasAssumedIRAttr<Attribute::NoCapture>(
A, this, getIRPosition(), DepClassTy::OPTIONAL, IsKnownNoCapture))
return indicatePessimisticFixpoint();
bool IsKnown = false;
if (AA::isAssumedReadNone(A, getIRPosition(), *this, IsKnown))
return ChangeStatus::UNCHANGED;
bool ReadOnly = AA::isAssumedReadOnly(A, getIRPosition(), *this, IsKnown);
auto Kind =
ReadOnly ? AccessKind::AK_MAY_READ : AccessKind::AK_MAY_READ_WRITE;
return addAccess(A, AA::RangeTy::getUnknown(), *getCtxI(), nullptr, Kind,
nullptr);
}
/// See AbstractAttribute::trackStatistics()
void trackStatistics() const override {
AAPointerInfoImpl::trackPointerInfoStatistics(getIRPosition());
}
};
struct AAPointerInfoCallSiteReturned final : AAPointerInfoFloating {
AAPointerInfoCallSiteReturned(const IRPosition &IRP, Attributor &A)
: AAPointerInfoFloating(IRP, A) {}
/// See AbstractAttribute::trackStatistics()
void trackStatistics() const override {
AAPointerInfoImpl::trackPointerInfoStatistics(getIRPosition());
}
};
} // namespace
/// -----------------------NoUnwind Function Attribute--------------------------
namespace {
struct AANoUnwindImpl : AANoUnwind {
AANoUnwindImpl(const IRPosition &IRP, Attributor &A) : AANoUnwind(IRP, A) {}
/// See AbstractAttribute::initialize(...).
void initialize(Attributor &A) override {
bool IsKnown;
assert(!AA::hasAssumedIRAttr<Attribute::NoUnwind>(
A, nullptr, getIRPosition(), DepClassTy::NONE, IsKnown));
(void)IsKnown;
}
const std::string getAsStr(Attributor *A) const override {
return getAssumed() ? "nounwind" : "may-unwind";
}
/// See AbstractAttribute::updateImpl(...).
ChangeStatus updateImpl(Attributor &A) override {
auto Opcodes = {
(unsigned)Instruction::Invoke, (unsigned)Instruction::CallBr,
(unsigned)Instruction::Call, (unsigned)Instruction::CleanupRet,
(unsigned)Instruction::CatchSwitch, (unsigned)Instruction::Resume};
auto CheckForNoUnwind = [&](Instruction &I) {
if (!I.mayThrow(/* IncludePhaseOneUnwind */ true))
return true;
if (const auto *CB = dyn_cast<CallBase>(&I)) {
bool IsKnownNoUnwind;
return AA::hasAssumedIRAttr<Attribute::NoUnwind>(
A, this, IRPosition::callsite_function(*CB), DepClassTy::REQUIRED,
IsKnownNoUnwind);
}
return false;
};
bool UsedAssumedInformation = false;
if (!A.checkForAllInstructions(CheckForNoUnwind, *this, Opcodes,
UsedAssumedInformation))
return indicatePessimisticFixpoint();
return ChangeStatus::UNCHANGED;
}
};
struct AANoUnwindFunction final : public AANoUnwindImpl {
AANoUnwindFunction(const IRPosition &IRP, Attributor &A)
: AANoUnwindImpl(IRP, A) {}
/// See AbstractAttribute::trackStatistics()
void trackStatistics() const override { STATS_DECLTRACK_FN_ATTR(nounwind) }
};
/// NoUnwind attribute deduction for a call sites.
struct AANoUnwindCallSite final
: AACalleeToCallSite<AANoUnwind, AANoUnwindImpl> {
AANoUnwindCallSite(const IRPosition &IRP, Attributor &A)
: AACalleeToCallSite<AANoUnwind, AANoUnwindImpl>(IRP, A) {}
/// See AbstractAttribute::trackStatistics()
void trackStatistics() const override { STATS_DECLTRACK_CS_ATTR(nounwind); }
};
} // namespace
/// ------------------------ NoSync Function Attribute -------------------------
bool AANoSync::isAlignedBarrier(const CallBase &CB, bool ExecutedAligned) {
switch (CB.getIntrinsicID()) {
case Intrinsic::nvvm_barrier0:
case Intrinsic::nvvm_barrier0_and:
case Intrinsic::nvvm_barrier0_or:
case Intrinsic::nvvm_barrier0_popc:
return true;
case Intrinsic::amdgcn_s_barrier:
if (ExecutedAligned)
return true;
break;
default:
break;
}
return hasAssumption(CB, KnownAssumptionString("ompx_aligned_barrier"));
}
bool AANoSync::isNonRelaxedAtomic(const Instruction *I) {
if (!I->isAtomic())
return false;
if (auto *FI = dyn_cast<FenceInst>(I))
// All legal orderings for fence are stronger than monotonic.
return FI->getSyncScopeID() != SyncScope::SingleThread;
if (auto *AI = dyn_cast<AtomicCmpXchgInst>(I)) {
// Unordered is not a legal ordering for cmpxchg.
return (AI->getSuccessOrdering() != AtomicOrdering::Monotonic ||
AI->getFailureOrdering() != AtomicOrdering::Monotonic);
}
AtomicOrdering Ordering;
switch (I->getOpcode()) {
case Instruction::AtomicRMW:
Ordering = cast<AtomicRMWInst>(I)->getOrdering();
break;
case Instruction::Store:
Ordering = cast<StoreInst>(I)->getOrdering();
break;
case Instruction::Load:
Ordering = cast<LoadInst>(I)->getOrdering();
break;
default:
llvm_unreachable(
"New atomic operations need to be known in the attributor.");
}
return (Ordering != AtomicOrdering::Unordered &&
Ordering != AtomicOrdering::Monotonic);
}
/// Return true if this intrinsic is nosync. This is only used for intrinsics
/// which would be nosync except that they have a volatile flag. All other
/// intrinsics are simply annotated with the nosync attribute in Intrinsics.td.
bool AANoSync::isNoSyncIntrinsic(const Instruction *I) {
if (auto *MI = dyn_cast<MemIntrinsic>(I))
return !MI->isVolatile();
return false;
}
namespace {
struct AANoSyncImpl : AANoSync {
AANoSyncImpl(const IRPosition &IRP, Attributor &A) : AANoSync(IRP, A) {}
/// See AbstractAttribute::initialize(...).
void initialize(Attributor &A) override {
bool IsKnown;
assert(!AA::hasAssumedIRAttr<Attribute::NoSync>(A, nullptr, getIRPosition(),
DepClassTy::NONE, IsKnown));
(void)IsKnown;
}
const std::string getAsStr(Attributor *A) const override {
return getAssumed() ? "nosync" : "may-sync";
}
/// See AbstractAttribute::updateImpl(...).
ChangeStatus updateImpl(Attributor &A) override;
};
ChangeStatus AANoSyncImpl::updateImpl(Attributor &A) {
auto CheckRWInstForNoSync = [&](Instruction &I) {
return AA::isNoSyncInst(A, I, *this);
};
auto CheckForNoSync = [&](Instruction &I) {
// At this point we handled all read/write effects and they are all
// nosync, so they can be skipped.
if (I.mayReadOrWriteMemory())
return true;
bool IsKnown;
CallBase &CB = cast<CallBase>(I);
if (AA::hasAssumedIRAttr<Attribute::NoSync>(
A, this, IRPosition::callsite_function(CB), DepClassTy::OPTIONAL,
IsKnown))
return true;
// non-convergent and readnone imply nosync.
return !CB.isConvergent();
};
bool UsedAssumedInformation = false;
if (!A.checkForAllReadWriteInstructions(CheckRWInstForNoSync, *this,
UsedAssumedInformation) ||
!A.checkForAllCallLikeInstructions(CheckForNoSync, *this,
UsedAssumedInformation))
return indicatePessimisticFixpoint();
return ChangeStatus::UNCHANGED;
}
struct AANoSyncFunction final : public AANoSyncImpl {
AANoSyncFunction(const IRPosition &IRP, Attributor &A)
: AANoSyncImpl(IRP, A) {}
/// See AbstractAttribute::trackStatistics()
void trackStatistics() const override { STATS_DECLTRACK_FN_ATTR(nosync) }
};
/// NoSync attribute deduction for a call sites.
struct AANoSyncCallSite final : AACalleeToCallSite<AANoSync, AANoSyncImpl> {
AANoSyncCallSite(const IRPosition &IRP, Attributor &A)
: AACalleeToCallSite<AANoSync, AANoSyncImpl>(IRP, A) {}
/// See AbstractAttribute::trackStatistics()
void trackStatistics() const override { STATS_DECLTRACK_CS_ATTR(nosync); }
};
} // namespace
/// ------------------------ No-Free Attributes ----------------------------
namespace {
struct AANoFreeImpl : public AANoFree {
AANoFreeImpl(const IRPosition &IRP, Attributor &A) : AANoFree(IRP, A) {}
/// See AbstractAttribute::initialize(...).
void initialize(Attributor &A) override {
bool IsKnown;
assert(!AA::hasAssumedIRAttr<Attribute::NoFree>(A, nullptr, getIRPosition(),
DepClassTy::NONE, IsKnown));
(void)IsKnown;
}
/// See AbstractAttribute::updateImpl(...).
ChangeStatus updateImpl(Attributor &A) override {
auto CheckForNoFree = [&](Instruction &I) {
bool IsKnown;
return AA::hasAssumedIRAttr<Attribute::NoFree>(
A, this, IRPosition::callsite_function(cast<CallBase>(I)),
DepClassTy::REQUIRED, IsKnown);
};
bool UsedAssumedInformation = false;
if (!A.checkForAllCallLikeInstructions(CheckForNoFree, *this,
UsedAssumedInformation))
return indicatePessimisticFixpoint();
return ChangeStatus::UNCHANGED;
}
/// See AbstractAttribute::getAsStr().
const std::string getAsStr(Attributor *A) const override {
return getAssumed() ? "nofree" : "may-free";
}
};
struct AANoFreeFunction final : public AANoFreeImpl {
AANoFreeFunction(const IRPosition &IRP, Attributor &A)
: AANoFreeImpl(IRP, A) {}
/// See AbstractAttribute::trackStatistics()
void trackStatistics() const override { STATS_DECLTRACK_FN_ATTR(nofree) }
};
/// NoFree attribute deduction for a call sites.
struct AANoFreeCallSite final : AACalleeToCallSite<AANoFree, AANoFreeImpl> {
AANoFreeCallSite(const IRPosition &IRP, Attributor &A)
: AACalleeToCallSite<AANoFree, AANoFreeImpl>(IRP, A) {}
/// See AbstractAttribute::trackStatistics()
void trackStatistics() const override { STATS_DECLTRACK_CS_ATTR(nofree); }
};
/// NoFree attribute for floating values.
struct AANoFreeFloating : AANoFreeImpl {
AANoFreeFloating(const IRPosition &IRP, Attributor &A)
: AANoFreeImpl(IRP, A) {}
/// See AbstractAttribute::trackStatistics()
void trackStatistics() const override{STATS_DECLTRACK_FLOATING_ATTR(nofree)}
/// See Abstract Attribute::updateImpl(...).
ChangeStatus updateImpl(Attributor &A) override {
const IRPosition &IRP = getIRPosition();
bool IsKnown;
if (AA::hasAssumedIRAttr<Attribute::NoFree>(A, this,
IRPosition::function_scope(IRP),
DepClassTy::OPTIONAL, IsKnown))
return ChangeStatus::UNCHANGED;
Value &AssociatedValue = getIRPosition().getAssociatedValue();
auto Pred = [&](const Use &U, bool &Follow) -> bool {
Instruction *UserI = cast<Instruction>(U.getUser());
if (auto *CB = dyn_cast<CallBase>(UserI)) {
if (CB->isBundleOperand(&U))
return false;
if (!CB->isArgOperand(&U))
return true;
unsigned ArgNo = CB->getArgOperandNo(&U);
bool IsKnown;
return AA::hasAssumedIRAttr<Attribute::NoFree>(
A, this, IRPosition::callsite_argument(*CB, ArgNo),
DepClassTy::REQUIRED, IsKnown);
}
if (isa<GetElementPtrInst>(UserI) || isa<BitCastInst>(UserI) ||
isa<PHINode>(UserI) || isa<SelectInst>(UserI)) {
Follow = true;
return true;
}
if (isa<StoreInst>(UserI) || isa<LoadInst>(UserI) ||
isa<ReturnInst>(UserI))
return true;
// Unknown user.
return false;
};
if (!A.checkForAllUses(Pred, *this, AssociatedValue))
return indicatePessimisticFixpoint();
return ChangeStatus::UNCHANGED;
}
};
/// NoFree attribute for a call site argument.
struct AANoFreeArgument final : AANoFreeFloating {
AANoFreeArgument(const IRPosition &IRP, Attributor &A)
: AANoFreeFloating(IRP, A) {}
/// See AbstractAttribute::trackStatistics()
void trackStatistics() const override { STATS_DECLTRACK_ARG_ATTR(nofree) }
};
/// NoFree attribute for call site arguments.
struct AANoFreeCallSiteArgument final : AANoFreeFloating {
AANoFreeCallSiteArgument(const IRPosition &IRP, Attributor &A)
: AANoFreeFloating(IRP, A) {}
/// See AbstractAttribute::updateImpl(...).
ChangeStatus updateImpl(Attributor &A) override {
// TODO: Once we have call site specific value information we can provide
// call site specific liveness information and then it makes
// sense to specialize attributes for call sites arguments instead of
// redirecting requests to the callee argument.
Argument *Arg = getAssociatedArgument();
if (!Arg)
return indicatePessimisticFixpoint();
const IRPosition &ArgPos = IRPosition::argument(*Arg);
bool IsKnown;
if (AA::hasAssumedIRAttr<Attribute::NoFree>(A, this, ArgPos,
DepClassTy::REQUIRED, IsKnown))
return ChangeStatus::UNCHANGED;
return indicatePessimisticFixpoint();
}
/// See AbstractAttribute::trackStatistics()
void trackStatistics() const override{STATS_DECLTRACK_CSARG_ATTR(nofree)};
};
/// NoFree attribute for function return value.
struct AANoFreeReturned final : AANoFreeFloating {
AANoFreeReturned(const IRPosition &IRP, Attributor &A)
: AANoFreeFloating(IRP, A) {
llvm_unreachable("NoFree is not applicable to function returns!");
}
/// See AbstractAttribute::initialize(...).
void initialize(Attributor &A) override {
llvm_unreachable("NoFree is not applicable to function returns!");
}
/// See AbstractAttribute::updateImpl(...).
ChangeStatus updateImpl(Attributor &A) override {
llvm_unreachable("NoFree is not applicable to function returns!");
}
/// See AbstractAttribute::trackStatistics()
void trackStatistics() const override {}
};
/// NoFree attribute deduction for a call site return value.
struct AANoFreeCallSiteReturned final : AANoFreeFloating {
AANoFreeCallSiteReturned(const IRPosition &IRP, Attributor &A)
: AANoFreeFloating(IRP, A) {}
ChangeStatus manifest(Attributor &A) override {
return ChangeStatus::UNCHANGED;
}
/// See AbstractAttribute::trackStatistics()
void trackStatistics() const override { STATS_DECLTRACK_CSRET_ATTR(nofree) }
};
} // namespace
/// ------------------------ NonNull Argument Attribute ------------------------
bool AANonNull::isImpliedByIR(Attributor &A, const IRPosition &IRP,
Attribute::AttrKind ImpliedAttributeKind,
bool IgnoreSubsumingPositions) {
SmallVector<Attribute::AttrKind, 2> AttrKinds;
AttrKinds.push_back(Attribute::NonNull);
if (!NullPointerIsDefined(IRP.getAnchorScope(),
IRP.getAssociatedType()->getPointerAddressSpace()))
AttrKinds.push_back(Attribute::Dereferenceable);
if (A.hasAttr(IRP, AttrKinds, IgnoreSubsumingPositions, Attribute::NonNull))
return true;
DominatorTree *DT = nullptr;
AssumptionCache *AC = nullptr;
InformationCache &InfoCache = A.getInfoCache();
if (const Function *Fn = IRP.getAnchorScope()) {
if (!Fn->isDeclaration()) {
DT = InfoCache.getAnalysisResultForFunction<DominatorTreeAnalysis>(*Fn);
AC = InfoCache.getAnalysisResultForFunction<AssumptionAnalysis>(*Fn);
}
}
SmallVector<AA::ValueAndContext> Worklist;
if (IRP.getPositionKind() != IRP_RETURNED) {
Worklist.push_back({IRP.getAssociatedValue(), IRP.getCtxI()});
} else {
bool UsedAssumedInformation = false;
if (!A.checkForAllInstructions(
[&](Instruction &I) {
Worklist.push_back({*cast<ReturnInst>(I).getReturnValue(), &I});
return true;
},
IRP.getAssociatedFunction(), nullptr, {Instruction::Ret},
UsedAssumedInformation))
return false;
}
if (llvm::any_of(Worklist, [&](AA::ValueAndContext VAC) {
return !isKnownNonZero(
VAC.getValue(),
SimplifyQuery(A.getDataLayout(), DT, AC, VAC.getCtxI()));
}))
return false;
A.manifestAttrs(IRP, {Attribute::get(IRP.getAnchorValue().getContext(),
Attribute::NonNull)});
return true;
}
namespace {
static int64_t getKnownNonNullAndDerefBytesForUse(
Attributor &A, const AbstractAttribute &QueryingAA, Value &AssociatedValue,
const Use *U, const Instruction *I, bool &IsNonNull, bool &TrackUse) {
TrackUse = false;
const Value *UseV = U->get();
if (!UseV->getType()->isPointerTy())
return 0;
// We need to follow common pointer manipulation uses to the accesses they
// feed into. We can try to be smart to avoid looking through things we do not
// like for now, e.g., non-inbounds GEPs.
if (isa<CastInst>(I)) {
TrackUse = true;
return 0;
}
if (isa<GetElementPtrInst>(I)) {
TrackUse = true;
return 0;
}
Type *PtrTy = UseV->getType();
const Function *F = I->getFunction();
bool NullPointerIsDefined =
F ? llvm::NullPointerIsDefined(F, PtrTy->getPointerAddressSpace()) : true;
const DataLayout &DL = A.getInfoCache().getDL();
if (const auto *CB = dyn_cast<CallBase>(I)) {
if (CB->isBundleOperand(U)) {
if (RetainedKnowledge RK = getKnowledgeFromUse(
U, {Attribute::NonNull, Attribute::Dereferenceable})) {
IsNonNull |=
(RK.AttrKind == Attribute::NonNull || !NullPointerIsDefined);
return RK.ArgValue;
}
return 0;
}
if (CB->isCallee(U)) {
IsNonNull |= !NullPointerIsDefined;
return 0;
}
unsigned ArgNo = CB->getArgOperandNo(U);
IRPosition IRP = IRPosition::callsite_argument(*CB, ArgNo);
// As long as we only use known information there is no need to track
// dependences here.
bool IsKnownNonNull;
AA::hasAssumedIRAttr<Attribute::NonNull>(A, &QueryingAA, IRP,
DepClassTy::NONE, IsKnownNonNull);
IsNonNull |= IsKnownNonNull;
auto *DerefAA =
A.getAAFor<AADereferenceable>(QueryingAA, IRP, DepClassTy::NONE);
return DerefAA ? DerefAA->getKnownDereferenceableBytes() : 0;
}
std::optional<MemoryLocation> Loc = MemoryLocation::getOrNone(I);
if (!Loc || Loc->Ptr != UseV || !Loc->Size.isPrecise() ||
Loc->Size.isScalable() || I->isVolatile())
return 0;
int64_t Offset;
const Value *Base =
getMinimalBaseOfPointer(A, QueryingAA, Loc->Ptr, Offset, DL);
if (Base && Base == &AssociatedValue) {
int64_t DerefBytes = Loc->Size.getValue() + Offset;
IsNonNull |= !NullPointerIsDefined;
return std::max(int64_t(0), DerefBytes);
}
/// Corner case when an offset is 0.
Base = GetPointerBaseWithConstantOffset(Loc->Ptr, Offset, DL,
/*AllowNonInbounds*/ true);
if (Base && Base == &AssociatedValue && Offset == 0) {
int64_t DerefBytes = Loc->Size.getValue();
IsNonNull |= !NullPointerIsDefined;
return std::max(int64_t(0), DerefBytes);
}
return 0;
}
struct AANonNullImpl : AANonNull {
AANonNullImpl(const IRPosition &IRP, Attributor &A) : AANonNull(IRP, A) {}
/// See AbstractAttribute::initialize(...).
void initialize(Attributor &A) override {
Value &V = *getAssociatedValue().stripPointerCasts();
if (isa<ConstantPointerNull>(V)) {
indicatePessimisticFixpoint();
return;
}
if (Instruction *CtxI = getCtxI())
followUsesInMBEC(*this, A, getState(), *CtxI);
}
/// See followUsesInMBEC
bool followUseInMBEC(Attributor &A, const Use *U, const Instruction *I,
AANonNull::StateType &State) {
bool IsNonNull = false;
bool TrackUse = false;
getKnownNonNullAndDerefBytesForUse(A, *this, getAssociatedValue(), U, I,
IsNonNull, TrackUse);
State.setKnown(IsNonNull);
return TrackUse;
}
/// See AbstractAttribute::getAsStr().
const std::string getAsStr(Attributor *A) const override {
return getAssumed() ? "nonnull" : "may-null";
}
};
/// NonNull attribute for a floating value.
struct AANonNullFloating : public AANonNullImpl {
AANonNullFloating(const IRPosition &IRP, Attributor &A)
: AANonNullImpl(IRP, A) {}
/// See AbstractAttribute::updateImpl(...).
ChangeStatus updateImpl(Attributor &A) override {
auto CheckIRP = [&](const IRPosition &IRP) {
bool IsKnownNonNull;
return AA::hasAssumedIRAttr<Attribute::NonNull>(
A, *this, IRP, DepClassTy::OPTIONAL, IsKnownNonNull);
};
bool Stripped;
bool UsedAssumedInformation = false;
Value *AssociatedValue = &getAssociatedValue();
SmallVector<AA::ValueAndContext> Values;
if (!A.getAssumedSimplifiedValues(getIRPosition(), *this, Values,
AA::AnyScope, UsedAssumedInformation))
Stripped = false;
else
Stripped =
Values.size() != 1 || Values.front().getValue() != AssociatedValue;
if (!Stripped) {
bool IsKnown;
if (auto *PHI = dyn_cast<PHINode>(AssociatedValue))
if (llvm::all_of(PHI->incoming_values(), [&](Value *Op) {
return AA::hasAssumedIRAttr<Attribute::NonNull>(
A, this, IRPosition::value(*Op), DepClassTy::OPTIONAL,
IsKnown);
}))
return ChangeStatus::UNCHANGED;
if (auto *Select = dyn_cast<SelectInst>(AssociatedValue))
if (AA::hasAssumedIRAttr<Attribute::NonNull>(
A, this, IRPosition::value(*Select->getFalseValue()),
DepClassTy::OPTIONAL, IsKnown) &&
AA::hasAssumedIRAttr<Attribute::NonNull>(
A, this, IRPosition::value(*Select->getTrueValue()),
DepClassTy::OPTIONAL, IsKnown))
return ChangeStatus::UNCHANGED;