//===- 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 #include #include #include using namespace llvm; #define DEBUG_TYPE "attributor" static cl::opt ManifestInternal( "attributor-manifest-internal", cl::Hidden, cl::desc("Manifest Attributor internal string attributes."), cl::init(false)); static cl::opt 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 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 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(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 &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(Ty)) return isDenselyPacked(SeqTy->getElementType(), DL); // For array types, check for padding within members. if (ArrayType *SeqTy = dyn_cast(Ty)) return isDenselyPacked(SeqTy->getElementType(), DL); if (!isa(Ty)) return true; // Check for padding within and between elements of a struct. StructType *StructTy = cast(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(I)) { return LI->getPointerOperand(); } if (auto *SI = dyn_cast(I)) { return SI->getPointerOperand(); } if (auto *CXI = dyn_cast(I)) { return CXI->getPointerOperand(); } if (auto *RMWI = dyn_cast(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 &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(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 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 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( A, &QueryingAA, RVPos, DepClassTy::REQUIRED, IsKnown); } const AAType *AA = A.getAAFor(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 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( 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(this->getState(), S); } }; /// Clamp the information known at all call sites for a given argument /// (identified by \p QueryingAA) into \p S. template 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 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( A, &QueryingAA, ACSArgPos, DepClassTy::REQUIRED, IsKnown); } const AAType *AA = A.getAAFor(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 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( A, &QueryingAttribute, CBArgPos, DepClassTy::REQUIRED, IsKnown); } const auto *AA = A.getAAFor(QueryingAttribute, CBArgPos, DepClassTy::REQUIRED); if (!AA) return false; const StateType &CBArgumentState = static_cast(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 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( A, *this, this->getIRPosition(), S); if (Success) return clampStateAndIndicateChange(this->getState(), S); } clampCallSiteArgumentStates(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(this->getState(), S); } }; /// Helper class for generic replication: function returned -> cs returned. template 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(this->getAnchorValue()); if (IntroduceCallBaseContext) LLVM_DEBUG(dbgs() << "[Attributor] Introducing call base context:" << CB << "\n"); ChangeStatus Changed = ChangeStatus::UNCHANGED; auto CalleePred = [&](ArrayRef 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( A, this, FnPos, DepClassTy::REQUIRED, IsKnown)) return false; continue; } const AAType *AA = A.getAAFor(*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 static void followUsesInContext(AAType &AA, Attributor &A, MustBeExecutedContextExplorer &Explorer, const Instruction *CtxI, SetVector &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(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 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 Uses; for (const Use &U : AA.getIRPosition().getAssociatedValue().uses()) Uses.insert(&U); followUsesInContext(AA, A, *Explorer, &CtxI, Uses, S); if (S.isAtFixpoint()) return; SmallVector BrInsts; auto Pred = [&](const Instruction *I) { if (const BranchInst *Br = dyn_cast(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 : DenseMapInfo { 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 { static inline AA::RangeTy getEmptyKey() { auto EmptyKey = DenseMapInfo::getEmptyKey(); return AA::RangeTy{EmptyKey, EmptyKey}; } static inline AA::RangeTy getTombstoneKey() { auto TombstoneKey = DenseMapInfo::getTombstoneKey(); return AA::RangeTy{TombstoneKey, TombstoneKey}; } static unsigned getHashValue(const AA::RangeTy &Range) { return detail::combineHashValue( DenseMapInfo::getHashValue(Range.Offset), DenseMapInfo::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 { using Base = DenseMapInfo; 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 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 AccessList; AAPointerInfo::OffsetBinsTy OffsetBins; DenseMap> RemoteIMap; /// See AAPointerInfo::forallInterferingAccesses. bool forallInterferingAccesses( AA::RangeTy Range, function_ref 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 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 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; 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 { using BaseTy = StateWrapper; 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") : ""); } /// 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 CB) const override { return State::forallInterferingAccesses(Range, CB); } bool forallInterferingAccesses( Attributor &A, const AbstractAttribute &QueryingAA, Instruction &I, bool FindInterferingWrites, bool FindInterferingReads, function_ref UserCB, bool &HasBeenWrittenTo, AA::RangeTy &Range, function_ref SkipCB) const override { HasBeenWrittenTo = false; SmallPtrSet DominatingWrites; SmallVector, 8> InterferingAccesses; Function &Scope = *I.getFunction(); bool IsKnownNoSync; bool IsAssumedNoSync = AA::hasAssumedIRAttr( A, &QueryingAA, IRPosition::function(Scope), DepClassTy::OPTIONAL, IsKnownNoSync); const auto *ExecDomainAA = A.lookupAAFor( 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( 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( 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(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 IsLiveInCalleeCB; if (auto *AI = dyn_cast(&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( A, this, IRPosition::function(*AIFn), DepClassTy::OPTIONAL, IsKnownNoRecurse)) { IsLiveInCalleeCB = [AIFn](const Function &Fn) { return AIFn != &Fn; }; } } else if (auto *GV = dyn_cast(&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(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( 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(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(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(Acc.getWrittenValue())) O << " - c: func " << Acc.getWrittenValue()->getName() << "\n"; else if (Acc.getWrittenValue()) O << " - c: " << *Acc.getWrittenValue() << "\n"; else O << " - c: \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 Content, AccessKind Kind, SmallVectorImpl &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 = llvm::unique(Offsets); Offsets.erase(Last, Offsets.end()); VectorType *VT = dyn_cast(&Ty); if (!VT || VT->getElementCount().isScalable() || !Content.value_or(nullptr) || !isa(*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(*Content); Type *Int32Ty = Type::getInt32Ty(ElementType->getContext()); SmallVector 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 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( *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 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. assert(OffsetInfoMap.contains(CurPtr) && "CurPtr does not exist in the map!"); auto &UsrOI = OffsetInfoMap[Usr]; auto &PtrOI = OffsetInfoMap[CurPtr]; assert(!PtrOI.isUnassigned() && "Cannot pass through if the input Ptr was not visited!"); UsrOI.merge(PtrOI); Follow = true; return true; }; 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(Usr)) { if (CE->isCast()) return HandlePassthroughUser(Usr, CurPtr, Follow); if (!isa(CE)) { LLVM_DEBUG(dbgs() << "[AAPointerInfo] Unhandled constant user " << *CE << "\n"); return false; } } if (auto *GEP = dyn_cast(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(Usr)) return false; if (isa(Usr) || isa(Usr) || isa(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 (auto *PHI = dyn_cast(Usr)) { // Note the order here, the Usr access might change the map, CurPtr is // already in it though. bool IsFirstPHIUser = !OffsetInfoMap.count(PHI); auto &UsrOI = OffsetInfoMap[PHI]; 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 " << *PHI << "\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 " << *PHI << " (base: " << *CurPtrBase << ")\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. const auto *CI = A.getInfoCache().getAnalysisResultForFunction( *PHI->getFunction()); if (mayBeInCycle(CI, cast(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 " << *PHI << "\n"); UsrOI.setUnknown(); Follow = true; return true; } UsrOI.merge(PtrOI); Follow = true; return true; } if (auto *LoadI = dyn_cast(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(&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(SuccBB->getTerminator())) continue; return false; } if (IsImpactedInRange(LoadI->getNextNonDebugInstruction(), BB->getTerminator())) return false; if (IsImpactedInRange(&IntrBB->front(), &IntrI)) return false; } return true; }; std::pair Assumption; for (const Use &LoadU : LoadI->uses()) { if (auto *CmpI = dyn_cast(LoadU.getUser())) { if (!CmpI->isEquality() || !CmpI->isTrueWhenEqual()) continue; for (const Use &CmpU : CmpI->uses()) { if (auto *IntrI = dyn_cast(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 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 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 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(Usr)) return HandleStoreLike(*StoreI, StoreI->getValueOperand(), *StoreI->getValueOperand()->getType(), {StoreI->getValueOperand()}, AccessKind::AK_W); if (auto *RMWI = dyn_cast(Usr)) return HandleStoreLike(*RMWI, nullptr, *RMWI->getValOperand()->getType(), {RMWI->getValOperand()}, AccessKind::AK_RW); if (auto *CXI = dyn_cast(Usr)) return HandleStoreLike( *CXI, nullptr, *CXI->getNewValOperand()->getType(), {CXI->getCompareOperand(), CXI->getNewValOperand()}, AccessKind::AK_RW); if (auto *CB = dyn_cast(Usr)) { if (CB->isLifetimeStartOrEnd()) return true; const auto *TLI = A.getInfoCache().getTargetLibraryInfoForFunction(*CB->getFunction()); if (getFreedOperand(CB, TLI) == U) return true; if (CB->isArgOperand(&U)) { unsigned ArgNo = CB->getArgOperandNo(&U); const auto *CSArgPI = A.getAAFor( *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(getCtxI())) { ConstantInt *Length = dyn_cast(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(*this, ArgPos, DepClassTy::REQUIRED); if (ArgAA && ArgAA->getState().isValidState()) return translateAndAddStateFromCallee(A, *ArgAA, *cast(getCtxI())); if (!Arg->getParent()->isDeclaration()) return indicatePessimisticFixpoint(); } bool IsKnownNoCapture; if (!AA::hasAssumedIRAttr( 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( 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(&I)) { bool IsKnownNoUnwind; return AA::hasAssumedIRAttr( 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 { AANoUnwindCallSite(const IRPosition &IRP, Attributor &A) : AACalleeToCallSite(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(I)) // All legal orderings for fence are stronger than monotonic. return FI->getSyncScopeID() != SyncScope::SingleThread; if (auto *AI = dyn_cast(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(I)->getOrdering(); break; case Instruction::Store: Ordering = cast(I)->getOrdering(); break; case Instruction::Load: Ordering = cast(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(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(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(I); if (AA::hasAssumedIRAttr( 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 { AANoSyncCallSite(const IRPosition &IRP, Attributor &A) : AACalleeToCallSite(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(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( A, this, IRPosition::callsite_function(cast(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 { AANoFreeCallSite(const IRPosition &IRP, Attributor &A) : AACalleeToCallSite(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(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(U.getUser()); if (auto *CB = dyn_cast(UserI)) { if (CB->isBundleOperand(&U)) return false; if (!CB->isArgOperand(&U)) return true; unsigned ArgNo = CB->getArgOperandNo(&U); bool IsKnown; return AA::hasAssumedIRAttr( A, this, IRPosition::callsite_argument(*CB, ArgNo), DepClassTy::REQUIRED, IsKnown); } if (isa(UserI) || isa(UserI) || isa(UserI) || isa(UserI)) { Follow = true; return true; } if (isa(UserI) || isa(UserI) || isa(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(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 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(*Fn); AC = InfoCache.getAnalysisResultForFunction(*Fn); } } SmallVector 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(I).getReturnValue(), &I}); return true; }, IRP.getAssociatedFunction(), nullptr, {Instruction::Ret}, UsedAssumedInformation, false, /*CheckPotentiallyDead=*/true)) 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(I)) { TrackUse = true; return 0; } if (isa(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(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(A, &QueryingAA, IRP, DepClassTy::NONE, IsKnownNonNull); IsNonNull |= IsKnownNonNull; auto *DerefAA = A.getAAFor(QueryingAA, IRP, DepClassTy::NONE); return DerefAA ? DerefAA->getKnownDereferenceableBytes() : 0; } std::optional 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(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( A, *this, IRP, DepClassTy::OPTIONAL, IsKnownNonNull); }; bool Stripped; bool UsedAssumedInformation = false; Value *AssociatedValue = &getAssociatedValue(); SmallVector 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(AssociatedValue)) if (llvm::all_of(PHI->incoming_values(), [&](Value *Op) { return AA::hasAssumedIRAttr( A, this, IRPosition::value(*Op), DepClassTy::OPTIONAL, IsKnown); })) return ChangeStatus::UNCHANGED; if (auto *Select = dyn_cast(AssociatedValue)) if (AA::hasAssumedIRAttr( A, this, IRPosition::value(*Select->getFalseValue()), DepClassTy::OPTIONAL, IsKnown) && AA::hasAssumedIRAttr( A, this, IRPosition::value(*Select->getTrueValue()), DepClassTy::OPTIONAL, IsKnown)) return ChangeStatus::UNCHANGED; // If we haven't stripped anything we might still be able to use a // different AA, but only if the IRP changes. Effectively when we // interpret this not as a call site value but as a floating/argument // value. const IRPosition AVIRP = IRPosition::value(*AssociatedValue); if (AVIRP == getIRPosition() || !CheckIRP(AVIRP)) return indicatePessimisticFixpoint(); return ChangeStatus::UNCHANGED; } for (const auto &VAC : Values) if (!CheckIRP(IRPosition::value(*VAC.getValue()))) return indicatePessimisticFixpoint(); return ChangeStatus::UNCHANGED; } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_FNRET_ATTR(nonnull) } }; /// NonNull attribute for function return value. struct AANonNullReturned final : AAReturnedFromReturnedValues { AANonNullReturned(const IRPosition &IRP, Attributor &A) : AAReturnedFromReturnedValues(IRP, A) { } /// See AbstractAttribute::getAsStr(). const std::string getAsStr(Attributor *A) const override { return getAssumed() ? "nonnull" : "may-null"; } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_FNRET_ATTR(nonnull) } }; /// NonNull attribute for function argument. struct AANonNullArgument final : AAArgumentFromCallSiteArguments { AANonNullArgument(const IRPosition &IRP, Attributor &A) : AAArgumentFromCallSiteArguments(IRP, A) {} /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_ARG_ATTR(nonnull) } }; struct AANonNullCallSiteArgument final : AANonNullFloating { AANonNullCallSiteArgument(const IRPosition &IRP, Attributor &A) : AANonNullFloating(IRP, A) {} /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_CSARG_ATTR(nonnull) } }; /// NonNull attribute for a call site return position. struct AANonNullCallSiteReturned final : AACalleeToCallSite { AANonNullCallSiteReturned(const IRPosition &IRP, Attributor &A) : AACalleeToCallSite(IRP, A) {} /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_CSRET_ATTR(nonnull) } }; } // namespace /// ------------------------ Must-Progress Attributes -------------------------- namespace { struct AAMustProgressImpl : public AAMustProgress { AAMustProgressImpl(const IRPosition &IRP, Attributor &A) : AAMustProgress(IRP, A) {} /// See AbstractAttribute::initialize(...). void initialize(Attributor &A) override { bool IsKnown; assert(!AA::hasAssumedIRAttr( A, nullptr, getIRPosition(), DepClassTy::NONE, IsKnown)); (void)IsKnown; } /// See AbstractAttribute::getAsStr() const std::string getAsStr(Attributor *A) const override { return getAssumed() ? "mustprogress" : "may-not-progress"; } }; struct AAMustProgressFunction final : AAMustProgressImpl { AAMustProgressFunction(const IRPosition &IRP, Attributor &A) : AAMustProgressImpl(IRP, A) {} /// See AbstractAttribute::updateImpl(...). ChangeStatus updateImpl(Attributor &A) override { bool IsKnown; if (AA::hasAssumedIRAttr( A, this, getIRPosition(), DepClassTy::OPTIONAL, IsKnown)) { if (IsKnown) return indicateOptimisticFixpoint(); return ChangeStatus::UNCHANGED; } auto CheckForMustProgress = [&](AbstractCallSite ACS) { IRPosition IPos = IRPosition::callsite_function(*ACS.getInstruction()); bool IsKnownMustProgress; return AA::hasAssumedIRAttr( A, this, IPos, DepClassTy::REQUIRED, IsKnownMustProgress, /* IgnoreSubsumingPositions */ true); }; bool AllCallSitesKnown = true; if (!A.checkForAllCallSites(CheckForMustProgress, *this, /* RequireAllCallSites */ true, AllCallSitesKnown)) return indicatePessimisticFixpoint(); return ChangeStatus::UNCHANGED; } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_FN_ATTR(mustprogress) } }; /// MustProgress attribute deduction for a call sites. struct AAMustProgressCallSite final : AAMustProgressImpl { AAMustProgressCallSite(const IRPosition &IRP, Attributor &A) : AAMustProgressImpl(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. const IRPosition &FnPos = IRPosition::function(*getAnchorScope()); bool IsKnownMustProgress; if (!AA::hasAssumedIRAttr( A, this, FnPos, DepClassTy::REQUIRED, IsKnownMustProgress)) return indicatePessimisticFixpoint(); return ChangeStatus::UNCHANGED; } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_CS_ATTR(mustprogress); } }; } // namespace /// ------------------------ No-Recurse Attributes ---------------------------- namespace { struct AANoRecurseImpl : public AANoRecurse { AANoRecurseImpl(const IRPosition &IRP, Attributor &A) : AANoRecurse(IRP, A) {} /// See AbstractAttribute::initialize(...). void initialize(Attributor &A) override { bool IsKnown; assert(!AA::hasAssumedIRAttr( A, nullptr, getIRPosition(), DepClassTy::NONE, IsKnown)); (void)IsKnown; } /// See AbstractAttribute::getAsStr() const std::string getAsStr(Attributor *A) const override { return getAssumed() ? "norecurse" : "may-recurse"; } }; struct AANoRecurseFunction final : AANoRecurseImpl { AANoRecurseFunction(const IRPosition &IRP, Attributor &A) : AANoRecurseImpl(IRP, A) {} /// See AbstractAttribute::updateImpl(...). ChangeStatus updateImpl(Attributor &A) override { // If all live call sites are known to be no-recurse, we are as well. auto CallSitePred = [&](AbstractCallSite ACS) { bool IsKnownNoRecurse; if (!AA::hasAssumedIRAttr( A, this, IRPosition::function(*ACS.getInstruction()->getFunction()), DepClassTy::NONE, IsKnownNoRecurse)) return false; return IsKnownNoRecurse; }; bool UsedAssumedInformation = false; if (A.checkForAllCallSites(CallSitePred, *this, true, UsedAssumedInformation)) { // If we know all call sites and all are known no-recurse, we are done. // If all known call sites, which might not be all that exist, are known // to be no-recurse, we are not done but we can continue to assume // no-recurse. If one of the call sites we have not visited will become // live, another update is triggered. if (!UsedAssumedInformation) indicateOptimisticFixpoint(); return ChangeStatus::UNCHANGED; } const AAInterFnReachability *EdgeReachability = A.getAAFor(*this, getIRPosition(), DepClassTy::REQUIRED); if (EdgeReachability && EdgeReachability->canReach(A, *getAnchorScope())) return indicatePessimisticFixpoint(); return ChangeStatus::UNCHANGED; } void trackStatistics() const override { STATS_DECLTRACK_FN_ATTR(norecurse) } }; /// NoRecurse attribute deduction for a call sites. struct AANoRecurseCallSite final : AACalleeToCallSite { AANoRecurseCallSite(const IRPosition &IRP, Attributor &A) : AACalleeToCallSite(IRP, A) {} /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_CS_ATTR(norecurse); } }; } // namespace /// ------------------------ No-Convergent Attribute -------------------------- namespace { struct AANonConvergentImpl : public AANonConvergent { AANonConvergentImpl(const IRPosition &IRP, Attributor &A) : AANonConvergent(IRP, A) {} /// See AbstractAttribute::getAsStr() const std::string getAsStr(Attributor *A) const override { return getAssumed() ? "non-convergent" : "may-be-convergent"; } }; struct AANonConvergentFunction final : AANonConvergentImpl { AANonConvergentFunction(const IRPosition &IRP, Attributor &A) : AANonConvergentImpl(IRP, A) {} /// See AbstractAttribute::updateImpl(...). ChangeStatus updateImpl(Attributor &A) override { // If all function calls are known to not be convergent, we are not // convergent. auto CalleeIsNotConvergent = [&](Instruction &Inst) { CallBase &CB = cast(Inst); auto *Callee = dyn_cast_if_present(CB.getCalledOperand()); if (!Callee || Callee->isIntrinsic()) { return false; } if (Callee->isDeclaration()) { return !Callee->hasFnAttribute(Attribute::Convergent); } const auto *ConvergentAA = A.getAAFor( *this, IRPosition::function(*Callee), DepClassTy::REQUIRED); return ConvergentAA && ConvergentAA->isAssumedNotConvergent(); }; bool UsedAssumedInformation = false; if (!A.checkForAllCallLikeInstructions(CalleeIsNotConvergent, *this, UsedAssumedInformation)) { return indicatePessimisticFixpoint(); } return ChangeStatus::UNCHANGED; } ChangeStatus manifest(Attributor &A) override { if (isKnownNotConvergent() && A.hasAttr(getIRPosition(), Attribute::Convergent)) { A.removeAttrs(getIRPosition(), {Attribute::Convergent}); return ChangeStatus::CHANGED; } return ChangeStatus::UNCHANGED; } void trackStatistics() const override { STATS_DECLTRACK_FN_ATTR(convergent) } }; } // namespace /// -------------------- Undefined-Behavior Attributes ------------------------ namespace { struct AAUndefinedBehaviorImpl : public AAUndefinedBehavior { AAUndefinedBehaviorImpl(const IRPosition &IRP, Attributor &A) : AAUndefinedBehavior(IRP, A) {} /// See AbstractAttribute::updateImpl(...). // through a pointer (i.e. also branches etc.) ChangeStatus updateImpl(Attributor &A) override { const size_t UBPrevSize = KnownUBInsts.size(); const size_t NoUBPrevSize = AssumedNoUBInsts.size(); auto InspectMemAccessInstForUB = [&](Instruction &I) { // Lang ref now states volatile store is not UB, let's skip them. if (I.isVolatile() && I.mayWriteToMemory()) return true; // Skip instructions that are already saved. if (AssumedNoUBInsts.count(&I) || KnownUBInsts.count(&I)) return true; // If we reach here, we know we have an instruction // that accesses memory through a pointer operand, // for which getPointerOperand() should give it to us. Value *PtrOp = const_cast(getPointerOperand(&I, /* AllowVolatile */ true)); assert(PtrOp && "Expected pointer operand of memory accessing instruction"); // Either we stopped and the appropriate action was taken, // or we got back a simplified value to continue. std::optional SimplifiedPtrOp = stopOnUndefOrAssumed(A, PtrOp, &I); if (!SimplifiedPtrOp || !*SimplifiedPtrOp) return true; const Value *PtrOpVal = *SimplifiedPtrOp; // A memory access through a pointer is considered UB // only if the pointer has constant null value. // TODO: Expand it to not only check constant values. if (!isa(PtrOpVal)) { AssumedNoUBInsts.insert(&I); return true; } const Type *PtrTy = PtrOpVal->getType(); // Because we only consider instructions inside functions, // assume that a parent function exists. const Function *F = I.getFunction(); // A memory access using constant null pointer is only considered UB // if null pointer is _not_ defined for the target platform. if (llvm::NullPointerIsDefined(F, PtrTy->getPointerAddressSpace())) AssumedNoUBInsts.insert(&I); else KnownUBInsts.insert(&I); return true; }; auto InspectBrInstForUB = [&](Instruction &I) { // A conditional branch instruction is considered UB if it has `undef` // condition. // Skip instructions that are already saved. if (AssumedNoUBInsts.count(&I) || KnownUBInsts.count(&I)) return true; // We know we have a branch instruction. auto *BrInst = cast(&I); // Unconditional branches are never considered UB. if (BrInst->isUnconditional()) return true; // Either we stopped and the appropriate action was taken, // or we got back a simplified value to continue. std::optional SimplifiedCond = stopOnUndefOrAssumed(A, BrInst->getCondition(), BrInst); if (!SimplifiedCond || !*SimplifiedCond) return true; AssumedNoUBInsts.insert(&I); return true; }; auto InspectCallSiteForUB = [&](Instruction &I) { // Check whether a callsite always cause UB or not // Skip instructions that are already saved. if (AssumedNoUBInsts.count(&I) || KnownUBInsts.count(&I)) return true; // Check nonnull and noundef argument attribute violation for each // callsite. CallBase &CB = cast(I); auto *Callee = dyn_cast_if_present(CB.getCalledOperand()); if (!Callee) return true; for (unsigned idx = 0; idx < CB.arg_size(); idx++) { // If current argument is known to be simplified to null pointer and the // corresponding argument position is known to have nonnull attribute, // the argument is poison. Furthermore, if the argument is poison and // the position is known to have noundef attriubte, this callsite is // considered UB. if (idx >= Callee->arg_size()) break; Value *ArgVal = CB.getArgOperand(idx); if (!ArgVal) continue; // Here, we handle three cases. // (1) Not having a value means it is dead. (we can replace the value // with undef) // (2) Simplified to undef. The argument violate noundef attriubte. // (3) Simplified to null pointer where known to be nonnull. // The argument is a poison value and violate noundef attribute. IRPosition CalleeArgumentIRP = IRPosition::callsite_argument(CB, idx); bool IsKnownNoUndef; AA::hasAssumedIRAttr( A, this, CalleeArgumentIRP, DepClassTy::NONE, IsKnownNoUndef); if (!IsKnownNoUndef) continue; bool UsedAssumedInformation = false; std::optional SimplifiedVal = A.getAssumedSimplified(IRPosition::value(*ArgVal), *this, UsedAssumedInformation, AA::Interprocedural); if (UsedAssumedInformation) continue; if (SimplifiedVal && !*SimplifiedVal) return true; if (!SimplifiedVal || isa(**SimplifiedVal)) { KnownUBInsts.insert(&I); continue; } if (!ArgVal->getType()->isPointerTy() || !isa(**SimplifiedVal)) continue; bool IsKnownNonNull; AA::hasAssumedIRAttr( A, this, CalleeArgumentIRP, DepClassTy::NONE, IsKnownNonNull); if (IsKnownNonNull) KnownUBInsts.insert(&I); } return true; }; auto InspectReturnInstForUB = [&](Instruction &I) { auto &RI = cast(I); // Either we stopped and the appropriate action was taken, // or we got back a simplified return value to continue. std::optional SimplifiedRetValue = stopOnUndefOrAssumed(A, RI.getReturnValue(), &I); if (!SimplifiedRetValue || !*SimplifiedRetValue) return true; // Check if a return instruction always cause UB or not // Note: It is guaranteed that the returned position of the anchor // scope has noundef attribute when this is called. // We also ensure the return position is not "assumed dead" // because the returned value was then potentially simplified to // `undef` in AAReturnedValues without removing the `noundef` // attribute yet. // When the returned position has noundef attriubte, UB occurs in the // following cases. // (1) Returned value is known to be undef. // (2) The value is known to be a null pointer and the returned // position has nonnull attribute (because the returned value is // poison). if (isa(*SimplifiedRetValue)) { bool IsKnownNonNull; AA::hasAssumedIRAttr( A, this, IRPosition::returned(*getAnchorScope()), DepClassTy::NONE, IsKnownNonNull); if (IsKnownNonNull) KnownUBInsts.insert(&I); } return true; }; bool UsedAssumedInformation = false; A.checkForAllInstructions(InspectMemAccessInstForUB, *this, {Instruction::Load, Instruction::Store, Instruction::AtomicCmpXchg, Instruction::AtomicRMW}, UsedAssumedInformation, /* CheckBBLivenessOnly */ true); A.checkForAllInstructions(InspectBrInstForUB, *this, {Instruction::Br}, UsedAssumedInformation, /* CheckBBLivenessOnly */ true); A.checkForAllCallLikeInstructions(InspectCallSiteForUB, *this, UsedAssumedInformation); // If the returned position of the anchor scope has noundef attriubte, check // all returned instructions. if (!getAnchorScope()->getReturnType()->isVoidTy()) { const IRPosition &ReturnIRP = IRPosition::returned(*getAnchorScope()); if (!A.isAssumedDead(ReturnIRP, this, nullptr, UsedAssumedInformation)) { bool IsKnownNoUndef; AA::hasAssumedIRAttr( A, this, ReturnIRP, DepClassTy::NONE, IsKnownNoUndef); if (IsKnownNoUndef) A.checkForAllInstructions(InspectReturnInstForUB, *this, {Instruction::Ret}, UsedAssumedInformation, /* CheckBBLivenessOnly */ true); } } if (NoUBPrevSize != AssumedNoUBInsts.size() || UBPrevSize != KnownUBInsts.size()) return ChangeStatus::CHANGED; return ChangeStatus::UNCHANGED; } bool isKnownToCauseUB(Instruction *I) const override { return KnownUBInsts.count(I); } bool isAssumedToCauseUB(Instruction *I) const override { // In simple words, if an instruction is not in the assumed to _not_ // cause UB, then it is assumed UB (that includes those // in the KnownUBInsts set). The rest is boilerplate // is to ensure that it is one of the instructions we test // for UB. switch (I->getOpcode()) { case Instruction::Load: case Instruction::Store: case Instruction::AtomicCmpXchg: case Instruction::AtomicRMW: return !AssumedNoUBInsts.count(I); case Instruction::Br: { auto *BrInst = cast(I); if (BrInst->isUnconditional()) return false; return !AssumedNoUBInsts.count(I); } break; default: return false; } return false; } ChangeStatus manifest(Attributor &A) override { if (KnownUBInsts.empty()) return ChangeStatus::UNCHANGED; for (Instruction *I : KnownUBInsts) A.changeToUnreachableAfterManifest(I); return ChangeStatus::CHANGED; } /// See AbstractAttribute::getAsStr() const std::string getAsStr(Attributor *A) const override { return getAssumed() ? "undefined-behavior" : "no-ub"; } /// Note: The correctness of this analysis depends on the fact that the /// following 2 sets will stop changing after some point. /// "Change" here means that their size changes. /// The size of each set is monotonically increasing /// (we only add items to them) and it is upper bounded by the number of /// instructions in the processed function (we can never save more /// elements in either set than this number). Hence, at some point, /// they will stop increasing. /// Consequently, at some point, both sets will have stopped /// changing, effectively making the analysis reach a fixpoint. /// Note: These 2 sets are disjoint and an instruction can be considered /// one of 3 things: /// 1) Known to cause UB (AAUndefinedBehavior could prove it) and put it in /// the KnownUBInsts set. /// 2) Assumed to cause UB (in every updateImpl, AAUndefinedBehavior /// has a reason to assume it). /// 3) Assumed to not cause UB. very other instruction - AAUndefinedBehavior /// could not find a reason to assume or prove that it can cause UB, /// hence it assumes it doesn't. We have a set for these instructions /// so that we don't reprocess them in every update. /// Note however that instructions in this set may cause UB. protected: /// A set of all live instructions _known_ to cause UB. SmallPtrSet KnownUBInsts; private: /// A set of all the (live) instructions that are assumed to _not_ cause UB. SmallPtrSet AssumedNoUBInsts; // Should be called on updates in which if we're processing an instruction // \p I that depends on a value \p V, one of the following has to happen: // - If the value is assumed, then stop. // - If the value is known but undef, then consider it UB. // - Otherwise, do specific processing with the simplified value. // We return std::nullopt in the first 2 cases to signify that an appropriate // action was taken and the caller should stop. // Otherwise, we return the simplified value that the caller should // use for specific processing. std::optional stopOnUndefOrAssumed(Attributor &A, Value *V, Instruction *I) { bool UsedAssumedInformation = false; std::optional SimplifiedV = A.getAssumedSimplified(IRPosition::value(*V), *this, UsedAssumedInformation, AA::Interprocedural); if (!UsedAssumedInformation) { // Don't depend on assumed values. if (!SimplifiedV) { // If it is known (which we tested above) but it doesn't have a value, // then we can assume `undef` and hence the instruction is UB. KnownUBInsts.insert(I); return std::nullopt; } if (!*SimplifiedV) return nullptr; V = *SimplifiedV; } if (isa(V)) { KnownUBInsts.insert(I); return std::nullopt; } return V; } }; struct AAUndefinedBehaviorFunction final : AAUndefinedBehaviorImpl { AAUndefinedBehaviorFunction(const IRPosition &IRP, Attributor &A) : AAUndefinedBehaviorImpl(IRP, A) {} /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECL(UndefinedBehaviorInstruction, Instruction, "Number of instructions known to have UB"); BUILD_STAT_NAME(UndefinedBehaviorInstruction, Instruction) += KnownUBInsts.size(); } }; } // namespace /// ------------------------ Will-Return Attributes ---------------------------- namespace { // Helper function that checks whether a function has any cycle which we don't // know if it is bounded or not. // Loops with maximum trip count are considered bounded, any other cycle not. static bool mayContainUnboundedCycle(Function &F, Attributor &A) { ScalarEvolution *SE = A.getInfoCache().getAnalysisResultForFunction(F); LoopInfo *LI = A.getInfoCache().getAnalysisResultForFunction(F); // If either SCEV or LoopInfo is not available for the function then we assume // any cycle to be unbounded cycle. // We use scc_iterator which uses Tarjan algorithm to find all the maximal // SCCs.To detect if there's a cycle, we only need to find the maximal ones. if (!SE || !LI) { for (scc_iterator SCCI = scc_begin(&F); !SCCI.isAtEnd(); ++SCCI) if (SCCI.hasCycle()) return true; return false; } // If there's irreducible control, the function may contain non-loop cycles. if (mayContainIrreducibleControl(F, LI)) return true; // Any loop that does not have a max trip count is considered unbounded cycle. for (auto *L : LI->getLoopsInPreorder()) { if (!SE->getSmallConstantMaxTripCount(L)) return true; } return false; } struct AAWillReturnImpl : public AAWillReturn { AAWillReturnImpl(const IRPosition &IRP, Attributor &A) : AAWillReturn(IRP, A) {} /// See AbstractAttribute::initialize(...). void initialize(Attributor &A) override { bool IsKnown; assert(!AA::hasAssumedIRAttr( A, nullptr, getIRPosition(), DepClassTy::NONE, IsKnown)); (void)IsKnown; } /// Check for `mustprogress` and `readonly` as they imply `willreturn`. bool isImpliedByMustprogressAndReadonly(Attributor &A, bool KnownOnly) { if (!A.hasAttr(getIRPosition(), {Attribute::MustProgress})) return false; bool IsKnown; if (AA::isAssumedReadOnly(A, getIRPosition(), *this, IsKnown)) return IsKnown || !KnownOnly; return false; } /// See AbstractAttribute::updateImpl(...). ChangeStatus updateImpl(Attributor &A) override { if (isImpliedByMustprogressAndReadonly(A, /* KnownOnly */ false)) return ChangeStatus::UNCHANGED; auto CheckForWillReturn = [&](Instruction &I) { IRPosition IPos = IRPosition::callsite_function(cast(I)); bool IsKnown; if (AA::hasAssumedIRAttr( A, this, IPos, DepClassTy::REQUIRED, IsKnown)) { if (IsKnown) return true; } else { return false; } bool IsKnownNoRecurse; return AA::hasAssumedIRAttr( A, this, IPos, DepClassTy::REQUIRED, IsKnownNoRecurse); }; bool UsedAssumedInformation = false; if (!A.checkForAllCallLikeInstructions(CheckForWillReturn, *this, UsedAssumedInformation)) return indicatePessimisticFixpoint(); return ChangeStatus::UNCHANGED; } /// See AbstractAttribute::getAsStr() const std::string getAsStr(Attributor *A) const override { return getAssumed() ? "willreturn" : "may-noreturn"; } }; struct AAWillReturnFunction final : AAWillReturnImpl { AAWillReturnFunction(const IRPosition &IRP, Attributor &A) : AAWillReturnImpl(IRP, A) {} /// See AbstractAttribute::initialize(...). void initialize(Attributor &A) override { AAWillReturnImpl::initialize(A); Function *F = getAnchorScope(); assert(F && "Did expect an anchor function"); if (F->isDeclaration() || mayContainUnboundedCycle(*F, A)) indicatePessimisticFixpoint(); } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_FN_ATTR(willreturn) } }; /// WillReturn attribute deduction for a call sites. struct AAWillReturnCallSite final : AACalleeToCallSite { AAWillReturnCallSite(const IRPosition &IRP, Attributor &A) : AACalleeToCallSite(IRP, A) {} /// See AbstractAttribute::updateImpl(...). ChangeStatus updateImpl(Attributor &A) override { if (isImpliedByMustprogressAndReadonly(A, /* KnownOnly */ false)) return ChangeStatus::UNCHANGED; return AACalleeToCallSite::updateImpl(A); } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_CS_ATTR(willreturn); } }; } // namespace /// -------------------AAIntraFnReachability Attribute-------------------------- /// All information associated with a reachability query. This boilerplate code /// is used by both AAIntraFnReachability and AAInterFnReachability, with /// different \p ToTy values. template struct ReachabilityQueryInfo { enum class Reachable { No, Yes, }; /// Start here, const Instruction *From = nullptr; /// reach this place, const ToTy *To = nullptr; /// without going through any of these instructions, const AA::InstExclusionSetTy *ExclusionSet = nullptr; /// and remember if it worked: Reachable Result = Reachable::No; /// Precomputed hash for this RQI. unsigned Hash = 0; unsigned computeHashValue() const { assert(Hash == 0 && "Computed hash twice!"); using InstSetDMI = DenseMapInfo; using PairDMI = DenseMapInfo>; return const_cast *>(this)->Hash = detail::combineHashValue(PairDMI ::getHashValue({From, To}), InstSetDMI::getHashValue(ExclusionSet)); } ReachabilityQueryInfo(const Instruction *From, const ToTy *To) : From(From), To(To) {} /// 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, bool MakeUnique) : From(&From), To(&To), ExclusionSet(ES) { 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) {} }; namespace llvm { template struct DenseMapInfo *> { using InstSetDMI = DenseMapInfo; using PairDMI = DenseMapInfo>; static ReachabilityQueryInfo EmptyKey; static ReachabilityQueryInfo TombstoneKey; static inline ReachabilityQueryInfo *getEmptyKey() { return &EmptyKey; } static inline ReachabilityQueryInfo *getTombstoneKey() { return &TombstoneKey; } static unsigned getHashValue(const ReachabilityQueryInfo *RQI) { return RQI->Hash ? RQI->Hash : RQI->computeHashValue(); } static bool isEqual(const ReachabilityQueryInfo *LHS, const ReachabilityQueryInfo *RHS) { if (!PairDMI::isEqual({LHS->From, LHS->To}, {RHS->From, RHS->To})) return false; return InstSetDMI::isEqual(LHS->ExclusionSet, RHS->ExclusionSet); } }; #define DefineKeys(ToTy) \ template <> \ ReachabilityQueryInfo \ DenseMapInfo *>::EmptyKey = \ ReachabilityQueryInfo( \ DenseMapInfo::getEmptyKey(), \ DenseMapInfo::getEmptyKey()); \ template <> \ ReachabilityQueryInfo \ DenseMapInfo *>::TombstoneKey = \ ReachabilityQueryInfo( \ DenseMapInfo::getTombstoneKey(), \ DenseMapInfo::getTombstoneKey()); DefineKeys(Instruction) DefineKeys(Function) #undef DefineKeys } // namespace llvm namespace { template struct CachedReachabilityAA : public BaseTy { using RQITy = ReachabilityQueryInfo; CachedReachabilityAA(const IRPosition &IRP, Attributor &A) : BaseTy(IRP, A) {} /// See AbstractAttribute::isQueryAA. bool isQueryAA() const override { return true; } /// See AbstractAttribute::updateImpl(...). ChangeStatus updateImpl(Attributor &A) override { ChangeStatus Changed = ChangeStatus::UNCHANGED; for (unsigned u = 0, e = QueryVector.size(); u < e; ++u) { RQITy *RQI = QueryVector[u]; if (RQI->Result == RQITy::Reachable::No && isReachableImpl(A, *RQI, /*IsTemporaryRQI=*/false)) Changed = ChangeStatus::CHANGED; } return Changed; } virtual bool isReachableImpl(Attributor &A, RQITy &RQI, bool IsTemporaryRQI) = 0; bool rememberResult(Attributor &A, typename RQITy::Reachable Result, RQITy &RQI, bool UsedExclusionSet, bool IsTemporaryRQI) { RQI.Result = Result; // Remove the temporary RQI from the cache. if (IsTemporaryRQI) QueryCache.erase(&RQI); // Insert a plain RQI (w/o exclusion set) if that makes sense. Two options: // 1) If it is reachable, it doesn't matter if we have an exclusion set for // this query. 2) We did not use the exclusion set, potentially because // there is none. if (Result == RQITy::Reachable::Yes || !UsedExclusionSet) { 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 with the exclusion set. if (IsTemporaryRQI && Result != RQITy::Reachable::Yes && UsedExclusionSet) { 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 && IsTemporaryRQI) A.registerForUpdate(*this); return Result == RQITy::Reachable::Yes; } const std::string getAsStr(Attributor *A) const override { // TODO: Return the number of reachable queries. return "#queries(" + std::to_string(QueryVector.size()) + ")"; } bool checkQueryCache(Attributor &A, RQITy &StackRQI, typename RQITy::Reachable &Result) { if (!this->getState().isValidState()) { Result = RQITy::Reachable::Yes; 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 true; } // Insert a temporary for recursive queries. We will replace it with a // permanent entry later. QueryCache.insert(&StackRQI); return false; } private: SmallVector QueryVector; DenseSet QueryCache; }; struct AAIntraFnReachabilityFunction final : public CachedReachabilityAA { using Base = CachedReachabilityAA; AAIntraFnReachabilityFunction(const IRPosition &IRP, Attributor &A) : Base(IRP, A) { DT = A.getInfoCache().getAnalysisResultForFunction( *IRP.getAssociatedFunction()); } bool isAssumedReachable( Attributor &A, const Instruction &From, const Instruction &To, const AA::InstExclusionSetTy *ExclusionSet) const override { auto *NonConstThis = const_cast(this); if (&From == &To) return true; RQITy StackRQI(A, From, To, ExclusionSet, false); typename RQITy::Reachable Result; if (!NonConstThis->checkQueryCache(A, StackRQI, Result)) return NonConstThis->isReachableImpl(A, StackRQI, /*IsTemporaryRQI=*/true); 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(*this, getIRPosition(), DepClassTy::OPTIONAL); if (LivenessAA && llvm::all_of(DeadEdges, [&](const auto &DeadEdge) { return LivenessAA->isEdgeDead(DeadEdge.first, DeadEdge.second); }) && llvm::all_of(DeadBlocks, [&](const BasicBlock *BB) { return LivenessAA->isAssumedDead(BB); })) { return ChangeStatus::UNCHANGED; } DeadEdges.clear(); DeadBlocks.clear(); return Base::updateImpl(A); } bool isReachableImpl(Attributor &A, RQITy &RQI, bool IsTemporaryRQI) override { const Instruction *Origin = RQI.From; bool UsedExclusionSet = false; 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)) { UsedExclusionSet = true; break; } IP = IP->getNextNode(); } return IP == &To; }; const BasicBlock *FromBB = RQI.From->getParent(); const BasicBlock *ToBB = RQI.To->getParent(); assert(FromBB->getParent() == ToBB->getParent() && "Not an intra-procedural query!"); // Check intra-block reachability, however, other reaching paths are still // possible. if (FromBB == ToBB && WillReachInBlock(*RQI.From, *RQI.To, RQI.ExclusionSet)) return rememberResult(A, RQITy::Reachable::Yes, RQI, UsedExclusionSet, IsTemporaryRQI); // 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, IsTemporaryRQI); const Function *Fn = FromBB->getParent(); SmallPtrSet ExclusionBlocks; if (RQI.ExclusionSet) for (auto *I : *RQI.ExclusionSet) if (I->getFunction() == Fn) ExclusionBlocks.insert(I->getParent()); // Check if we make it out of the FromBB block at all. if (ExclusionBlocks.count(FromBB) && !WillReachInBlock(*RQI.From, *FromBB->getTerminator(), RQI.ExclusionSet)) return rememberResult(A, RQITy::Reachable::No, RQI, true, IsTemporaryRQI); auto *LivenessAA = A.getAAFor(*this, getIRPosition(), DepClassTy::OPTIONAL); if (LivenessAA && LivenessAA->isAssumedDead(ToBB)) { DeadBlocks.insert(ToBB); return rememberResult(A, RQITy::Reachable::No, RQI, UsedExclusionSet, IsTemporaryRQI); } SmallPtrSet Visited; SmallVector Worklist; Worklist.push_back(FromBB); DenseSet> LocalDeadEdges; while (!Worklist.empty()) { const BasicBlock *BB = Worklist.pop_back_val(); if (!Visited.insert(BB).second) continue; for (const BasicBlock *SuccBB : successors(BB)) { if (LivenessAA && LivenessAA->isEdgeDead(BB, SuccBB)) { LocalDeadEdges.insert({BB, SuccBB}); continue; } // We checked before if we just need to reach the ToBB block. if (SuccBB == ToBB) return rememberResult(A, RQITy::Reachable::Yes, RQI, UsedExclusionSet, IsTemporaryRQI); if (DT && ExclusionBlocks.empty() && DT->dominates(BB, ToBB)) return rememberResult(A, RQITy::Reachable::Yes, RQI, UsedExclusionSet, IsTemporaryRQI); if (ExclusionBlocks.count(SuccBB)) { UsedExclusionSet = true; continue; } Worklist.push_back(SuccBB); } } DeadEdges.insert(LocalDeadEdges.begin(), LocalDeadEdges.end()); return rememberResult(A, RQITy::Reachable::No, RQI, UsedExclusionSet, IsTemporaryRQI); } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override {} private: // Set of assumed dead blocks we used in the last query. If any changes we // update the state. DenseSet DeadBlocks; // Set of assumed dead edges we used in the last query. If any changes we // update the state. DenseSet> DeadEdges; /// The dominator tree of the function to short-circuit reasoning. const DominatorTree *DT = nullptr; }; } // namespace /// ------------------------ NoAlias Argument Attribute ------------------------ bool AANoAlias::isImpliedByIR(Attributor &A, const IRPosition &IRP, Attribute::AttrKind ImpliedAttributeKind, bool IgnoreSubsumingPositions) { assert(ImpliedAttributeKind == Attribute::NoAlias && "Unexpected attribute kind"); Value *Val = &IRP.getAssociatedValue(); if (IRP.getPositionKind() != IRP_CALL_SITE_ARGUMENT) { if (isa(Val)) return true; } else { IgnoreSubsumingPositions = true; } if (isa(Val)) return true; if (isa(Val) && !NullPointerIsDefined(IRP.getAnchorScope(), Val->getType()->getPointerAddressSpace())) return true; if (A.hasAttr(IRP, {Attribute::ByVal, Attribute::NoAlias}, IgnoreSubsumingPositions, Attribute::NoAlias)) return true; return false; } namespace { struct AANoAliasImpl : AANoAlias { AANoAliasImpl(const IRPosition &IRP, Attributor &A) : AANoAlias(IRP, A) { assert(getAssociatedType()->isPointerTy() && "Noalias is a pointer attribute"); } const std::string getAsStr(Attributor *A) const override { return getAssumed() ? "noalias" : "may-alias"; } }; /// NoAlias attribute for a floating value. struct AANoAliasFloating final : AANoAliasImpl { AANoAliasFloating(const IRPosition &IRP, Attributor &A) : AANoAliasImpl(IRP, A) {} /// See AbstractAttribute::updateImpl(...). ChangeStatus updateImpl(Attributor &A) override { // TODO: Implement this. return indicatePessimisticFixpoint(); } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_FLOATING_ATTR(noalias) } }; /// NoAlias attribute for an argument. struct AANoAliasArgument final : AAArgumentFromCallSiteArguments { using Base = AAArgumentFromCallSiteArguments; AANoAliasArgument(const IRPosition &IRP, Attributor &A) : Base(IRP, A) {} /// See AbstractAttribute::update(...). ChangeStatus updateImpl(Attributor &A) override { // We have to make sure no-alias on the argument does not break // synchronization when this is a callback argument, see also [1] below. // If synchronization cannot be affected, we delegate to the base updateImpl // function, otherwise we give up for now. // If the function is no-sync, no-alias cannot break synchronization. bool IsKnownNoSycn; if (AA::hasAssumedIRAttr( A, this, IRPosition::function_scope(getIRPosition()), DepClassTy::OPTIONAL, IsKnownNoSycn)) return Base::updateImpl(A); // If the argument is read-only, no-alias cannot break synchronization. bool IsKnown; if (AA::isAssumedReadOnly(A, getIRPosition(), *this, IsKnown)) return Base::updateImpl(A); // If the argument is never passed through callbacks, no-alias cannot break // synchronization. bool UsedAssumedInformation = false; if (A.checkForAllCallSites( [](AbstractCallSite ACS) { return !ACS.isCallbackCall(); }, *this, true, UsedAssumedInformation)) return Base::updateImpl(A); // TODO: add no-alias but make sure it doesn't break synchronization by // introducing fake uses. See: // [1] Compiler Optimizations for OpenMP, J. Doerfert and H. Finkel, // International Workshop on OpenMP 2018, // http://compilers.cs.uni-saarland.de/people/doerfert/par_opt18.pdf return indicatePessimisticFixpoint(); } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_ARG_ATTR(noalias) } }; struct AANoAliasCallSiteArgument final : AANoAliasImpl { AANoAliasCallSiteArgument(const IRPosition &IRP, Attributor &A) : AANoAliasImpl(IRP, A) {} /// Determine if the underlying value may alias with the call site argument /// \p OtherArgNo of \p ICS (= the underlying call site). bool mayAliasWithArgument(Attributor &A, AAResults *&AAR, const AAMemoryBehavior &MemBehaviorAA, const CallBase &CB, unsigned OtherArgNo) { // We do not need to worry about aliasing with the underlying IRP. if (this->getCalleeArgNo() == (int)OtherArgNo) return false; // If it is not a pointer or pointer vector we do not alias. const Value *ArgOp = CB.getArgOperand(OtherArgNo); if (!ArgOp->getType()->isPtrOrPtrVectorTy()) return false; auto *CBArgMemBehaviorAA = A.getAAFor( *this, IRPosition::callsite_argument(CB, OtherArgNo), DepClassTy::NONE); // If the argument is readnone, there is no read-write aliasing. if (CBArgMemBehaviorAA && CBArgMemBehaviorAA->isAssumedReadNone()) { A.recordDependence(*CBArgMemBehaviorAA, *this, DepClassTy::OPTIONAL); return false; } // If the argument is readonly and the underlying value is readonly, there // is no read-write aliasing. bool IsReadOnly = MemBehaviorAA.isAssumedReadOnly(); if (CBArgMemBehaviorAA && CBArgMemBehaviorAA->isAssumedReadOnly() && IsReadOnly) { A.recordDependence(MemBehaviorAA, *this, DepClassTy::OPTIONAL); A.recordDependence(*CBArgMemBehaviorAA, *this, DepClassTy::OPTIONAL); return false; } // We have to utilize actual alias analysis queries so we need the object. if (!AAR) AAR = A.getInfoCache().getAnalysisResultForFunction( *getAnchorScope()); // Try to rule it out at the call site. bool IsAliasing = !AAR || !AAR->isNoAlias(&getAssociatedValue(), ArgOp); LLVM_DEBUG(dbgs() << "[NoAliasCSArg] Check alias between " "callsite arguments: " << getAssociatedValue() << " " << *ArgOp << " => " << (IsAliasing ? "" : "no-") << "alias \n"); return IsAliasing; } bool isKnownNoAliasDueToNoAliasPreservation( Attributor &A, AAResults *&AAR, const AAMemoryBehavior &MemBehaviorAA) { // We can deduce "noalias" if the following conditions hold. // (i) Associated value is assumed to be noalias in the definition. // (ii) Associated value is assumed to be no-capture in all the uses // possibly executed before this callsite. // (iii) There is no other pointer argument which could alias with the // value. auto IsDereferenceableOrNull = [&](Value *O, const DataLayout &DL) { const auto *DerefAA = A.getAAFor( *this, IRPosition::value(*O), DepClassTy::OPTIONAL); return DerefAA ? DerefAA->getAssumedDereferenceableBytes() : 0; }; const IRPosition &VIRP = IRPosition::value(getAssociatedValue()); const Function *ScopeFn = VIRP.getAnchorScope(); // Check whether the value is captured in the scope using AANoCapture. // Look at CFG and check only uses possibly executed before this // callsite. auto UsePred = [&](const Use &U, bool &Follow) -> bool { Instruction *UserI = cast(U.getUser()); // If UserI is the curr instruction and there is a single potential use of // the value in UserI we allow the use. // TODO: We should inspect the operands and allow those that cannot alias // with the value. if (UserI == getCtxI() && UserI->getNumOperands() == 1) return true; if (ScopeFn) { if (auto *CB = dyn_cast(UserI)) { if (CB->isArgOperand(&U)) { unsigned ArgNo = CB->getArgOperandNo(&U); bool IsKnownNoCapture; if (AA::hasAssumedIRAttr( A, this, IRPosition::callsite_argument(*CB, ArgNo), DepClassTy::OPTIONAL, IsKnownNoCapture)) return true; } } if (!AA::isPotentiallyReachable( A, *UserI, *getCtxI(), *this, /* ExclusionSet */ nullptr, [ScopeFn](const Function &Fn) { return &Fn != ScopeFn; })) return true; } // TODO: We should track the capturing uses in AANoCapture but the problem // is CGSCC runs. For those we would need to "allow" AANoCapture for // a value in the module slice. switch (DetermineUseCaptureKind(U, IsDereferenceableOrNull)) { case UseCaptureKind::NO_CAPTURE: return true; case UseCaptureKind::MAY_CAPTURE: LLVM_DEBUG(dbgs() << "[AANoAliasCSArg] Unknown user: " << *UserI << "\n"); return false; case UseCaptureKind::PASSTHROUGH: Follow = true; return true; } llvm_unreachable("unknown UseCaptureKind"); }; bool IsKnownNoCapture; const AANoCapture *NoCaptureAA = nullptr; bool IsAssumedNoCapture = AA::hasAssumedIRAttr( A, this, VIRP, DepClassTy::NONE, IsKnownNoCapture, false, &NoCaptureAA); if (!IsAssumedNoCapture && (!NoCaptureAA || !NoCaptureAA->isAssumedNoCaptureMaybeReturned())) { if (!A.checkForAllUses(UsePred, *this, getAssociatedValue())) { LLVM_DEBUG( dbgs() << "[AANoAliasCSArg] " << getAssociatedValue() << " cannot be noalias as it is potentially captured\n"); return false; } } if (NoCaptureAA) A.recordDependence(*NoCaptureAA, *this, DepClassTy::OPTIONAL); // Check there is no other pointer argument which could alias with the // value passed at this call site. // TODO: AbstractCallSite const auto &CB = cast(getAnchorValue()); for (unsigned OtherArgNo = 0; OtherArgNo < CB.arg_size(); OtherArgNo++) if (mayAliasWithArgument(A, AAR, MemBehaviorAA, CB, OtherArgNo)) return false; return true; } /// See AbstractAttribute::updateImpl(...). ChangeStatus updateImpl(Attributor &A) override { // If the argument is readnone we are done as there are no accesses via the // argument. auto *MemBehaviorAA = A.getAAFor(*this, getIRPosition(), DepClassTy::NONE); if (MemBehaviorAA && MemBehaviorAA->isAssumedReadNone()) { A.recordDependence(*MemBehaviorAA, *this, DepClassTy::OPTIONAL); return ChangeStatus::UNCHANGED; } bool IsKnownNoAlias; const IRPosition &VIRP = IRPosition::value(getAssociatedValue()); if (!AA::hasAssumedIRAttr( A, this, VIRP, DepClassTy::REQUIRED, IsKnownNoAlias)) { LLVM_DEBUG(dbgs() << "[AANoAlias] " << getAssociatedValue() << " is not no-alias at the definition\n"); return indicatePessimisticFixpoint(); } AAResults *AAR = nullptr; if (MemBehaviorAA && isKnownNoAliasDueToNoAliasPreservation(A, AAR, *MemBehaviorAA)) { LLVM_DEBUG( dbgs() << "[AANoAlias] No-Alias deduced via no-alias preservation\n"); return ChangeStatus::UNCHANGED; } return indicatePessimisticFixpoint(); } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_CSARG_ATTR(noalias) } }; /// NoAlias attribute for function return value. struct AANoAliasReturned final : AANoAliasImpl { AANoAliasReturned(const IRPosition &IRP, Attributor &A) : AANoAliasImpl(IRP, A) {} /// See AbstractAttribute::updateImpl(...). ChangeStatus updateImpl(Attributor &A) override { auto CheckReturnValue = [&](Value &RV) -> bool { if (Constant *C = dyn_cast(&RV)) if (C->isNullValue() || isa(C)) return true; /// For now, we can only deduce noalias if we have call sites. /// FIXME: add more support. if (!isa(&RV)) return false; const IRPosition &RVPos = IRPosition::value(RV); bool IsKnownNoAlias; if (!AA::hasAssumedIRAttr( A, this, RVPos, DepClassTy::REQUIRED, IsKnownNoAlias)) return false; bool IsKnownNoCapture; const AANoCapture *NoCaptureAA = nullptr; bool IsAssumedNoCapture = AA::hasAssumedIRAttr( A, this, RVPos, DepClassTy::REQUIRED, IsKnownNoCapture, false, &NoCaptureAA); return IsAssumedNoCapture || (NoCaptureAA && NoCaptureAA->isAssumedNoCaptureMaybeReturned()); }; if (!A.checkForAllReturnedValues(CheckReturnValue, *this)) return indicatePessimisticFixpoint(); return ChangeStatus::UNCHANGED; } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_FNRET_ATTR(noalias) } }; /// NoAlias attribute deduction for a call site return value. struct AANoAliasCallSiteReturned final : AACalleeToCallSite { AANoAliasCallSiteReturned(const IRPosition &IRP, Attributor &A) : AACalleeToCallSite(IRP, A) {} /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_CSRET_ATTR(noalias); } }; } // namespace /// -------------------AAIsDead Function Attribute----------------------- namespace { struct AAIsDeadValueImpl : public AAIsDead { AAIsDeadValueImpl(const IRPosition &IRP, Attributor &A) : AAIsDead(IRP, A) {} /// See AAIsDead::isAssumedDead(). bool isAssumedDead() const override { return isAssumed(IS_DEAD); } /// See AAIsDead::isKnownDead(). bool isKnownDead() const override { return isKnown(IS_DEAD); } /// See AAIsDead::isAssumedDead(BasicBlock *). bool isAssumedDead(const BasicBlock *BB) const override { return false; } /// See AAIsDead::isKnownDead(BasicBlock *). bool isKnownDead(const BasicBlock *BB) const override { return false; } /// See AAIsDead::isAssumedDead(Instruction *I). bool isAssumedDead(const Instruction *I) const override { return I == getCtxI() && isAssumedDead(); } /// See AAIsDead::isKnownDead(Instruction *I). bool isKnownDead(const Instruction *I) const override { return isAssumedDead(I) && isKnownDead(); } /// See AbstractAttribute::getAsStr(). const std::string getAsStr(Attributor *A) const override { return isAssumedDead() ? "assumed-dead" : "assumed-live"; } /// Check if all uses are assumed dead. bool areAllUsesAssumedDead(Attributor &A, Value &V) { // Callers might not check the type, void has no uses. if (V.getType()->isVoidTy() || V.use_empty()) return true; // If we replace a value with a constant there are no uses left afterwards. if (!isa(V)) { if (auto *I = dyn_cast(&V)) if (!A.isRunOn(*I->getFunction())) return false; bool UsedAssumedInformation = false; std::optional C = A.getAssumedConstant(V, *this, UsedAssumedInformation); if (!C || *C) return true; } auto UsePred = [&](const Use &U, bool &Follow) { return false; }; // Explicitly set the dependence class to required because we want a long // chain of N dependent instructions to be considered live as soon as one is // without going through N update cycles. This is not required for // correctness. return A.checkForAllUses(UsePred, *this, V, /* CheckBBLivenessOnly */ false, DepClassTy::REQUIRED, /* IgnoreDroppableUses */ false); } /// Determine if \p I is assumed to be side-effect free. bool isAssumedSideEffectFree(Attributor &A, Instruction *I) { if (!I || wouldInstructionBeTriviallyDead(I)) return true; auto *CB = dyn_cast(I); if (!CB || isa(CB)) return false; const IRPosition &CallIRP = IRPosition::callsite_function(*CB); bool IsKnownNoUnwind; if (!AA::hasAssumedIRAttr( A, this, CallIRP, DepClassTy::OPTIONAL, IsKnownNoUnwind)) return false; bool IsKnown; return AA::isAssumedReadOnly(A, CallIRP, *this, IsKnown); } }; struct AAIsDeadFloating : public AAIsDeadValueImpl { AAIsDeadFloating(const IRPosition &IRP, Attributor &A) : AAIsDeadValueImpl(IRP, A) {} /// See AbstractAttribute::initialize(...). void initialize(Attributor &A) override { AAIsDeadValueImpl::initialize(A); if (isa(getAssociatedValue())) { indicatePessimisticFixpoint(); return; } Instruction *I = dyn_cast(&getAssociatedValue()); if (!isAssumedSideEffectFree(A, I)) { if (!isa_and_nonnull(I) && !isa_and_nonnull(I)) indicatePessimisticFixpoint(); else removeAssumedBits(HAS_NO_EFFECT); } } bool isDeadFence(Attributor &A, FenceInst &FI) { const auto *ExecDomainAA = A.lookupAAFor( IRPosition::function(*FI.getFunction()), *this, DepClassTy::NONE); if (!ExecDomainAA || !ExecDomainAA->isNoOpFence(FI)) return false; A.recordDependence(*ExecDomainAA, *this, DepClassTy::OPTIONAL); return true; } bool isDeadStore(Attributor &A, StoreInst &SI, SmallSetVector *AssumeOnlyInst = nullptr) { // Lang ref now states volatile store is not UB/dead, let's skip them. if (SI.isVolatile()) return false; // If we are collecting assumes to be deleted we are in the manifest stage. // It's problematic to collect the potential copies again now so we use the // cached ones. bool UsedAssumedInformation = false; if (!AssumeOnlyInst) { PotentialCopies.clear(); if (!AA::getPotentialCopiesOfStoredValue(A, SI, PotentialCopies, *this, UsedAssumedInformation)) { LLVM_DEBUG( dbgs() << "[AAIsDead] Could not determine potential copies of store!\n"); return false; } } LLVM_DEBUG(dbgs() << "[AAIsDead] Store has " << PotentialCopies.size() << " potential copies.\n"); InformationCache &InfoCache = A.getInfoCache(); return llvm::all_of(PotentialCopies, [&](Value *V) { if (A.isAssumedDead(IRPosition::value(*V), this, nullptr, UsedAssumedInformation)) return true; if (auto *LI = dyn_cast(V)) { if (llvm::all_of(LI->uses(), [&](const Use &U) { auto &UserI = cast(*U.getUser()); if (InfoCache.isOnlyUsedByAssume(UserI)) { if (AssumeOnlyInst) AssumeOnlyInst->insert(&UserI); return true; } return A.isAssumedDead(U, this, nullptr, UsedAssumedInformation); })) { return true; } } LLVM_DEBUG(dbgs() << "[AAIsDead] Potential copy " << *V << " is assumed live!\n"); return false; }); } /// See AbstractAttribute::getAsStr(). const std::string getAsStr(Attributor *A) const override { Instruction *I = dyn_cast(&getAssociatedValue()); if (isa_and_nonnull(I)) if (isValidState()) return "assumed-dead-store"; if (isa_and_nonnull(I)) if (isValidState()) return "assumed-dead-fence"; return AAIsDeadValueImpl::getAsStr(A); } /// See AbstractAttribute::updateImpl(...). ChangeStatus updateImpl(Attributor &A) override { Instruction *I = dyn_cast(&getAssociatedValue()); if (auto *SI = dyn_cast_or_null(I)) { if (!isDeadStore(A, *SI)) return indicatePessimisticFixpoint(); } else if (auto *FI = dyn_cast_or_null(I)) { if (!isDeadFence(A, *FI)) return indicatePessimisticFixpoint(); } else { if (!isAssumedSideEffectFree(A, I)) return indicatePessimisticFixpoint(); if (!areAllUsesAssumedDead(A, getAssociatedValue())) return indicatePessimisticFixpoint(); } return ChangeStatus::UNCHANGED; } bool isRemovableStore() const override { return isAssumed(IS_REMOVABLE) && isa(&getAssociatedValue()); } /// See AbstractAttribute::manifest(...). ChangeStatus manifest(Attributor &A) override { Value &V = getAssociatedValue(); if (auto *I = dyn_cast(&V)) { // If we get here we basically know the users are all dead. We check if // isAssumedSideEffectFree returns true here again because it might not be // the case and only the users are dead but the instruction (=call) is // still needed. if (auto *SI = dyn_cast(I)) { SmallSetVector AssumeOnlyInst; bool IsDead = isDeadStore(A, *SI, &AssumeOnlyInst); (void)IsDead; assert(IsDead && "Store was assumed to be dead!"); A.deleteAfterManifest(*I); for (size_t i = 0; i < AssumeOnlyInst.size(); ++i) { Instruction *AOI = AssumeOnlyInst[i]; for (auto *Usr : AOI->users()) AssumeOnlyInst.insert(cast(Usr)); A.deleteAfterManifest(*AOI); } return ChangeStatus::CHANGED; } if (auto *FI = dyn_cast(I)) { assert(isDeadFence(A, *FI)); A.deleteAfterManifest(*FI); return ChangeStatus::CHANGED; } if (isAssumedSideEffectFree(A, I) && !isa(I)) { A.deleteAfterManifest(*I); return ChangeStatus::CHANGED; } } return ChangeStatus::UNCHANGED; } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_FLOATING_ATTR(IsDead) } private: // The potential copies of a dead store, used for deletion during manifest. SmallSetVector PotentialCopies; }; struct AAIsDeadArgument : public AAIsDeadFloating { AAIsDeadArgument(const IRPosition &IRP, Attributor &A) : AAIsDeadFloating(IRP, A) {} /// See AbstractAttribute::manifest(...). ChangeStatus manifest(Attributor &A) override { Argument &Arg = *getAssociatedArgument(); if (A.isValidFunctionSignatureRewrite(Arg, /* ReplacementTypes */ {})) if (A.registerFunctionSignatureRewrite( Arg, /* ReplacementTypes */ {}, Attributor::ArgumentReplacementInfo::CalleeRepairCBTy{}, Attributor::ArgumentReplacementInfo::ACSRepairCBTy{})) { return ChangeStatus::CHANGED; } return ChangeStatus::UNCHANGED; } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_ARG_ATTR(IsDead) } }; struct AAIsDeadCallSiteArgument : public AAIsDeadValueImpl { AAIsDeadCallSiteArgument(const IRPosition &IRP, Attributor &A) : AAIsDeadValueImpl(IRP, A) {} /// See AbstractAttribute::initialize(...). void initialize(Attributor &A) override { AAIsDeadValueImpl::initialize(A); if (isa(getAssociatedValue())) indicatePessimisticFixpoint(); } /// 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); auto *ArgAA = A.getAAFor(*this, ArgPos, DepClassTy::REQUIRED); if (!ArgAA) return indicatePessimisticFixpoint(); return clampStateAndIndicateChange(getState(), ArgAA->getState()); } /// See AbstractAttribute::manifest(...). ChangeStatus manifest(Attributor &A) override { CallBase &CB = cast(getAnchorValue()); Use &U = CB.getArgOperandUse(getCallSiteArgNo()); assert(!isa(U.get()) && "Expected undef values to be filtered out!"); UndefValue &UV = *UndefValue::get(U->getType()); if (A.changeUseAfterManifest(U, UV)) return ChangeStatus::CHANGED; return ChangeStatus::UNCHANGED; } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_CSARG_ATTR(IsDead) } }; struct AAIsDeadCallSiteReturned : public AAIsDeadFloating { AAIsDeadCallSiteReturned(const IRPosition &IRP, Attributor &A) : AAIsDeadFloating(IRP, A) {} /// See AAIsDead::isAssumedDead(). bool isAssumedDead() const override { return AAIsDeadFloating::isAssumedDead() && IsAssumedSideEffectFree; } /// See AbstractAttribute::initialize(...). void initialize(Attributor &A) override { AAIsDeadFloating::initialize(A); if (isa(getAssociatedValue())) { indicatePessimisticFixpoint(); return; } // We track this separately as a secondary state. IsAssumedSideEffectFree = isAssumedSideEffectFree(A, getCtxI()); } /// See AbstractAttribute::updateImpl(...). ChangeStatus updateImpl(Attributor &A) override { ChangeStatus Changed = ChangeStatus::UNCHANGED; if (IsAssumedSideEffectFree && !isAssumedSideEffectFree(A, getCtxI())) { IsAssumedSideEffectFree = false; Changed = ChangeStatus::CHANGED; } if (!areAllUsesAssumedDead(A, getAssociatedValue())) return indicatePessimisticFixpoint(); return Changed; } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { if (IsAssumedSideEffectFree) STATS_DECLTRACK_CSRET_ATTR(IsDead) else STATS_DECLTRACK_CSRET_ATTR(UnusedResult) } /// See AbstractAttribute::getAsStr(). const std::string getAsStr(Attributor *A) const override { return isAssumedDead() ? "assumed-dead" : (getAssumed() ? "assumed-dead-users" : "assumed-live"); } private: bool IsAssumedSideEffectFree = true; }; struct AAIsDeadReturned : public AAIsDeadValueImpl { AAIsDeadReturned(const IRPosition &IRP, Attributor &A) : AAIsDeadValueImpl(IRP, A) {} /// See AbstractAttribute::updateImpl(...). ChangeStatus updateImpl(Attributor &A) override { bool UsedAssumedInformation = false; A.checkForAllInstructions([](Instruction &) { return true; }, *this, {Instruction::Ret}, UsedAssumedInformation); auto PredForCallSite = [&](AbstractCallSite ACS) { if (ACS.isCallbackCall() || !ACS.getInstruction()) return false; return areAllUsesAssumedDead(A, *ACS.getInstruction()); }; if (!A.checkForAllCallSites(PredForCallSite, *this, true, UsedAssumedInformation)) return indicatePessimisticFixpoint(); return ChangeStatus::UNCHANGED; } /// See AbstractAttribute::manifest(...). ChangeStatus manifest(Attributor &A) override { // TODO: Rewrite the signature to return void? bool AnyChange = false; UndefValue &UV = *UndefValue::get(getAssociatedFunction()->getReturnType()); auto RetInstPred = [&](Instruction &I) { ReturnInst &RI = cast(I); if (!isa(RI.getReturnValue())) AnyChange |= A.changeUseAfterManifest(RI.getOperandUse(0), UV); return true; }; bool UsedAssumedInformation = false; A.checkForAllInstructions(RetInstPred, *this, {Instruction::Ret}, UsedAssumedInformation); return AnyChange ? ChangeStatus::CHANGED : ChangeStatus::UNCHANGED; } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_FNRET_ATTR(IsDead) } }; struct AAIsDeadFunction : public AAIsDead { AAIsDeadFunction(const IRPosition &IRP, Attributor &A) : AAIsDead(IRP, A) {} /// See AbstractAttribute::initialize(...). void initialize(Attributor &A) override { Function *F = getAnchorScope(); assert(F && "Did expect an anchor function"); if (!isAssumedDeadInternalFunction(A)) { ToBeExploredFrom.insert(&F->getEntryBlock().front()); assumeLive(A, F->getEntryBlock()); } } bool isAssumedDeadInternalFunction(Attributor &A) { if (!getAnchorScope()->hasLocalLinkage()) return false; bool UsedAssumedInformation = false; return A.checkForAllCallSites([](AbstractCallSite) { return false; }, *this, true, UsedAssumedInformation); } /// See AbstractAttribute::getAsStr(). const std::string getAsStr(Attributor *A) const override { return "Live[#BB " + std::to_string(AssumedLiveBlocks.size()) + "/" + std::to_string(getAnchorScope()->size()) + "][#TBEP " + std::to_string(ToBeExploredFrom.size()) + "][#KDE " + std::to_string(KnownDeadEnds.size()) + "]"; } /// See AbstractAttribute::manifest(...). ChangeStatus manifest(Attributor &A) override { assert(getState().isValidState() && "Attempted to manifest an invalid state!"); ChangeStatus HasChanged = ChangeStatus::UNCHANGED; Function &F = *getAnchorScope(); if (AssumedLiveBlocks.empty()) { A.deleteAfterManifest(F); return ChangeStatus::CHANGED; } // Flag to determine if we can change an invoke to a call assuming the // callee is nounwind. This is not possible if the personality of the // function allows to catch asynchronous exceptions. bool Invoke2CallAllowed = !mayCatchAsynchronousExceptions(F); KnownDeadEnds.set_union(ToBeExploredFrom); for (const Instruction *DeadEndI : KnownDeadEnds) { auto *CB = dyn_cast(DeadEndI); if (!CB) continue; bool IsKnownNoReturn; bool MayReturn = !AA::hasAssumedIRAttr( A, this, IRPosition::callsite_function(*CB), DepClassTy::OPTIONAL, IsKnownNoReturn); if (MayReturn && (!Invoke2CallAllowed || !isa(CB))) continue; if (auto *II = dyn_cast(DeadEndI)) A.registerInvokeWithDeadSuccessor(const_cast(*II)); else A.changeToUnreachableAfterManifest( const_cast(DeadEndI->getNextNode())); HasChanged = ChangeStatus::CHANGED; } STATS_DECL(AAIsDead, BasicBlock, "Number of dead basic blocks deleted."); for (BasicBlock &BB : F) if (!AssumedLiveBlocks.count(&BB)) { A.deleteAfterManifest(BB); ++BUILD_STAT_NAME(AAIsDead, BasicBlock); HasChanged = ChangeStatus::CHANGED; } return HasChanged; } /// See AbstractAttribute::updateImpl(...). ChangeStatus updateImpl(Attributor &A) override; bool isEdgeDead(const BasicBlock *From, const BasicBlock *To) const override { assert(From->getParent() == getAnchorScope() && To->getParent() == getAnchorScope() && "Used AAIsDead of the wrong function"); return isValidState() && !AssumedLiveEdges.count(std::make_pair(From, To)); } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override {} /// Returns true if the function is assumed dead. bool isAssumedDead() const override { return false; } /// See AAIsDead::isKnownDead(). bool isKnownDead() const override { return false; } /// See AAIsDead::isAssumedDead(BasicBlock *). bool isAssumedDead(const BasicBlock *BB) const override { assert(BB->getParent() == getAnchorScope() && "BB must be in the same anchor scope function."); if (!getAssumed()) return false; return !AssumedLiveBlocks.count(BB); } /// See AAIsDead::isKnownDead(BasicBlock *). bool isKnownDead(const BasicBlock *BB) const override { return getKnown() && isAssumedDead(BB); } /// See AAIsDead::isAssumed(Instruction *I). bool isAssumedDead(const Instruction *I) const override { assert(I->getParent()->getParent() == getAnchorScope() && "Instruction must be in the same anchor scope function."); if (!getAssumed()) return false; // If it is not in AssumedLiveBlocks then it for sure dead. // Otherwise, it can still be after noreturn call in a live block. if (!AssumedLiveBlocks.count(I->getParent())) return true; // If it is not after a liveness barrier it is live. const Instruction *PrevI = I->getPrevNode(); while (PrevI) { if (KnownDeadEnds.count(PrevI) || ToBeExploredFrom.count(PrevI)) return true; PrevI = PrevI->getPrevNode(); } return false; } /// See AAIsDead::isKnownDead(Instruction *I). bool isKnownDead(const Instruction *I) const override { return getKnown() && isAssumedDead(I); } /// Assume \p BB is (partially) live now and indicate to the Attributor \p A /// that internal function called from \p BB should now be looked at. bool assumeLive(Attributor &A, const BasicBlock &BB) { if (!AssumedLiveBlocks.insert(&BB).second) return false; // We assume that all of BB is (probably) live now and if there are calls to // internal functions we will assume that those are now live as well. This // is a performance optimization for blocks with calls to a lot of internal // functions. It can however cause dead functions to be treated as live. for (const Instruction &I : BB) if (const auto *CB = dyn_cast(&I)) if (auto *F = dyn_cast_if_present(CB->getCalledOperand())) if (F->hasLocalLinkage()) A.markLiveInternalFunction(*F); return true; } /// Collection of instructions that need to be explored again, e.g., we /// did assume they do not transfer control to (one of their) successors. SmallSetVector ToBeExploredFrom; /// Collection of instructions that are known to not transfer control. SmallSetVector KnownDeadEnds; /// Collection of all assumed live edges DenseSet> AssumedLiveEdges; /// Collection of all assumed live BasicBlocks. DenseSet AssumedLiveBlocks; }; static bool identifyAliveSuccessors(Attributor &A, const CallBase &CB, AbstractAttribute &AA, SmallVectorImpl &AliveSuccessors) { const IRPosition &IPos = IRPosition::callsite_function(CB); bool IsKnownNoReturn; if (AA::hasAssumedIRAttr( A, &AA, IPos, DepClassTy::OPTIONAL, IsKnownNoReturn)) return !IsKnownNoReturn; if (CB.isTerminator()) AliveSuccessors.push_back(&CB.getSuccessor(0)->front()); else AliveSuccessors.push_back(CB.getNextNode()); return false; } static bool identifyAliveSuccessors(Attributor &A, const InvokeInst &II, AbstractAttribute &AA, SmallVectorImpl &AliveSuccessors) { bool UsedAssumedInformation = identifyAliveSuccessors(A, cast(II), AA, AliveSuccessors); // First, determine if we can change an invoke to a call assuming the // callee is nounwind. This is not possible if the personality of the // function allows to catch asynchronous exceptions. if (AAIsDeadFunction::mayCatchAsynchronousExceptions(*II.getFunction())) { AliveSuccessors.push_back(&II.getUnwindDest()->front()); } else { const IRPosition &IPos = IRPosition::callsite_function(II); bool IsKnownNoUnwind; if (AA::hasAssumedIRAttr( A, &AA, IPos, DepClassTy::OPTIONAL, IsKnownNoUnwind)) { UsedAssumedInformation |= !IsKnownNoUnwind; } else { AliveSuccessors.push_back(&II.getUnwindDest()->front()); } } return UsedAssumedInformation; } static bool identifyAliveSuccessors(Attributor &A, const BranchInst &BI, AbstractAttribute &AA, SmallVectorImpl &AliveSuccessors) { bool UsedAssumedInformation = false; if (BI.getNumSuccessors() == 1) { AliveSuccessors.push_back(&BI.getSuccessor(0)->front()); } else { std::optional C = A.getAssumedConstant(*BI.getCondition(), AA, UsedAssumedInformation); if (!C || isa_and_nonnull(*C)) { // No value yet, assume both edges are dead. } else if (isa_and_nonnull(*C)) { const BasicBlock *SuccBB = BI.getSuccessor(1 - cast(*C)->getValue().getZExtValue()); AliveSuccessors.push_back(&SuccBB->front()); } else { AliveSuccessors.push_back(&BI.getSuccessor(0)->front()); AliveSuccessors.push_back(&BI.getSuccessor(1)->front()); UsedAssumedInformation = false; } } return UsedAssumedInformation; } static bool identifyAliveSuccessors(Attributor &A, const SwitchInst &SI, AbstractAttribute &AA, SmallVectorImpl &AliveSuccessors) { bool UsedAssumedInformation = false; SmallVector Values; if (!A.getAssumedSimplifiedValues(IRPosition::value(*SI.getCondition()), &AA, Values, AA::AnyScope, UsedAssumedInformation)) { // Something went wrong, assume all successors are live. for (const BasicBlock *SuccBB : successors(SI.getParent())) AliveSuccessors.push_back(&SuccBB->front()); return false; } if (Values.empty() || (Values.size() == 1 && isa_and_nonnull(Values.front().getValue()))) { // No valid value yet, assume all edges are dead. return UsedAssumedInformation; } Type &Ty = *SI.getCondition()->getType(); SmallPtrSet Constants; auto CheckForConstantInt = [&](Value *V) { if (auto *CI = dyn_cast_if_present(AA::getWithType(*V, Ty))) { Constants.insert(CI); return true; } return false; }; if (!all_of(Values, [&](AA::ValueAndContext &VAC) { return CheckForConstantInt(VAC.getValue()); })) { for (const BasicBlock *SuccBB : successors(SI.getParent())) AliveSuccessors.push_back(&SuccBB->front()); return UsedAssumedInformation; } unsigned MatchedCases = 0; for (const auto &CaseIt : SI.cases()) { if (Constants.count(CaseIt.getCaseValue())) { ++MatchedCases; AliveSuccessors.push_back(&CaseIt.getCaseSuccessor()->front()); } } // If all potential values have been matched, we will not visit the default // case. if (MatchedCases < Constants.size()) AliveSuccessors.push_back(&SI.getDefaultDest()->front()); return UsedAssumedInformation; } ChangeStatus AAIsDeadFunction::updateImpl(Attributor &A) { ChangeStatus Change = ChangeStatus::UNCHANGED; if (AssumedLiveBlocks.empty()) { if (isAssumedDeadInternalFunction(A)) return ChangeStatus::UNCHANGED; Function *F = getAnchorScope(); ToBeExploredFrom.insert(&F->getEntryBlock().front()); assumeLive(A, F->getEntryBlock()); Change = ChangeStatus::CHANGED; } LLVM_DEBUG(dbgs() << "[AAIsDead] Live [" << AssumedLiveBlocks.size() << "/" << getAnchorScope()->size() << "] BBs and " << ToBeExploredFrom.size() << " exploration points and " << KnownDeadEnds.size() << " known dead ends\n"); // Copy and clear the list of instructions we need to explore from. It is // refilled with instructions the next update has to look at. SmallVector Worklist(ToBeExploredFrom.begin(), ToBeExploredFrom.end()); decltype(ToBeExploredFrom) NewToBeExploredFrom; SmallVector AliveSuccessors; while (!Worklist.empty()) { const Instruction *I = Worklist.pop_back_val(); LLVM_DEBUG(dbgs() << "[AAIsDead] Exploration inst: " << *I << "\n"); // Fast forward for uninteresting instructions. We could look for UB here // though. while (!I->isTerminator() && !isa(I)) I = I->getNextNode(); AliveSuccessors.clear(); bool UsedAssumedInformation = false; switch (I->getOpcode()) { // TODO: look for (assumed) UB to backwards propagate "deadness". default: assert(I->isTerminator() && "Expected non-terminators to be handled already!"); for (const BasicBlock *SuccBB : successors(I->getParent())) AliveSuccessors.push_back(&SuccBB->front()); break; case Instruction::Call: UsedAssumedInformation = identifyAliveSuccessors(A, cast(*I), *this, AliveSuccessors); break; case Instruction::Invoke: UsedAssumedInformation = identifyAliveSuccessors(A, cast(*I), *this, AliveSuccessors); break; case Instruction::Br: UsedAssumedInformation = identifyAliveSuccessors(A, cast(*I), *this, AliveSuccessors); break; case Instruction::Switch: UsedAssumedInformation = identifyAliveSuccessors(A, cast(*I), *this, AliveSuccessors); break; } if (UsedAssumedInformation) { NewToBeExploredFrom.insert(I); } else if (AliveSuccessors.empty() || (I->isTerminator() && AliveSuccessors.size() < I->getNumSuccessors())) { if (KnownDeadEnds.insert(I)) Change = ChangeStatus::CHANGED; } LLVM_DEBUG(dbgs() << "[AAIsDead] #AliveSuccessors: " << AliveSuccessors.size() << " UsedAssumedInformation: " << UsedAssumedInformation << "\n"); for (const Instruction *AliveSuccessor : AliveSuccessors) { if (!I->isTerminator()) { assert(AliveSuccessors.size() == 1 && "Non-terminator expected to have a single successor!"); Worklist.push_back(AliveSuccessor); } else { // record the assumed live edge auto Edge = std::make_pair(I->getParent(), AliveSuccessor->getParent()); if (AssumedLiveEdges.insert(Edge).second) Change = ChangeStatus::CHANGED; if (assumeLive(A, *AliveSuccessor->getParent())) Worklist.push_back(AliveSuccessor); } } } // Check if the content of ToBeExploredFrom changed, ignore the order. if (NewToBeExploredFrom.size() != ToBeExploredFrom.size() || llvm::any_of(NewToBeExploredFrom, [&](const Instruction *I) { return !ToBeExploredFrom.count(I); })) { Change = ChangeStatus::CHANGED; ToBeExploredFrom = std::move(NewToBeExploredFrom); } // If we know everything is live there is no need to query for liveness. // Instead, indicating a pessimistic fixpoint will cause the state to be // "invalid" and all queries to be answered conservatively without lookups. // To be in this state we have to (1) finished the exploration and (3) not // discovered any non-trivial dead end and (2) not ruled unreachable code // dead. if (ToBeExploredFrom.empty() && getAnchorScope()->size() == AssumedLiveBlocks.size() && llvm::all_of(KnownDeadEnds, [](const Instruction *DeadEndI) { return DeadEndI->isTerminator() && DeadEndI->getNumSuccessors() == 0; })) return indicatePessimisticFixpoint(); return Change; } /// Liveness information for a call sites. struct AAIsDeadCallSite final : AAIsDeadFunction { AAIsDeadCallSite(const IRPosition &IRP, Attributor &A) : AAIsDeadFunction(IRP, A) {} /// See AbstractAttribute::initialize(...). void initialize(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 instead of // redirecting requests to the callee. llvm_unreachable("Abstract attributes for liveness are not " "supported for call sites yet!"); } /// See AbstractAttribute::updateImpl(...). ChangeStatus updateImpl(Attributor &A) override { return indicatePessimisticFixpoint(); } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override {} }; } // namespace /// -------------------- Dereferenceable Argument Attribute -------------------- namespace { struct AADereferenceableImpl : AADereferenceable { AADereferenceableImpl(const IRPosition &IRP, Attributor &A) : AADereferenceable(IRP, A) {} using StateType = DerefState; /// See AbstractAttribute::initialize(...). void initialize(Attributor &A) override { Value &V = *getAssociatedValue().stripPointerCasts(); SmallVector Attrs; A.getAttrs(getIRPosition(), {Attribute::Dereferenceable, Attribute::DereferenceableOrNull}, Attrs, /* IgnoreSubsumingPositions */ false); for (const Attribute &Attr : Attrs) takeKnownDerefBytesMaximum(Attr.getValueAsInt()); // Ensure we initialize the non-null AA (if necessary). bool IsKnownNonNull; AA::hasAssumedIRAttr( A, this, getIRPosition(), DepClassTy::OPTIONAL, IsKnownNonNull); bool CanBeNull, CanBeFreed; takeKnownDerefBytesMaximum(V.getPointerDereferenceableBytes( A.getDataLayout(), CanBeNull, CanBeFreed)); if (Instruction *CtxI = getCtxI()) followUsesInMBEC(*this, A, getState(), *CtxI); } /// See AbstractAttribute::getState() /// { StateType &getState() override { return *this; } const StateType &getState() const override { return *this; } /// } /// Helper function for collecting accessed bytes in must-be-executed-context void addAccessedBytesForUse(Attributor &A, const Use *U, const Instruction *I, DerefState &State) { const Value *UseV = U->get(); if (!UseV->getType()->isPointerTy()) return; std::optional Loc = MemoryLocation::getOrNone(I); if (!Loc || Loc->Ptr != UseV || !Loc->Size.isPrecise() || I->isVolatile()) return; int64_t Offset; const Value *Base = GetPointerBaseWithConstantOffset( Loc->Ptr, Offset, A.getDataLayout(), /*AllowNonInbounds*/ true); if (Base && Base == &getAssociatedValue()) State.addAccessedBytes(Offset, Loc->Size.getValue()); } /// See followUsesInMBEC bool followUseInMBEC(Attributor &A, const Use *U, const Instruction *I, AADereferenceable::StateType &State) { bool IsNonNull = false; bool TrackUse = false; int64_t DerefBytes = getKnownNonNullAndDerefBytesForUse( A, *this, getAssociatedValue(), U, I, IsNonNull, TrackUse); LLVM_DEBUG(dbgs() << "[AADereferenceable] Deref bytes: " << DerefBytes << " for instruction " << *I << "\n"); addAccessedBytesForUse(A, U, I, State); State.takeKnownDerefBytesMaximum(DerefBytes); return TrackUse; } /// See AbstractAttribute::manifest(...). ChangeStatus manifest(Attributor &A) override { ChangeStatus Change = AADereferenceable::manifest(A); bool IsKnownNonNull; bool IsAssumedNonNull = AA::hasAssumedIRAttr( A, this, getIRPosition(), DepClassTy::NONE, IsKnownNonNull); if (IsAssumedNonNull && A.hasAttr(getIRPosition(), Attribute::DereferenceableOrNull)) { A.removeAttrs(getIRPosition(), {Attribute::DereferenceableOrNull}); return ChangeStatus::CHANGED; } return Change; } void getDeducedAttributes(Attributor &A, LLVMContext &Ctx, SmallVectorImpl &Attrs) const override { // TODO: Add *_globally support bool IsKnownNonNull; bool IsAssumedNonNull = AA::hasAssumedIRAttr( A, this, getIRPosition(), DepClassTy::NONE, IsKnownNonNull); if (IsAssumedNonNull) Attrs.emplace_back(Attribute::getWithDereferenceableBytes( Ctx, getAssumedDereferenceableBytes())); else Attrs.emplace_back(Attribute::getWithDereferenceableOrNullBytes( Ctx, getAssumedDereferenceableBytes())); } /// See AbstractAttribute::getAsStr(). const std::string getAsStr(Attributor *A) const override { if (!getAssumedDereferenceableBytes()) return "unknown-dereferenceable"; bool IsKnownNonNull; bool IsAssumedNonNull = false; if (A) IsAssumedNonNull = AA::hasAssumedIRAttr( *A, this, getIRPosition(), DepClassTy::NONE, IsKnownNonNull); return std::string("dereferenceable") + (IsAssumedNonNull ? "" : "_or_null") + (isAssumedGlobal() ? "_globally" : "") + "<" + std::to_string(getKnownDereferenceableBytes()) + "-" + std::to_string(getAssumedDereferenceableBytes()) + ">" + (!A ? " [non-null is unknown]" : ""); } }; /// Dereferenceable attribute for a floating value. struct AADereferenceableFloating : AADereferenceableImpl { AADereferenceableFloating(const IRPosition &IRP, Attributor &A) : AADereferenceableImpl(IRP, A) {} /// See AbstractAttribute::updateImpl(...). ChangeStatus updateImpl(Attributor &A) override { bool Stripped; bool UsedAssumedInformation = false; SmallVector Values; if (!A.getAssumedSimplifiedValues(getIRPosition(), *this, Values, AA::AnyScope, UsedAssumedInformation)) { Values.push_back({getAssociatedValue(), getCtxI()}); Stripped = false; } else { Stripped = Values.size() != 1 || Values.front().getValue() != &getAssociatedValue(); } const DataLayout &DL = A.getDataLayout(); DerefState T; auto VisitValueCB = [&](const Value &V) -> bool { unsigned IdxWidth = DL.getIndexSizeInBits(V.getType()->getPointerAddressSpace()); APInt Offset(IdxWidth, 0); const Value *Base = stripAndAccumulateOffsets( A, *this, &V, DL, Offset, /* GetMinOffset */ false, /* AllowNonInbounds */ true); const auto *AA = A.getAAFor( *this, IRPosition::value(*Base), DepClassTy::REQUIRED); int64_t DerefBytes = 0; if (!AA || (!Stripped && this == AA)) { // Use IR information if we did not strip anything. // TODO: track globally. bool CanBeNull, CanBeFreed; DerefBytes = Base->getPointerDereferenceableBytes(DL, CanBeNull, CanBeFreed); T.GlobalState.indicatePessimisticFixpoint(); } else { const DerefState &DS = AA->getState(); DerefBytes = DS.DerefBytesState.getAssumed(); T.GlobalState &= DS.GlobalState; } // For now we do not try to "increase" dereferenceability due to negative // indices as we first have to come up with code to deal with loops and // for overflows of the dereferenceable bytes. int64_t OffsetSExt = Offset.getSExtValue(); if (OffsetSExt < 0) OffsetSExt = 0; T.takeAssumedDerefBytesMinimum( std::max(int64_t(0), DerefBytes - OffsetSExt)); if (this == AA) { if (!Stripped) { // If nothing was stripped IR information is all we got. T.takeKnownDerefBytesMaximum( std::max(int64_t(0), DerefBytes - OffsetSExt)); T.indicatePessimisticFixpoint(); } else if (OffsetSExt > 0) { // If something was stripped but there is circular reasoning we look // for the offset. If it is positive we basically decrease the // dereferenceable bytes in a circular loop now, which will simply // drive them down to the known value in a very slow way which we // can accelerate. T.indicatePessimisticFixpoint(); } } return T.isValidState(); }; for (const auto &VAC : Values) if (!VisitValueCB(*VAC.getValue())) return indicatePessimisticFixpoint(); return clampStateAndIndicateChange(getState(), T); } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_FLOATING_ATTR(dereferenceable) } }; /// Dereferenceable attribute for a return value. struct AADereferenceableReturned final : AAReturnedFromReturnedValues { using Base = AAReturnedFromReturnedValues; AADereferenceableReturned(const IRPosition &IRP, Attributor &A) : Base(IRP, A) {} /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_FNRET_ATTR(dereferenceable) } }; /// Dereferenceable attribute for an argument struct AADereferenceableArgument final : AAArgumentFromCallSiteArguments { using Base = AAArgumentFromCallSiteArguments; AADereferenceableArgument(const IRPosition &IRP, Attributor &A) : Base(IRP, A) {} /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_ARG_ATTR(dereferenceable) } }; /// Dereferenceable attribute for a call site argument. struct AADereferenceableCallSiteArgument final : AADereferenceableFloating { AADereferenceableCallSiteArgument(const IRPosition &IRP, Attributor &A) : AADereferenceableFloating(IRP, A) {} /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_CSARG_ATTR(dereferenceable) } }; /// Dereferenceable attribute deduction for a call site return value. struct AADereferenceableCallSiteReturned final : AACalleeToCallSite { using Base = AACalleeToCallSite; AADereferenceableCallSiteReturned(const IRPosition &IRP, Attributor &A) : Base(IRP, A) {} /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_CS_ATTR(dereferenceable); } }; } // namespace // ------------------------ Align Argument Attribute ------------------------ namespace { static unsigned getKnownAlignForUse(Attributor &A, AAAlign &QueryingAA, Value &AssociatedValue, const Use *U, const Instruction *I, bool &TrackUse) { // We need to follow common pointer manipulation uses to the accesses they // feed into. if (isa(I)) { // Follow all but ptr2int casts. TrackUse = !isa(I); return 0; } if (auto *GEP = dyn_cast(I)) { if (GEP->hasAllConstantIndices()) TrackUse = true; return 0; } MaybeAlign MA; if (const auto *CB = dyn_cast(I)) { if (CB->isBundleOperand(U) || CB->isCallee(U)) 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. auto *AlignAA = A.getAAFor(QueryingAA, IRP, DepClassTy::NONE); if (AlignAA) MA = MaybeAlign(AlignAA->getKnownAlign()); } const DataLayout &DL = A.getDataLayout(); const Value *UseV = U->get(); if (auto *SI = dyn_cast(I)) { if (SI->getPointerOperand() == UseV) MA = SI->getAlign(); } else if (auto *LI = dyn_cast(I)) { if (LI->getPointerOperand() == UseV) MA = LI->getAlign(); } else if (auto *AI = dyn_cast(I)) { if (AI->getPointerOperand() == UseV) MA = AI->getAlign(); } else if (auto *AI = dyn_cast(I)) { if (AI->getPointerOperand() == UseV) MA = AI->getAlign(); } if (!MA || *MA <= QueryingAA.getKnownAlign()) return 0; unsigned Alignment = MA->value(); int64_t Offset; if (const Value *Base = GetPointerBaseWithConstantOffset(UseV, Offset, DL)) { if (Base == &AssociatedValue) { // BasePointerAddr + Offset = Alignment * Q for some integer Q. // So we can say that the maximum power of two which is a divisor of // gcd(Offset, Alignment) is an alignment. uint32_t gcd = std::gcd(uint32_t(abs((int32_t)Offset)), Alignment); Alignment = llvm::bit_floor(gcd); } } return Alignment; } struct AAAlignImpl : AAAlign { AAAlignImpl(const IRPosition &IRP, Attributor &A) : AAAlign(IRP, A) {} /// See AbstractAttribute::initialize(...). void initialize(Attributor &A) override { SmallVector Attrs; A.getAttrs(getIRPosition(), {Attribute::Alignment}, Attrs); for (const Attribute &Attr : Attrs) takeKnownMaximum(Attr.getValueAsInt()); Value &V = *getAssociatedValue().stripPointerCasts(); takeKnownMaximum(V.getPointerAlignment(A.getDataLayout()).value()); if (Instruction *CtxI = getCtxI()) followUsesInMBEC(*this, A, getState(), *CtxI); } /// See AbstractAttribute::manifest(...). ChangeStatus manifest(Attributor &A) override { ChangeStatus LoadStoreChanged = ChangeStatus::UNCHANGED; // Check for users that allow alignment annotations. Value &AssociatedValue = getAssociatedValue(); for (const Use &U : AssociatedValue.uses()) { if (auto *SI = dyn_cast(U.getUser())) { if (SI->getPointerOperand() == &AssociatedValue) if (SI->getAlign() < getAssumedAlign()) { STATS_DECLTRACK(AAAlign, Store, "Number of times alignment added to a store"); SI->setAlignment(getAssumedAlign()); LoadStoreChanged = ChangeStatus::CHANGED; } } else if (auto *LI = dyn_cast(U.getUser())) { if (LI->getPointerOperand() == &AssociatedValue) if (LI->getAlign() < getAssumedAlign()) { LI->setAlignment(getAssumedAlign()); STATS_DECLTRACK(AAAlign, Load, "Number of times alignment added to a load"); LoadStoreChanged = ChangeStatus::CHANGED; } } } ChangeStatus Changed = AAAlign::manifest(A); Align InheritAlign = getAssociatedValue().getPointerAlignment(A.getDataLayout()); if (InheritAlign >= getAssumedAlign()) return LoadStoreChanged; return Changed | LoadStoreChanged; } // TODO: Provide a helper to determine the implied ABI alignment and check in // the existing manifest method and a new one for AAAlignImpl that value // to avoid making the alignment explicit if it did not improve. /// See AbstractAttribute::getDeducedAttributes void getDeducedAttributes(Attributor &A, LLVMContext &Ctx, SmallVectorImpl &Attrs) const override { if (getAssumedAlign() > 1) Attrs.emplace_back( Attribute::getWithAlignment(Ctx, Align(getAssumedAlign()))); } /// See followUsesInMBEC bool followUseInMBEC(Attributor &A, const Use *U, const Instruction *I, AAAlign::StateType &State) { bool TrackUse = false; unsigned int KnownAlign = getKnownAlignForUse(A, *this, getAssociatedValue(), U, I, TrackUse); State.takeKnownMaximum(KnownAlign); return TrackUse; } /// See AbstractAttribute::getAsStr(). const std::string getAsStr(Attributor *A) const override { return "align<" + std::to_string(getKnownAlign().value()) + "-" + std::to_string(getAssumedAlign().value()) + ">"; } }; /// Align attribute for a floating value. struct AAAlignFloating : AAAlignImpl { AAAlignFloating(const IRPosition &IRP, Attributor &A) : AAAlignImpl(IRP, A) {} /// See AbstractAttribute::updateImpl(...). ChangeStatus updateImpl(Attributor &A) override { const DataLayout &DL = A.getDataLayout(); bool Stripped; bool UsedAssumedInformation = false; SmallVector Values; if (!A.getAssumedSimplifiedValues(getIRPosition(), *this, Values, AA::AnyScope, UsedAssumedInformation)) { Values.push_back({getAssociatedValue(), getCtxI()}); Stripped = false; } else { Stripped = Values.size() != 1 || Values.front().getValue() != &getAssociatedValue(); } StateType T; auto VisitValueCB = [&](Value &V) -> bool { if (isa(V) || isa(V)) return true; const auto *AA = A.getAAFor(*this, IRPosition::value(V), DepClassTy::REQUIRED); if (!AA || (!Stripped && this == AA)) { int64_t Offset; unsigned Alignment = 1; if (const Value *Base = GetPointerBaseWithConstantOffset(&V, Offset, DL)) { // TODO: Use AAAlign for the base too. Align PA = Base->getPointerAlignment(DL); // BasePointerAddr + Offset = Alignment * Q for some integer Q. // So we can say that the maximum power of two which is a divisor of // gcd(Offset, Alignment) is an alignment. uint32_t gcd = std::gcd(uint32_t(abs((int32_t)Offset)), uint32_t(PA.value())); Alignment = llvm::bit_floor(gcd); } else { Alignment = V.getPointerAlignment(DL).value(); } // Use only IR information if we did not strip anything. T.takeKnownMaximum(Alignment); T.indicatePessimisticFixpoint(); } else { // Use abstract attribute information. const AAAlign::StateType &DS = AA->getState(); T ^= DS; } return T.isValidState(); }; for (const auto &VAC : Values) { if (!VisitValueCB(*VAC.getValue())) return indicatePessimisticFixpoint(); } // 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(getState(), T); } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_FLOATING_ATTR(align) } }; /// Align attribute for function return value. struct AAAlignReturned final : AAReturnedFromReturnedValues { using Base = AAReturnedFromReturnedValues; AAAlignReturned(const IRPosition &IRP, Attributor &A) : Base(IRP, A) {} /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_FNRET_ATTR(aligned) } }; /// Align attribute for function argument. struct AAAlignArgument final : AAArgumentFromCallSiteArguments { using Base = AAArgumentFromCallSiteArguments; AAAlignArgument(const IRPosition &IRP, Attributor &A) : Base(IRP, A) {} /// See AbstractAttribute::manifest(...). ChangeStatus manifest(Attributor &A) override { // If the associated argument is involved in a must-tail call we give up // because we would need to keep the argument alignments of caller and // callee in-sync. Just does not seem worth the trouble right now. if (A.getInfoCache().isInvolvedInMustTailCall(*getAssociatedArgument())) return ChangeStatus::UNCHANGED; return Base::manifest(A); } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_ARG_ATTR(aligned) } }; struct AAAlignCallSiteArgument final : AAAlignFloating { AAAlignCallSiteArgument(const IRPosition &IRP, Attributor &A) : AAAlignFloating(IRP, A) {} /// See AbstractAttribute::manifest(...). ChangeStatus manifest(Attributor &A) override { // If the associated argument is involved in a must-tail call we give up // because we would need to keep the argument alignments of caller and // callee in-sync. Just does not seem worth the trouble right now. if (Argument *Arg = getAssociatedArgument()) if (A.getInfoCache().isInvolvedInMustTailCall(*Arg)) return ChangeStatus::UNCHANGED; ChangeStatus Changed = AAAlignImpl::manifest(A); Align InheritAlign = getAssociatedValue().getPointerAlignment(A.getDataLayout()); if (InheritAlign >= getAssumedAlign()) Changed = ChangeStatus::UNCHANGED; return Changed; } /// See AbstractAttribute::updateImpl(Attributor &A). ChangeStatus updateImpl(Attributor &A) override { ChangeStatus Changed = AAAlignFloating::updateImpl(A); if (Argument *Arg = getAssociatedArgument()) { // We only take known information from the argument // so we do not need to track a dependence. const auto *ArgAlignAA = A.getAAFor( *this, IRPosition::argument(*Arg), DepClassTy::NONE); if (ArgAlignAA) takeKnownMaximum(ArgAlignAA->getKnownAlign().value()); } return Changed; } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_CSARG_ATTR(aligned) } }; /// Align attribute deduction for a call site return value. struct AAAlignCallSiteReturned final : AACalleeToCallSite { using Base = AACalleeToCallSite; AAAlignCallSiteReturned(const IRPosition &IRP, Attributor &A) : Base(IRP, A) {} /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_CS_ATTR(align); } }; } // namespace /// ------------------ Function No-Return Attribute ---------------------------- namespace { struct AANoReturnImpl : public AANoReturn { AANoReturnImpl(const IRPosition &IRP, Attributor &A) : AANoReturn(IRP, A) {} /// See AbstractAttribute::initialize(...). void initialize(Attributor &A) override { bool IsKnown; assert(!AA::hasAssumedIRAttr( A, nullptr, getIRPosition(), DepClassTy::NONE, IsKnown)); (void)IsKnown; } /// See AbstractAttribute::getAsStr(). const std::string getAsStr(Attributor *A) const override { return getAssumed() ? "noreturn" : "may-return"; } /// See AbstractAttribute::updateImpl(Attributor &A). ChangeStatus updateImpl(Attributor &A) override { auto CheckForNoReturn = [](Instruction &) { return false; }; bool UsedAssumedInformation = false; if (!A.checkForAllInstructions(CheckForNoReturn, *this, {(unsigned)Instruction::Ret}, UsedAssumedInformation)) return indicatePessimisticFixpoint(); return ChangeStatus::UNCHANGED; } }; struct AANoReturnFunction final : AANoReturnImpl { AANoReturnFunction(const IRPosition &IRP, Attributor &A) : AANoReturnImpl(IRP, A) {} /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_FN_ATTR(noreturn) } }; /// NoReturn attribute deduction for a call sites. struct AANoReturnCallSite final : AACalleeToCallSite { AANoReturnCallSite(const IRPosition &IRP, Attributor &A) : AACalleeToCallSite(IRP, A) {} /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_CS_ATTR(noreturn); } }; } // namespace /// ----------------------- Instance Info --------------------------------- namespace { /// A class to hold the state of for no-capture attributes. struct AAInstanceInfoImpl : public AAInstanceInfo { AAInstanceInfoImpl(const IRPosition &IRP, Attributor &A) : AAInstanceInfo(IRP, A) {} /// See AbstractAttribute::initialize(...). void initialize(Attributor &A) override { Value &V = getAssociatedValue(); if (auto *C = dyn_cast(&V)) { if (C->isThreadDependent()) indicatePessimisticFixpoint(); else indicateOptimisticFixpoint(); return; } if (auto *CB = dyn_cast(&V)) if (CB->arg_size() == 0 && !CB->mayHaveSideEffects() && !CB->mayReadFromMemory()) { indicateOptimisticFixpoint(); return; } if (auto *I = dyn_cast(&V)) { const auto *CI = A.getInfoCache().getAnalysisResultForFunction( *I->getFunction()); if (mayBeInCycle(CI, I, /* HeaderOnly */ false)) { indicatePessimisticFixpoint(); return; } } } /// See AbstractAttribute::updateImpl(...). ChangeStatus updateImpl(Attributor &A) override { ChangeStatus Changed = ChangeStatus::UNCHANGED; Value &V = getAssociatedValue(); const Function *Scope = nullptr; if (auto *I = dyn_cast(&V)) Scope = I->getFunction(); if (auto *A = dyn_cast(&V)) { Scope = A->getParent(); if (!Scope->hasLocalLinkage()) return Changed; } if (!Scope) return indicateOptimisticFixpoint(); bool IsKnownNoRecurse; if (AA::hasAssumedIRAttr( A, this, IRPosition::function(*Scope), DepClassTy::OPTIONAL, IsKnownNoRecurse)) return Changed; auto UsePred = [&](const Use &U, bool &Follow) { const Instruction *UserI = dyn_cast(U.getUser()); if (!UserI || isa(UserI) || isa(UserI) || isa(UserI) || isa(UserI)) { Follow = true; return true; } if (isa(UserI) || isa(UserI) || (isa(UserI) && cast(UserI)->getValueOperand() != U.get())) return true; if (auto *CB = dyn_cast(UserI)) { // This check is not guaranteeing uniqueness but for now that we cannot // end up with two versions of \p U thinking it was one. auto *Callee = dyn_cast_if_present(CB->getCalledOperand()); if (!Callee || !Callee->hasLocalLinkage()) return true; if (!CB->isArgOperand(&U)) return false; const auto *ArgInstanceInfoAA = A.getAAFor( *this, IRPosition::callsite_argument(*CB, CB->getArgOperandNo(&U)), DepClassTy::OPTIONAL); if (!ArgInstanceInfoAA || !ArgInstanceInfoAA->isAssumedUniqueForAnalysis()) return false; // If this call base might reach the scope again we might forward the // argument back here. This is very conservative. if (AA::isPotentiallyReachable( A, *CB, *Scope, *this, /* ExclusionSet */ nullptr, [Scope](const Function &Fn) { return &Fn != Scope; })) return false; return true; } return false; }; auto EquivalentUseCB = [&](const Use &OldU, const Use &NewU) { if (auto *SI = dyn_cast(OldU.getUser())) { auto *Ptr = SI->getPointerOperand()->stripPointerCasts(); if ((isa(Ptr) || isNoAliasCall(Ptr)) && AA::isDynamicallyUnique(A, *this, *Ptr)) return true; } return false; }; if (!A.checkForAllUses(UsePred, *this, V, /* CheckBBLivenessOnly */ true, DepClassTy::OPTIONAL, /* IgnoreDroppableUses */ true, EquivalentUseCB)) return indicatePessimisticFixpoint(); return Changed; } /// See AbstractState::getAsStr(). const std::string getAsStr(Attributor *A) const override { return isAssumedUniqueForAnalysis() ? "" : ""; } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override {} }; /// InstanceInfo attribute for floating values. struct AAInstanceInfoFloating : AAInstanceInfoImpl { AAInstanceInfoFloating(const IRPosition &IRP, Attributor &A) : AAInstanceInfoImpl(IRP, A) {} }; /// NoCapture attribute for function arguments. struct AAInstanceInfoArgument final : AAInstanceInfoFloating { AAInstanceInfoArgument(const IRPosition &IRP, Attributor &A) : AAInstanceInfoFloating(IRP, A) {} }; /// InstanceInfo attribute for call site arguments. struct AAInstanceInfoCallSiteArgument final : AAInstanceInfoImpl { AAInstanceInfoCallSiteArgument(const IRPosition &IRP, Attributor &A) : AAInstanceInfoImpl(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); auto *ArgAA = A.getAAFor(*this, ArgPos, DepClassTy::REQUIRED); if (!ArgAA) return indicatePessimisticFixpoint(); return clampStateAndIndicateChange(getState(), ArgAA->getState()); } }; /// InstanceInfo attribute for function return value. struct AAInstanceInfoReturned final : AAInstanceInfoImpl { AAInstanceInfoReturned(const IRPosition &IRP, Attributor &A) : AAInstanceInfoImpl(IRP, A) { llvm_unreachable("InstanceInfo is not applicable to function returns!"); } /// See AbstractAttribute::initialize(...). void initialize(Attributor &A) override { llvm_unreachable("InstanceInfo is not applicable to function returns!"); } /// See AbstractAttribute::updateImpl(...). ChangeStatus updateImpl(Attributor &A) override { llvm_unreachable("InstanceInfo is not applicable to function returns!"); } }; /// InstanceInfo attribute deduction for a call site return value. struct AAInstanceInfoCallSiteReturned final : AAInstanceInfoFloating { AAInstanceInfoCallSiteReturned(const IRPosition &IRP, Attributor &A) : AAInstanceInfoFloating(IRP, A) {} }; } // namespace /// ----------------------- Variable Capturing --------------------------------- bool AANoCapture::isImpliedByIR(Attributor &A, const IRPosition &IRP, Attribute::AttrKind ImpliedAttributeKind, bool IgnoreSubsumingPositions) { assert(ImpliedAttributeKind == Attribute::NoCapture && "Unexpected attribute kind"); Value &V = IRP.getAssociatedValue(); if (!IRP.isArgumentPosition()) return V.use_empty(); // You cannot "capture" null in the default address space. // // FIXME: This should use NullPointerIsDefined to account for the function // attribute. if (isa(V) || (isa(V) && V.getType()->getPointerAddressSpace() == 0)) { return true; } if (A.hasAttr(IRP, {Attribute::NoCapture}, /* IgnoreSubsumingPositions */ true, Attribute::NoCapture)) return true; if (IRP.getPositionKind() == IRP_CALL_SITE_ARGUMENT) if (Argument *Arg = IRP.getAssociatedArgument()) if (A.hasAttr(IRPosition::argument(*Arg), {Attribute::NoCapture, Attribute::ByVal}, /* IgnoreSubsumingPositions */ true)) { A.manifestAttrs(IRP, Attribute::get(V.getContext(), Attribute::NoCapture)); return true; } if (const Function *F = IRP.getAssociatedFunction()) { // Check what state the associated function can actually capture. AANoCapture::StateType State; determineFunctionCaptureCapabilities(IRP, *F, State); if (State.isKnown(NO_CAPTURE)) { A.manifestAttrs(IRP, Attribute::get(V.getContext(), Attribute::NoCapture)); return true; } } return false; } /// Set the NOT_CAPTURED_IN_MEM and NOT_CAPTURED_IN_RET bits in \p Known /// depending on the ability of the function associated with \p IRP to capture /// state in memory and through "returning/throwing", respectively. void AANoCapture::determineFunctionCaptureCapabilities(const IRPosition &IRP, const Function &F, BitIntegerState &State) { // TODO: Once we have memory behavior attributes we should use them here. // If we know we cannot communicate or write to memory, we do not care about // ptr2int anymore. bool ReadOnly = F.onlyReadsMemory(); bool NoThrow = F.doesNotThrow(); bool IsVoidReturn = F.getReturnType()->isVoidTy(); if (ReadOnly && NoThrow && IsVoidReturn) { State.addKnownBits(NO_CAPTURE); return; } // A function cannot capture state in memory if it only reads memory, it can // however return/throw state and the state might be influenced by the // pointer value, e.g., loading from a returned pointer might reveal a bit. if (ReadOnly) State.addKnownBits(NOT_CAPTURED_IN_MEM); // A function cannot communicate state back if it does not through // exceptions and doesn not return values. if (NoThrow && IsVoidReturn) State.addKnownBits(NOT_CAPTURED_IN_RET); // Check existing "returned" attributes. int ArgNo = IRP.getCalleeArgNo(); if (!NoThrow || ArgNo < 0 || !F.getAttributes().hasAttrSomewhere(Attribute::Returned)) return; for (unsigned U = 0, E = F.arg_size(); U < E; ++U) if (F.hasParamAttribute(U, Attribute::Returned)) { if (U == unsigned(ArgNo)) State.removeAssumedBits(NOT_CAPTURED_IN_RET); else if (ReadOnly) State.addKnownBits(NO_CAPTURE); else State.addKnownBits(NOT_CAPTURED_IN_RET); break; } } namespace { /// A class to hold the state of for no-capture attributes. struct AANoCaptureImpl : public AANoCapture { AANoCaptureImpl(const IRPosition &IRP, Attributor &A) : AANoCapture(IRP, A) {} /// See AbstractAttribute::initialize(...). void initialize(Attributor &A) override { bool IsKnown; assert(!AA::hasAssumedIRAttr( A, nullptr, getIRPosition(), DepClassTy::NONE, IsKnown)); (void)IsKnown; } /// See AbstractAttribute::updateImpl(...). ChangeStatus updateImpl(Attributor &A) override; /// see AbstractAttribute::isAssumedNoCaptureMaybeReturned(...). void getDeducedAttributes(Attributor &A, LLVMContext &Ctx, SmallVectorImpl &Attrs) const override { if (!isAssumedNoCaptureMaybeReturned()) return; if (isArgumentPosition()) { if (isAssumedNoCapture()) Attrs.emplace_back(Attribute::get(Ctx, Attribute::NoCapture)); else if (ManifestInternal) Attrs.emplace_back(Attribute::get(Ctx, "no-capture-maybe-returned")); } } /// See AbstractState::getAsStr(). const std::string getAsStr(Attributor *A) const override { if (isKnownNoCapture()) return "known not-captured"; if (isAssumedNoCapture()) return "assumed not-captured"; if (isKnownNoCaptureMaybeReturned()) return "known not-captured-maybe-returned"; if (isAssumedNoCaptureMaybeReturned()) return "assumed not-captured-maybe-returned"; return "assumed-captured"; } /// Check the use \p U and update \p State accordingly. Return true if we /// should continue to update the state. bool checkUse(Attributor &A, AANoCapture::StateType &State, const Use &U, bool &Follow) { Instruction *UInst = cast(U.getUser()); LLVM_DEBUG(dbgs() << "[AANoCapture] Check use: " << *U.get() << " in " << *UInst << "\n"); // Deal with ptr2int by following uses. if (isa(UInst)) { LLVM_DEBUG(dbgs() << " - ptr2int assume the worst!\n"); return isCapturedIn(State, /* Memory */ true, /* Integer */ true, /* Return */ true); } // For stores we already checked if we can follow them, if they make it // here we give up. if (isa(UInst)) return isCapturedIn(State, /* Memory */ true, /* Integer */ true, /* Return */ true); // Explicitly catch return instructions. if (isa(UInst)) { if (UInst->getFunction() == getAnchorScope()) return isCapturedIn(State, /* Memory */ false, /* Integer */ false, /* Return */ true); return isCapturedIn(State, /* Memory */ true, /* Integer */ true, /* Return */ true); } // For now we only use special logic for call sites. However, the tracker // itself knows about a lot of other non-capturing cases already. auto *CB = dyn_cast(UInst); if (!CB || !CB->isArgOperand(&U)) return isCapturedIn(State, /* Memory */ true, /* Integer */ true, /* Return */ true); unsigned ArgNo = CB->getArgOperandNo(&U); const IRPosition &CSArgPos = IRPosition::callsite_argument(*CB, ArgNo); // If we have a abstract no-capture attribute for the argument we can use // it to justify a non-capture attribute here. This allows recursion! bool IsKnownNoCapture; const AANoCapture *ArgNoCaptureAA = nullptr; bool IsAssumedNoCapture = AA::hasAssumedIRAttr( A, this, CSArgPos, DepClassTy::REQUIRED, IsKnownNoCapture, false, &ArgNoCaptureAA); if (IsAssumedNoCapture) return isCapturedIn(State, /* Memory */ false, /* Integer */ false, /* Return */ false); if (ArgNoCaptureAA && ArgNoCaptureAA->isAssumedNoCaptureMaybeReturned()) { Follow = true; return isCapturedIn(State, /* Memory */ false, /* Integer */ false, /* Return */ false); } // Lastly, we could not find a reason no-capture can be assumed so we don't. return isCapturedIn(State, /* Memory */ true, /* Integer */ true, /* Return */ true); } /// Update \p State according to \p CapturedInMem, \p CapturedInInt, and /// \p CapturedInRet, then return true if we should continue updating the /// state. static bool isCapturedIn(AANoCapture::StateType &State, bool CapturedInMem, bool CapturedInInt, bool CapturedInRet) { LLVM_DEBUG(dbgs() << " - captures [Mem " << CapturedInMem << "|Int " << CapturedInInt << "|Ret " << CapturedInRet << "]\n"); if (CapturedInMem) State.removeAssumedBits(AANoCapture::NOT_CAPTURED_IN_MEM); if (CapturedInInt) State.removeAssumedBits(AANoCapture::NOT_CAPTURED_IN_INT); if (CapturedInRet) State.removeAssumedBits(AANoCapture::NOT_CAPTURED_IN_RET); return State.isAssumed(AANoCapture::NO_CAPTURE_MAYBE_RETURNED); } }; ChangeStatus AANoCaptureImpl::updateImpl(Attributor &A) { const IRPosition &IRP = getIRPosition(); Value *V = isArgumentPosition() ? IRP.getAssociatedArgument() : &IRP.getAssociatedValue(); if (!V) return indicatePessimisticFixpoint(); const Function *F = isArgumentPosition() ? IRP.getAssociatedFunction() : IRP.getAnchorScope(); // TODO: Is the checkForAllUses below useful for constants? if (!F) return indicatePessimisticFixpoint(); AANoCapture::StateType T; const IRPosition &FnPos = IRPosition::function(*F); // Readonly means we cannot capture through memory. bool IsKnown; if (AA::isAssumedReadOnly(A, FnPos, *this, IsKnown)) { T.addKnownBits(NOT_CAPTURED_IN_MEM); if (IsKnown) addKnownBits(NOT_CAPTURED_IN_MEM); } // Make sure all returned values are different than the underlying value. // TODO: we could do this in a more sophisticated way inside // AAReturnedValues, e.g., track all values that escape through returns // directly somehow. auto CheckReturnedArgs = [&](bool &UsedAssumedInformation) { SmallVector Values; if (!A.getAssumedSimplifiedValues(IRPosition::returned(*F), this, Values, AA::ValueScope::Intraprocedural, UsedAssumedInformation)) return false; bool SeenConstant = false; for (const AA::ValueAndContext &VAC : Values) { if (isa(VAC.getValue())) { if (SeenConstant) return false; SeenConstant = true; } else if (!isa(VAC.getValue()) || VAC.getValue() == getAssociatedArgument()) return false; } return true; }; bool IsKnownNoUnwind; if (AA::hasAssumedIRAttr( A, this, FnPos, DepClassTy::OPTIONAL, IsKnownNoUnwind)) { bool IsVoidTy = F->getReturnType()->isVoidTy(); bool UsedAssumedInformation = false; if (IsVoidTy || CheckReturnedArgs(UsedAssumedInformation)) { T.addKnownBits(NOT_CAPTURED_IN_RET); if (T.isKnown(NOT_CAPTURED_IN_MEM)) return ChangeStatus::UNCHANGED; if (IsKnownNoUnwind && (IsVoidTy || !UsedAssumedInformation)) { addKnownBits(NOT_CAPTURED_IN_RET); if (isKnown(NOT_CAPTURED_IN_MEM)) return indicateOptimisticFixpoint(); } } } auto IsDereferenceableOrNull = [&](Value *O, const DataLayout &DL) { const auto *DerefAA = A.getAAFor( *this, IRPosition::value(*O), DepClassTy::OPTIONAL); return DerefAA && DerefAA->getAssumedDereferenceableBytes(); }; auto UseCheck = [&](const Use &U, bool &Follow) -> bool { switch (DetermineUseCaptureKind(U, IsDereferenceableOrNull)) { case UseCaptureKind::NO_CAPTURE: return true; case UseCaptureKind::MAY_CAPTURE: return checkUse(A, T, U, Follow); case UseCaptureKind::PASSTHROUGH: Follow = true; return true; } llvm_unreachable("Unexpected use capture kind!"); }; if (!A.checkForAllUses(UseCheck, *this, *V)) return indicatePessimisticFixpoint(); AANoCapture::StateType &S = getState(); auto Assumed = S.getAssumed(); S.intersectAssumedBits(T.getAssumed()); if (!isAssumedNoCaptureMaybeReturned()) return indicatePessimisticFixpoint(); return Assumed == S.getAssumed() ? ChangeStatus::UNCHANGED : ChangeStatus::CHANGED; } /// NoCapture attribute for function arguments. struct AANoCaptureArgument final : AANoCaptureImpl { AANoCaptureArgument(const IRPosition &IRP, Attributor &A) : AANoCaptureImpl(IRP, A) {} /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_ARG_ATTR(nocapture) } }; /// NoCapture attribute for call site arguments. struct AANoCaptureCallSiteArgument final : AANoCaptureImpl { AANoCaptureCallSiteArgument(const IRPosition &IRP, Attributor &A) : AANoCaptureImpl(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 IsKnownNoCapture; const AANoCapture *ArgAA = nullptr; if (AA::hasAssumedIRAttr( A, this, ArgPos, DepClassTy::REQUIRED, IsKnownNoCapture, false, &ArgAA)) return ChangeStatus::UNCHANGED; if (!ArgAA || !ArgAA->isAssumedNoCaptureMaybeReturned()) return indicatePessimisticFixpoint(); return clampStateAndIndicateChange(getState(), ArgAA->getState()); } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override{STATS_DECLTRACK_CSARG_ATTR(nocapture)}; }; /// NoCapture attribute for floating values. struct AANoCaptureFloating final : AANoCaptureImpl { AANoCaptureFloating(const IRPosition &IRP, Attributor &A) : AANoCaptureImpl(IRP, A) {} /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_FLOATING_ATTR(nocapture) } }; /// NoCapture attribute for function return value. struct AANoCaptureReturned final : AANoCaptureImpl { AANoCaptureReturned(const IRPosition &IRP, Attributor &A) : AANoCaptureImpl(IRP, A) { llvm_unreachable("NoCapture is not applicable to function returns!"); } /// See AbstractAttribute::initialize(...). void initialize(Attributor &A) override { llvm_unreachable("NoCapture is not applicable to function returns!"); } /// See AbstractAttribute::updateImpl(...). ChangeStatus updateImpl(Attributor &A) override { llvm_unreachable("NoCapture is not applicable to function returns!"); } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override {} }; /// NoCapture attribute deduction for a call site return value. struct AANoCaptureCallSiteReturned final : AANoCaptureImpl { AANoCaptureCallSiteReturned(const IRPosition &IRP, Attributor &A) : AANoCaptureImpl(IRP, A) {} /// See AbstractAttribute::initialize(...). void initialize(Attributor &A) override { const Function *F = getAnchorScope(); // Check what state the associated function can actually capture. determineFunctionCaptureCapabilities(getIRPosition(), *F, *this); } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_CSRET_ATTR(nocapture) } }; } // namespace /// ------------------ Value Simplify Attribute ---------------------------- bool ValueSimplifyStateType::unionAssumed(std::optional Other) { // FIXME: Add a typecast support. SimplifiedAssociatedValue = AA::combineOptionalValuesInAAValueLatice( SimplifiedAssociatedValue, Other, Ty); if (SimplifiedAssociatedValue == std::optional(nullptr)) return false; LLVM_DEBUG({ if (SimplifiedAssociatedValue) dbgs() << "[ValueSimplify] is assumed to be " << **SimplifiedAssociatedValue << "\n"; else dbgs() << "[ValueSimplify] is assumed to be \n"; }); return true; } namespace { struct AAValueSimplifyImpl : AAValueSimplify { AAValueSimplifyImpl(const IRPosition &IRP, Attributor &A) : AAValueSimplify(IRP, A) {} /// See AbstractAttribute::initialize(...). void initialize(Attributor &A) override { if (getAssociatedValue().getType()->isVoidTy()) indicatePessimisticFixpoint(); if (A.hasSimplificationCallback(getIRPosition())) indicatePessimisticFixpoint(); } /// See AbstractAttribute::getAsStr(). const std::string getAsStr(Attributor *A) const override { LLVM_DEBUG({ dbgs() << "SAV: " << (bool)SimplifiedAssociatedValue << " "; if (SimplifiedAssociatedValue && *SimplifiedAssociatedValue) dbgs() << "SAV: " << **SimplifiedAssociatedValue << " "; }); return isValidState() ? (isAtFixpoint() ? "simplified" : "maybe-simple") : "not-simple"; } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override {} /// See AAValueSimplify::getAssumedSimplifiedValue() std::optional getAssumedSimplifiedValue(Attributor &A) const override { return SimplifiedAssociatedValue; } /// Ensure the return value is \p V with type \p Ty, if not possible return /// nullptr. If \p Check is true we will only verify such an operation would /// suceed and return a non-nullptr value if that is the case. No IR is /// generated or modified. static Value *ensureType(Attributor &A, Value &V, Type &Ty, Instruction *CtxI, bool Check) { if (auto *TypedV = AA::getWithType(V, Ty)) return TypedV; if (CtxI && V.getType()->canLosslesslyBitCastTo(&Ty)) return Check ? &V : BitCastInst::CreatePointerBitCastOrAddrSpaceCast( &V, &Ty, "", CtxI->getIterator()); return nullptr; } /// Reproduce \p I with type \p Ty or return nullptr if that is not posisble. /// If \p Check is true we will only verify such an operation would suceed and /// return a non-nullptr value if that is the case. No IR is generated or /// modified. static Value *reproduceInst(Attributor &A, const AbstractAttribute &QueryingAA, Instruction &I, Type &Ty, Instruction *CtxI, bool Check, ValueToValueMapTy &VMap) { assert(CtxI && "Cannot reproduce an instruction without context!"); if (Check && (I.mayReadFromMemory() || !isSafeToSpeculativelyExecute(&I, CtxI, /* DT */ nullptr, /* TLI */ nullptr))) return nullptr; for (Value *Op : I.operands()) { Value *NewOp = reproduceValue(A, QueryingAA, *Op, Ty, CtxI, Check, VMap); if (!NewOp) { assert(Check && "Manifest of new value unexpectedly failed!"); return nullptr; } if (!Check) VMap[Op] = NewOp; } if (Check) return &I; Instruction *CloneI = I.clone(); // TODO: Try to salvage debug information here. CloneI->setDebugLoc(DebugLoc()); VMap[&I] = CloneI; CloneI->insertBefore(CtxI); RemapInstruction(CloneI, VMap); return CloneI; } /// Reproduce \p V with type \p Ty or return nullptr if that is not posisble. /// If \p Check is true we will only verify such an operation would suceed and /// return a non-nullptr value if that is the case. No IR is generated or /// modified. static Value *reproduceValue(Attributor &A, const AbstractAttribute &QueryingAA, Value &V, Type &Ty, Instruction *CtxI, bool Check, ValueToValueMapTy &VMap) { if (const auto &NewV = VMap.lookup(&V)) return NewV; bool UsedAssumedInformation = false; std::optional SimpleV = A.getAssumedSimplified( V, QueryingAA, UsedAssumedInformation, AA::Interprocedural); if (!SimpleV.has_value()) return PoisonValue::get(&Ty); Value *EffectiveV = &V; if (*SimpleV) EffectiveV = *SimpleV; if (auto *C = dyn_cast(EffectiveV)) return C; if (CtxI && AA::isValidAtPosition(AA::ValueAndContext(*EffectiveV, *CtxI), A.getInfoCache())) return ensureType(A, *EffectiveV, Ty, CtxI, Check); if (auto *I = dyn_cast(EffectiveV)) if (Value *NewV = reproduceInst(A, QueryingAA, *I, Ty, CtxI, Check, VMap)) return ensureType(A, *NewV, Ty, CtxI, Check); return nullptr; } /// Return a value we can use as replacement for the associated one, or /// nullptr if we don't have one that makes sense. Value *manifestReplacementValue(Attributor &A, Instruction *CtxI) const { Value *NewV = SimplifiedAssociatedValue ? *SimplifiedAssociatedValue : UndefValue::get(getAssociatedType()); if (NewV && NewV != &getAssociatedValue()) { ValueToValueMapTy VMap; // First verify we can reprduce the value with the required type at the // context location before we actually start modifying the IR. if (reproduceValue(A, *this, *NewV, *getAssociatedType(), CtxI, /* CheckOnly */ true, VMap)) return reproduceValue(A, *this, *NewV, *getAssociatedType(), CtxI, /* CheckOnly */ false, VMap); } return nullptr; } /// Helper function for querying AAValueSimplify and updating candidate. /// \param IRP The value position we are trying to unify with SimplifiedValue bool checkAndUpdate(Attributor &A, const AbstractAttribute &QueryingAA, const IRPosition &IRP, bool Simplify = true) { bool UsedAssumedInformation = false; std::optional QueryingValueSimplified = &IRP.getAssociatedValue(); if (Simplify) QueryingValueSimplified = A.getAssumedSimplified( IRP, QueryingAA, UsedAssumedInformation, AA::Interprocedural); return unionAssumed(QueryingValueSimplified); } /// Returns a candidate is found or not template bool askSimplifiedValueFor(Attributor &A) { if (!getAssociatedValue().getType()->isIntegerTy()) return false; // This will also pass the call base context. const auto *AA = A.getAAFor(*this, getIRPosition(), DepClassTy::NONE); if (!AA) return false; std::optional COpt = AA->getAssumedConstant(A); if (!COpt) { SimplifiedAssociatedValue = std::nullopt; A.recordDependence(*AA, *this, DepClassTy::OPTIONAL); return true; } if (auto *C = *COpt) { SimplifiedAssociatedValue = C; A.recordDependence(*AA, *this, DepClassTy::OPTIONAL); return true; } return false; } bool askSimplifiedValueForOtherAAs(Attributor &A) { if (askSimplifiedValueFor(A)) return true; if (askSimplifiedValueFor(A)) return true; return false; } /// See AbstractAttribute::manifest(...). ChangeStatus manifest(Attributor &A) override { ChangeStatus Changed = ChangeStatus::UNCHANGED; for (auto &U : getAssociatedValue().uses()) { // Check if we need to adjust the insertion point to make sure the IR is // valid. Instruction *IP = dyn_cast(U.getUser()); if (auto *PHI = dyn_cast_or_null(IP)) IP = PHI->getIncomingBlock(U)->getTerminator(); if (auto *NewV = manifestReplacementValue(A, IP)) { LLVM_DEBUG(dbgs() << "[ValueSimplify] " << getAssociatedValue() << " -> " << *NewV << " :: " << *this << "\n"); if (A.changeUseAfterManifest(U, *NewV)) Changed = ChangeStatus::CHANGED; } } return Changed | AAValueSimplify::manifest(A); } /// See AbstractState::indicatePessimisticFixpoint(...). ChangeStatus indicatePessimisticFixpoint() override { SimplifiedAssociatedValue = &getAssociatedValue(); return AAValueSimplify::indicatePessimisticFixpoint(); } }; struct AAValueSimplifyArgument final : AAValueSimplifyImpl { AAValueSimplifyArgument(const IRPosition &IRP, Attributor &A) : AAValueSimplifyImpl(IRP, A) {} void initialize(Attributor &A) override { AAValueSimplifyImpl::initialize(A); if (A.hasAttr(getIRPosition(), {Attribute::InAlloca, Attribute::Preallocated, Attribute::StructRet, Attribute::Nest, Attribute::ByVal}, /* IgnoreSubsumingPositions */ true)) indicatePessimisticFixpoint(); } /// See AbstractAttribute::updateImpl(...). ChangeStatus updateImpl(Attributor &A) override { // Byval is only replacable if it is readonly otherwise we would write into // the replaced value and not the copy that byval creates implicitly. Argument *Arg = getAssociatedArgument(); if (Arg->hasByValAttr()) { // TODO: We probably need to verify synchronization is not an issue, e.g., // there is no race by not copying a constant byval. bool IsKnown; if (!AA::isAssumedReadOnly(A, getIRPosition(), *this, IsKnown)) return indicatePessimisticFixpoint(); } auto Before = SimplifiedAssociatedValue; auto PredForCallSite = [&](AbstractCallSite ACS) { const IRPosition &ACSArgPos = IRPosition::callsite_argument(ACS, getCallSiteArgNo()); // 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; // Simplify the argument operand explicitly and check if the result is // valid in the current scope. This avoids refering to simplified values // in other functions, e.g., we don't want to say a an argument in a // static function is actually an argument in a different function. bool UsedAssumedInformation = false; std::optional SimpleArgOp = A.getAssumedConstant(ACSArgPos, *this, UsedAssumedInformation); if (!SimpleArgOp) return true; if (!*SimpleArgOp) return false; if (!AA::isDynamicallyUnique(A, *this, **SimpleArgOp)) return false; return unionAssumed(*SimpleArgOp); }; // Generate a answer specific to a call site context. bool Success; bool UsedAssumedInformation = false; if (hasCallBaseContext() && getCallBaseContext()->getCalledOperand() == Arg->getParent()) Success = PredForCallSite( AbstractCallSite(&getCallBaseContext()->getCalledOperandUse())); else Success = A.checkForAllCallSites(PredForCallSite, *this, true, UsedAssumedInformation); if (!Success) if (!askSimplifiedValueForOtherAAs(A)) return indicatePessimisticFixpoint(); // If a candidate was found in this update, return CHANGED. return Before == SimplifiedAssociatedValue ? ChangeStatus::UNCHANGED : ChangeStatus ::CHANGED; } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_ARG_ATTR(value_simplify) } }; struct AAValueSimplifyReturned : AAValueSimplifyImpl { AAValueSimplifyReturned(const IRPosition &IRP, Attributor &A) : AAValueSimplifyImpl(IRP, A) {} /// See AAValueSimplify::getAssumedSimplifiedValue() std::optional getAssumedSimplifiedValue(Attributor &A) const override { if (!isValidState()) return nullptr; return SimplifiedAssociatedValue; } /// See AbstractAttribute::updateImpl(...). ChangeStatus updateImpl(Attributor &A) override { auto Before = SimplifiedAssociatedValue; auto ReturnInstCB = [&](Instruction &I) { auto &RI = cast(I); return checkAndUpdate( A, *this, IRPosition::value(*RI.getReturnValue(), getCallBaseContext())); }; bool UsedAssumedInformation = false; if (!A.checkForAllInstructions(ReturnInstCB, *this, {Instruction::Ret}, UsedAssumedInformation)) if (!askSimplifiedValueForOtherAAs(A)) return indicatePessimisticFixpoint(); // If a candidate was found in this update, return CHANGED. return Before == SimplifiedAssociatedValue ? ChangeStatus::UNCHANGED : ChangeStatus ::CHANGED; } ChangeStatus manifest(Attributor &A) override { // We queried AAValueSimplify for the returned values so they will be // replaced if a simplified form was found. Nothing to do here. return ChangeStatus::UNCHANGED; } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_FNRET_ATTR(value_simplify) } }; struct AAValueSimplifyFloating : AAValueSimplifyImpl { AAValueSimplifyFloating(const IRPosition &IRP, Attributor &A) : AAValueSimplifyImpl(IRP, A) {} /// See AbstractAttribute::initialize(...). void initialize(Attributor &A) override { AAValueSimplifyImpl::initialize(A); Value &V = getAnchorValue(); // TODO: add other stuffs if (isa(V)) indicatePessimisticFixpoint(); } /// See AbstractAttribute::updateImpl(...). ChangeStatus updateImpl(Attributor &A) override { auto Before = SimplifiedAssociatedValue; if (!askSimplifiedValueForOtherAAs(A)) return indicatePessimisticFixpoint(); // If a candidate was found in this update, return CHANGED. return Before == SimplifiedAssociatedValue ? ChangeStatus::UNCHANGED : ChangeStatus ::CHANGED; } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_FLOATING_ATTR(value_simplify) } }; struct AAValueSimplifyFunction : AAValueSimplifyImpl { AAValueSimplifyFunction(const IRPosition &IRP, Attributor &A) : AAValueSimplifyImpl(IRP, A) {} /// See AbstractAttribute::initialize(...). void initialize(Attributor &A) override { SimplifiedAssociatedValue = nullptr; indicateOptimisticFixpoint(); } /// See AbstractAttribute::initialize(...). ChangeStatus updateImpl(Attributor &A) override { llvm_unreachable( "AAValueSimplify(Function|CallSite)::updateImpl will not be called"); } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_FN_ATTR(value_simplify) } }; struct AAValueSimplifyCallSite : AAValueSimplifyFunction { AAValueSimplifyCallSite(const IRPosition &IRP, Attributor &A) : AAValueSimplifyFunction(IRP, A) {} /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_CS_ATTR(value_simplify) } }; struct AAValueSimplifyCallSiteReturned : AAValueSimplifyImpl { AAValueSimplifyCallSiteReturned(const IRPosition &IRP, Attributor &A) : AAValueSimplifyImpl(IRP, A) {} void initialize(Attributor &A) override { AAValueSimplifyImpl::initialize(A); Function *Fn = getAssociatedFunction(); assert(Fn && "Did expect an associted function"); for (Argument &Arg : Fn->args()) { if (Arg.hasReturnedAttr()) { auto IRP = IRPosition::callsite_argument(*cast(getCtxI()), Arg.getArgNo()); if (IRP.getPositionKind() == IRPosition::IRP_CALL_SITE_ARGUMENT && checkAndUpdate(A, *this, IRP)) indicateOptimisticFixpoint(); else indicatePessimisticFixpoint(); return; } } } /// See AbstractAttribute::updateImpl(...). ChangeStatus updateImpl(Attributor &A) override { return indicatePessimisticFixpoint(); } void trackStatistics() const override { STATS_DECLTRACK_CSRET_ATTR(value_simplify) } }; struct AAValueSimplifyCallSiteArgument : AAValueSimplifyFloating { AAValueSimplifyCallSiteArgument(const IRPosition &IRP, Attributor &A) : AAValueSimplifyFloating(IRP, A) {} /// See AbstractAttribute::manifest(...). ChangeStatus manifest(Attributor &A) override { ChangeStatus Changed = ChangeStatus::UNCHANGED; // TODO: We should avoid simplification duplication to begin with. auto *FloatAA = A.lookupAAFor( IRPosition::value(getAssociatedValue()), this, DepClassTy::NONE); if (FloatAA && FloatAA->getState().isValidState()) return Changed; if (auto *NewV = manifestReplacementValue(A, getCtxI())) { Use &U = cast(&getAnchorValue()) ->getArgOperandUse(getCallSiteArgNo()); if (A.changeUseAfterManifest(U, *NewV)) Changed = ChangeStatus::CHANGED; } return Changed | AAValueSimplify::manifest(A); } void trackStatistics() const override { STATS_DECLTRACK_CSARG_ATTR(value_simplify) } }; } // namespace /// ----------------------- Heap-To-Stack Conversion --------------------------- namespace { struct AAHeapToStackFunction final : public AAHeapToStack { struct AllocationInfo { /// The call that allocates the memory. CallBase *const CB; /// The library function id for the allocation. LibFunc LibraryFunctionId = NotLibFunc; /// The status wrt. a rewrite. enum { STACK_DUE_TO_USE, STACK_DUE_TO_FREE, INVALID, } Status = STACK_DUE_TO_USE; /// Flag to indicate if we encountered a use that might free this allocation /// but which is not in the deallocation infos. bool HasPotentiallyFreeingUnknownUses = false; /// Flag to indicate that we should place the new alloca in the function /// entry block rather than where the call site (CB) is. bool MoveAllocaIntoEntry = true; /// The set of free calls that use this allocation. SmallSetVector PotentialFreeCalls{}; }; struct DeallocationInfo { /// The call that deallocates the memory. CallBase *const CB; /// The value freed by the call. Value *FreedOp; /// Flag to indicate if we don't know all objects this deallocation might /// free. bool MightFreeUnknownObjects = false; /// The set of allocation calls that are potentially freed. SmallSetVector PotentialAllocationCalls{}; }; AAHeapToStackFunction(const IRPosition &IRP, Attributor &A) : AAHeapToStack(IRP, A) {} ~AAHeapToStackFunction() { // Ensure we call the destructor so we release any memory allocated in the // sets. for (auto &It : AllocationInfos) It.second->~AllocationInfo(); for (auto &It : DeallocationInfos) It.second->~DeallocationInfo(); } void initialize(Attributor &A) override { AAHeapToStack::initialize(A); const Function *F = getAnchorScope(); const auto *TLI = A.getInfoCache().getTargetLibraryInfoForFunction(*F); auto AllocationIdentifierCB = [&](Instruction &I) { CallBase *CB = dyn_cast(&I); if (!CB) return true; if (Value *FreedOp = getFreedOperand(CB, TLI)) { DeallocationInfos[CB] = new (A.Allocator) DeallocationInfo{CB, FreedOp}; return true; } // To do heap to stack, we need to know that the allocation itself is // removable once uses are rewritten, and that we can initialize the // alloca to the same pattern as the original allocation result. if (isRemovableAlloc(CB, TLI)) { auto *I8Ty = Type::getInt8Ty(CB->getParent()->getContext()); if (nullptr != getInitialValueOfAllocation(CB, TLI, I8Ty)) { AllocationInfo *AI = new (A.Allocator) AllocationInfo{CB}; AllocationInfos[CB] = AI; if (TLI) TLI->getLibFunc(*CB, AI->LibraryFunctionId); } } return true; }; bool UsedAssumedInformation = false; bool Success = A.checkForAllCallLikeInstructions( AllocationIdentifierCB, *this, UsedAssumedInformation, /* CheckBBLivenessOnly */ false, /* CheckPotentiallyDead */ true); (void)Success; assert(Success && "Did not expect the call base visit callback to fail!"); Attributor::SimplifictionCallbackTy SCB = [](const IRPosition &, const AbstractAttribute *, bool &) -> std::optional { return nullptr; }; for (const auto &It : AllocationInfos) A.registerSimplificationCallback(IRPosition::callsite_returned(*It.first), SCB); for (const auto &It : DeallocationInfos) A.registerSimplificationCallback(IRPosition::callsite_returned(*It.first), SCB); } const std::string getAsStr(Attributor *A) const override { unsigned NumH2SMallocs = 0, NumInvalidMallocs = 0; for (const auto &It : AllocationInfos) { if (It.second->Status == AllocationInfo::INVALID) ++NumInvalidMallocs; else ++NumH2SMallocs; } return "[H2S] Mallocs Good/Bad: " + std::to_string(NumH2SMallocs) + "/" + std::to_string(NumInvalidMallocs); } /// See AbstractAttribute::trackStatistics(). void trackStatistics() const override { STATS_DECL( MallocCalls, Function, "Number of malloc/calloc/aligned_alloc calls converted to allocas"); for (const auto &It : AllocationInfos) if (It.second->Status != AllocationInfo::INVALID) ++BUILD_STAT_NAME(MallocCalls, Function); } bool isAssumedHeapToStack(const CallBase &CB) const override { if (isValidState()) if (AllocationInfo *AI = AllocationInfos.lookup(const_cast(&CB))) return AI->Status != AllocationInfo::INVALID; return false; } bool isAssumedHeapToStackRemovedFree(CallBase &CB) const override { if (!isValidState()) return false; for (const auto &It : AllocationInfos) { AllocationInfo &AI = *It.second; if (AI.Status == AllocationInfo::INVALID) continue; if (AI.PotentialFreeCalls.count(&CB)) return true; } return false; } ChangeStatus manifest(Attributor &A) override { assert(getState().isValidState() && "Attempted to manifest an invalid state!"); ChangeStatus HasChanged = ChangeStatus::UNCHANGED; Function *F = getAnchorScope(); const auto *TLI = A.getInfoCache().getTargetLibraryInfoForFunction(*F); for (auto &It : AllocationInfos) { AllocationInfo &AI = *It.second; if (AI.Status == AllocationInfo::INVALID) continue; for (CallBase *FreeCall : AI.PotentialFreeCalls) { LLVM_DEBUG(dbgs() << "H2S: Removing free call: " << *FreeCall << "\n"); A.deleteAfterManifest(*FreeCall); HasChanged = ChangeStatus::CHANGED; } LLVM_DEBUG(dbgs() << "H2S: Removing malloc-like call: " << *AI.CB << "\n"); auto Remark = [&](OptimizationRemark OR) { LibFunc IsAllocShared; if (TLI->getLibFunc(*AI.CB, IsAllocShared)) if (IsAllocShared == LibFunc___kmpc_alloc_shared) return OR << "Moving globalized variable to the stack."; return OR << "Moving memory allocation from the heap to the stack."; }; if (AI.LibraryFunctionId == LibFunc___kmpc_alloc_shared) A.emitRemark(AI.CB, "OMP110", Remark); else A.emitRemark(AI.CB, "HeapToStack", Remark); const DataLayout &DL = A.getInfoCache().getDL(); Value *Size; std::optional SizeAPI = getSize(A, *this, AI); if (SizeAPI) { Size = ConstantInt::get(AI.CB->getContext(), *SizeAPI); } else { LLVMContext &Ctx = AI.CB->getContext(); ObjectSizeOpts Opts; ObjectSizeOffsetEvaluator Eval(DL, TLI, Ctx, Opts); SizeOffsetValue SizeOffsetPair = Eval.compute(AI.CB); assert(SizeOffsetPair != ObjectSizeOffsetEvaluator::unknown() && cast(SizeOffsetPair.Offset)->isZero()); Size = SizeOffsetPair.Size; } BasicBlock::iterator IP = AI.MoveAllocaIntoEntry ? F->getEntryBlock().begin() : AI.CB->getIterator(); Align Alignment(1); if (MaybeAlign RetAlign = AI.CB->getRetAlign()) Alignment = std::max(Alignment, *RetAlign); if (Value *Align = getAllocAlignment(AI.CB, TLI)) { std::optional AlignmentAPI = getAPInt(A, *this, *Align); assert(AlignmentAPI && AlignmentAPI->getZExtValue() > 0 && "Expected an alignment during manifest!"); Alignment = std::max(Alignment, assumeAligned(AlignmentAPI->getZExtValue())); } // TODO: Hoist the alloca towards the function entry. unsigned AS = DL.getAllocaAddrSpace(); Instruction *Alloca = new AllocaInst(Type::getInt8Ty(F->getContext()), AS, Size, Alignment, AI.CB->getName() + ".h2s", IP); if (Alloca->getType() != AI.CB->getType()) Alloca = BitCastInst::CreatePointerBitCastOrAddrSpaceCast( Alloca, AI.CB->getType(), "malloc_cast", AI.CB->getIterator()); auto *I8Ty = Type::getInt8Ty(F->getContext()); auto *InitVal = getInitialValueOfAllocation(AI.CB, TLI, I8Ty); assert(InitVal && "Must be able to materialize initial memory state of allocation"); A.changeAfterManifest(IRPosition::inst(*AI.CB), *Alloca); if (auto *II = dyn_cast(AI.CB)) { auto *NBB = II->getNormalDest(); BranchInst::Create(NBB, AI.CB->getParent()); A.deleteAfterManifest(*AI.CB); } else { A.deleteAfterManifest(*AI.CB); } // Initialize the alloca with the same value as used by the allocation // function. We can skip undef as the initial value of an alloc is // undef, and the memset would simply end up being DSEd. if (!isa(InitVal)) { IRBuilder<> Builder(Alloca->getNextNode()); // TODO: Use alignment above if align!=1 Builder.CreateMemSet(Alloca, InitVal, Size, std::nullopt); } HasChanged = ChangeStatus::CHANGED; } return HasChanged; } std::optional getAPInt(Attributor &A, const AbstractAttribute &AA, Value &V) { bool UsedAssumedInformation = false; std::optional SimpleV = A.getAssumedConstant(V, AA, UsedAssumedInformation); if (!SimpleV) return APInt(64, 0); if (auto *CI = dyn_cast_or_null(*SimpleV)) return CI->getValue(); return std::nullopt; } std::optional getSize(Attributor &A, const AbstractAttribute &AA, AllocationInfo &AI) { auto Mapper = [&](const Value *V) -> const Value * { bool UsedAssumedInformation = false; if (std::optional SimpleV = A.getAssumedConstant(*V, AA, UsedAssumedInformation)) if (*SimpleV) return *SimpleV; return V; }; const Function *F = getAnchorScope(); const auto *TLI = A.getInfoCache().getTargetLibraryInfoForFunction(*F); return getAllocSize(AI.CB, TLI, Mapper); } /// Collection of all malloc-like calls in a function with associated /// information. MapVector AllocationInfos; /// Collection of all free-like calls in a function with associated /// information. MapVector DeallocationInfos; ChangeStatus updateImpl(Attributor &A) override; }; ChangeStatus AAHeapToStackFunction::updateImpl(Attributor &A) { ChangeStatus Changed = ChangeStatus::UNCHANGED; const Function *F = getAnchorScope(); const auto *TLI = A.getInfoCache().getTargetLibraryInfoForFunction(*F); const auto *LivenessAA = A.getAAFor(*this, IRPosition::function(*F), DepClassTy::NONE); MustBeExecutedContextExplorer *Explorer = A.getInfoCache().getMustBeExecutedContextExplorer(); bool StackIsAccessibleByOtherThreads = A.getInfoCache().stackIsAccessibleByOtherThreads(); LoopInfo *LI = A.getInfoCache().getAnalysisResultForFunction(*F); std::optional MayContainIrreducibleControl; auto IsInLoop = [&](BasicBlock &BB) { if (&F->getEntryBlock() == &BB) return false; if (!MayContainIrreducibleControl.has_value()) MayContainIrreducibleControl = mayContainIrreducibleControl(*F, LI); if (*MayContainIrreducibleControl) return true; if (!LI) return true; return LI->getLoopFor(&BB) != nullptr; }; // Flag to ensure we update our deallocation information at most once per // updateImpl call and only if we use the free check reasoning. bool HasUpdatedFrees = false; auto UpdateFrees = [&]() { HasUpdatedFrees = true; for (auto &It : DeallocationInfos) { DeallocationInfo &DI = *It.second; // For now we cannot use deallocations that have unknown inputs, skip // them. if (DI.MightFreeUnknownObjects) continue; // No need to analyze dead calls, ignore them instead. bool UsedAssumedInformation = false; if (A.isAssumedDead(*DI.CB, this, LivenessAA, UsedAssumedInformation, /* CheckBBLivenessOnly */ true)) continue; // Use the non-optimistic version to get the freed object. Value *Obj = getUnderlyingObject(DI.FreedOp); if (!Obj) { LLVM_DEBUG(dbgs() << "[H2S] Unknown underlying object for free!\n"); DI.MightFreeUnknownObjects = true; continue; } // Free of null and undef can be ignored as no-ops (or UB in the latter // case). if (isa(Obj) || isa(Obj)) continue; CallBase *ObjCB = dyn_cast(Obj); if (!ObjCB) { LLVM_DEBUG(dbgs() << "[H2S] Free of a non-call object: " << *Obj << "\n"); DI.MightFreeUnknownObjects = true; continue; } AllocationInfo *AI = AllocationInfos.lookup(ObjCB); if (!AI) { LLVM_DEBUG(dbgs() << "[H2S] Free of a non-allocation object: " << *Obj << "\n"); DI.MightFreeUnknownObjects = true; continue; } DI.PotentialAllocationCalls.insert(ObjCB); } }; auto FreeCheck = [&](AllocationInfo &AI) { // If the stack is not accessible by other threads, the "must-free" logic // doesn't apply as the pointer could be shared and needs to be places in // "shareable" memory. if (!StackIsAccessibleByOtherThreads) { bool IsKnownNoSycn; if (!AA::hasAssumedIRAttr( A, this, getIRPosition(), DepClassTy::OPTIONAL, IsKnownNoSycn)) { LLVM_DEBUG( dbgs() << "[H2S] found an escaping use, stack is not accessible by " "other threads and function is not nosync:\n"); return false; } } if (!HasUpdatedFrees) UpdateFrees(); // TODO: Allow multi exit functions that have different free calls. if (AI.PotentialFreeCalls.size() != 1) { LLVM_DEBUG(dbgs() << "[H2S] did not find one free call but " << AI.PotentialFreeCalls.size() << "\n"); return false; } CallBase *UniqueFree = *AI.PotentialFreeCalls.begin(); DeallocationInfo *DI = DeallocationInfos.lookup(UniqueFree); if (!DI) { LLVM_DEBUG( dbgs() << "[H2S] unique free call was not known as deallocation call " << *UniqueFree << "\n"); return false; } if (DI->MightFreeUnknownObjects) { LLVM_DEBUG( dbgs() << "[H2S] unique free call might free unknown allocations\n"); return false; } if (DI->PotentialAllocationCalls.empty()) return true; if (DI->PotentialAllocationCalls.size() > 1) { LLVM_DEBUG(dbgs() << "[H2S] unique free call might free " << DI->PotentialAllocationCalls.size() << " different allocations\n"); return false; } if (*DI->PotentialAllocationCalls.begin() != AI.CB) { LLVM_DEBUG( dbgs() << "[H2S] unique free call not known to free this allocation but " << **DI->PotentialAllocationCalls.begin() << "\n"); return false; } // __kmpc_alloc_shared and __kmpc_alloc_free are by construction matched. if (AI.LibraryFunctionId != LibFunc___kmpc_alloc_shared) { Instruction *CtxI = isa(AI.CB) ? AI.CB : AI.CB->getNextNode(); if (!Explorer || !Explorer->findInContextOf(UniqueFree, CtxI)) { LLVM_DEBUG(dbgs() << "[H2S] unique free call might not be executed " "with the allocation " << *UniqueFree << "\n"); return false; } } return true; }; auto UsesCheck = [&](AllocationInfo &AI) { bool ValidUsesOnly = true; auto Pred = [&](const Use &U, bool &Follow) -> bool { Instruction *UserI = cast(U.getUser()); if (isa(UserI)) return true; if (auto *SI = dyn_cast(UserI)) { if (SI->getValueOperand() == U.get()) { LLVM_DEBUG(dbgs() << "[H2S] escaping store to memory: " << *UserI << "\n"); ValidUsesOnly = false; } else { // A store into the malloc'ed memory is fine. } return true; } if (auto *CB = dyn_cast(UserI)) { if (!CB->isArgOperand(&U) || CB->isLifetimeStartOrEnd()) return true; if (DeallocationInfos.count(CB)) { AI.PotentialFreeCalls.insert(CB); return true; } unsigned ArgNo = CB->getArgOperandNo(&U); auto CBIRP = IRPosition::callsite_argument(*CB, ArgNo); bool IsKnownNoCapture; bool IsAssumedNoCapture = AA::hasAssumedIRAttr( A, this, CBIRP, DepClassTy::OPTIONAL, IsKnownNoCapture); // If a call site argument use is nofree, we are fine. bool IsKnownNoFree; bool IsAssumedNoFree = AA::hasAssumedIRAttr( A, this, CBIRP, DepClassTy::OPTIONAL, IsKnownNoFree); if (!IsAssumedNoCapture || (AI.LibraryFunctionId != LibFunc___kmpc_alloc_shared && !IsAssumedNoFree)) { AI.HasPotentiallyFreeingUnknownUses |= !IsAssumedNoFree; // Emit a missed remark if this is missed OpenMP globalization. auto Remark = [&](OptimizationRemarkMissed ORM) { return ORM << "Could not move globalized variable to the stack. " "Variable is potentially captured in call. Mark " "parameter as `__attribute__((noescape))` to override."; }; if (ValidUsesOnly && AI.LibraryFunctionId == LibFunc___kmpc_alloc_shared) A.emitRemark(CB, "OMP113", Remark); LLVM_DEBUG(dbgs() << "[H2S] Bad user: " << *UserI << "\n"); ValidUsesOnly = false; } return true; } if (isa(UserI) || isa(UserI) || isa(UserI) || isa(UserI)) { Follow = true; return true; } // Unknown user for which we can not track uses further (in a way that // makes sense). LLVM_DEBUG(dbgs() << "[H2S] Unknown user: " << *UserI << "\n"); ValidUsesOnly = false; return true; }; if (!A.checkForAllUses(Pred, *this, *AI.CB, /* CheckBBLivenessOnly */ false, DepClassTy::OPTIONAL, /* IgnoreDroppableUses */ true, [&](const Use &OldU, const Use &NewU) { auto *SI = dyn_cast(OldU.getUser()); return !SI || StackIsAccessibleByOtherThreads || AA::isAssumedThreadLocalObject( A, *SI->getPointerOperand(), *this); })) return false; return ValidUsesOnly; }; // The actual update starts here. We look at all allocations and depending on // their status perform the appropriate check(s). for (auto &It : AllocationInfos) { AllocationInfo &AI = *It.second; if (AI.Status == AllocationInfo::INVALID) continue; if (Value *Align = getAllocAlignment(AI.CB, TLI)) { std::optional APAlign = getAPInt(A, *this, *Align); if (!APAlign) { // Can't generate an alloca which respects the required alignment // on the allocation. LLVM_DEBUG(dbgs() << "[H2S] Unknown allocation alignment: " << *AI.CB << "\n"); AI.Status = AllocationInfo::INVALID; Changed = ChangeStatus::CHANGED; continue; } if (APAlign->ugt(llvm::Value::MaximumAlignment) || !APAlign->isPowerOf2()) { LLVM_DEBUG(dbgs() << "[H2S] Invalid allocation alignment: " << APAlign << "\n"); AI.Status = AllocationInfo::INVALID; Changed = ChangeStatus::CHANGED; continue; } } std::optional Size = getSize(A, *this, AI); if (AI.LibraryFunctionId != LibFunc___kmpc_alloc_shared && MaxHeapToStackSize != -1) { if (!Size || Size->ugt(MaxHeapToStackSize)) { LLVM_DEBUG({ if (!Size) dbgs() << "[H2S] Unknown allocation size: " << *AI.CB << "\n"; else dbgs() << "[H2S] Allocation size too large: " << *AI.CB << " vs. " << MaxHeapToStackSize << "\n"; }); AI.Status = AllocationInfo::INVALID; Changed = ChangeStatus::CHANGED; continue; } } switch (AI.Status) { case AllocationInfo::STACK_DUE_TO_USE: if (UsesCheck(AI)) break; AI.Status = AllocationInfo::STACK_DUE_TO_FREE; [[fallthrough]]; case AllocationInfo::STACK_DUE_TO_FREE: if (FreeCheck(AI)) break; AI.Status = AllocationInfo::INVALID; Changed = ChangeStatus::CHANGED; break; case AllocationInfo::INVALID: llvm_unreachable("Invalid allocations should never reach this point!"); }; // Check if we still think we can move it into the entry block. If the // alloca comes from a converted __kmpc_alloc_shared then we can usually // ignore the potential compilations associated with loops. bool IsGlobalizedLocal = AI.LibraryFunctionId == LibFunc___kmpc_alloc_shared; if (AI.MoveAllocaIntoEntry && (!Size.has_value() || (!IsGlobalizedLocal && IsInLoop(*AI.CB->getParent())))) AI.MoveAllocaIntoEntry = false; } return Changed; } } // namespace /// ----------------------- Privatizable Pointers ------------------------------ namespace { struct AAPrivatizablePtrImpl : public AAPrivatizablePtr { AAPrivatizablePtrImpl(const IRPosition &IRP, Attributor &A) : AAPrivatizablePtr(IRP, A), PrivatizableType(std::nullopt) {} ChangeStatus indicatePessimisticFixpoint() override { AAPrivatizablePtr::indicatePessimisticFixpoint(); PrivatizableType = nullptr; return ChangeStatus::CHANGED; } /// Identify the type we can chose for a private copy of the underlying /// argument. std::nullopt means it is not clear yet, nullptr means there is /// none. virtual std::optional identifyPrivatizableType(Attributor &A) = 0; /// Return a privatizable type that encloses both T0 and T1. /// TODO: This is merely a stub for now as we should manage a mapping as well. std::optional combineTypes(std::optional T0, std::optional T1) { if (!T0) return T1; if (!T1) return T0; if (T0 == T1) return T0; return nullptr; } std::optional getPrivatizableType() const override { return PrivatizableType; } const std::string getAsStr(Attributor *A) const override { return isAssumedPrivatizablePtr() ? "[priv]" : "[no-priv]"; } protected: std::optional PrivatizableType; }; // TODO: Do this for call site arguments (probably also other values) as well. struct AAPrivatizablePtrArgument final : public AAPrivatizablePtrImpl { AAPrivatizablePtrArgument(const IRPosition &IRP, Attributor &A) : AAPrivatizablePtrImpl(IRP, A) {} /// See AAPrivatizablePtrImpl::identifyPrivatizableType(...) std::optional identifyPrivatizableType(Attributor &A) override { // If this is a byval argument and we know all the call sites (so we can // rewrite them), there is no need to check them explicitly. bool UsedAssumedInformation = false; SmallVector Attrs; A.getAttrs(getIRPosition(), {Attribute::ByVal}, Attrs, /* IgnoreSubsumingPositions */ true); if (!Attrs.empty() && A.checkForAllCallSites([](AbstractCallSite ACS) { return true; }, *this, true, UsedAssumedInformation)) return Attrs[0].getValueAsType(); std::optional Ty; unsigned ArgNo = getIRPosition().getCallSiteArgNo(); // Make sure the associated call site argument has the same type at all call // sites and it is an allocation we know is safe to privatize, for now that // means we only allow alloca instructions. // TODO: We can additionally analyze the accesses in the callee to create // the type from that information instead. That is a little more // involved and will be done in a follow up patch. auto CallSiteCheck = [&](AbstractCallSite ACS) { IRPosition ACSArgPos = IRPosition::callsite_argument(ACS, ArgNo); // Check if a coresponding argument was found or if it is one not // associated (which can happen for callback calls). if (ACSArgPos.getPositionKind() == IRPosition::IRP_INVALID) return false; // Check that all call sites agree on a type. auto *PrivCSArgAA = A.getAAFor(*this, ACSArgPos, DepClassTy::REQUIRED); if (!PrivCSArgAA) return false; std::optional CSTy = PrivCSArgAA->getPrivatizableType(); LLVM_DEBUG({ dbgs() << "[AAPrivatizablePtr] ACSPos: " << ACSArgPos << ", CSTy: "; if (CSTy && *CSTy) (*CSTy)->print(dbgs()); else if (CSTy) dbgs() << ""; else dbgs() << ""; }); Ty = combineTypes(Ty, CSTy); LLVM_DEBUG({ dbgs() << " : New Type: "; if (Ty && *Ty) (*Ty)->print(dbgs()); else if (Ty) dbgs() << ""; else dbgs() << ""; dbgs() << "\n"; }); return !Ty || *Ty; }; if (!A.checkForAllCallSites(CallSiteCheck, *this, true, UsedAssumedInformation)) return nullptr; return Ty; } /// See AbstractAttribute::updateImpl(...). ChangeStatus updateImpl(Attributor &A) override { PrivatizableType = identifyPrivatizableType(A); if (!PrivatizableType) return ChangeStatus::UNCHANGED; if (!*PrivatizableType) return indicatePessimisticFixpoint(); // The dependence is optional so we don't give up once we give up on the // alignment. A.getAAFor(*this, IRPosition::value(getAssociatedValue()), DepClassTy::OPTIONAL); // Avoid arguments with padding for now. if (!A.hasAttr(getIRPosition(), Attribute::ByVal) && !isDenselyPacked(*PrivatizableType, A.getInfoCache().getDL())) { LLVM_DEBUG(dbgs() << "[AAPrivatizablePtr] Padding detected\n"); return indicatePessimisticFixpoint(); } // Collect the types that will replace the privatizable type in the function // signature. SmallVector ReplacementTypes; identifyReplacementTypes(*PrivatizableType, ReplacementTypes); // Verify callee and caller agree on how the promoted argument would be // passed. Function &Fn = *getIRPosition().getAnchorScope(); const auto *TTI = A.getInfoCache().getAnalysisResultForFunction(Fn); if (!TTI) { LLVM_DEBUG(dbgs() << "[AAPrivatizablePtr] Missing TTI for function " << Fn.getName() << "\n"); return indicatePessimisticFixpoint(); } auto CallSiteCheck = [&](AbstractCallSite ACS) { CallBase *CB = ACS.getInstruction(); return TTI->areTypesABICompatible( CB->getCaller(), dyn_cast_if_present(CB->getCalledOperand()), ReplacementTypes); }; bool UsedAssumedInformation = false; if (!A.checkForAllCallSites(CallSiteCheck, *this, true, UsedAssumedInformation)) { LLVM_DEBUG( dbgs() << "[AAPrivatizablePtr] ABI incompatibility detected for " << Fn.getName() << "\n"); return indicatePessimisticFixpoint(); } // Register a rewrite of the argument. Argument *Arg = getAssociatedArgument(); if (!A.isValidFunctionSignatureRewrite(*Arg, ReplacementTypes)) { LLVM_DEBUG(dbgs() << "[AAPrivatizablePtr] Rewrite not valid\n"); return indicatePessimisticFixpoint(); } unsigned ArgNo = Arg->getArgNo(); // Helper to check if for the given call site the associated argument is // passed to a callback where the privatization would be different. auto IsCompatiblePrivArgOfCallback = [&](CallBase &CB) { SmallVector CallbackUses; AbstractCallSite::getCallbackUses(CB, CallbackUses); for (const Use *U : CallbackUses) { AbstractCallSite CBACS(U); assert(CBACS && CBACS.isCallbackCall()); for (Argument &CBArg : CBACS.getCalledFunction()->args()) { int CBArgNo = CBACS.getCallArgOperandNo(CBArg); LLVM_DEBUG({ dbgs() << "[AAPrivatizablePtr] Argument " << *Arg << "check if can be privatized in the context of its parent (" << Arg->getParent()->getName() << ")\n[AAPrivatizablePtr] because it is an argument in a " "callback (" << CBArgNo << "@" << CBACS.getCalledFunction()->getName() << ")\n[AAPrivatizablePtr] " << CBArg << " : " << CBACS.getCallArgOperand(CBArg) << " vs " << CB.getArgOperand(ArgNo) << "\n" << "[AAPrivatizablePtr] " << CBArg << " : " << CBACS.getCallArgOperandNo(CBArg) << " vs " << ArgNo << "\n"; }); if (CBArgNo != int(ArgNo)) continue; const auto *CBArgPrivAA = A.getAAFor( *this, IRPosition::argument(CBArg), DepClassTy::REQUIRED); if (CBArgPrivAA && CBArgPrivAA->isValidState()) { auto CBArgPrivTy = CBArgPrivAA->getPrivatizableType(); if (!CBArgPrivTy) continue; if (*CBArgPrivTy == PrivatizableType) continue; } LLVM_DEBUG({ dbgs() << "[AAPrivatizablePtr] Argument " << *Arg << " cannot be privatized in the context of its parent (" << Arg->getParent()->getName() << ")\n[AAPrivatizablePtr] because it is an argument in a " "callback (" << CBArgNo << "@" << CBACS.getCalledFunction()->getName() << ").\n[AAPrivatizablePtr] for which the argument " "privatization is not compatible.\n"; }); return false; } } return true; }; // Helper to check if for the given call site the associated argument is // passed to a direct call where the privatization would be different. auto IsCompatiblePrivArgOfDirectCS = [&](AbstractCallSite ACS) { CallBase *DC = cast(ACS.getInstruction()); int DCArgNo = ACS.getCallArgOperandNo(ArgNo); assert(DCArgNo >= 0 && unsigned(DCArgNo) < DC->arg_size() && "Expected a direct call operand for callback call operand"); Function *DCCallee = dyn_cast_if_present(DC->getCalledOperand()); LLVM_DEBUG({ dbgs() << "[AAPrivatizablePtr] Argument " << *Arg << " check if be privatized in the context of its parent (" << Arg->getParent()->getName() << ")\n[AAPrivatizablePtr] because it is an argument in a " "direct call of (" << DCArgNo << "@" << DCCallee->getName() << ").\n"; }); if (unsigned(DCArgNo) < DCCallee->arg_size()) { const auto *DCArgPrivAA = A.getAAFor( *this, IRPosition::argument(*DCCallee->getArg(DCArgNo)), DepClassTy::REQUIRED); if (DCArgPrivAA && DCArgPrivAA->isValidState()) { auto DCArgPrivTy = DCArgPrivAA->getPrivatizableType(); if (!DCArgPrivTy) return true; if (*DCArgPrivTy == PrivatizableType) return true; } } LLVM_DEBUG({ dbgs() << "[AAPrivatizablePtr] Argument " << *Arg << " cannot be privatized in the context of its parent (" << Arg->getParent()->getName() << ")\n[AAPrivatizablePtr] because it is an argument in a " "direct call of (" << ACS.getInstruction()->getCalledOperand()->getName() << ").\n[AAPrivatizablePtr] for which the argument " "privatization is not compatible.\n"; }); return false; }; // Helper to check if the associated argument is used at the given abstract // call site in a way that is incompatible with the privatization assumed // here. auto IsCompatiblePrivArgOfOtherCallSite = [&](AbstractCallSite ACS) { if (ACS.isDirectCall()) return IsCompatiblePrivArgOfCallback(*ACS.getInstruction()); if (ACS.isCallbackCall()) return IsCompatiblePrivArgOfDirectCS(ACS); return false; }; if (!A.checkForAllCallSites(IsCompatiblePrivArgOfOtherCallSite, *this, true, UsedAssumedInformation)) return indicatePessimisticFixpoint(); return ChangeStatus::UNCHANGED; } /// Given a type to private \p PrivType, collect the constituates (which are /// used) in \p ReplacementTypes. static void identifyReplacementTypes(Type *PrivType, SmallVectorImpl &ReplacementTypes) { // TODO: For now we expand the privatization type to the fullest which can // lead to dead arguments that need to be removed later. assert(PrivType && "Expected privatizable type!"); // Traverse the type, extract constituate types on the outermost level. if (auto *PrivStructType = dyn_cast(PrivType)) { for (unsigned u = 0, e = PrivStructType->getNumElements(); u < e; u++) ReplacementTypes.push_back(PrivStructType->getElementType(u)); } else if (auto *PrivArrayType = dyn_cast(PrivType)) { ReplacementTypes.append(PrivArrayType->getNumElements(), PrivArrayType->getElementType()); } else { ReplacementTypes.push_back(PrivType); } } /// Initialize \p Base according to the type \p PrivType at position \p IP. /// The values needed are taken from the arguments of \p F starting at /// position \p ArgNo. static void createInitialization(Type *PrivType, Value &Base, Function &F, unsigned ArgNo, BasicBlock::iterator IP) { assert(PrivType && "Expected privatizable type!"); IRBuilder IRB(IP->getParent(), IP); const DataLayout &DL = F.getDataLayout(); // Traverse the type, build GEPs and stores. if (auto *PrivStructType = dyn_cast(PrivType)) { const StructLayout *PrivStructLayout = DL.getStructLayout(PrivStructType); for (unsigned u = 0, e = PrivStructType->getNumElements(); u < e; u++) { Value *Ptr = constructPointer(&Base, PrivStructLayout->getElementOffset(u), IRB); new StoreInst(F.getArg(ArgNo + u), Ptr, IP); } } else if (auto *PrivArrayType = dyn_cast(PrivType)) { Type *PointeeTy = PrivArrayType->getElementType(); uint64_t PointeeTySize = DL.getTypeStoreSize(PointeeTy); for (unsigned u = 0, e = PrivArrayType->getNumElements(); u < e; u++) { Value *Ptr = constructPointer(&Base, u * PointeeTySize, IRB); new StoreInst(F.getArg(ArgNo + u), Ptr, IP); } } else { new StoreInst(F.getArg(ArgNo), &Base, IP); } } /// Extract values from \p Base according to the type \p PrivType at the /// call position \p ACS. The values are appended to \p ReplacementValues. void createReplacementValues(Align Alignment, Type *PrivType, AbstractCallSite ACS, Value *Base, SmallVectorImpl &ReplacementValues) { assert(Base && "Expected base value!"); assert(PrivType && "Expected privatizable type!"); Instruction *IP = ACS.getInstruction(); IRBuilder IRB(IP); const DataLayout &DL = IP->getDataLayout(); // Traverse the type, build GEPs and loads. if (auto *PrivStructType = dyn_cast(PrivType)) { const StructLayout *PrivStructLayout = DL.getStructLayout(PrivStructType); for (unsigned u = 0, e = PrivStructType->getNumElements(); u < e; u++) { Type *PointeeTy = PrivStructType->getElementType(u); Value *Ptr = constructPointer(Base, PrivStructLayout->getElementOffset(u), IRB); LoadInst *L = new LoadInst(PointeeTy, Ptr, "", IP->getIterator()); L->setAlignment(Alignment); ReplacementValues.push_back(L); } } else if (auto *PrivArrayType = dyn_cast(PrivType)) { Type *PointeeTy = PrivArrayType->getElementType(); uint64_t PointeeTySize = DL.getTypeStoreSize(PointeeTy); for (unsigned u = 0, e = PrivArrayType->getNumElements(); u < e; u++) { Value *Ptr = constructPointer(Base, u * PointeeTySize, IRB); LoadInst *L = new LoadInst(PointeeTy, Ptr, "", IP->getIterator()); L->setAlignment(Alignment); ReplacementValues.push_back(L); } } else { LoadInst *L = new LoadInst(PrivType, Base, "", IP->getIterator()); L->setAlignment(Alignment); ReplacementValues.push_back(L); } } /// See AbstractAttribute::manifest(...) ChangeStatus manifest(Attributor &A) override { if (!PrivatizableType) return ChangeStatus::UNCHANGED; assert(*PrivatizableType && "Expected privatizable type!"); // Collect all tail calls in the function as we cannot allow new allocas to // escape into tail recursion. // TODO: Be smarter about new allocas escaping into tail calls. SmallVector TailCalls; bool UsedAssumedInformation = false; if (!A.checkForAllInstructions( [&](Instruction &I) { CallInst &CI = cast(I); if (CI.isTailCall()) TailCalls.push_back(&CI); return true; }, *this, {Instruction::Call}, UsedAssumedInformation)) return ChangeStatus::UNCHANGED; Argument *Arg = getAssociatedArgument(); // Query AAAlign attribute for alignment of associated argument to // determine the best alignment of loads. const auto *AlignAA = A.getAAFor(*this, IRPosition::value(*Arg), DepClassTy::NONE); // Callback to repair the associated function. A new alloca is placed at the // beginning and initialized with the values passed through arguments. The // new alloca replaces the use of the old pointer argument. Attributor::ArgumentReplacementInfo::CalleeRepairCBTy FnRepairCB = [=](const Attributor::ArgumentReplacementInfo &ARI, Function &ReplacementFn, Function::arg_iterator ArgIt) { BasicBlock &EntryBB = ReplacementFn.getEntryBlock(); BasicBlock::iterator IP = EntryBB.getFirstInsertionPt(); const DataLayout &DL = IP->getDataLayout(); unsigned AS = DL.getAllocaAddrSpace(); Instruction *AI = new AllocaInst(*PrivatizableType, AS, Arg->getName() + ".priv", IP); createInitialization(*PrivatizableType, *AI, ReplacementFn, ArgIt->getArgNo(), IP); if (AI->getType() != Arg->getType()) AI = BitCastInst::CreatePointerBitCastOrAddrSpaceCast( AI, Arg->getType(), "", IP); Arg->replaceAllUsesWith(AI); for (CallInst *CI : TailCalls) CI->setTailCall(false); }; // Callback to repair a call site of the associated function. The elements // of the privatizable type are loaded prior to the call and passed to the // new function version. Attributor::ArgumentReplacementInfo::ACSRepairCBTy ACSRepairCB = [=](const Attributor::ArgumentReplacementInfo &ARI, AbstractCallSite ACS, SmallVectorImpl &NewArgOperands) { // When no alignment is specified for the load instruction, // natural alignment is assumed. createReplacementValues( AlignAA ? AlignAA->getAssumedAlign() : Align(0), *PrivatizableType, ACS, ACS.getCallArgOperand(ARI.getReplacedArg().getArgNo()), NewArgOperands); }; // Collect the types that will replace the privatizable type in the function // signature. SmallVector ReplacementTypes; identifyReplacementTypes(*PrivatizableType, ReplacementTypes); // Register a rewrite of the argument. if (A.registerFunctionSignatureRewrite(*Arg, ReplacementTypes, std::move(FnRepairCB), std::move(ACSRepairCB))) return ChangeStatus::CHANGED; return ChangeStatus::UNCHANGED; } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_ARG_ATTR(privatizable_ptr); } }; struct AAPrivatizablePtrFloating : public AAPrivatizablePtrImpl { AAPrivatizablePtrFloating(const IRPosition &IRP, Attributor &A) : AAPrivatizablePtrImpl(IRP, A) {} /// See AbstractAttribute::initialize(...). void initialize(Attributor &A) override { // TODO: We can privatize more than arguments. indicatePessimisticFixpoint(); } ChangeStatus updateImpl(Attributor &A) override { llvm_unreachable("AAPrivatizablePtr(Floating|Returned|CallSiteReturned)::" "updateImpl will not be called"); } /// See AAPrivatizablePtrImpl::identifyPrivatizableType(...) std::optional identifyPrivatizableType(Attributor &A) override { Value *Obj = getUnderlyingObject(&getAssociatedValue()); if (!Obj) { LLVM_DEBUG(dbgs() << "[AAPrivatizablePtr] No underlying object found!\n"); return nullptr; } if (auto *AI = dyn_cast(Obj)) if (auto *CI = dyn_cast(AI->getArraySize())) if (CI->isOne()) return AI->getAllocatedType(); if (auto *Arg = dyn_cast(Obj)) { auto *PrivArgAA = A.getAAFor( *this, IRPosition::argument(*Arg), DepClassTy::REQUIRED); if (PrivArgAA && PrivArgAA->isAssumedPrivatizablePtr()) return PrivArgAA->getPrivatizableType(); } LLVM_DEBUG(dbgs() << "[AAPrivatizablePtr] Underlying object neither valid " "alloca nor privatizable argument: " << *Obj << "!\n"); return nullptr; } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_FLOATING_ATTR(privatizable_ptr); } }; struct AAPrivatizablePtrCallSiteArgument final : public AAPrivatizablePtrFloating { AAPrivatizablePtrCallSiteArgument(const IRPosition &IRP, Attributor &A) : AAPrivatizablePtrFloating(IRP, A) {} /// See AbstractAttribute::initialize(...). void initialize(Attributor &A) override { if (A.hasAttr(getIRPosition(), Attribute::ByVal)) indicateOptimisticFixpoint(); } /// See AbstractAttribute::updateImpl(...). ChangeStatus updateImpl(Attributor &A) override { PrivatizableType = identifyPrivatizableType(A); if (!PrivatizableType) return ChangeStatus::UNCHANGED; if (!*PrivatizableType) return indicatePessimisticFixpoint(); const IRPosition &IRP = getIRPosition(); bool IsKnownNoCapture; bool IsAssumedNoCapture = AA::hasAssumedIRAttr( A, this, IRP, DepClassTy::REQUIRED, IsKnownNoCapture); if (!IsAssumedNoCapture) { LLVM_DEBUG(dbgs() << "[AAPrivatizablePtr] pointer might be captured!\n"); return indicatePessimisticFixpoint(); } bool IsKnownNoAlias; if (!AA::hasAssumedIRAttr( A, this, IRP, DepClassTy::REQUIRED, IsKnownNoAlias)) { LLVM_DEBUG(dbgs() << "[AAPrivatizablePtr] pointer might alias!\n"); return indicatePessimisticFixpoint(); } bool IsKnown; if (!AA::isAssumedReadOnly(A, IRP, *this, IsKnown)) { LLVM_DEBUG(dbgs() << "[AAPrivatizablePtr] pointer is written!\n"); return indicatePessimisticFixpoint(); } return ChangeStatus::UNCHANGED; } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_CSARG_ATTR(privatizable_ptr); } }; struct AAPrivatizablePtrCallSiteReturned final : public AAPrivatizablePtrFloating { AAPrivatizablePtrCallSiteReturned(const IRPosition &IRP, Attributor &A) : AAPrivatizablePtrFloating(IRP, A) {} /// See AbstractAttribute::initialize(...). void initialize(Attributor &A) override { // TODO: We can privatize more than arguments. indicatePessimisticFixpoint(); } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_CSRET_ATTR(privatizable_ptr); } }; struct AAPrivatizablePtrReturned final : public AAPrivatizablePtrFloating { AAPrivatizablePtrReturned(const IRPosition &IRP, Attributor &A) : AAPrivatizablePtrFloating(IRP, A) {} /// See AbstractAttribute::initialize(...). void initialize(Attributor &A) override { // TODO: We can privatize more than arguments. indicatePessimisticFixpoint(); } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_FNRET_ATTR(privatizable_ptr); } }; } // namespace /// -------------------- Memory Behavior Attributes ---------------------------- /// Includes read-none, read-only, and write-only. /// ---------------------------------------------------------------------------- namespace { struct AAMemoryBehaviorImpl : public AAMemoryBehavior { AAMemoryBehaviorImpl(const IRPosition &IRP, Attributor &A) : AAMemoryBehavior(IRP, A) {} /// See AbstractAttribute::initialize(...). void initialize(Attributor &A) override { intersectAssumedBits(BEST_STATE); getKnownStateFromValue(A, getIRPosition(), getState()); AAMemoryBehavior::initialize(A); } /// Return the memory behavior information encoded in the IR for \p IRP. static void getKnownStateFromValue(Attributor &A, const IRPosition &IRP, BitIntegerState &State, bool IgnoreSubsumingPositions = false) { SmallVector Attrs; A.getAttrs(IRP, AttrKinds, Attrs, IgnoreSubsumingPositions); for (const Attribute &Attr : Attrs) { switch (Attr.getKindAsEnum()) { case Attribute::ReadNone: State.addKnownBits(NO_ACCESSES); break; case Attribute::ReadOnly: State.addKnownBits(NO_WRITES); break; case Attribute::WriteOnly: State.addKnownBits(NO_READS); break; default: llvm_unreachable("Unexpected attribute!"); } } if (auto *I = dyn_cast(&IRP.getAnchorValue())) { if (!I->mayReadFromMemory()) State.addKnownBits(NO_READS); if (!I->mayWriteToMemory()) State.addKnownBits(NO_WRITES); } } /// See AbstractAttribute::getDeducedAttributes(...). void getDeducedAttributes(Attributor &A, LLVMContext &Ctx, SmallVectorImpl &Attrs) const override { assert(Attrs.size() == 0); if (isAssumedReadNone()) Attrs.push_back(Attribute::get(Ctx, Attribute::ReadNone)); else if (isAssumedReadOnly()) Attrs.push_back(Attribute::get(Ctx, Attribute::ReadOnly)); else if (isAssumedWriteOnly()) Attrs.push_back(Attribute::get(Ctx, Attribute::WriteOnly)); assert(Attrs.size() <= 1); } /// See AbstractAttribute::manifest(...). ChangeStatus manifest(Attributor &A) override { const IRPosition &IRP = getIRPosition(); if (A.hasAttr(IRP, Attribute::ReadNone, /* IgnoreSubsumingPositions */ true)) return ChangeStatus::UNCHANGED; // Check if we would improve the existing attributes first. SmallVector DeducedAttrs; getDeducedAttributes(A, IRP.getAnchorValue().getContext(), DeducedAttrs); if (llvm::all_of(DeducedAttrs, [&](const Attribute &Attr) { return A.hasAttr(IRP, Attr.getKindAsEnum(), /* IgnoreSubsumingPositions */ true); })) return ChangeStatus::UNCHANGED; // Clear existing attributes. A.removeAttrs(IRP, AttrKinds); // Clear conflicting writable attribute. if (isAssumedReadOnly()) A.removeAttrs(IRP, Attribute::Writable); // Use the generic manifest method. return IRAttribute::manifest(A); } /// See AbstractState::getAsStr(). const std::string getAsStr(Attributor *A) const override { if (isAssumedReadNone()) return "readnone"; if (isAssumedReadOnly()) return "readonly"; if (isAssumedWriteOnly()) return "writeonly"; return "may-read/write"; } /// The set of IR attributes AAMemoryBehavior deals with. static const Attribute::AttrKind AttrKinds[3]; }; const Attribute::AttrKind AAMemoryBehaviorImpl::AttrKinds[] = { Attribute::ReadNone, Attribute::ReadOnly, Attribute::WriteOnly}; /// Memory behavior attribute for a floating value. struct AAMemoryBehaviorFloating : AAMemoryBehaviorImpl { AAMemoryBehaviorFloating(const IRPosition &IRP, Attributor &A) : AAMemoryBehaviorImpl(IRP, A) {} /// See AbstractAttribute::updateImpl(...). ChangeStatus updateImpl(Attributor &A) override; /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { if (isAssumedReadNone()) STATS_DECLTRACK_FLOATING_ATTR(readnone) else if (isAssumedReadOnly()) STATS_DECLTRACK_FLOATING_ATTR(readonly) else if (isAssumedWriteOnly()) STATS_DECLTRACK_FLOATING_ATTR(writeonly) } private: /// Return true if users of \p UserI might access the underlying /// variable/location described by \p U and should therefore be analyzed. bool followUsersOfUseIn(Attributor &A, const Use &U, const Instruction *UserI); /// Update the state according to the effect of use \p U in \p UserI. void analyzeUseIn(Attributor &A, const Use &U, const Instruction *UserI); }; /// Memory behavior attribute for function argument. struct AAMemoryBehaviorArgument : AAMemoryBehaviorFloating { AAMemoryBehaviorArgument(const IRPosition &IRP, Attributor &A) : AAMemoryBehaviorFloating(IRP, A) {} /// See AbstractAttribute::initialize(...). void initialize(Attributor &A) override { intersectAssumedBits(BEST_STATE); const IRPosition &IRP = getIRPosition(); // TODO: Make IgnoreSubsumingPositions a property of an IRAttribute so we // can query it when we use has/getAttr. That would allow us to reuse the // initialize of the base class here. bool HasByVal = A.hasAttr(IRP, {Attribute::ByVal}, /* IgnoreSubsumingPositions */ true); getKnownStateFromValue(A, IRP, getState(), /* IgnoreSubsumingPositions */ HasByVal); } ChangeStatus manifest(Attributor &A) override { // TODO: Pointer arguments are not supported on vectors of pointers yet. if (!getAssociatedValue().getType()->isPointerTy()) return ChangeStatus::UNCHANGED; // TODO: From readattrs.ll: "inalloca parameters are always // considered written" if (A.hasAttr(getIRPosition(), {Attribute::InAlloca, Attribute::Preallocated})) { removeKnownBits(NO_WRITES); removeAssumedBits(NO_WRITES); } A.removeAttrs(getIRPosition(), AttrKinds); return AAMemoryBehaviorFloating::manifest(A); } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { if (isAssumedReadNone()) STATS_DECLTRACK_ARG_ATTR(readnone) else if (isAssumedReadOnly()) STATS_DECLTRACK_ARG_ATTR(readonly) else if (isAssumedWriteOnly()) STATS_DECLTRACK_ARG_ATTR(writeonly) } }; struct AAMemoryBehaviorCallSiteArgument final : AAMemoryBehaviorArgument { AAMemoryBehaviorCallSiteArgument(const IRPosition &IRP, Attributor &A) : AAMemoryBehaviorArgument(IRP, A) {} /// See AbstractAttribute::initialize(...). void initialize(Attributor &A) override { // If we don't have an associated attribute this is either a variadic call // or an indirect call, either way, nothing to do here. Argument *Arg = getAssociatedArgument(); if (!Arg) { indicatePessimisticFixpoint(); return; } if (Arg->hasByValAttr()) { addKnownBits(NO_WRITES); removeKnownBits(NO_READS); removeAssumedBits(NO_READS); } AAMemoryBehaviorArgument::initialize(A); if (getAssociatedFunction()->isDeclaration()) indicatePessimisticFixpoint(); } /// See AbstractAttribute::updateImpl(...). ChangeStatus updateImpl(Attributor &A) override { // TODO: Once we have call site specific value information we can provide // call site specific liveness 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(); const IRPosition &ArgPos = IRPosition::argument(*Arg); auto *ArgAA = A.getAAFor(*this, ArgPos, DepClassTy::REQUIRED); if (!ArgAA) return indicatePessimisticFixpoint(); return clampStateAndIndicateChange(getState(), ArgAA->getState()); } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { if (isAssumedReadNone()) STATS_DECLTRACK_CSARG_ATTR(readnone) else if (isAssumedReadOnly()) STATS_DECLTRACK_CSARG_ATTR(readonly) else if (isAssumedWriteOnly()) STATS_DECLTRACK_CSARG_ATTR(writeonly) } }; /// Memory behavior attribute for a call site return position. struct AAMemoryBehaviorCallSiteReturned final : AAMemoryBehaviorFloating { AAMemoryBehaviorCallSiteReturned(const IRPosition &IRP, Attributor &A) : AAMemoryBehaviorFloating(IRP, A) {} /// See AbstractAttribute::initialize(...). void initialize(Attributor &A) override { AAMemoryBehaviorImpl::initialize(A); } /// See AbstractAttribute::manifest(...). ChangeStatus manifest(Attributor &A) override { // We do not annotate returned values. return ChangeStatus::UNCHANGED; } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override {} }; /// An AA to represent the memory behavior function attributes. struct AAMemoryBehaviorFunction final : public AAMemoryBehaviorImpl { AAMemoryBehaviorFunction(const IRPosition &IRP, Attributor &A) : AAMemoryBehaviorImpl(IRP, A) {} /// See AbstractAttribute::updateImpl(Attributor &A). ChangeStatus updateImpl(Attributor &A) override; /// See AbstractAttribute::manifest(...). ChangeStatus manifest(Attributor &A) override { // TODO: It would be better to merge this with AAMemoryLocation, so that // we could determine read/write per location. This would also have the // benefit of only one place trying to manifest the memory attribute. Function &F = cast(getAnchorValue()); MemoryEffects ME = MemoryEffects::unknown(); if (isAssumedReadNone()) ME = MemoryEffects::none(); else if (isAssumedReadOnly()) ME = MemoryEffects::readOnly(); else if (isAssumedWriteOnly()) ME = MemoryEffects::writeOnly(); A.removeAttrs(getIRPosition(), AttrKinds); // Clear conflicting writable attribute. if (ME.onlyReadsMemory()) for (Argument &Arg : F.args()) A.removeAttrs(IRPosition::argument(Arg), Attribute::Writable); return A.manifestAttrs(getIRPosition(), Attribute::getWithMemoryEffects(F.getContext(), ME)); } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { if (isAssumedReadNone()) STATS_DECLTRACK_FN_ATTR(readnone) else if (isAssumedReadOnly()) STATS_DECLTRACK_FN_ATTR(readonly) else if (isAssumedWriteOnly()) STATS_DECLTRACK_FN_ATTR(writeonly) } }; /// AAMemoryBehavior attribute for call sites. struct AAMemoryBehaviorCallSite final : AACalleeToCallSite { AAMemoryBehaviorCallSite(const IRPosition &IRP, Attributor &A) : AACalleeToCallSite(IRP, A) {} /// See AbstractAttribute::manifest(...). ChangeStatus manifest(Attributor &A) override { // TODO: Deduplicate this with AAMemoryBehaviorFunction. CallBase &CB = cast(getAnchorValue()); MemoryEffects ME = MemoryEffects::unknown(); if (isAssumedReadNone()) ME = MemoryEffects::none(); else if (isAssumedReadOnly()) ME = MemoryEffects::readOnly(); else if (isAssumedWriteOnly()) ME = MemoryEffects::writeOnly(); A.removeAttrs(getIRPosition(), AttrKinds); // Clear conflicting writable attribute. if (ME.onlyReadsMemory()) for (Use &U : CB.args()) A.removeAttrs(IRPosition::callsite_argument(CB, U.getOperandNo()), Attribute::Writable); return A.manifestAttrs( getIRPosition(), Attribute::getWithMemoryEffects(CB.getContext(), ME)); } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { if (isAssumedReadNone()) STATS_DECLTRACK_CS_ATTR(readnone) else if (isAssumedReadOnly()) STATS_DECLTRACK_CS_ATTR(readonly) else if (isAssumedWriteOnly()) STATS_DECLTRACK_CS_ATTR(writeonly) } }; ChangeStatus AAMemoryBehaviorFunction::updateImpl(Attributor &A) { // The current assumed state used to determine a change. auto AssumedState = getAssumed(); auto CheckRWInst = [&](Instruction &I) { // If the instruction has an own memory behavior state, use it to restrict // the local state. No further analysis is required as the other memory // state is as optimistic as it gets. if (const auto *CB = dyn_cast(&I)) { const auto *MemBehaviorAA = A.getAAFor( *this, IRPosition::callsite_function(*CB), DepClassTy::REQUIRED); if (MemBehaviorAA) { intersectAssumedBits(MemBehaviorAA->getAssumed()); return !isAtFixpoint(); } } // Remove access kind modifiers if necessary. if (I.mayReadFromMemory()) removeAssumedBits(NO_READS); if (I.mayWriteToMemory()) removeAssumedBits(NO_WRITES); return !isAtFixpoint(); }; bool UsedAssumedInformation = false; if (!A.checkForAllReadWriteInstructions(CheckRWInst, *this, UsedAssumedInformation)) return indicatePessimisticFixpoint(); return (AssumedState != getAssumed()) ? ChangeStatus::CHANGED : ChangeStatus::UNCHANGED; } ChangeStatus AAMemoryBehaviorFloating::updateImpl(Attributor &A) { const IRPosition &IRP = getIRPosition(); const IRPosition &FnPos = IRPosition::function_scope(IRP); AAMemoryBehavior::StateType &S = getState(); // First, check the function scope. We take the known information and we avoid // work if the assumed information implies the current assumed information for // this attribute. This is a valid for all but byval arguments. Argument *Arg = IRP.getAssociatedArgument(); AAMemoryBehavior::base_t FnMemAssumedState = AAMemoryBehavior::StateType::getWorstState(); if (!Arg || !Arg->hasByValAttr()) { const auto *FnMemAA = A.getAAFor(*this, FnPos, DepClassTy::OPTIONAL); if (FnMemAA) { FnMemAssumedState = FnMemAA->getAssumed(); S.addKnownBits(FnMemAA->getKnown()); if ((S.getAssumed() & FnMemAA->getAssumed()) == S.getAssumed()) return ChangeStatus::UNCHANGED; } } // The current assumed state used to determine a change. auto AssumedState = S.getAssumed(); // Make sure the value is not captured (except through "return"), if // it is, any information derived would be irrelevant anyway as we cannot // check the potential aliases introduced by the capture. However, no need // to fall back to anythign less optimistic than the function state. bool IsKnownNoCapture; const AANoCapture *ArgNoCaptureAA = nullptr; bool IsAssumedNoCapture = AA::hasAssumedIRAttr( A, this, IRP, DepClassTy::OPTIONAL, IsKnownNoCapture, false, &ArgNoCaptureAA); if (!IsAssumedNoCapture && (!ArgNoCaptureAA || !ArgNoCaptureAA->isAssumedNoCaptureMaybeReturned())) { S.intersectAssumedBits(FnMemAssumedState); return (AssumedState != getAssumed()) ? ChangeStatus::CHANGED : ChangeStatus::UNCHANGED; } // Visit and expand uses until all are analyzed or a fixpoint is reached. auto UsePred = [&](const Use &U, bool &Follow) -> bool { Instruction *UserI = cast(U.getUser()); LLVM_DEBUG(dbgs() << "[AAMemoryBehavior] Use: " << *U << " in " << *UserI << " \n"); // Droppable users, e.g., llvm::assume does not actually perform any action. if (UserI->isDroppable()) return true; // Check if the users of UserI should also be visited. Follow = followUsersOfUseIn(A, U, UserI); // If UserI might touch memory we analyze the use in detail. if (UserI->mayReadOrWriteMemory()) analyzeUseIn(A, U, UserI); return !isAtFixpoint(); }; if (!A.checkForAllUses(UsePred, *this, getAssociatedValue())) return indicatePessimisticFixpoint(); return (AssumedState != getAssumed()) ? ChangeStatus::CHANGED : ChangeStatus::UNCHANGED; } bool AAMemoryBehaviorFloating::followUsersOfUseIn(Attributor &A, const Use &U, const Instruction *UserI) { // The loaded value is unrelated to the pointer argument, no need to // follow the users of the load. if (isa(UserI) || isa(UserI)) return false; // By default we follow all uses assuming UserI might leak information on U, // we have special handling for call sites operands though. const auto *CB = dyn_cast(UserI); if (!CB || !CB->isArgOperand(&U)) return true; // If the use is a call argument known not to be captured, the users of // the call do not need to be visited because they have to be unrelated to // the input. Note that this check is not trivial even though we disallow // general capturing of the underlying argument. The reason is that the // call might the argument "through return", which we allow and for which we // need to check call users. if (U.get()->getType()->isPointerTy()) { unsigned ArgNo = CB->getArgOperandNo(&U); bool IsKnownNoCapture; return !AA::hasAssumedIRAttr( A, this, IRPosition::callsite_argument(*CB, ArgNo), DepClassTy::OPTIONAL, IsKnownNoCapture); } return true; } void AAMemoryBehaviorFloating::analyzeUseIn(Attributor &A, const Use &U, const Instruction *UserI) { assert(UserI->mayReadOrWriteMemory()); switch (UserI->getOpcode()) { default: // TODO: Handle all atomics and other side-effect operations we know of. break; case Instruction::Load: // Loads cause the NO_READS property to disappear. removeAssumedBits(NO_READS); return; case Instruction::Store: // Stores cause the NO_WRITES property to disappear if the use is the // pointer operand. Note that while capturing was taken care of somewhere // else we need to deal with stores of the value that is not looked through. if (cast(UserI)->getPointerOperand() == U.get()) removeAssumedBits(NO_WRITES); else indicatePessimisticFixpoint(); return; case Instruction::Call: case Instruction::CallBr: case Instruction::Invoke: { // For call sites we look at the argument memory behavior attribute (this // could be recursive!) in order to restrict our own state. const auto *CB = cast(UserI); // Give up on operand bundles. if (CB->isBundleOperand(&U)) { indicatePessimisticFixpoint(); return; } // Calling a function does read the function pointer, maybe write it if the // function is self-modifying. if (CB->isCallee(&U)) { removeAssumedBits(NO_READS); break; } // Adjust the possible access behavior based on the information on the // argument. IRPosition Pos; if (U.get()->getType()->isPointerTy()) Pos = IRPosition::callsite_argument(*CB, CB->getArgOperandNo(&U)); else Pos = IRPosition::callsite_function(*CB); const auto *MemBehaviorAA = A.getAAFor(*this, Pos, DepClassTy::OPTIONAL); if (!MemBehaviorAA) break; // "assumed" has at most the same bits as the MemBehaviorAA assumed // and at least "known". intersectAssumedBits(MemBehaviorAA->getAssumed()); return; } }; // Generally, look at the "may-properties" and adjust the assumed state if we // did not trigger special handling before. if (UserI->mayReadFromMemory()) removeAssumedBits(NO_READS); if (UserI->mayWriteToMemory()) removeAssumedBits(NO_WRITES); } } // namespace /// -------------------- Memory Locations Attributes --------------------------- /// Includes read-none, argmemonly, inaccessiblememonly, /// inaccessiblememorargmemonly /// ---------------------------------------------------------------------------- std::string AAMemoryLocation::getMemoryLocationsAsStr( AAMemoryLocation::MemoryLocationsKind MLK) { if (0 == (MLK & AAMemoryLocation::NO_LOCATIONS)) return "all memory"; if (MLK == AAMemoryLocation::NO_LOCATIONS) return "no memory"; std::string S = "memory:"; if (0 == (MLK & AAMemoryLocation::NO_LOCAL_MEM)) S += "stack,"; if (0 == (MLK & AAMemoryLocation::NO_CONST_MEM)) S += "constant,"; if (0 == (MLK & AAMemoryLocation::NO_GLOBAL_INTERNAL_MEM)) S += "internal global,"; if (0 == (MLK & AAMemoryLocation::NO_GLOBAL_EXTERNAL_MEM)) S += "external global,"; if (0 == (MLK & AAMemoryLocation::NO_ARGUMENT_MEM)) S += "argument,"; if (0 == (MLK & AAMemoryLocation::NO_INACCESSIBLE_MEM)) S += "inaccessible,"; if (0 == (MLK & AAMemoryLocation::NO_MALLOCED_MEM)) S += "malloced,"; if (0 == (MLK & AAMemoryLocation::NO_UNKOWN_MEM)) S += "unknown,"; S.pop_back(); return S; } namespace { struct AAMemoryLocationImpl : public AAMemoryLocation { AAMemoryLocationImpl(const IRPosition &IRP, Attributor &A) : AAMemoryLocation(IRP, A), Allocator(A.Allocator) { AccessKind2Accesses.fill(nullptr); } ~AAMemoryLocationImpl() { // The AccessSets are allocated via a BumpPtrAllocator, we call // the destructor manually. for (AccessSet *AS : AccessKind2Accesses) if (AS) AS->~AccessSet(); } /// See AbstractAttribute::initialize(...). void initialize(Attributor &A) override { intersectAssumedBits(BEST_STATE); getKnownStateFromValue(A, getIRPosition(), getState()); AAMemoryLocation::initialize(A); } /// Return the memory behavior information encoded in the IR for \p IRP. static void getKnownStateFromValue(Attributor &A, const IRPosition &IRP, BitIntegerState &State, bool IgnoreSubsumingPositions = false) { // For internal functions we ignore `argmemonly` and // `inaccessiblememorargmemonly` as we might break it via interprocedural // constant propagation. It is unclear if this is the best way but it is // unlikely this will cause real performance problems. If we are deriving // attributes for the anchor function we even remove the attribute in // addition to ignoring it. // TODO: A better way to handle this would be to add ~NO_GLOBAL_MEM / // MemoryEffects::Other as a possible location. bool UseArgMemOnly = true; Function *AnchorFn = IRP.getAnchorScope(); if (AnchorFn && A.isRunOn(*AnchorFn)) UseArgMemOnly = !AnchorFn->hasLocalLinkage(); SmallVector Attrs; A.getAttrs(IRP, {Attribute::Memory}, Attrs, IgnoreSubsumingPositions); for (const Attribute &Attr : Attrs) { // TODO: We can map MemoryEffects to Attributor locations more precisely. MemoryEffects ME = Attr.getMemoryEffects(); if (ME.doesNotAccessMemory()) { State.addKnownBits(NO_LOCAL_MEM | NO_CONST_MEM); continue; } if (ME.onlyAccessesInaccessibleMem()) { State.addKnownBits(inverseLocation(NO_INACCESSIBLE_MEM, true, true)); continue; } if (ME.onlyAccessesArgPointees()) { if (UseArgMemOnly) State.addKnownBits(inverseLocation(NO_ARGUMENT_MEM, true, true)); else { // Remove location information, only keep read/write info. ME = MemoryEffects(ME.getModRef()); A.manifestAttrs(IRP, Attribute::getWithMemoryEffects( IRP.getAnchorValue().getContext(), ME), /*ForceReplace*/ true); } continue; } if (ME.onlyAccessesInaccessibleOrArgMem()) { if (UseArgMemOnly) State.addKnownBits(inverseLocation( NO_INACCESSIBLE_MEM | NO_ARGUMENT_MEM, true, true)); else { // Remove location information, only keep read/write info. ME = MemoryEffects(ME.getModRef()); A.manifestAttrs(IRP, Attribute::getWithMemoryEffects( IRP.getAnchorValue().getContext(), ME), /*ForceReplace*/ true); } continue; } } } /// See AbstractAttribute::getDeducedAttributes(...). void getDeducedAttributes(Attributor &A, LLVMContext &Ctx, SmallVectorImpl &Attrs) const override { // TODO: We can map Attributor locations to MemoryEffects more precisely. assert(Attrs.size() == 0); if (getIRPosition().getPositionKind() == IRPosition::IRP_FUNCTION) { if (isAssumedReadNone()) Attrs.push_back( Attribute::getWithMemoryEffects(Ctx, MemoryEffects::none())); else if (isAssumedInaccessibleMemOnly()) Attrs.push_back(Attribute::getWithMemoryEffects( Ctx, MemoryEffects::inaccessibleMemOnly())); else if (isAssumedArgMemOnly()) Attrs.push_back( Attribute::getWithMemoryEffects(Ctx, MemoryEffects::argMemOnly())); else if (isAssumedInaccessibleOrArgMemOnly()) Attrs.push_back(Attribute::getWithMemoryEffects( Ctx, MemoryEffects::inaccessibleOrArgMemOnly())); } assert(Attrs.size() <= 1); } /// See AbstractAttribute::manifest(...). ChangeStatus manifest(Attributor &A) override { // TODO: If AAMemoryLocation and AAMemoryBehavior are merged, we could // provide per-location modref information here. const IRPosition &IRP = getIRPosition(); SmallVector DeducedAttrs; getDeducedAttributes(A, IRP.getAnchorValue().getContext(), DeducedAttrs); if (DeducedAttrs.size() != 1) return ChangeStatus::UNCHANGED; MemoryEffects ME = DeducedAttrs[0].getMemoryEffects(); return A.manifestAttrs(IRP, Attribute::getWithMemoryEffects( IRP.getAnchorValue().getContext(), ME)); } /// See AAMemoryLocation::checkForAllAccessesToMemoryKind(...). bool checkForAllAccessesToMemoryKind( function_ref Pred, MemoryLocationsKind RequestedMLK) const override { if (!isValidState()) return false; MemoryLocationsKind AssumedMLK = getAssumedNotAccessedLocation(); if (AssumedMLK == NO_LOCATIONS) return true; unsigned Idx = 0; for (MemoryLocationsKind CurMLK = 1; CurMLK < NO_LOCATIONS; CurMLK *= 2, ++Idx) { if (CurMLK & RequestedMLK) continue; if (const AccessSet *Accesses = AccessKind2Accesses[Idx]) for (const AccessInfo &AI : *Accesses) if (!Pred(AI.I, AI.Ptr, AI.Kind, CurMLK)) return false; } return true; } ChangeStatus indicatePessimisticFixpoint() override { // If we give up and indicate a pessimistic fixpoint this instruction will // become an access for all potential access kinds: // TODO: Add pointers for argmemonly and globals to improve the results of // checkForAllAccessesToMemoryKind. bool Changed = false; MemoryLocationsKind KnownMLK = getKnown(); Instruction *I = dyn_cast(&getAssociatedValue()); for (MemoryLocationsKind CurMLK = 1; CurMLK < NO_LOCATIONS; CurMLK *= 2) if (!(CurMLK & KnownMLK)) updateStateAndAccessesMap(getState(), CurMLK, I, nullptr, Changed, getAccessKindFromInst(I)); return AAMemoryLocation::indicatePessimisticFixpoint(); } protected: /// Helper struct to tie together an instruction that has a read or write /// effect with the pointer it accesses (if any). struct AccessInfo { /// The instruction that caused the access. const Instruction *I; /// The base pointer that is accessed, or null if unknown. const Value *Ptr; /// The kind of access (read/write/read+write). AccessKind Kind; bool operator==(const AccessInfo &RHS) const { return I == RHS.I && Ptr == RHS.Ptr && Kind == RHS.Kind; } bool operator()(const AccessInfo &LHS, const AccessInfo &RHS) const { if (LHS.I != RHS.I) return LHS.I < RHS.I; if (LHS.Ptr != RHS.Ptr) return LHS.Ptr < RHS.Ptr; if (LHS.Kind != RHS.Kind) return LHS.Kind < RHS.Kind; return false; } }; /// Mapping from *single* memory location kinds, e.g., LOCAL_MEM with the /// value of NO_LOCAL_MEM, to the accesses encountered for this memory kind. using AccessSet = SmallSet; std::array()> AccessKind2Accesses; /// Categorize the pointer arguments of CB that might access memory in /// AccessedLoc and update the state and access map accordingly. void categorizeArgumentPointerLocations(Attributor &A, CallBase &CB, AAMemoryLocation::StateType &AccessedLocs, bool &Changed); /// Return the kind(s) of location that may be accessed by \p V. AAMemoryLocation::MemoryLocationsKind categorizeAccessedLocations(Attributor &A, Instruction &I, bool &Changed); /// Return the access kind as determined by \p I. AccessKind getAccessKindFromInst(const Instruction *I) { AccessKind AK = READ_WRITE; if (I) { AK = I->mayReadFromMemory() ? READ : NONE; AK = AccessKind(AK | (I->mayWriteToMemory() ? WRITE : NONE)); } return AK; } /// Update the state \p State and the AccessKind2Accesses given that \p I is /// an access of kind \p AK to a \p MLK memory location with the access /// pointer \p Ptr. void updateStateAndAccessesMap(AAMemoryLocation::StateType &State, MemoryLocationsKind MLK, const Instruction *I, const Value *Ptr, bool &Changed, AccessKind AK = READ_WRITE) { assert(isPowerOf2_32(MLK) && "Expected a single location set!"); auto *&Accesses = AccessKind2Accesses[llvm::Log2_32(MLK)]; if (!Accesses) Accesses = new (Allocator) AccessSet(); Changed |= Accesses->insert(AccessInfo{I, Ptr, AK}).second; if (MLK == NO_UNKOWN_MEM) MLK = NO_LOCATIONS; State.removeAssumedBits(MLK); } /// Determine the underlying locations kinds for \p Ptr, e.g., globals or /// arguments, and update the state and access map accordingly. void categorizePtrValue(Attributor &A, const Instruction &I, const Value &Ptr, AAMemoryLocation::StateType &State, bool &Changed, unsigned AccessAS = 0); /// Used to allocate access sets. BumpPtrAllocator &Allocator; }; void AAMemoryLocationImpl::categorizePtrValue( Attributor &A, const Instruction &I, const Value &Ptr, AAMemoryLocation::StateType &State, bool &Changed, unsigned AccessAS) { LLVM_DEBUG(dbgs() << "[AAMemoryLocation] Categorize pointer locations for " << Ptr << " [" << getMemoryLocationsAsStr(State.getAssumed()) << "]\n"); auto Pred = [&](Value &Obj) { unsigned ObjectAS = Obj.getType()->getPointerAddressSpace(); // TODO: recognize the TBAA used for constant accesses. MemoryLocationsKind MLK = NO_LOCATIONS; // Filter accesses to constant (GPU) memory if we have an AS at the access // site or the object is known to actually have the associated AS. if ((AccessAS == (unsigned)AA::GPUAddressSpace::Constant || (ObjectAS == (unsigned)AA::GPUAddressSpace::Constant && isIdentifiedObject(&Obj))) && AA::isGPU(*I.getModule())) return true; if (isa(&Obj)) return true; if (isa(&Obj)) { // TODO: For now we do not treat byval arguments as local copies performed // on the call edge, though, we should. To make that happen we need to // teach various passes, e.g., DSE, about the copy effect of a byval. That // would also allow us to mark functions only accessing byval arguments as // readnone again, arguably their accesses have no effect outside of the // function, like accesses to allocas. MLK = NO_ARGUMENT_MEM; } else if (auto *GV = dyn_cast(&Obj)) { // Reading constant memory is not treated as a read "effect" by the // function attr pass so we won't neither. Constants defined by TBAA are // similar. (We know we do not write it because it is constant.) if (auto *GVar = dyn_cast(GV)) if (GVar->isConstant()) return true; if (GV->hasLocalLinkage()) MLK = NO_GLOBAL_INTERNAL_MEM; else MLK = NO_GLOBAL_EXTERNAL_MEM; } else if (isa(&Obj) && (!NullPointerIsDefined(getAssociatedFunction(), AccessAS) || !NullPointerIsDefined(getAssociatedFunction(), ObjectAS))) { return true; } else if (isa(&Obj)) { MLK = NO_LOCAL_MEM; } else if (const auto *CB = dyn_cast(&Obj)) { bool IsKnownNoAlias; if (AA::hasAssumedIRAttr( A, this, IRPosition::callsite_returned(*CB), DepClassTy::OPTIONAL, IsKnownNoAlias)) MLK = NO_MALLOCED_MEM; else MLK = NO_UNKOWN_MEM; } else { MLK = NO_UNKOWN_MEM; } assert(MLK != NO_LOCATIONS && "No location specified!"); LLVM_DEBUG(dbgs() << "[AAMemoryLocation] Ptr value can be categorized: " << Obj << " -> " << getMemoryLocationsAsStr(MLK) << "\n"); updateStateAndAccessesMap(State, MLK, &I, &Obj, Changed, getAccessKindFromInst(&I)); return true; }; const auto *AA = A.getAAFor( *this, IRPosition::value(Ptr), DepClassTy::OPTIONAL); if (!AA || !AA->forallUnderlyingObjects(Pred, AA::Intraprocedural)) { LLVM_DEBUG( dbgs() << "[AAMemoryLocation] Pointer locations not categorized\n"); updateStateAndAccessesMap(State, NO_UNKOWN_MEM, &I, nullptr, Changed, getAccessKindFromInst(&I)); return; } LLVM_DEBUG( dbgs() << "[AAMemoryLocation] Accessed locations with pointer locations: " << getMemoryLocationsAsStr(State.getAssumed()) << "\n"); } void AAMemoryLocationImpl::categorizeArgumentPointerLocations( Attributor &A, CallBase &CB, AAMemoryLocation::StateType &AccessedLocs, bool &Changed) { for (unsigned ArgNo = 0, E = CB.arg_size(); ArgNo < E; ++ArgNo) { // Skip non-pointer arguments. const Value *ArgOp = CB.getArgOperand(ArgNo); if (!ArgOp->getType()->isPtrOrPtrVectorTy()) continue; // Skip readnone arguments. const IRPosition &ArgOpIRP = IRPosition::callsite_argument(CB, ArgNo); const auto *ArgOpMemLocationAA = A.getAAFor(*this, ArgOpIRP, DepClassTy::OPTIONAL); if (ArgOpMemLocationAA && ArgOpMemLocationAA->isAssumedReadNone()) continue; // Categorize potentially accessed pointer arguments as if there was an // access instruction with them as pointer. categorizePtrValue(A, CB, *ArgOp, AccessedLocs, Changed); } } AAMemoryLocation::MemoryLocationsKind AAMemoryLocationImpl::categorizeAccessedLocations(Attributor &A, Instruction &I, bool &Changed) { LLVM_DEBUG(dbgs() << "[AAMemoryLocation] Categorize accessed locations for " << I << "\n"); AAMemoryLocation::StateType AccessedLocs; AccessedLocs.intersectAssumedBits(NO_LOCATIONS); if (auto *CB = dyn_cast(&I)) { // First check if we assume any memory is access is visible. const auto *CBMemLocationAA = A.getAAFor( *this, IRPosition::callsite_function(*CB), DepClassTy::OPTIONAL); LLVM_DEBUG(dbgs() << "[AAMemoryLocation] Categorize call site: " << I << " [" << CBMemLocationAA << "]\n"); if (!CBMemLocationAA) { updateStateAndAccessesMap(AccessedLocs, NO_UNKOWN_MEM, &I, nullptr, Changed, getAccessKindFromInst(&I)); return NO_UNKOWN_MEM; } if (CBMemLocationAA->isAssumedReadNone()) return NO_LOCATIONS; if (CBMemLocationAA->isAssumedInaccessibleMemOnly()) { updateStateAndAccessesMap(AccessedLocs, NO_INACCESSIBLE_MEM, &I, nullptr, Changed, getAccessKindFromInst(&I)); return AccessedLocs.getAssumed(); } uint32_t CBAssumedNotAccessedLocs = CBMemLocationAA->getAssumedNotAccessedLocation(); // Set the argmemonly and global bit as we handle them separately below. uint32_t CBAssumedNotAccessedLocsNoArgMem = CBAssumedNotAccessedLocs | NO_ARGUMENT_MEM | NO_GLOBAL_MEM; for (MemoryLocationsKind CurMLK = 1; CurMLK < NO_LOCATIONS; CurMLK *= 2) { if (CBAssumedNotAccessedLocsNoArgMem & CurMLK) continue; updateStateAndAccessesMap(AccessedLocs, CurMLK, &I, nullptr, Changed, getAccessKindFromInst(&I)); } // Now handle global memory if it might be accessed. This is slightly tricky // as NO_GLOBAL_MEM has multiple bits set. bool HasGlobalAccesses = ((~CBAssumedNotAccessedLocs) & NO_GLOBAL_MEM); if (HasGlobalAccesses) { auto AccessPred = [&](const Instruction *, const Value *Ptr, AccessKind Kind, MemoryLocationsKind MLK) { updateStateAndAccessesMap(AccessedLocs, MLK, &I, Ptr, Changed, getAccessKindFromInst(&I)); return true; }; if (!CBMemLocationAA->checkForAllAccessesToMemoryKind( AccessPred, inverseLocation(NO_GLOBAL_MEM, false, false))) return AccessedLocs.getWorstState(); } LLVM_DEBUG( dbgs() << "[AAMemoryLocation] Accessed state before argument handling: " << getMemoryLocationsAsStr(AccessedLocs.getAssumed()) << "\n"); // Now handle argument memory if it might be accessed. bool HasArgAccesses = ((~CBAssumedNotAccessedLocs) & NO_ARGUMENT_MEM); if (HasArgAccesses) categorizeArgumentPointerLocations(A, *CB, AccessedLocs, Changed); LLVM_DEBUG( dbgs() << "[AAMemoryLocation] Accessed state after argument handling: " << getMemoryLocationsAsStr(AccessedLocs.getAssumed()) << "\n"); return AccessedLocs.getAssumed(); } if (const Value *Ptr = getPointerOperand(&I, /* AllowVolatile */ true)) { LLVM_DEBUG( dbgs() << "[AAMemoryLocation] Categorize memory access with pointer: " << I << " [" << *Ptr << "]\n"); categorizePtrValue(A, I, *Ptr, AccessedLocs, Changed, Ptr->getType()->getPointerAddressSpace()); return AccessedLocs.getAssumed(); } LLVM_DEBUG(dbgs() << "[AAMemoryLocation] Failed to categorize instruction: " << I << "\n"); updateStateAndAccessesMap(AccessedLocs, NO_UNKOWN_MEM, &I, nullptr, Changed, getAccessKindFromInst(&I)); return AccessedLocs.getAssumed(); } /// An AA to represent the memory behavior function attributes. struct AAMemoryLocationFunction final : public AAMemoryLocationImpl { AAMemoryLocationFunction(const IRPosition &IRP, Attributor &A) : AAMemoryLocationImpl(IRP, A) {} /// See AbstractAttribute::updateImpl(Attributor &A). ChangeStatus updateImpl(Attributor &A) override { const auto *MemBehaviorAA = A.getAAFor(*this, getIRPosition(), DepClassTy::NONE); if (MemBehaviorAA && MemBehaviorAA->isAssumedReadNone()) { if (MemBehaviorAA->isKnownReadNone()) return indicateOptimisticFixpoint(); assert(isAssumedReadNone() && "AAMemoryLocation was not read-none but AAMemoryBehavior was!"); A.recordDependence(*MemBehaviorAA, *this, DepClassTy::OPTIONAL); return ChangeStatus::UNCHANGED; } // The current assumed state used to determine a change. auto AssumedState = getAssumed(); bool Changed = false; auto CheckRWInst = [&](Instruction &I) { MemoryLocationsKind MLK = categorizeAccessedLocations(A, I, Changed); LLVM_DEBUG(dbgs() << "[AAMemoryLocation] Accessed locations for " << I << ": " << getMemoryLocationsAsStr(MLK) << "\n"); removeAssumedBits(inverseLocation(MLK, false, false)); // Stop once only the valid bit set in the *not assumed location*, thus // once we don't actually exclude any memory locations in the state. return getAssumedNotAccessedLocation() != VALID_STATE; }; bool UsedAssumedInformation = false; if (!A.checkForAllReadWriteInstructions(CheckRWInst, *this, UsedAssumedInformation)) return indicatePessimisticFixpoint(); Changed |= AssumedState != getAssumed(); return Changed ? ChangeStatus::CHANGED : ChangeStatus::UNCHANGED; } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { if (isAssumedReadNone()) STATS_DECLTRACK_FN_ATTR(readnone) else if (isAssumedArgMemOnly()) STATS_DECLTRACK_FN_ATTR(argmemonly) else if (isAssumedInaccessibleMemOnly()) STATS_DECLTRACK_FN_ATTR(inaccessiblememonly) else if (isAssumedInaccessibleOrArgMemOnly()) STATS_DECLTRACK_FN_ATTR(inaccessiblememorargmemonly) } }; /// AAMemoryLocation attribute for call sites. struct AAMemoryLocationCallSite final : AAMemoryLocationImpl { AAMemoryLocationCallSite(const IRPosition &IRP, Attributor &A) : AAMemoryLocationImpl(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 liveness information and then it makes // sense to specialize attributes for call sites arguments instead of // redirecting requests to the callee argument. Function *F = getAssociatedFunction(); const IRPosition &FnPos = IRPosition::function(*F); auto *FnAA = A.getAAFor(*this, FnPos, DepClassTy::REQUIRED); if (!FnAA) return indicatePessimisticFixpoint(); bool Changed = false; auto AccessPred = [&](const Instruction *I, const Value *Ptr, AccessKind Kind, MemoryLocationsKind MLK) { updateStateAndAccessesMap(getState(), MLK, I, Ptr, Changed, getAccessKindFromInst(I)); return true; }; if (!FnAA->checkForAllAccessesToMemoryKind(AccessPred, ALL_LOCATIONS)) return indicatePessimisticFixpoint(); return Changed ? ChangeStatus::CHANGED : ChangeStatus::UNCHANGED; } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { if (isAssumedReadNone()) STATS_DECLTRACK_CS_ATTR(readnone) } }; } // namespace /// ------------------ denormal-fp-math Attribute ------------------------- namespace { struct AADenormalFPMathImpl : public AADenormalFPMath { AADenormalFPMathImpl(const IRPosition &IRP, Attributor &A) : AADenormalFPMath(IRP, A) {} const std::string getAsStr(Attributor *A) const override { std::string Str("AADenormalFPMath["); raw_string_ostream OS(Str); DenormalState Known = getKnown(); if (Known.Mode.isValid()) OS << "denormal-fp-math=" << Known.Mode; else OS << "invalid"; if (Known.ModeF32.isValid()) OS << " denormal-fp-math-f32=" << Known.ModeF32; OS << ']'; return Str; } }; struct AADenormalFPMathFunction final : AADenormalFPMathImpl { AADenormalFPMathFunction(const IRPosition &IRP, Attributor &A) : AADenormalFPMathImpl(IRP, A) {} void initialize(Attributor &A) override { const Function *F = getAnchorScope(); DenormalMode Mode = F->getDenormalModeRaw(); DenormalMode ModeF32 = F->getDenormalModeF32Raw(); // TODO: Handling this here prevents handling the case where a callee has a // fixed denormal-fp-math with dynamic denormal-fp-math-f32, but called from // a function with a fully fixed mode. if (ModeF32 == DenormalMode::getInvalid()) ModeF32 = Mode; Known = DenormalState{Mode, ModeF32}; if (isModeFixed()) indicateFixpoint(); } ChangeStatus updateImpl(Attributor &A) override { ChangeStatus Change = ChangeStatus::UNCHANGED; auto CheckCallSite = [=, &Change, &A](AbstractCallSite CS) { Function *Caller = CS.getInstruction()->getFunction(); LLVM_DEBUG(dbgs() << "[AADenormalFPMath] Call " << Caller->getName() << "->" << getAssociatedFunction()->getName() << '\n'); const auto *CallerInfo = A.getAAFor( *this, IRPosition::function(*Caller), DepClassTy::REQUIRED); if (!CallerInfo) return false; Change = Change | clampStateAndIndicateChange(this->getState(), CallerInfo->getState()); return true; }; bool AllCallSitesKnown = true; if (!A.checkForAllCallSites(CheckCallSite, *this, true, AllCallSitesKnown)) return indicatePessimisticFixpoint(); if (Change == ChangeStatus::CHANGED && isModeFixed()) indicateFixpoint(); return Change; } ChangeStatus manifest(Attributor &A) override { LLVMContext &Ctx = getAssociatedFunction()->getContext(); SmallVector AttrToAdd; SmallVector AttrToRemove; if (Known.Mode == DenormalMode::getDefault()) { AttrToRemove.push_back("denormal-fp-math"); } else { AttrToAdd.push_back( Attribute::get(Ctx, "denormal-fp-math", Known.Mode.str())); } if (Known.ModeF32 != Known.Mode) { AttrToAdd.push_back( Attribute::get(Ctx, "denormal-fp-math-f32", Known.ModeF32.str())); } else { AttrToRemove.push_back("denormal-fp-math-f32"); } auto &IRP = getIRPosition(); // TODO: There should be a combined add and remove API. return A.removeAttrs(IRP, AttrToRemove) | A.manifestAttrs(IRP, AttrToAdd, /*ForceReplace=*/true); } void trackStatistics() const override { STATS_DECLTRACK_FN_ATTR(denormal_fp_math) } }; } // namespace /// ------------------ Value Constant Range Attribute ------------------------- namespace { struct AAValueConstantRangeImpl : AAValueConstantRange { using StateType = IntegerRangeState; AAValueConstantRangeImpl(const IRPosition &IRP, Attributor &A) : AAValueConstantRange(IRP, A) {} /// See AbstractAttribute::initialize(..). void initialize(Attributor &A) override { if (A.hasSimplificationCallback(getIRPosition())) { indicatePessimisticFixpoint(); return; } // Intersect a range given by SCEV. intersectKnown(getConstantRangeFromSCEV(A, getCtxI())); // Intersect a range given by LVI. intersectKnown(getConstantRangeFromLVI(A, getCtxI())); } /// See AbstractAttribute::getAsStr(). const std::string getAsStr(Attributor *A) const override { std::string Str; llvm::raw_string_ostream OS(Str); OS << "range(" << getBitWidth() << ")<"; getKnown().print(OS); OS << " / "; getAssumed().print(OS); OS << ">"; return Str; } /// Helper function to get a SCEV expr for the associated value at program /// point \p I. const SCEV *getSCEV(Attributor &A, const Instruction *I = nullptr) const { if (!getAnchorScope()) return nullptr; ScalarEvolution *SE = A.getInfoCache().getAnalysisResultForFunction( *getAnchorScope()); LoopInfo *LI = A.getInfoCache().getAnalysisResultForFunction( *getAnchorScope()); if (!SE || !LI) return nullptr; const SCEV *S = SE->getSCEV(&getAssociatedValue()); if (!I) return S; return SE->getSCEVAtScope(S, LI->getLoopFor(I->getParent())); } /// Helper function to get a range from SCEV for the associated value at /// program point \p I. ConstantRange getConstantRangeFromSCEV(Attributor &A, const Instruction *I = nullptr) const { if (!getAnchorScope()) return getWorstState(getBitWidth()); ScalarEvolution *SE = A.getInfoCache().getAnalysisResultForFunction( *getAnchorScope()); const SCEV *S = getSCEV(A, I); if (!SE || !S) return getWorstState(getBitWidth()); return SE->getUnsignedRange(S); } /// Helper function to get a range from LVI for the associated value at /// program point \p I. ConstantRange getConstantRangeFromLVI(Attributor &A, const Instruction *CtxI = nullptr) const { if (!getAnchorScope()) return getWorstState(getBitWidth()); LazyValueInfo *LVI = A.getInfoCache().getAnalysisResultForFunction( *getAnchorScope()); if (!LVI || !CtxI) return getWorstState(getBitWidth()); return LVI->getConstantRange(&getAssociatedValue(), const_cast(CtxI), /*UndefAllowed*/ false); } /// Return true if \p CtxI is valid for querying outside analyses. /// This basically makes sure we do not ask intra-procedural analysis /// about a context in the wrong function or a context that violates /// dominance assumptions they might have. The \p AllowAACtxI flag indicates /// if the original context of this AA is OK or should be considered invalid. bool isValidCtxInstructionForOutsideAnalysis(Attributor &A, const Instruction *CtxI, bool AllowAACtxI) const { if (!CtxI || (!AllowAACtxI && CtxI == getCtxI())) return false; // Our context might be in a different function, neither intra-procedural // analysis (ScalarEvolution nor LazyValueInfo) can handle that. if (!AA::isValidInScope(getAssociatedValue(), CtxI->getFunction())) return false; // If the context is not dominated by the value there are paths to the // context that do not define the value. This cannot be handled by // LazyValueInfo so we need to bail. if (auto *I = dyn_cast(&getAssociatedValue())) { InformationCache &InfoCache = A.getInfoCache(); const DominatorTree *DT = InfoCache.getAnalysisResultForFunction( *I->getFunction()); return DT && DT->dominates(I, CtxI); } return true; } /// See AAValueConstantRange::getKnownConstantRange(..). ConstantRange getKnownConstantRange(Attributor &A, const Instruction *CtxI = nullptr) const override { if (!isValidCtxInstructionForOutsideAnalysis(A, CtxI, /* AllowAACtxI */ false)) return getKnown(); ConstantRange LVIR = getConstantRangeFromLVI(A, CtxI); ConstantRange SCEVR = getConstantRangeFromSCEV(A, CtxI); return getKnown().intersectWith(SCEVR).intersectWith(LVIR); } /// See AAValueConstantRange::getAssumedConstantRange(..). ConstantRange getAssumedConstantRange(Attributor &A, const Instruction *CtxI = nullptr) const override { // TODO: Make SCEV use Attributor assumption. // We may be able to bound a variable range via assumptions in // Attributor. ex.) If x is assumed to be in [1, 3] and y is known to // evolve to x^2 + x, then we can say that y is in [2, 12]. if (!isValidCtxInstructionForOutsideAnalysis(A, CtxI, /* AllowAACtxI */ false)) return getAssumed(); ConstantRange LVIR = getConstantRangeFromLVI(A, CtxI); ConstantRange SCEVR = getConstantRangeFromSCEV(A, CtxI); return getAssumed().intersectWith(SCEVR).intersectWith(LVIR); } /// Helper function to create MDNode for range metadata. static MDNode * getMDNodeForConstantRange(Type *Ty, LLVMContext &Ctx, const ConstantRange &AssumedConstantRange) { Metadata *LowAndHigh[] = {ConstantAsMetadata::get(ConstantInt::get( Ty, AssumedConstantRange.getLower())), ConstantAsMetadata::get(ConstantInt::get( Ty, AssumedConstantRange.getUpper()))}; return MDNode::get(Ctx, LowAndHigh); } /// Return true if \p Assumed is included in \p KnownRanges. static bool isBetterRange(const ConstantRange &Assumed, MDNode *KnownRanges) { if (Assumed.isFullSet()) return false; if (!KnownRanges) return true; // If multiple ranges are annotated in IR, we give up to annotate assumed // range for now. // TODO: If there exists a known range which containts assumed range, we // can say assumed range is better. if (KnownRanges->getNumOperands() > 2) return false; ConstantInt *Lower = mdconst::extract(KnownRanges->getOperand(0)); ConstantInt *Upper = mdconst::extract(KnownRanges->getOperand(1)); ConstantRange Known(Lower->getValue(), Upper->getValue()); return Known.contains(Assumed) && Known != Assumed; } /// Helper function to set range metadata. static bool setRangeMetadataIfisBetterRange(Instruction *I, const ConstantRange &AssumedConstantRange) { auto *OldRangeMD = I->getMetadata(LLVMContext::MD_range); if (isBetterRange(AssumedConstantRange, OldRangeMD)) { if (!AssumedConstantRange.isEmptySet()) { I->setMetadata(LLVMContext::MD_range, getMDNodeForConstantRange(I->getType(), I->getContext(), AssumedConstantRange)); return true; } } return false; } /// See AbstractAttribute::manifest() ChangeStatus manifest(Attributor &A) override { ChangeStatus Changed = ChangeStatus::UNCHANGED; ConstantRange AssumedConstantRange = getAssumedConstantRange(A); assert(!AssumedConstantRange.isFullSet() && "Invalid state"); auto &V = getAssociatedValue(); if (!AssumedConstantRange.isEmptySet() && !AssumedConstantRange.isSingleElement()) { if (Instruction *I = dyn_cast(&V)) { assert(I == getCtxI() && "Should not annotate an instruction which is " "not the context instruction"); if (isa(I) || isa(I)) if (setRangeMetadataIfisBetterRange(I, AssumedConstantRange)) Changed = ChangeStatus::CHANGED; } } return Changed; } }; struct AAValueConstantRangeArgument final : AAArgumentFromCallSiteArguments< AAValueConstantRange, AAValueConstantRangeImpl, IntegerRangeState, true /* BridgeCallBaseContext */> { using Base = AAArgumentFromCallSiteArguments< AAValueConstantRange, AAValueConstantRangeImpl, IntegerRangeState, true /* BridgeCallBaseContext */>; AAValueConstantRangeArgument(const IRPosition &IRP, Attributor &A) : Base(IRP, A) {} /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_ARG_ATTR(value_range) } }; struct AAValueConstantRangeReturned : AAReturnedFromReturnedValues { using Base = AAReturnedFromReturnedValues; AAValueConstantRangeReturned(const IRPosition &IRP, Attributor &A) : Base(IRP, A) {} /// See AbstractAttribute::initialize(...). void initialize(Attributor &A) override { if (!A.isFunctionIPOAmendable(*getAssociatedFunction())) indicatePessimisticFixpoint(); } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_FNRET_ATTR(value_range) } }; struct AAValueConstantRangeFloating : AAValueConstantRangeImpl { AAValueConstantRangeFloating(const IRPosition &IRP, Attributor &A) : AAValueConstantRangeImpl(IRP, A) {} /// See AbstractAttribute::initialize(...). void initialize(Attributor &A) override { AAValueConstantRangeImpl::initialize(A); if (isAtFixpoint()) return; Value &V = getAssociatedValue(); if (auto *C = dyn_cast(&V)) { unionAssumed(ConstantRange(C->getValue())); indicateOptimisticFixpoint(); return; } if (isa(&V)) { // Collapse the undef state to 0. unionAssumed(ConstantRange(APInt(getBitWidth(), 0))); indicateOptimisticFixpoint(); return; } if (isa(&V)) return; if (isa(&V) || isa(&V) || isa(&V)) return; // If it is a load instruction with range metadata, use it. if (LoadInst *LI = dyn_cast(&V)) if (auto *RangeMD = LI->getMetadata(LLVMContext::MD_range)) { intersectKnown(getConstantRangeFromMetadata(*RangeMD)); return; } // We can work with PHI and select instruction as we traverse their operands // during update. if (isa(V) || isa(V)) return; // Otherwise we give up. indicatePessimisticFixpoint(); LLVM_DEBUG(dbgs() << "[AAValueConstantRange] We give up: " << getAssociatedValue() << "\n"); } bool calculateBinaryOperator( Attributor &A, BinaryOperator *BinOp, IntegerRangeState &T, const Instruction *CtxI, SmallVectorImpl &QuerriedAAs) { Value *LHS = BinOp->getOperand(0); Value *RHS = BinOp->getOperand(1); // Simplify the operands first. bool UsedAssumedInformation = false; const auto &SimplifiedLHS = A.getAssumedSimplified( IRPosition::value(*LHS, getCallBaseContext()), *this, UsedAssumedInformation, AA::Interprocedural); if (!SimplifiedLHS.has_value()) return true; if (!*SimplifiedLHS) return false; LHS = *SimplifiedLHS; const auto &SimplifiedRHS = A.getAssumedSimplified( IRPosition::value(*RHS, getCallBaseContext()), *this, UsedAssumedInformation, AA::Interprocedural); if (!SimplifiedRHS.has_value()) return true; if (!*SimplifiedRHS) return false; RHS = *SimplifiedRHS; // TODO: Allow non integers as well. if (!LHS->getType()->isIntegerTy() || !RHS->getType()->isIntegerTy()) return false; auto *LHSAA = A.getAAFor( *this, IRPosition::value(*LHS, getCallBaseContext()), DepClassTy::REQUIRED); if (!LHSAA) return false; QuerriedAAs.push_back(LHSAA); auto LHSAARange = LHSAA->getAssumedConstantRange(A, CtxI); auto *RHSAA = A.getAAFor( *this, IRPosition::value(*RHS, getCallBaseContext()), DepClassTy::REQUIRED); if (!RHSAA) return false; QuerriedAAs.push_back(RHSAA); auto RHSAARange = RHSAA->getAssumedConstantRange(A, CtxI); auto AssumedRange = LHSAARange.binaryOp(BinOp->getOpcode(), RHSAARange); T.unionAssumed(AssumedRange); // TODO: Track a known state too. return T.isValidState(); } bool calculateCastInst( Attributor &A, CastInst *CastI, IntegerRangeState &T, const Instruction *CtxI, SmallVectorImpl &QuerriedAAs) { assert(CastI->getNumOperands() == 1 && "Expected cast to be unary!"); // TODO: Allow non integers as well. Value *OpV = CastI->getOperand(0); // Simplify the operand first. bool UsedAssumedInformation = false; const auto &SimplifiedOpV = A.getAssumedSimplified( IRPosition::value(*OpV, getCallBaseContext()), *this, UsedAssumedInformation, AA::Interprocedural); if (!SimplifiedOpV.has_value()) return true; if (!*SimplifiedOpV) return false; OpV = *SimplifiedOpV; if (!OpV->getType()->isIntegerTy()) return false; auto *OpAA = A.getAAFor( *this, IRPosition::value(*OpV, getCallBaseContext()), DepClassTy::REQUIRED); if (!OpAA) return false; QuerriedAAs.push_back(OpAA); T.unionAssumed(OpAA->getAssumed().castOp(CastI->getOpcode(), getState().getBitWidth())); return T.isValidState(); } bool calculateCmpInst(Attributor &A, CmpInst *CmpI, IntegerRangeState &T, const Instruction *CtxI, SmallVectorImpl &QuerriedAAs) { Value *LHS = CmpI->getOperand(0); Value *RHS = CmpI->getOperand(1); // Simplify the operands first. bool UsedAssumedInformation = false; const auto &SimplifiedLHS = A.getAssumedSimplified( IRPosition::value(*LHS, getCallBaseContext()), *this, UsedAssumedInformation, AA::Interprocedural); if (!SimplifiedLHS.has_value()) return true; if (!*SimplifiedLHS) return false; LHS = *SimplifiedLHS; const auto &SimplifiedRHS = A.getAssumedSimplified( IRPosition::value(*RHS, getCallBaseContext()), *this, UsedAssumedInformation, AA::Interprocedural); if (!SimplifiedRHS.has_value()) return true; if (!*SimplifiedRHS) return false; RHS = *SimplifiedRHS; // TODO: Allow non integers as well. if (!LHS->getType()->isIntegerTy() || !RHS->getType()->isIntegerTy()) return false; auto *LHSAA = A.getAAFor( *this, IRPosition::value(*LHS, getCallBaseContext()), DepClassTy::REQUIRED); if (!LHSAA) return false; QuerriedAAs.push_back(LHSAA); auto *RHSAA = A.getAAFor( *this, IRPosition::value(*RHS, getCallBaseContext()), DepClassTy::REQUIRED); if (!RHSAA) return false; QuerriedAAs.push_back(RHSAA); auto LHSAARange = LHSAA->getAssumedConstantRange(A, CtxI); auto RHSAARange = RHSAA->getAssumedConstantRange(A, CtxI); // If one of them is empty set, we can't decide. if (LHSAARange.isEmptySet() || RHSAARange.isEmptySet()) return true; bool MustTrue = false, MustFalse = false; auto AllowedRegion = ConstantRange::makeAllowedICmpRegion(CmpI->getPredicate(), RHSAARange); if (AllowedRegion.intersectWith(LHSAARange).isEmptySet()) MustFalse = true; if (LHSAARange.icmp(CmpI->getPredicate(), RHSAARange)) MustTrue = true; assert((!MustTrue || !MustFalse) && "Either MustTrue or MustFalse should be false!"); if (MustTrue) T.unionAssumed(ConstantRange(APInt(/* numBits */ 1, /* val */ 1))); else if (MustFalse) T.unionAssumed(ConstantRange(APInt(/* numBits */ 1, /* val */ 0))); else T.unionAssumed(ConstantRange(/* BitWidth */ 1, /* isFullSet */ true)); LLVM_DEBUG(dbgs() << "[AAValueConstantRange] " << *CmpI << " after " << (MustTrue ? "true" : (MustFalse ? "false" : "unknown")) << ": " << T << "\n\t" << *LHSAA << "\t\n\t" << *RHSAA); // TODO: Track a known state too. return T.isValidState(); } /// See AbstractAttribute::updateImpl(...). ChangeStatus updateImpl(Attributor &A) override { IntegerRangeState T(getBitWidth()); auto VisitValueCB = [&](Value &V, const Instruction *CtxI) -> bool { Instruction *I = dyn_cast(&V); if (!I || isa(I)) { // Simplify the operand first. bool UsedAssumedInformation = false; const auto &SimplifiedOpV = A.getAssumedSimplified( IRPosition::value(V, getCallBaseContext()), *this, UsedAssumedInformation, AA::Interprocedural); if (!SimplifiedOpV.has_value()) return true; if (!*SimplifiedOpV) return false; Value *VPtr = *SimplifiedOpV; // If the value is not instruction, we query AA to Attributor. const auto *AA = A.getAAFor( *this, IRPosition::value(*VPtr, getCallBaseContext()), DepClassTy::REQUIRED); // Clamp operator is not used to utilize a program point CtxI. if (AA) T.unionAssumed(AA->getAssumedConstantRange(A, CtxI)); else return false; return T.isValidState(); } SmallVector QuerriedAAs; if (auto *BinOp = dyn_cast(I)) { if (!calculateBinaryOperator(A, BinOp, T, CtxI, QuerriedAAs)) return false; } else if (auto *CmpI = dyn_cast(I)) { if (!calculateCmpInst(A, CmpI, T, CtxI, QuerriedAAs)) return false; } else if (auto *CastI = dyn_cast(I)) { if (!calculateCastInst(A, CastI, T, CtxI, QuerriedAAs)) return false; } else { // Give up with other instructions. // TODO: Add other instructions T.indicatePessimisticFixpoint(); return false; } // Catch circular reasoning in a pessimistic way for now. // TODO: Check how the range evolves and if we stripped anything, see also // AADereferenceable or AAAlign for similar situations. for (const AAValueConstantRange *QueriedAA : QuerriedAAs) { if (QueriedAA != this) continue; // If we are in a stady state we do not need to worry. if (T.getAssumed() == getState().getAssumed()) continue; T.indicatePessimisticFixpoint(); } return T.isValidState(); }; if (!VisitValueCB(getAssociatedValue(), getCtxI())) return indicatePessimisticFixpoint(); // Ensure that long def-use chains can't cause circular reasoning either by // introducing a cutoff below. if (clampStateAndIndicateChange(getState(), T) == ChangeStatus::UNCHANGED) return ChangeStatus::UNCHANGED; if (++NumChanges > MaxNumChanges) { LLVM_DEBUG(dbgs() << "[AAValueConstantRange] performed " << NumChanges << " but only " << MaxNumChanges << " are allowed to avoid cyclic reasoning."); return indicatePessimisticFixpoint(); } return ChangeStatus::CHANGED; } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_FLOATING_ATTR(value_range) } /// Tracker to bail after too many widening steps of the constant range. int NumChanges = 0; /// Upper bound for the number of allowed changes (=widening steps) for the /// constant range before we give up. static constexpr int MaxNumChanges = 5; }; struct AAValueConstantRangeFunction : AAValueConstantRangeImpl { AAValueConstantRangeFunction(const IRPosition &IRP, Attributor &A) : AAValueConstantRangeImpl(IRP, A) {} /// See AbstractAttribute::initialize(...). ChangeStatus updateImpl(Attributor &A) override { llvm_unreachable("AAValueConstantRange(Function|CallSite)::updateImpl will " "not be called"); } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_FN_ATTR(value_range) } }; struct AAValueConstantRangeCallSite : AAValueConstantRangeFunction { AAValueConstantRangeCallSite(const IRPosition &IRP, Attributor &A) : AAValueConstantRangeFunction(IRP, A) {} /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_CS_ATTR(value_range) } }; struct AAValueConstantRangeCallSiteReturned : AACalleeToCallSite { AAValueConstantRangeCallSiteReturned(const IRPosition &IRP, Attributor &A) : AACalleeToCallSite(IRP, A) {} /// See AbstractAttribute::initialize(...). void initialize(Attributor &A) override { // If it is a load instruction with range metadata, use the metadata. if (CallInst *CI = dyn_cast(&getAssociatedValue())) if (auto *RangeMD = CI->getMetadata(LLVMContext::MD_range)) intersectKnown(getConstantRangeFromMetadata(*RangeMD)); AAValueConstantRangeImpl::initialize(A); } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_CSRET_ATTR(value_range) } }; struct AAValueConstantRangeCallSiteArgument : AAValueConstantRangeFloating { AAValueConstantRangeCallSiteArgument(const IRPosition &IRP, Attributor &A) : AAValueConstantRangeFloating(IRP, A) {} /// See AbstractAttribute::manifest() ChangeStatus manifest(Attributor &A) override { return ChangeStatus::UNCHANGED; } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_CSARG_ATTR(value_range) } }; } // namespace /// ------------------ Potential Values Attribute ------------------------- namespace { struct AAPotentialConstantValuesImpl : AAPotentialConstantValues { using StateType = PotentialConstantIntValuesState; AAPotentialConstantValuesImpl(const IRPosition &IRP, Attributor &A) : AAPotentialConstantValues(IRP, A) {} /// See AbstractAttribute::initialize(..). void initialize(Attributor &A) override { if (A.hasSimplificationCallback(getIRPosition())) indicatePessimisticFixpoint(); else AAPotentialConstantValues::initialize(A); } bool fillSetWithConstantValues(Attributor &A, const IRPosition &IRP, SetTy &S, bool &ContainsUndef, bool ForSelf) { SmallVector Values; bool UsedAssumedInformation = false; if (!A.getAssumedSimplifiedValues(IRP, *this, Values, AA::Interprocedural, UsedAssumedInformation)) { // Avoid recursion when the caller is computing constant values for this // IRP itself. if (ForSelf) return false; if (!IRP.getAssociatedType()->isIntegerTy()) return false; auto *PotentialValuesAA = A.getAAFor( *this, IRP, DepClassTy::REQUIRED); if (!PotentialValuesAA || !PotentialValuesAA->getState().isValidState()) return false; ContainsUndef = PotentialValuesAA->getState().undefIsContained(); S = PotentialValuesAA->getState().getAssumedSet(); return true; } // Copy all the constant values, except UndefValue. ContainsUndef is true // iff Values contains only UndefValue instances. If there are other known // constants, then UndefValue is dropped. ContainsUndef = false; for (auto &It : Values) { if (isa(It.getValue())) { ContainsUndef = true; continue; } auto *CI = dyn_cast(It.getValue()); if (!CI) return false; S.insert(CI->getValue()); } ContainsUndef &= S.empty(); return true; } /// See AbstractAttribute::getAsStr(). const std::string getAsStr(Attributor *A) const override { std::string Str; llvm::raw_string_ostream OS(Str); OS << getState(); return Str; } /// See AbstractAttribute::updateImpl(...). ChangeStatus updateImpl(Attributor &A) override { return indicatePessimisticFixpoint(); } }; struct AAPotentialConstantValuesArgument final : AAArgumentFromCallSiteArguments { using Base = AAArgumentFromCallSiteArguments; AAPotentialConstantValuesArgument(const IRPosition &IRP, Attributor &A) : Base(IRP, A) {} /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_ARG_ATTR(potential_values) } }; struct AAPotentialConstantValuesReturned : AAReturnedFromReturnedValues { using Base = AAReturnedFromReturnedValues; AAPotentialConstantValuesReturned(const IRPosition &IRP, Attributor &A) : Base(IRP, A) {} void initialize(Attributor &A) override { if (!A.isFunctionIPOAmendable(*getAssociatedFunction())) indicatePessimisticFixpoint(); Base::initialize(A); } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_FNRET_ATTR(potential_values) } }; struct AAPotentialConstantValuesFloating : AAPotentialConstantValuesImpl { AAPotentialConstantValuesFloating(const IRPosition &IRP, Attributor &A) : AAPotentialConstantValuesImpl(IRP, A) {} /// See AbstractAttribute::initialize(..). void initialize(Attributor &A) override { AAPotentialConstantValuesImpl::initialize(A); if (isAtFixpoint()) return; Value &V = getAssociatedValue(); if (auto *C = dyn_cast(&V)) { unionAssumed(C->getValue()); indicateOptimisticFixpoint(); return; } if (isa(&V)) { unionAssumedWithUndef(); indicateOptimisticFixpoint(); return; } if (isa(&V) || isa(&V) || isa(&V)) return; if (isa(V) || isa(V) || isa(V)) return; indicatePessimisticFixpoint(); LLVM_DEBUG(dbgs() << "[AAPotentialConstantValues] We give up: " << getAssociatedValue() << "\n"); } static bool calculateICmpInst(const ICmpInst *ICI, const APInt &LHS, const APInt &RHS) { return ICmpInst::compare(LHS, RHS, ICI->getPredicate()); } static APInt calculateCastInst(const CastInst *CI, const APInt &Src, uint32_t ResultBitWidth) { Instruction::CastOps CastOp = CI->getOpcode(); switch (CastOp) { default: llvm_unreachable("unsupported or not integer cast"); case Instruction::Trunc: return Src.trunc(ResultBitWidth); case Instruction::SExt: return Src.sext(ResultBitWidth); case Instruction::ZExt: return Src.zext(ResultBitWidth); case Instruction::BitCast: return Src; } } static APInt calculateBinaryOperator(const BinaryOperator *BinOp, const APInt &LHS, const APInt &RHS, bool &SkipOperation, bool &Unsupported) { Instruction::BinaryOps BinOpcode = BinOp->getOpcode(); // Unsupported is set to true when the binary operator is not supported. // SkipOperation is set to true when UB occur with the given operand pair // (LHS, RHS). // TODO: we should look at nsw and nuw keywords to handle operations // that create poison or undef value. switch (BinOpcode) { default: Unsupported = true; return LHS; case Instruction::Add: return LHS + RHS; case Instruction::Sub: return LHS - RHS; case Instruction::Mul: return LHS * RHS; case Instruction::UDiv: if (RHS.isZero()) { SkipOperation = true; return LHS; } return LHS.udiv(RHS); case Instruction::SDiv: if (RHS.isZero()) { SkipOperation = true; return LHS; } return LHS.sdiv(RHS); case Instruction::URem: if (RHS.isZero()) { SkipOperation = true; return LHS; } return LHS.urem(RHS); case Instruction::SRem: if (RHS.isZero()) { SkipOperation = true; return LHS; } return LHS.srem(RHS); case Instruction::Shl: return LHS.shl(RHS); case Instruction::LShr: return LHS.lshr(RHS); case Instruction::AShr: return LHS.ashr(RHS); case Instruction::And: return LHS & RHS; case Instruction::Or: return LHS | RHS; case Instruction::Xor: return LHS ^ RHS; } } bool calculateBinaryOperatorAndTakeUnion(const BinaryOperator *BinOp, const APInt &LHS, const APInt &RHS) { bool SkipOperation = false; bool Unsupported = false; APInt Result = calculateBinaryOperator(BinOp, LHS, RHS, SkipOperation, Unsupported); if (Unsupported) return false; // If SkipOperation is true, we can ignore this operand pair (L, R). if (!SkipOperation) unionAssumed(Result); return isValidState(); } ChangeStatus updateWithICmpInst(Attributor &A, ICmpInst *ICI) { auto AssumedBefore = getAssumed(); Value *LHS = ICI->getOperand(0); Value *RHS = ICI->getOperand(1); bool LHSContainsUndef = false, RHSContainsUndef = false; SetTy LHSAAPVS, RHSAAPVS; if (!fillSetWithConstantValues(A, IRPosition::value(*LHS), LHSAAPVS, LHSContainsUndef, /* ForSelf */ false) || !fillSetWithConstantValues(A, IRPosition::value(*RHS), RHSAAPVS, RHSContainsUndef, /* ForSelf */ false)) return indicatePessimisticFixpoint(); // TODO: make use of undef flag to limit potential values aggressively. bool MaybeTrue = false, MaybeFalse = false; const APInt Zero(RHS->getType()->getIntegerBitWidth(), 0); if (LHSContainsUndef && RHSContainsUndef) { // The result of any comparison between undefs can be soundly replaced // with undef. unionAssumedWithUndef(); } else if (LHSContainsUndef) { for (const APInt &R : RHSAAPVS) { bool CmpResult = calculateICmpInst(ICI, Zero, R); MaybeTrue |= CmpResult; MaybeFalse |= !CmpResult; if (MaybeTrue & MaybeFalse) return indicatePessimisticFixpoint(); } } else if (RHSContainsUndef) { for (const APInt &L : LHSAAPVS) { bool CmpResult = calculateICmpInst(ICI, L, Zero); MaybeTrue |= CmpResult; MaybeFalse |= !CmpResult; if (MaybeTrue & MaybeFalse) return indicatePessimisticFixpoint(); } } else { for (const APInt &L : LHSAAPVS) { for (const APInt &R : RHSAAPVS) { bool CmpResult = calculateICmpInst(ICI, L, R); MaybeTrue |= CmpResult; MaybeFalse |= !CmpResult; if (MaybeTrue & MaybeFalse) return indicatePessimisticFixpoint(); } } } if (MaybeTrue) unionAssumed(APInt(/* numBits */ 1, /* val */ 1)); if (MaybeFalse) unionAssumed(APInt(/* numBits */ 1, /* val */ 0)); return AssumedBefore == getAssumed() ? ChangeStatus::UNCHANGED : ChangeStatus::CHANGED; } ChangeStatus updateWithSelectInst(Attributor &A, SelectInst *SI) { auto AssumedBefore = getAssumed(); Value *LHS = SI->getTrueValue(); Value *RHS = SI->getFalseValue(); bool UsedAssumedInformation = false; std::optional C = A.getAssumedConstant( *SI->getCondition(), *this, UsedAssumedInformation); // Check if we only need one operand. bool OnlyLeft = false, OnlyRight = false; if (C && *C && (*C)->isOneValue()) OnlyLeft = true; else if (C && *C && (*C)->isZeroValue()) OnlyRight = true; bool LHSContainsUndef = false, RHSContainsUndef = false; SetTy LHSAAPVS, RHSAAPVS; if (!OnlyRight && !fillSetWithConstantValues(A, IRPosition::value(*LHS), LHSAAPVS, LHSContainsUndef, /* ForSelf */ false)) return indicatePessimisticFixpoint(); if (!OnlyLeft && !fillSetWithConstantValues(A, IRPosition::value(*RHS), RHSAAPVS, RHSContainsUndef, /* ForSelf */ false)) return indicatePessimisticFixpoint(); if (OnlyLeft || OnlyRight) { // select (true/false), lhs, rhs auto *OpAA = OnlyLeft ? &LHSAAPVS : &RHSAAPVS; auto Undef = OnlyLeft ? LHSContainsUndef : RHSContainsUndef; if (Undef) unionAssumedWithUndef(); else { for (const auto &It : *OpAA) unionAssumed(It); } } else if (LHSContainsUndef && RHSContainsUndef) { // select i1 *, undef , undef => undef unionAssumedWithUndef(); } else { for (const auto &It : LHSAAPVS) unionAssumed(It); for (const auto &It : RHSAAPVS) unionAssumed(It); } return AssumedBefore == getAssumed() ? ChangeStatus::UNCHANGED : ChangeStatus::CHANGED; } ChangeStatus updateWithCastInst(Attributor &A, CastInst *CI) { auto AssumedBefore = getAssumed(); if (!CI->isIntegerCast()) return indicatePessimisticFixpoint(); assert(CI->getNumOperands() == 1 && "Expected cast to be unary!"); uint32_t ResultBitWidth = CI->getDestTy()->getIntegerBitWidth(); Value *Src = CI->getOperand(0); bool SrcContainsUndef = false; SetTy SrcPVS; if (!fillSetWithConstantValues(A, IRPosition::value(*Src), SrcPVS, SrcContainsUndef, /* ForSelf */ false)) return indicatePessimisticFixpoint(); if (SrcContainsUndef) unionAssumedWithUndef(); else { for (const APInt &S : SrcPVS) { APInt T = calculateCastInst(CI, S, ResultBitWidth); unionAssumed(T); } } return AssumedBefore == getAssumed() ? ChangeStatus::UNCHANGED : ChangeStatus::CHANGED; } ChangeStatus updateWithBinaryOperator(Attributor &A, BinaryOperator *BinOp) { auto AssumedBefore = getAssumed(); Value *LHS = BinOp->getOperand(0); Value *RHS = BinOp->getOperand(1); bool LHSContainsUndef = false, RHSContainsUndef = false; SetTy LHSAAPVS, RHSAAPVS; if (!fillSetWithConstantValues(A, IRPosition::value(*LHS), LHSAAPVS, LHSContainsUndef, /* ForSelf */ false) || !fillSetWithConstantValues(A, IRPosition::value(*RHS), RHSAAPVS, RHSContainsUndef, /* ForSelf */ false)) return indicatePessimisticFixpoint(); const APInt Zero = APInt(LHS->getType()->getIntegerBitWidth(), 0); // TODO: make use of undef flag to limit potential values aggressively. if (LHSContainsUndef && RHSContainsUndef) { if (!calculateBinaryOperatorAndTakeUnion(BinOp, Zero, Zero)) return indicatePessimisticFixpoint(); } else if (LHSContainsUndef) { for (const APInt &R : RHSAAPVS) { if (!calculateBinaryOperatorAndTakeUnion(BinOp, Zero, R)) return indicatePessimisticFixpoint(); } } else if (RHSContainsUndef) { for (const APInt &L : LHSAAPVS) { if (!calculateBinaryOperatorAndTakeUnion(BinOp, L, Zero)) return indicatePessimisticFixpoint(); } } else { for (const APInt &L : LHSAAPVS) { for (const APInt &R : RHSAAPVS) { if (!calculateBinaryOperatorAndTakeUnion(BinOp, L, R)) return indicatePessimisticFixpoint(); } } } return AssumedBefore == getAssumed() ? ChangeStatus::UNCHANGED : ChangeStatus::CHANGED; } ChangeStatus updateWithInstruction(Attributor &A, Instruction *Inst) { auto AssumedBefore = getAssumed(); SetTy Incoming; bool ContainsUndef; if (!fillSetWithConstantValues(A, IRPosition::value(*Inst), Incoming, ContainsUndef, /* ForSelf */ true)) return indicatePessimisticFixpoint(); if (ContainsUndef) { unionAssumedWithUndef(); } else { for (const auto &It : Incoming) unionAssumed(It); } return AssumedBefore == getAssumed() ? ChangeStatus::UNCHANGED : ChangeStatus::CHANGED; } /// See AbstractAttribute::updateImpl(...). ChangeStatus updateImpl(Attributor &A) override { Value &V = getAssociatedValue(); Instruction *I = dyn_cast(&V); if (auto *ICI = dyn_cast(I)) return updateWithICmpInst(A, ICI); if (auto *SI = dyn_cast(I)) return updateWithSelectInst(A, SI); if (auto *CI = dyn_cast(I)) return updateWithCastInst(A, CI); if (auto *BinOp = dyn_cast(I)) return updateWithBinaryOperator(A, BinOp); if (isa(I) || isa(I)) return updateWithInstruction(A, I); return indicatePessimisticFixpoint(); } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_FLOATING_ATTR(potential_values) } }; struct AAPotentialConstantValuesFunction : AAPotentialConstantValuesImpl { AAPotentialConstantValuesFunction(const IRPosition &IRP, Attributor &A) : AAPotentialConstantValuesImpl(IRP, A) {} /// See AbstractAttribute::initialize(...). ChangeStatus updateImpl(Attributor &A) override { llvm_unreachable( "AAPotentialConstantValues(Function|CallSite)::updateImpl will " "not be called"); } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_FN_ATTR(potential_values) } }; struct AAPotentialConstantValuesCallSite : AAPotentialConstantValuesFunction { AAPotentialConstantValuesCallSite(const IRPosition &IRP, Attributor &A) : AAPotentialConstantValuesFunction(IRP, A) {} /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_CS_ATTR(potential_values) } }; struct AAPotentialConstantValuesCallSiteReturned : AACalleeToCallSite { AAPotentialConstantValuesCallSiteReturned(const IRPosition &IRP, Attributor &A) : AACalleeToCallSite(IRP, A) {} /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_CSRET_ATTR(potential_values) } }; struct AAPotentialConstantValuesCallSiteArgument : AAPotentialConstantValuesFloating { AAPotentialConstantValuesCallSiteArgument(const IRPosition &IRP, Attributor &A) : AAPotentialConstantValuesFloating(IRP, A) {} /// See AbstractAttribute::initialize(..). void initialize(Attributor &A) override { AAPotentialConstantValuesImpl::initialize(A); if (isAtFixpoint()) return; Value &V = getAssociatedValue(); if (auto *C = dyn_cast(&V)) { unionAssumed(C->getValue()); indicateOptimisticFixpoint(); return; } if (isa(&V)) { unionAssumedWithUndef(); indicateOptimisticFixpoint(); return; } } /// See AbstractAttribute::updateImpl(...). ChangeStatus updateImpl(Attributor &A) override { Value &V = getAssociatedValue(); auto AssumedBefore = getAssumed(); auto *AA = A.getAAFor( *this, IRPosition::value(V), DepClassTy::REQUIRED); if (!AA) return indicatePessimisticFixpoint(); const auto &S = AA->getAssumed(); unionAssumed(S); return AssumedBefore == getAssumed() ? ChangeStatus::UNCHANGED : ChangeStatus::CHANGED; } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_CSARG_ATTR(potential_values) } }; } // namespace /// ------------------------ NoUndef Attribute --------------------------------- bool AANoUndef::isImpliedByIR(Attributor &A, const IRPosition &IRP, Attribute::AttrKind ImpliedAttributeKind, bool IgnoreSubsumingPositions) { assert(ImpliedAttributeKind == Attribute::NoUndef && "Unexpected attribute kind"); if (A.hasAttr(IRP, {Attribute::NoUndef}, IgnoreSubsumingPositions, Attribute::NoUndef)) return true; Value &Val = IRP.getAssociatedValue(); if (IRP.getPositionKind() != IRPosition::IRP_RETURNED && isGuaranteedNotToBeUndefOrPoison(&Val)) { LLVMContext &Ctx = Val.getContext(); A.manifestAttrs(IRP, Attribute::get(Ctx, Attribute::NoUndef)); return true; } return false; } namespace { struct AANoUndefImpl : AANoUndef { AANoUndefImpl(const IRPosition &IRP, Attributor &A) : AANoUndef(IRP, A) {} /// See AbstractAttribute::initialize(...). void initialize(Attributor &A) override { Value &V = getAssociatedValue(); if (isa(V)) indicatePessimisticFixpoint(); assert(!isImpliedByIR(A, getIRPosition(), Attribute::NoUndef)); } /// See followUsesInMBEC bool followUseInMBEC(Attributor &A, const Use *U, const Instruction *I, AANoUndef::StateType &State) { const Value *UseV = U->get(); const DominatorTree *DT = nullptr; AssumptionCache *AC = nullptr; InformationCache &InfoCache = A.getInfoCache(); if (Function *F = getAnchorScope()) { DT = InfoCache.getAnalysisResultForFunction(*F); AC = InfoCache.getAnalysisResultForFunction(*F); } State.setKnown(isGuaranteedNotToBeUndefOrPoison(UseV, AC, I, DT)); bool TrackUse = false; // Track use for instructions which must produce undef or poison bits when // at least one operand contains such bits. if (isa(*I) || isa(*I)) TrackUse = true; return TrackUse; } /// See AbstractAttribute::getAsStr(). const std::string getAsStr(Attributor *A) const override { return getAssumed() ? "noundef" : "may-undef-or-poison"; } ChangeStatus manifest(Attributor &A) override { // We don't manifest noundef attribute for dead positions because the // associated values with dead positions would be replaced with undef // values. bool UsedAssumedInformation = false; if (A.isAssumedDead(getIRPosition(), nullptr, nullptr, UsedAssumedInformation)) return ChangeStatus::UNCHANGED; // A position whose simplified value does not have any value is // considered to be dead. We don't manifest noundef in such positions for // the same reason above. if (!A.getAssumedSimplified(getIRPosition(), *this, UsedAssumedInformation, AA::Interprocedural) .has_value()) return ChangeStatus::UNCHANGED; return AANoUndef::manifest(A); } }; struct AANoUndefFloating : public AANoUndefImpl { AANoUndefFloating(const IRPosition &IRP, Attributor &A) : AANoUndefImpl(IRP, A) {} /// See AbstractAttribute::initialize(...). void initialize(Attributor &A) override { AANoUndefImpl::initialize(A); if (!getState().isAtFixpoint() && getAnchorScope() && !getAnchorScope()->isDeclaration()) if (Instruction *CtxI = getCtxI()) followUsesInMBEC(*this, A, getState(), *CtxI); } /// See AbstractAttribute::updateImpl(...). ChangeStatus updateImpl(Attributor &A) override { auto VisitValueCB = [&](const IRPosition &IRP) -> bool { bool IsKnownNoUndef; return AA::hasAssumedIRAttr( A, this, IRP, DepClassTy::REQUIRED, IsKnownNoUndef); }; bool Stripped; bool UsedAssumedInformation = false; Value *AssociatedValue = &getAssociatedValue(); SmallVector Values; if (!A.getAssumedSimplifiedValues(getIRPosition(), *this, Values, AA::AnyScope, UsedAssumedInformation)) Stripped = false; else Stripped = Values.size() != 1 || Values.front().getValue() != AssociatedValue; if (!Stripped) { // If we haven't stripped anything we might still be able to use a // different AA, but only if the IRP changes. Effectively when we // interpret this not as a call site value but as a floating/argument // value. const IRPosition AVIRP = IRPosition::value(*AssociatedValue); if (AVIRP == getIRPosition() || !VisitValueCB(AVIRP)) return indicatePessimisticFixpoint(); return ChangeStatus::UNCHANGED; } for (const auto &VAC : Values) if (!VisitValueCB(IRPosition::value(*VAC.getValue()))) return indicatePessimisticFixpoint(); return ChangeStatus::UNCHANGED; } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_FNRET_ATTR(noundef) } }; struct AANoUndefReturned final : AAReturnedFromReturnedValues { AANoUndefReturned(const IRPosition &IRP, Attributor &A) : AAReturnedFromReturnedValues(IRP, A) {} /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_FNRET_ATTR(noundef) } }; struct AANoUndefArgument final : AAArgumentFromCallSiteArguments { AANoUndefArgument(const IRPosition &IRP, Attributor &A) : AAArgumentFromCallSiteArguments(IRP, A) {} /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_ARG_ATTR(noundef) } }; struct AANoUndefCallSiteArgument final : AANoUndefFloating { AANoUndefCallSiteArgument(const IRPosition &IRP, Attributor &A) : AANoUndefFloating(IRP, A) {} /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_CSARG_ATTR(noundef) } }; struct AANoUndefCallSiteReturned final : AACalleeToCallSite { AANoUndefCallSiteReturned(const IRPosition &IRP, Attributor &A) : AACalleeToCallSite(IRP, A) {} /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_CSRET_ATTR(noundef) } }; /// ------------------------ NoFPClass Attribute ------------------------------- struct AANoFPClassImpl : AANoFPClass { AANoFPClassImpl(const IRPosition &IRP, Attributor &A) : AANoFPClass(IRP, A) {} void initialize(Attributor &A) override { const IRPosition &IRP = getIRPosition(); Value &V = IRP.getAssociatedValue(); if (isa(V)) { indicateOptimisticFixpoint(); return; } SmallVector Attrs; A.getAttrs(getIRPosition(), {Attribute::NoFPClass}, Attrs, false); for (const auto &Attr : Attrs) { addKnownBits(Attr.getNoFPClass()); } const DataLayout &DL = A.getDataLayout(); if (getPositionKind() != IRPosition::IRP_RETURNED) { KnownFPClass KnownFPClass = computeKnownFPClass(&V, DL); addKnownBits(~KnownFPClass.KnownFPClasses); } if (Instruction *CtxI = getCtxI()) followUsesInMBEC(*this, A, getState(), *CtxI); } /// See followUsesInMBEC bool followUseInMBEC(Attributor &A, const Use *U, const Instruction *I, AANoFPClass::StateType &State) { // TODO: Determine what instructions can be looked through. auto *CB = dyn_cast(I); if (!CB) return false; if (!CB->isArgOperand(U)) return false; unsigned ArgNo = CB->getArgOperandNo(U); IRPosition IRP = IRPosition::callsite_argument(*CB, ArgNo); if (auto *NoFPAA = A.getAAFor(*this, IRP, DepClassTy::NONE)) State.addKnownBits(NoFPAA->getState().getKnown()); return false; } const std::string getAsStr(Attributor *A) const override { std::string Result = "nofpclass"; raw_string_ostream OS(Result); OS << getKnownNoFPClass() << '/' << getAssumedNoFPClass(); return Result; } void getDeducedAttributes(Attributor &A, LLVMContext &Ctx, SmallVectorImpl &Attrs) const override { Attrs.emplace_back(Attribute::getWithNoFPClass(Ctx, getAssumedNoFPClass())); } }; struct AANoFPClassFloating : public AANoFPClassImpl { AANoFPClassFloating(const IRPosition &IRP, Attributor &A) : AANoFPClassImpl(IRP, A) {} /// See AbstractAttribute::updateImpl(...). ChangeStatus updateImpl(Attributor &A) override { SmallVector Values; bool UsedAssumedInformation = false; if (!A.getAssumedSimplifiedValues(getIRPosition(), *this, Values, AA::AnyScope, UsedAssumedInformation)) { Values.push_back({getAssociatedValue(), getCtxI()}); } StateType T; auto VisitValueCB = [&](Value &V, const Instruction *CtxI) -> bool { const auto *AA = A.getAAFor(*this, IRPosition::value(V), DepClassTy::REQUIRED); if (!AA || this == AA) { T.indicatePessimisticFixpoint(); } else { const AANoFPClass::StateType &S = static_cast(AA->getState()); T ^= S; } return T.isValidState(); }; for (const auto &VAC : Values) if (!VisitValueCB(*VAC.getValue(), VAC.getCtxI())) return indicatePessimisticFixpoint(); return clampStateAndIndicateChange(getState(), T); } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_FNRET_ATTR(nofpclass) } }; struct AANoFPClassReturned final : AAReturnedFromReturnedValues { AANoFPClassReturned(const IRPosition &IRP, Attributor &A) : AAReturnedFromReturnedValues(IRP, A) {} /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_FNRET_ATTR(nofpclass) } }; struct AANoFPClassArgument final : AAArgumentFromCallSiteArguments { AANoFPClassArgument(const IRPosition &IRP, Attributor &A) : AAArgumentFromCallSiteArguments(IRP, A) {} /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_ARG_ATTR(nofpclass) } }; struct AANoFPClassCallSiteArgument final : AANoFPClassFloating { AANoFPClassCallSiteArgument(const IRPosition &IRP, Attributor &A) : AANoFPClassFloating(IRP, A) {} /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_CSARG_ATTR(nofpclass) } }; struct AANoFPClassCallSiteReturned final : AACalleeToCallSite { AANoFPClassCallSiteReturned(const IRPosition &IRP, Attributor &A) : AACalleeToCallSite(IRP, A) {} /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_CSRET_ATTR(nofpclass) } }; struct AACallEdgesImpl : public AACallEdges { AACallEdgesImpl(const IRPosition &IRP, Attributor &A) : AACallEdges(IRP, A) {} const SetVector &getOptimisticEdges() const override { return CalledFunctions; } bool hasUnknownCallee() const override { return HasUnknownCallee; } bool hasNonAsmUnknownCallee() const override { return HasUnknownCalleeNonAsm; } const std::string getAsStr(Attributor *A) const override { return "CallEdges[" + std::to_string(HasUnknownCallee) + "," + std::to_string(CalledFunctions.size()) + "]"; } void trackStatistics() const override {} protected: void addCalledFunction(Function *Fn, ChangeStatus &Change) { if (CalledFunctions.insert(Fn)) { Change = ChangeStatus::CHANGED; LLVM_DEBUG(dbgs() << "[AACallEdges] New call edge: " << Fn->getName() << "\n"); } } void setHasUnknownCallee(bool NonAsm, ChangeStatus &Change) { if (!HasUnknownCallee) Change = ChangeStatus::CHANGED; if (NonAsm && !HasUnknownCalleeNonAsm) Change = ChangeStatus::CHANGED; HasUnknownCalleeNonAsm |= NonAsm; HasUnknownCallee = true; } private: /// Optimistic set of functions that might be called by this position. SetVector CalledFunctions; /// Is there any call with a unknown callee. bool HasUnknownCallee = false; /// Is there any call with a unknown callee, excluding any inline asm. bool HasUnknownCalleeNonAsm = false; }; struct AACallEdgesCallSite : public AACallEdgesImpl { AACallEdgesCallSite(const IRPosition &IRP, Attributor &A) : AACallEdgesImpl(IRP, A) {} /// See AbstractAttribute::updateImpl(...). ChangeStatus updateImpl(Attributor &A) override { ChangeStatus Change = ChangeStatus::UNCHANGED; auto VisitValue = [&](Value &V, const Instruction *CtxI) -> bool { if (Function *Fn = dyn_cast(&V)) { addCalledFunction(Fn, Change); } else { LLVM_DEBUG(dbgs() << "[AACallEdges] Unrecognized value: " << V << "\n"); setHasUnknownCallee(true, Change); } // Explore all values. return true; }; SmallVector Values; // Process any value that we might call. auto ProcessCalledOperand = [&](Value *V, Instruction *CtxI) { if (isa(V)) { VisitValue(*V, CtxI); return; } bool UsedAssumedInformation = false; Values.clear(); if (!A.getAssumedSimplifiedValues(IRPosition::value(*V), *this, Values, AA::AnyScope, UsedAssumedInformation)) { Values.push_back({*V, CtxI}); } for (auto &VAC : Values) VisitValue(*VAC.getValue(), VAC.getCtxI()); }; CallBase *CB = cast(getCtxI()); if (auto *IA = dyn_cast(CB->getCalledOperand())) { if (IA->hasSideEffects() && !hasAssumption(*CB->getCaller(), "ompx_no_call_asm") && !hasAssumption(*CB, "ompx_no_call_asm")) { setHasUnknownCallee(false, Change); } return Change; } if (CB->isIndirectCall()) if (auto *IndirectCallAA = A.getAAFor( *this, getIRPosition(), DepClassTy::OPTIONAL)) if (IndirectCallAA->foreachCallee( [&](Function *Fn) { return VisitValue(*Fn, CB); })) return Change; // The most simple case. ProcessCalledOperand(CB->getCalledOperand(), CB); // Process callback functions. SmallVector CallbackUses; AbstractCallSite::getCallbackUses(*CB, CallbackUses); for (const Use *U : CallbackUses) ProcessCalledOperand(U->get(), CB); return Change; } }; struct AACallEdgesFunction : public AACallEdgesImpl { AACallEdgesFunction(const IRPosition &IRP, Attributor &A) : AACallEdgesImpl(IRP, A) {} /// See AbstractAttribute::updateImpl(...). ChangeStatus updateImpl(Attributor &A) override { ChangeStatus Change = ChangeStatus::UNCHANGED; auto ProcessCallInst = [&](Instruction &Inst) { CallBase &CB = cast(Inst); auto *CBEdges = A.getAAFor( *this, IRPosition::callsite_function(CB), DepClassTy::REQUIRED); if (!CBEdges) return false; if (CBEdges->hasNonAsmUnknownCallee()) setHasUnknownCallee(true, Change); if (CBEdges->hasUnknownCallee()) setHasUnknownCallee(false, Change); for (Function *F : CBEdges->getOptimisticEdges()) addCalledFunction(F, Change); return true; }; // Visit all callable instructions. bool UsedAssumedInformation = false; if (!A.checkForAllCallLikeInstructions(ProcessCallInst, *this, UsedAssumedInformation, /* CheckBBLivenessOnly */ true)) { // If we haven't looked at all call like instructions, assume that there // are unknown callees. setHasUnknownCallee(true, Change); } return Change; } }; /// -------------------AAInterFnReachability Attribute-------------------------- struct AAInterFnReachabilityFunction : public CachedReachabilityAA { using Base = CachedReachabilityAA; AAInterFnReachabilityFunction(const IRPosition &IRP, Attributor &A) : Base(IRP, A) {} bool instructionCanReach( Attributor &A, const Instruction &From, const Function &To, const AA::InstExclusionSetTy *ExclusionSet) const override { assert(From.getFunction() == getAnchorScope() && "Queried the wrong AA!"); auto *NonConstThis = const_cast(this); RQITy StackRQI(A, From, To, ExclusionSet, false); typename RQITy::Reachable Result; if (!NonConstThis->checkQueryCache(A, StackRQI, Result)) return NonConstThis->isReachableImpl(A, StackRQI, /*IsTemporaryRQI=*/true); return Result == RQITy::Reachable::Yes; } bool isReachableImpl(Attributor &A, RQITy &RQI, bool IsTemporaryRQI) override { const Instruction *EntryI = &RQI.From->getFunction()->getEntryBlock().front(); if (EntryI != RQI.From && !instructionCanReach(A, *EntryI, *RQI.To, nullptr)) return rememberResult(A, RQITy::Reachable::No, RQI, false, IsTemporaryRQI); auto CheckReachableCallBase = [&](CallBase *CB) { auto *CBEdges = A.getAAFor( *this, IRPosition::callsite_function(*CB), DepClassTy::OPTIONAL); if (!CBEdges || !CBEdges->getState().isValidState()) return false; // TODO Check To backwards in this case. if (CBEdges->hasUnknownCallee()) return false; for (Function *Fn : CBEdges->getOptimisticEdges()) { if (Fn == RQI.To) return false; if (Fn->isDeclaration()) { if (Fn->hasFnAttribute(Attribute::NoCallback)) continue; // TODO Check To backwards in this case. return false; } if (Fn == getAnchorScope()) { if (EntryI == RQI.From) continue; return false; } const AAInterFnReachability *InterFnReachability = A.getAAFor(*this, IRPosition::function(*Fn), DepClassTy::OPTIONAL); const Instruction &FnFirstInst = Fn->getEntryBlock().front(); if (!InterFnReachability || InterFnReachability->instructionCanReach(A, FnFirstInst, *RQI.To, RQI.ExclusionSet)) return false; } return true; }; const auto *IntraFnReachability = A.getAAFor( *this, IRPosition::function(*RQI.From->getFunction()), DepClassTy::OPTIONAL); // Determine call like instructions that we can reach from the inst. auto CheckCallBase = [&](Instruction &CBInst) { // There are usually less nodes in the call graph, check inter function // reachability first. if (CheckReachableCallBase(cast(&CBInst))) return true; return IntraFnReachability && !IntraFnReachability->isAssumedReachable( A, *RQI.From, CBInst, RQI.ExclusionSet); }; bool UsedExclusionSet = /* conservative */ true; bool UsedAssumedInformation = false; if (!A.checkForAllCallLikeInstructions(CheckCallBase, *this, UsedAssumedInformation, /* CheckBBLivenessOnly */ true)) return rememberResult(A, RQITy::Reachable::Yes, RQI, UsedExclusionSet, IsTemporaryRQI); return rememberResult(A, RQITy::Reachable::No, RQI, UsedExclusionSet, IsTemporaryRQI); } void trackStatistics() const override {} }; } // namespace template static std::optional askForAssumedConstant(Attributor &A, const AbstractAttribute &QueryingAA, const IRPosition &IRP, Type &Ty) { if (!Ty.isIntegerTy()) return nullptr; // This will also pass the call base context. const auto *AA = A.getAAFor(QueryingAA, IRP, DepClassTy::NONE); if (!AA) return nullptr; std::optional COpt = AA->getAssumedConstant(A); if (!COpt.has_value()) { A.recordDependence(*AA, QueryingAA, DepClassTy::OPTIONAL); return std::nullopt; } if (auto *C = *COpt) { A.recordDependence(*AA, QueryingAA, DepClassTy::OPTIONAL); return C; } return nullptr; } Value *AAPotentialValues::getSingleValue( Attributor &A, const AbstractAttribute &AA, const IRPosition &IRP, SmallVectorImpl &Values) { Type &Ty = *IRP.getAssociatedType(); std::optional V; for (auto &It : Values) { V = AA::combineOptionalValuesInAAValueLatice(V, It.getValue(), &Ty); if (V.has_value() && !*V) break; } if (!V.has_value()) return UndefValue::get(&Ty); return *V; } namespace { struct AAPotentialValuesImpl : AAPotentialValues { using StateType = PotentialLLVMValuesState; AAPotentialValuesImpl(const IRPosition &IRP, Attributor &A) : AAPotentialValues(IRP, A) {} /// See AbstractAttribute::initialize(..). void initialize(Attributor &A) override { if (A.hasSimplificationCallback(getIRPosition())) { indicatePessimisticFixpoint(); return; } Value *Stripped = getAssociatedValue().stripPointerCasts(); if (isa(Stripped) && !isa(Stripped)) { addValue(A, getState(), *Stripped, getCtxI(), AA::AnyScope, getAnchorScope()); indicateOptimisticFixpoint(); return; } AAPotentialValues::initialize(A); } /// See AbstractAttribute::getAsStr(). const std::string getAsStr(Attributor *A) const override { std::string Str; llvm::raw_string_ostream OS(Str); OS << getState(); return Str; } template static std::optional askOtherAA(Attributor &A, const AbstractAttribute &AA, const IRPosition &IRP, Type &Ty) { if (isa(IRP.getAssociatedValue())) return &IRP.getAssociatedValue(); std::optional C = askForAssumedConstant(A, AA, IRP, Ty); if (!C) return std::nullopt; if (*C) if (auto *CC = AA::getWithType(**C, Ty)) return CC; return nullptr; } virtual void addValue(Attributor &A, StateType &State, Value &V, const Instruction *CtxI, AA::ValueScope S, Function *AnchorScope) const { IRPosition ValIRP = IRPosition::value(V); if (auto *CB = dyn_cast_or_null(CtxI)) { for (const auto &U : CB->args()) { if (U.get() != &V) continue; ValIRP = IRPosition::callsite_argument(*CB, CB->getArgOperandNo(&U)); break; } } Value *VPtr = &V; if (ValIRP.getAssociatedType()->isIntegerTy()) { Type &Ty = *getAssociatedType(); std::optional SimpleV = askOtherAA(A, *this, ValIRP, Ty); if (SimpleV.has_value() && !*SimpleV) { auto *PotentialConstantsAA = A.getAAFor( *this, ValIRP, DepClassTy::OPTIONAL); if (PotentialConstantsAA && PotentialConstantsAA->isValidState()) { for (const auto &It : PotentialConstantsAA->getAssumedSet()) State.unionAssumed({{*ConstantInt::get(&Ty, It), nullptr}, S}); if (PotentialConstantsAA->undefIsContained()) State.unionAssumed({{*UndefValue::get(&Ty), nullptr}, S}); return; } } if (!SimpleV.has_value()) return; if (*SimpleV) VPtr = *SimpleV; } if (isa(VPtr)) CtxI = nullptr; if (!AA::isValidInScope(*VPtr, AnchorScope)) S = AA::ValueScope(S | AA::Interprocedural); State.unionAssumed({{*VPtr, CtxI}, S}); } /// Helper struct to tie a value+context pair together with the scope for /// which this is the simplified version. struct ItemInfo { AA::ValueAndContext I; AA::ValueScope S; bool operator==(const ItemInfo &II) const { return II.I == I && II.S == S; }; bool operator<(const ItemInfo &II) const { if (I == II.I) return S < II.S; return I < II.I; }; }; bool recurseForValue(Attributor &A, const IRPosition &IRP, AA::ValueScope S) { SmallMapVector ValueScopeMap; for (auto CS : {AA::Intraprocedural, AA::Interprocedural}) { if (!(CS & S)) continue; bool UsedAssumedInformation = false; SmallVector Values; if (!A.getAssumedSimplifiedValues(IRP, this, Values, CS, UsedAssumedInformation)) return false; for (auto &It : Values) ValueScopeMap[It] += CS; } for (auto &It : ValueScopeMap) addValue(A, getState(), *It.first.getValue(), It.first.getCtxI(), AA::ValueScope(It.second), getAnchorScope()); return true; } void giveUpOnIntraprocedural(Attributor &A) { auto NewS = StateType::getBestState(getState()); for (const auto &It : getAssumedSet()) { if (It.second == AA::Intraprocedural) continue; addValue(A, NewS, *It.first.getValue(), It.first.getCtxI(), AA::Interprocedural, getAnchorScope()); } assert(!undefIsContained() && "Undef should be an explicit value!"); addValue(A, NewS, getAssociatedValue(), getCtxI(), AA::Intraprocedural, getAnchorScope()); getState() = NewS; } /// See AbstractState::indicatePessimisticFixpoint(...). ChangeStatus indicatePessimisticFixpoint() override { getState() = StateType::getBestState(getState()); getState().unionAssumed({{getAssociatedValue(), getCtxI()}, AA::AnyScope}); AAPotentialValues::indicateOptimisticFixpoint(); return ChangeStatus::CHANGED; } /// See AbstractAttribute::updateImpl(...). ChangeStatus updateImpl(Attributor &A) override { return indicatePessimisticFixpoint(); } /// See AbstractAttribute::manifest(...). ChangeStatus manifest(Attributor &A) override { SmallVector Values; for (AA::ValueScope S : {AA::Interprocedural, AA::Intraprocedural}) { Values.clear(); if (!getAssumedSimplifiedValues(A, Values, S)) continue; Value &OldV = getAssociatedValue(); if (isa(OldV)) continue; Value *NewV = getSingleValue(A, *this, getIRPosition(), Values); if (!NewV || NewV == &OldV) continue; if (getCtxI() && !AA::isValidAtPosition({*NewV, *getCtxI()}, A.getInfoCache())) continue; if (A.changeAfterManifest(getIRPosition(), *NewV)) return ChangeStatus::CHANGED; } return ChangeStatus::UNCHANGED; } bool getAssumedSimplifiedValues( Attributor &A, SmallVectorImpl &Values, AA::ValueScope S, bool RecurseForSelectAndPHI = false) const override { if (!isValidState()) return false; bool UsedAssumedInformation = false; for (const auto &It : getAssumedSet()) if (It.second & S) { if (RecurseForSelectAndPHI && (isa(It.first.getValue()) || isa(It.first.getValue()))) { if (A.getAssumedSimplifiedValues( IRPosition::inst(*cast(It.first.getValue())), this, Values, S, UsedAssumedInformation)) continue; } Values.push_back(It.first); } assert(!undefIsContained() && "Undef should be an explicit value!"); return true; } }; struct AAPotentialValuesFloating : AAPotentialValuesImpl { AAPotentialValuesFloating(const IRPosition &IRP, Attributor &A) : AAPotentialValuesImpl(IRP, A) {} /// See AbstractAttribute::updateImpl(...). ChangeStatus updateImpl(Attributor &A) override { auto AssumedBefore = getAssumed(); genericValueTraversal(A, &getAssociatedValue()); return (AssumedBefore == getAssumed()) ? ChangeStatus::UNCHANGED : ChangeStatus::CHANGED; } /// Helper struct to remember which AAIsDead instances we actually used. struct LivenessInfo { const AAIsDead *LivenessAA = nullptr; bool AnyDead = false; }; /// Check if \p Cmp is a comparison we can simplify. /// /// We handle multiple cases, one in which at least one operand is an /// (assumed) nullptr. If so, try to simplify it using AANonNull on the other /// operand. Return true if successful, in that case Worklist will be updated. bool handleCmp(Attributor &A, Value &Cmp, Value *LHS, Value *RHS, CmpInst::Predicate Pred, ItemInfo II, SmallVectorImpl &Worklist) { // Simplify the operands first. bool UsedAssumedInformation = false; SmallVector LHSValues, RHSValues; auto GetSimplifiedValues = [&](Value &V, SmallVector &Values) { if (!A.getAssumedSimplifiedValues( IRPosition::value(V, getCallBaseContext()), this, Values, AA::Intraprocedural, UsedAssumedInformation)) { Values.clear(); Values.push_back(AA::ValueAndContext{V, II.I.getCtxI()}); } return Values.empty(); }; if (GetSimplifiedValues(*LHS, LHSValues)) return true; if (GetSimplifiedValues(*RHS, RHSValues)) return true; LLVMContext &Ctx = LHS->getContext(); InformationCache &InfoCache = A.getInfoCache(); Instruction *CmpI = dyn_cast(&Cmp); Function *F = CmpI ? CmpI->getFunction() : nullptr; const auto *DT = F ? InfoCache.getAnalysisResultForFunction(*F) : nullptr; const auto *TLI = F ? A.getInfoCache().getTargetLibraryInfoForFunction(*F) : nullptr; auto *AC = F ? InfoCache.getAnalysisResultForFunction(*F) : nullptr; const DataLayout &DL = A.getDataLayout(); SimplifyQuery Q(DL, TLI, DT, AC, CmpI); auto CheckPair = [&](Value &LHSV, Value &RHSV) { if (isa(LHSV) || isa(RHSV)) { addValue(A, getState(), *UndefValue::get(Cmp.getType()), /* CtxI */ nullptr, II.S, getAnchorScope()); return true; } // Handle the trivial case first in which we don't even need to think // about null or non-null. if (&LHSV == &RHSV && (CmpInst::isTrueWhenEqual(Pred) || CmpInst::isFalseWhenEqual(Pred))) { Constant *NewV = ConstantInt::get(Type::getInt1Ty(Ctx), CmpInst::isTrueWhenEqual(Pred)); addValue(A, getState(), *NewV, /* CtxI */ nullptr, II.S, getAnchorScope()); return true; } auto *TypedLHS = AA::getWithType(LHSV, *LHS->getType()); auto *TypedRHS = AA::getWithType(RHSV, *RHS->getType()); if (TypedLHS && TypedRHS) { Value *NewV = simplifyCmpInst(Pred, TypedLHS, TypedRHS, Q); if (NewV && NewV != &Cmp) { addValue(A, getState(), *NewV, /* CtxI */ nullptr, II.S, getAnchorScope()); return true; } } // From now on we only handle equalities (==, !=). if (!CmpInst::isEquality(Pred)) return false; bool LHSIsNull = isa(LHSV); bool RHSIsNull = isa(RHSV); if (!LHSIsNull && !RHSIsNull) return false; // Left is the nullptr ==/!= non-nullptr case. We'll use AANonNull on the // non-nullptr operand and if we assume it's non-null we can conclude the // result of the comparison. assert((LHSIsNull || RHSIsNull) && "Expected nullptr versus non-nullptr comparison at this point"); // The index is the operand that we assume is not null. unsigned PtrIdx = LHSIsNull; bool IsKnownNonNull; bool IsAssumedNonNull = AA::hasAssumedIRAttr( A, this, IRPosition::value(*(PtrIdx ? &RHSV : &LHSV)), DepClassTy::REQUIRED, IsKnownNonNull); if (!IsAssumedNonNull) return false; // The new value depends on the predicate, true for != and false for ==. Constant *NewV = ConstantInt::get(Type::getInt1Ty(Ctx), Pred == CmpInst::ICMP_NE); addValue(A, getState(), *NewV, /* CtxI */ nullptr, II.S, getAnchorScope()); return true; }; for (auto &LHSValue : LHSValues) for (auto &RHSValue : RHSValues) if (!CheckPair(*LHSValue.getValue(), *RHSValue.getValue())) return false; return true; } bool handleSelectInst(Attributor &A, SelectInst &SI, ItemInfo II, SmallVectorImpl &Worklist) { const Instruction *CtxI = II.I.getCtxI(); bool UsedAssumedInformation = false; std::optional C = A.getAssumedConstant(*SI.getCondition(), *this, UsedAssumedInformation); bool NoValueYet = !C.has_value(); if (NoValueYet || isa_and_nonnull(*C)) return true; if (auto *CI = dyn_cast_or_null(*C)) { if (CI->isZero()) Worklist.push_back({{*SI.getFalseValue(), CtxI}, II.S}); else Worklist.push_back({{*SI.getTrueValue(), CtxI}, II.S}); } else if (&SI == &getAssociatedValue()) { // We could not simplify the condition, assume both values. Worklist.push_back({{*SI.getTrueValue(), CtxI}, II.S}); Worklist.push_back({{*SI.getFalseValue(), CtxI}, II.S}); } else { std::optional SimpleV = A.getAssumedSimplified( IRPosition::inst(SI), *this, UsedAssumedInformation, II.S); if (!SimpleV.has_value()) return true; if (*SimpleV) { addValue(A, getState(), **SimpleV, CtxI, II.S, getAnchorScope()); return true; } return false; } return true; } bool handleLoadInst(Attributor &A, LoadInst &LI, ItemInfo II, SmallVectorImpl &Worklist) { SmallSetVector PotentialCopies; SmallSetVector PotentialValueOrigins; bool UsedAssumedInformation = false; if (!AA::getPotentiallyLoadedValues(A, LI, PotentialCopies, PotentialValueOrigins, *this, UsedAssumedInformation, /* OnlyExact */ true)) { LLVM_DEBUG(dbgs() << "[AAPotentialValues] Failed to get potentially " "loaded values for load instruction " << LI << "\n"); return false; } // Do not simplify loads that are only used in llvm.assume if we cannot also // remove all stores that may feed into the load. The reason is that the // assume is probably worth something as long as the stores are around. InformationCache &InfoCache = A.getInfoCache(); if (InfoCache.isOnlyUsedByAssume(LI)) { if (!llvm::all_of(PotentialValueOrigins, [&](Instruction *I) { if (!I || isa(I)) return true; if (auto *SI = dyn_cast(I)) return A.isAssumedDead(SI->getOperandUse(0), this, /* LivenessAA */ nullptr, UsedAssumedInformation, /* CheckBBLivenessOnly */ false); return A.isAssumedDead(*I, this, /* LivenessAA */ nullptr, UsedAssumedInformation, /* CheckBBLivenessOnly */ false); })) { LLVM_DEBUG(dbgs() << "[AAPotentialValues] Load is onl used by assumes " "and we cannot delete all the stores: " << LI << "\n"); return false; } } // Values have to be dynamically unique or we loose the fact that a // single llvm::Value might represent two runtime values (e.g., // stack locations in different recursive calls). const Instruction *CtxI = II.I.getCtxI(); bool ScopeIsLocal = (II.S & AA::Intraprocedural); bool AllLocal = ScopeIsLocal; bool DynamicallyUnique = llvm::all_of(PotentialCopies, [&](Value *PC) { AllLocal &= AA::isValidInScope(*PC, getAnchorScope()); return AA::isDynamicallyUnique(A, *this, *PC); }); if (!DynamicallyUnique) { LLVM_DEBUG(dbgs() << "[AAPotentialValues] Not all potentially loaded " "values are dynamically unique: " << LI << "\n"); return false; } for (auto *PotentialCopy : PotentialCopies) { if (AllLocal) { Worklist.push_back({{*PotentialCopy, CtxI}, II.S}); } else { Worklist.push_back({{*PotentialCopy, CtxI}, AA::Interprocedural}); } } if (!AllLocal && ScopeIsLocal) addValue(A, getState(), LI, CtxI, AA::Intraprocedural, getAnchorScope()); return true; } bool handlePHINode( Attributor &A, PHINode &PHI, ItemInfo II, SmallVectorImpl &Worklist, SmallMapVector &LivenessAAs) { auto GetLivenessInfo = [&](const Function &F) -> LivenessInfo & { LivenessInfo &LI = LivenessAAs[&F]; if (!LI.LivenessAA) LI.LivenessAA = A.getAAFor(*this, IRPosition::function(F), DepClassTy::NONE); return LI; }; if (&PHI == &getAssociatedValue()) { LivenessInfo &LI = GetLivenessInfo(*PHI.getFunction()); const auto *CI = A.getInfoCache().getAnalysisResultForFunction( *PHI.getFunction()); Cycle *C = nullptr; bool CyclePHI = mayBeInCycle(CI, &PHI, /* HeaderOnly */ true, &C); for (unsigned u = 0, e = PHI.getNumIncomingValues(); u < e; u++) { BasicBlock *IncomingBB = PHI.getIncomingBlock(u); if (LI.LivenessAA && LI.LivenessAA->isEdgeDead(IncomingBB, PHI.getParent())) { LI.AnyDead = true; continue; } Value *V = PHI.getIncomingValue(u); if (V == &PHI) continue; // If the incoming value is not the PHI but an instruction in the same // cycle we might have multiple versions of it flying around. if (CyclePHI && isa(V) && (!C || C->contains(cast(V)->getParent()))) return false; Worklist.push_back({{*V, IncomingBB->getTerminator()}, II.S}); } return true; } bool UsedAssumedInformation = false; std::optional SimpleV = A.getAssumedSimplified( IRPosition::inst(PHI), *this, UsedAssumedInformation, II.S); if (!SimpleV.has_value()) return true; if (!(*SimpleV)) return false; addValue(A, getState(), **SimpleV, &PHI, II.S, getAnchorScope()); return true; } /// Use the generic, non-optimistic InstSimplfy functionality if we managed to /// simplify any operand of the instruction \p I. Return true if successful, /// in that case Worklist will be updated. bool handleGenericInst(Attributor &A, Instruction &I, ItemInfo II, SmallVectorImpl &Worklist) { bool SomeSimplified = false; bool UsedAssumedInformation = false; SmallVector NewOps(I.getNumOperands()); int Idx = 0; for (Value *Op : I.operands()) { const auto &SimplifiedOp = A.getAssumedSimplified( IRPosition::value(*Op, getCallBaseContext()), *this, UsedAssumedInformation, AA::Intraprocedural); // If we are not sure about any operand we are not sure about the entire // instruction, we'll wait. if (!SimplifiedOp.has_value()) return true; if (*SimplifiedOp) NewOps[Idx] = *SimplifiedOp; else NewOps[Idx] = Op; SomeSimplified |= (NewOps[Idx] != Op); ++Idx; } // We won't bother with the InstSimplify interface if we didn't simplify any // operand ourselves. if (!SomeSimplified) return false; InformationCache &InfoCache = A.getInfoCache(); Function *F = I.getFunction(); const auto *DT = InfoCache.getAnalysisResultForFunction(*F); const auto *TLI = A.getInfoCache().getTargetLibraryInfoForFunction(*F); auto *AC = InfoCache.getAnalysisResultForFunction(*F); const DataLayout &DL = I.getDataLayout(); SimplifyQuery Q(DL, TLI, DT, AC, &I); Value *NewV = simplifyInstructionWithOperands(&I, NewOps, Q); if (!NewV || NewV == &I) return false; LLVM_DEBUG(dbgs() << "Generic inst " << I << " assumed simplified to " << *NewV << "\n"); Worklist.push_back({{*NewV, II.I.getCtxI()}, II.S}); return true; } bool simplifyInstruction( Attributor &A, Instruction &I, ItemInfo II, SmallVectorImpl &Worklist, SmallMapVector &LivenessAAs) { if (auto *CI = dyn_cast(&I)) return handleCmp(A, *CI, CI->getOperand(0), CI->getOperand(1), CI->getPredicate(), II, Worklist); switch (I.getOpcode()) { case Instruction::Select: return handleSelectInst(A, cast(I), II, Worklist); case Instruction::PHI: return handlePHINode(A, cast(I), II, Worklist, LivenessAAs); case Instruction::Load: return handleLoadInst(A, cast(I), II, Worklist); default: return handleGenericInst(A, I, II, Worklist); }; return false; } void genericValueTraversal(Attributor &A, Value *InitialV) { SmallMapVector LivenessAAs; SmallSet Visited; SmallVector Worklist; Worklist.push_back({{*InitialV, getCtxI()}, AA::AnyScope}); int Iteration = 0; do { ItemInfo II = Worklist.pop_back_val(); Value *V = II.I.getValue(); assert(V); const Instruction *CtxI = II.I.getCtxI(); AA::ValueScope S = II.S; // Check if we should process the current value. To prevent endless // recursion keep a record of the values we followed! if (!Visited.insert(II).second) continue; // Make sure we limit the compile time for complex expressions. if (Iteration++ >= MaxPotentialValuesIterations) { LLVM_DEBUG(dbgs() << "Generic value traversal reached iteration limit: " << Iteration << "!\n"); addValue(A, getState(), *V, CtxI, S, getAnchorScope()); continue; } // Explicitly look through calls with a "returned" attribute if we do // not have a pointer as stripPointerCasts only works on them. Value *NewV = nullptr; if (V->getType()->isPointerTy()) { NewV = AA::getWithType(*V->stripPointerCasts(), *V->getType()); } else { if (auto *CB = dyn_cast(V)) if (auto *Callee = dyn_cast_if_present(CB->getCalledOperand())) { for (Argument &Arg : Callee->args()) if (Arg.hasReturnedAttr()) { NewV = CB->getArgOperand(Arg.getArgNo()); break; } } } if (NewV && NewV != V) { Worklist.push_back({{*NewV, CtxI}, S}); continue; } if (auto *I = dyn_cast(V)) { if (simplifyInstruction(A, *I, II, Worklist, LivenessAAs)) continue; } if (V != InitialV || isa(V)) if (recurseForValue(A, IRPosition::value(*V), II.S)) continue; // If we haven't stripped anything we give up. if (V == InitialV && CtxI == getCtxI()) { indicatePessimisticFixpoint(); return; } addValue(A, getState(), *V, CtxI, S, getAnchorScope()); } while (!Worklist.empty()); // If we actually used liveness information so we have to record a // dependence. for (auto &It : LivenessAAs) if (It.second.AnyDead) A.recordDependence(*It.second.LivenessAA, *this, DepClassTy::OPTIONAL); } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_FLOATING_ATTR(potential_values) } }; struct AAPotentialValuesArgument final : AAPotentialValuesImpl { using Base = AAPotentialValuesImpl; AAPotentialValuesArgument(const IRPosition &IRP, Attributor &A) : Base(IRP, A) {} /// See AbstractAttribute::initialize(..). void initialize(Attributor &A) override { auto &Arg = cast(getAssociatedValue()); if (Arg.hasPointeeInMemoryValueAttr()) indicatePessimisticFixpoint(); } /// See AbstractAttribute::updateImpl(...). ChangeStatus updateImpl(Attributor &A) override { auto AssumedBefore = getAssumed(); unsigned ArgNo = getCalleeArgNo(); bool UsedAssumedInformation = false; SmallVector Values; auto CallSitePred = [&](AbstractCallSite ACS) { const auto CSArgIRP = IRPosition::callsite_argument(ACS, ArgNo); if (CSArgIRP.getPositionKind() == IRP_INVALID) return false; if (!A.getAssumedSimplifiedValues(CSArgIRP, this, Values, AA::Interprocedural, UsedAssumedInformation)) return false; return isValidState(); }; if (!A.checkForAllCallSites(CallSitePred, *this, /* RequireAllCallSites */ true, UsedAssumedInformation)) return indicatePessimisticFixpoint(); Function *Fn = getAssociatedFunction(); bool AnyNonLocal = false; for (auto &It : Values) { if (isa(It.getValue())) { addValue(A, getState(), *It.getValue(), It.getCtxI(), AA::AnyScope, getAnchorScope()); continue; } if (!AA::isDynamicallyUnique(A, *this, *It.getValue())) return indicatePessimisticFixpoint(); if (auto *Arg = dyn_cast(It.getValue())) if (Arg->getParent() == Fn) { addValue(A, getState(), *It.getValue(), It.getCtxI(), AA::AnyScope, getAnchorScope()); continue; } addValue(A, getState(), *It.getValue(), It.getCtxI(), AA::Interprocedural, getAnchorScope()); AnyNonLocal = true; } assert(!undefIsContained() && "Undef should be an explicit value!"); if (AnyNonLocal) giveUpOnIntraprocedural(A); return (AssumedBefore == getAssumed()) ? ChangeStatus::UNCHANGED : ChangeStatus::CHANGED; } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_ARG_ATTR(potential_values) } }; struct AAPotentialValuesReturned : public AAPotentialValuesFloating { using Base = AAPotentialValuesFloating; AAPotentialValuesReturned(const IRPosition &IRP, Attributor &A) : Base(IRP, A) {} /// See AbstractAttribute::initialize(..). void initialize(Attributor &A) override { Function *F = getAssociatedFunction(); if (!F || F->isDeclaration() || F->getReturnType()->isVoidTy()) { indicatePessimisticFixpoint(); return; } for (Argument &Arg : F->args()) if (Arg.hasReturnedAttr()) { addValue(A, getState(), Arg, nullptr, AA::AnyScope, F); ReturnedArg = &Arg; break; } if (!A.isFunctionIPOAmendable(*F) || A.hasSimplificationCallback(getIRPosition())) { if (!ReturnedArg) indicatePessimisticFixpoint(); else indicateOptimisticFixpoint(); } } /// See AbstractAttribute::updateImpl(...). ChangeStatus updateImpl(Attributor &A) override { auto AssumedBefore = getAssumed(); bool UsedAssumedInformation = false; SmallVector Values; Function *AnchorScope = getAnchorScope(); auto HandleReturnedValue = [&](Value &V, Instruction *CtxI, bool AddValues) { for (AA::ValueScope S : {AA::Interprocedural, AA::Intraprocedural}) { Values.clear(); if (!A.getAssumedSimplifiedValues(IRPosition::value(V), this, Values, S, UsedAssumedInformation, /* RecurseForSelectAndPHI */ true)) return false; if (!AddValues) continue; for (const AA::ValueAndContext &VAC : Values) addValue(A, getState(), *VAC.getValue(), VAC.getCtxI() ? VAC.getCtxI() : CtxI, S, AnchorScope); } return true; }; if (ReturnedArg) { HandleReturnedValue(*ReturnedArg, nullptr, true); } else { auto RetInstPred = [&](Instruction &RetI) { bool AddValues = true; if (isa(RetI.getOperand(0)) || isa(RetI.getOperand(0))) { addValue(A, getState(), *RetI.getOperand(0), &RetI, AA::AnyScope, AnchorScope); AddValues = false; } return HandleReturnedValue(*RetI.getOperand(0), &RetI, AddValues); }; if (!A.checkForAllInstructions(RetInstPred, *this, {Instruction::Ret}, UsedAssumedInformation, /* CheckBBLivenessOnly */ true)) return indicatePessimisticFixpoint(); } return (AssumedBefore == getAssumed()) ? ChangeStatus::UNCHANGED : ChangeStatus::CHANGED; } void addValue(Attributor &A, StateType &State, Value &V, const Instruction *CtxI, AA::ValueScope S, Function *AnchorScope) const override { Function *F = getAssociatedFunction(); if (auto *CB = dyn_cast(&V)) if (CB->getCalledOperand() == F) return; Base::addValue(A, State, V, CtxI, S, AnchorScope); } ChangeStatus manifest(Attributor &A) override { if (ReturnedArg) return ChangeStatus::UNCHANGED; SmallVector Values; if (!getAssumedSimplifiedValues(A, Values, AA::ValueScope::Intraprocedural, /* RecurseForSelectAndPHI */ true)) return ChangeStatus::UNCHANGED; Value *NewVal = getSingleValue(A, *this, getIRPosition(), Values); if (!NewVal) return ChangeStatus::UNCHANGED; ChangeStatus Changed = ChangeStatus::UNCHANGED; if (auto *Arg = dyn_cast(NewVal)) { STATS_DECLTRACK(UniqueReturnValue, FunctionReturn, "Number of function with unique return"); Changed |= A.manifestAttrs( IRPosition::argument(*Arg), {Attribute::get(Arg->getContext(), Attribute::Returned)}); STATS_DECLTRACK_ARG_ATTR(returned); } auto RetInstPred = [&](Instruction &RetI) { Value *RetOp = RetI.getOperand(0); if (isa(RetOp) || RetOp == NewVal) return true; if (AA::isValidAtPosition({*NewVal, RetI}, A.getInfoCache())) if (A.changeUseAfterManifest(RetI.getOperandUse(0), *NewVal)) Changed = ChangeStatus::CHANGED; return true; }; bool UsedAssumedInformation = false; (void)A.checkForAllInstructions(RetInstPred, *this, {Instruction::Ret}, UsedAssumedInformation, /* CheckBBLivenessOnly */ true); return Changed; } ChangeStatus indicatePessimisticFixpoint() override { return AAPotentialValues::indicatePessimisticFixpoint(); } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override{ STATS_DECLTRACK_FNRET_ATTR(potential_values)} /// The argumented with an existing `returned` attribute. Argument *ReturnedArg = nullptr; }; struct AAPotentialValuesFunction : AAPotentialValuesImpl { AAPotentialValuesFunction(const IRPosition &IRP, Attributor &A) : AAPotentialValuesImpl(IRP, A) {} /// See AbstractAttribute::updateImpl(...). ChangeStatus updateImpl(Attributor &A) override { llvm_unreachable("AAPotentialValues(Function|CallSite)::updateImpl will " "not be called"); } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_FN_ATTR(potential_values) } }; struct AAPotentialValuesCallSite : AAPotentialValuesFunction { AAPotentialValuesCallSite(const IRPosition &IRP, Attributor &A) : AAPotentialValuesFunction(IRP, A) {} /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_CS_ATTR(potential_values) } }; struct AAPotentialValuesCallSiteReturned : AAPotentialValuesImpl { AAPotentialValuesCallSiteReturned(const IRPosition &IRP, Attributor &A) : AAPotentialValuesImpl(IRP, A) {} /// See AbstractAttribute::updateImpl(...). ChangeStatus updateImpl(Attributor &A) override { auto AssumedBefore = getAssumed(); Function *Callee = getAssociatedFunction(); if (!Callee) return indicatePessimisticFixpoint(); bool UsedAssumedInformation = false; auto *CB = cast(getCtxI()); if (CB->isMustTailCall() && !A.isAssumedDead(IRPosition::inst(*CB), this, nullptr, UsedAssumedInformation)) return indicatePessimisticFixpoint(); SmallVector Values; if (!A.getAssumedSimplifiedValues(IRPosition::returned(*Callee), this, Values, AA::Intraprocedural, UsedAssumedInformation)) return indicatePessimisticFixpoint(); Function *Caller = CB->getCaller(); bool AnyNonLocal = false; for (auto &It : Values) { Value *V = It.getValue(); std::optional CallerV = A.translateArgumentToCallSiteContent( V, *CB, *this, UsedAssumedInformation); if (!CallerV.has_value()) { // Nothing to do as long as no value was determined. continue; } V = *CallerV ? *CallerV : V; if (AA::isDynamicallyUnique(A, *this, *V) && AA::isValidInScope(*V, Caller)) { if (*CallerV) { SmallVector ArgValues; IRPosition IRP = IRPosition::value(*V); if (auto *Arg = dyn_cast(V)) if (Arg->getParent() == CB->getCalledOperand()) IRP = IRPosition::callsite_argument(*CB, Arg->getArgNo()); if (recurseForValue(A, IRP, AA::AnyScope)) continue; } addValue(A, getState(), *V, CB, AA::AnyScope, getAnchorScope()); } else { AnyNonLocal = true; break; } } if (AnyNonLocal) { Values.clear(); if (!A.getAssumedSimplifiedValues(IRPosition::returned(*Callee), this, Values, AA::Interprocedural, UsedAssumedInformation)) return indicatePessimisticFixpoint(); AnyNonLocal = false; getState() = PotentialLLVMValuesState::getBestState(); for (auto &It : Values) { Value *V = It.getValue(); if (!AA::isDynamicallyUnique(A, *this, *V)) return indicatePessimisticFixpoint(); if (AA::isValidInScope(*V, Caller)) { addValue(A, getState(), *V, CB, AA::AnyScope, getAnchorScope()); } else { AnyNonLocal = true; addValue(A, getState(), *V, CB, AA::Interprocedural, getAnchorScope()); } } if (AnyNonLocal) giveUpOnIntraprocedural(A); } return (AssumedBefore == getAssumed()) ? ChangeStatus::UNCHANGED : ChangeStatus::CHANGED; } ChangeStatus indicatePessimisticFixpoint() override { return AAPotentialValues::indicatePessimisticFixpoint(); } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_CSRET_ATTR(potential_values) } }; struct AAPotentialValuesCallSiteArgument : AAPotentialValuesFloating { AAPotentialValuesCallSiteArgument(const IRPosition &IRP, Attributor &A) : AAPotentialValuesFloating(IRP, A) {} /// See AbstractAttribute::trackStatistics() void trackStatistics() const override { STATS_DECLTRACK_CSARG_ATTR(potential_values) } }; } // namespace /// ---------------------- Assumption Propagation ------------------------------ namespace { struct AAAssumptionInfoImpl : public AAAssumptionInfo { AAAssumptionInfoImpl(const IRPosition &IRP, Attributor &A, const DenseSet &Known) : AAAssumptionInfo(IRP, A, Known) {} /// See AbstractAttribute::manifest(...). ChangeStatus manifest(Attributor &A) override { // Don't manifest a universal set if it somehow made it here. if (getKnown().isUniversal()) return ChangeStatus::UNCHANGED; const IRPosition &IRP = getIRPosition(); SmallVector Set(getAssumed().getSet().begin(), getAssumed().getSet().end()); llvm::sort(Set); return A.manifestAttrs(IRP, Attribute::get(IRP.getAnchorValue().getContext(), AssumptionAttrKey, llvm::join(Set, ",")), /*ForceReplace=*/true); } bool hasAssumption(const StringRef Assumption) const override { return isValidState() && setContains(Assumption); } /// See AbstractAttribute::getAsStr() const std::string getAsStr(Attributor *A) const override { const SetContents &Known = getKnown(); const SetContents &Assumed = getAssumed(); SmallVector Set(Known.getSet().begin(), Known.getSet().end()); llvm::sort(Set); const std::string KnownStr = llvm::join(Set, ","); std::string AssumedStr = "Universal"; if (!Assumed.isUniversal()) { Set.assign(Assumed.getSet().begin(), Assumed.getSet().end()); AssumedStr = llvm::join(Set, ","); } return "Known [" + KnownStr + "]," + " Assumed [" + AssumedStr + "]"; } }; /// Propagates assumption information from parent functions to all of their /// successors. An assumption can be propagated if the containing function /// dominates the called function. /// /// We start with a "known" set of assumptions already valid for the associated /// function and an "assumed" set that initially contains all possible /// assumptions. The assumed set is inter-procedurally updated by narrowing its /// contents as concrete values are known. The concrete values are seeded by the /// first nodes that are either entries into the call graph, or contains no /// assumptions. Each node is updated as the intersection of the assumed state /// with all of its predecessors. struct AAAssumptionInfoFunction final : AAAssumptionInfoImpl { AAAssumptionInfoFunction(const IRPosition &IRP, Attributor &A) : AAAssumptionInfoImpl(IRP, A, getAssumptions(*IRP.getAssociatedFunction())) {} /// See AbstractAttribute::updateImpl(...). ChangeStatus updateImpl(Attributor &A) override { bool Changed = false; auto CallSitePred = [&](AbstractCallSite ACS) { const auto *AssumptionAA = A.getAAFor( *this, IRPosition::callsite_function(*ACS.getInstruction()), DepClassTy::REQUIRED); if (!AssumptionAA) return false; // Get the set of assumptions shared by all of this function's callers. Changed |= getIntersection(AssumptionAA->getAssumed()); return !getAssumed().empty() || !getKnown().empty(); }; bool UsedAssumedInformation = false; // Get the intersection of all assumptions held by this node's predecessors. // If we don't know all the call sites then this is either an entry into the // call graph or an empty node. This node is known to only contain its own // assumptions and can be propagated to its successors. if (!A.checkForAllCallSites(CallSitePred, *this, true, UsedAssumedInformation)) return indicatePessimisticFixpoint(); return Changed ? ChangeStatus::CHANGED : ChangeStatus::UNCHANGED; } void trackStatistics() const override {} }; /// Assumption Info defined for call sites. struct AAAssumptionInfoCallSite final : AAAssumptionInfoImpl { AAAssumptionInfoCallSite(const IRPosition &IRP, Attributor &A) : AAAssumptionInfoImpl(IRP, A, getInitialAssumptions(IRP)) {} /// See AbstractAttribute::initialize(...). void initialize(Attributor &A) override { const IRPosition &FnPos = IRPosition::function(*getAnchorScope()); A.getAAFor(*this, FnPos, DepClassTy::REQUIRED); } /// See AbstractAttribute::updateImpl(...). ChangeStatus updateImpl(Attributor &A) override { const IRPosition &FnPos = IRPosition::function(*getAnchorScope()); auto *AssumptionAA = A.getAAFor(*this, FnPos, DepClassTy::REQUIRED); if (!AssumptionAA) return indicatePessimisticFixpoint(); bool Changed = getIntersection(AssumptionAA->getAssumed()); return Changed ? ChangeStatus::CHANGED : ChangeStatus::UNCHANGED; } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override {} private: /// Helper to initialized the known set as all the assumptions this call and /// the callee contain. DenseSet getInitialAssumptions(const IRPosition &IRP) { const CallBase &CB = cast(IRP.getAssociatedValue()); auto Assumptions = getAssumptions(CB); if (const Function *F = CB.getCaller()) set_union(Assumptions, getAssumptions(*F)); if (Function *F = IRP.getAssociatedFunction()) set_union(Assumptions, getAssumptions(*F)); return Assumptions; } }; } // namespace AACallGraphNode *AACallEdgeIterator::operator*() const { return static_cast(const_cast( A.getOrCreateAAFor(IRPosition::function(**I)))); } void AttributorCallGraph::print() { llvm::WriteGraph(outs(), this); } /// ------------------------ UnderlyingObjects --------------------------------- namespace { struct AAUnderlyingObjectsImpl : StateWrapper { using BaseTy = StateWrapper; AAUnderlyingObjectsImpl(const IRPosition &IRP, Attributor &A) : BaseTy(IRP) {} /// See AbstractAttribute::getAsStr(). const std::string getAsStr(Attributor *A) const override { return std::string("UnderlyingObjects ") + (isValidState() ? (std::string("inter #") + std::to_string(InterAssumedUnderlyingObjects.size()) + " objs" + std::string(", intra #") + std::to_string(IntraAssumedUnderlyingObjects.size()) + " objs") : ""); } /// See AbstractAttribute::trackStatistics() void trackStatistics() const override {} /// See AbstractAttribute::updateImpl(...). ChangeStatus updateImpl(Attributor &A) override { auto &Ptr = getAssociatedValue(); auto DoUpdate = [&](SmallSetVector &UnderlyingObjects, AA::ValueScope Scope) { bool UsedAssumedInformation = false; SmallPtrSet SeenObjects; SmallVector Values; if (!A.getAssumedSimplifiedValues(IRPosition::value(Ptr), *this, Values, Scope, UsedAssumedInformation)) return UnderlyingObjects.insert(&Ptr); bool Changed = false; for (unsigned I = 0; I < Values.size(); ++I) { auto &VAC = Values[I]; auto *Obj = VAC.getValue(); Value *UO = getUnderlyingObject(Obj); if (UO && UO != VAC.getValue() && SeenObjects.insert(UO).second) { const auto *OtherAA = A.getAAFor( *this, IRPosition::value(*UO), DepClassTy::OPTIONAL); auto Pred = [&Values](Value &V) { Values.emplace_back(V, nullptr); return true; }; if (!OtherAA || !OtherAA->forallUnderlyingObjects(Pred, Scope)) llvm_unreachable( "The forall call should not return false at this position"); continue; } if (isa(Obj)) { Changed |= handleIndirect(A, *Obj, UnderlyingObjects, Scope); continue; } if (auto *PHI = dyn_cast(Obj)) { // Explicitly look through PHIs as we do not care about dynamically // uniqueness. for (unsigned u = 0, e = PHI->getNumIncomingValues(); u < e; u++) { Changed |= handleIndirect(A, *PHI->getIncomingValue(u), UnderlyingObjects, Scope); } continue; } Changed |= UnderlyingObjects.insert(Obj); } return Changed; }; bool Changed = false; Changed |= DoUpdate(IntraAssumedUnderlyingObjects, AA::Intraprocedural); Changed |= DoUpdate(InterAssumedUnderlyingObjects, AA::Interprocedural); return Changed ? ChangeStatus::CHANGED : ChangeStatus::UNCHANGED; } bool forallUnderlyingObjects( function_ref Pred, AA::ValueScope Scope = AA::Interprocedural) const override { if (!isValidState()) return Pred(getAssociatedValue()); auto &AssumedUnderlyingObjects = Scope == AA::Intraprocedural ? IntraAssumedUnderlyingObjects : InterAssumedUnderlyingObjects; for (Value *Obj : AssumedUnderlyingObjects) if (!Pred(*Obj)) return false; return true; } private: /// Handle the case where the value is not the actual underlying value, such /// as a phi node or a select instruction. bool handleIndirect(Attributor &A, Value &V, SmallSetVector &UnderlyingObjects, AA::ValueScope Scope) { bool Changed = false; const auto *AA = A.getAAFor( *this, IRPosition::value(V), DepClassTy::OPTIONAL); auto Pred = [&](Value &V) { Changed |= UnderlyingObjects.insert(&V); return true; }; if (!AA || !AA->forallUnderlyingObjects(Pred, Scope)) llvm_unreachable( "The forall call should not return false at this position"); return Changed; } /// All the underlying objects collected so far via intra procedural scope. SmallSetVector IntraAssumedUnderlyingObjects; /// All the underlying objects collected so far via inter procedural scope. SmallSetVector InterAssumedUnderlyingObjects; }; struct AAUnderlyingObjectsFloating final : AAUnderlyingObjectsImpl { AAUnderlyingObjectsFloating(const IRPosition &IRP, Attributor &A) : AAUnderlyingObjectsImpl(IRP, A) {} }; struct AAUnderlyingObjectsArgument final : AAUnderlyingObjectsImpl { AAUnderlyingObjectsArgument(const IRPosition &IRP, Attributor &A) : AAUnderlyingObjectsImpl(IRP, A) {} }; struct AAUnderlyingObjectsCallSite final : AAUnderlyingObjectsImpl { AAUnderlyingObjectsCallSite(const IRPosition &IRP, Attributor &A) : AAUnderlyingObjectsImpl(IRP, A) {} }; struct AAUnderlyingObjectsCallSiteArgument final : AAUnderlyingObjectsImpl { AAUnderlyingObjectsCallSiteArgument(const IRPosition &IRP, Attributor &A) : AAUnderlyingObjectsImpl(IRP, A) {} }; struct AAUnderlyingObjectsReturned final : AAUnderlyingObjectsImpl { AAUnderlyingObjectsReturned(const IRPosition &IRP, Attributor &A) : AAUnderlyingObjectsImpl(IRP, A) {} }; struct AAUnderlyingObjectsCallSiteReturned final : AAUnderlyingObjectsImpl { AAUnderlyingObjectsCallSiteReturned(const IRPosition &IRP, Attributor &A) : AAUnderlyingObjectsImpl(IRP, A) {} }; struct AAUnderlyingObjectsFunction final : AAUnderlyingObjectsImpl { AAUnderlyingObjectsFunction(const IRPosition &IRP, Attributor &A) : AAUnderlyingObjectsImpl(IRP, A) {} }; } // namespace /// ------------------------ Global Value Info ------------------------------- namespace { struct AAGlobalValueInfoFloating : public AAGlobalValueInfo { AAGlobalValueInfoFloating(const IRPosition &IRP, Attributor &A) : AAGlobalValueInfo(IRP, A) {} /// See AbstractAttribute::initialize(...). void initialize(Attributor &A) override {} bool checkUse(Attributor &A, const Use &U, bool &Follow, SmallVectorImpl &Worklist) { Instruction *UInst = dyn_cast(U.getUser()); if (!UInst) { Follow = true; return true; } LLVM_DEBUG(dbgs() << "[AAGlobalValueInfo] Check use: " << *U.get() << " in " << *UInst << "\n"); if (auto *Cmp = dyn_cast(U.getUser())) { int Idx = &Cmp->getOperandUse(0) == &U; if (isa(Cmp->getOperand(Idx))) return true; return U == &getAnchorValue(); } // Explicitly catch return instructions. if (isa(UInst)) { auto CallSitePred = [&](AbstractCallSite ACS) { Worklist.push_back(ACS.getInstruction()); return true; }; bool UsedAssumedInformation = false; // TODO: We should traverse the uses or add a "non-call-site" CB. if (!A.checkForAllCallSites(CallSitePred, *UInst->getFunction(), /*RequireAllCallSites=*/true, this, UsedAssumedInformation)) return false; return true; } // For now we only use special logic for call sites. However, the tracker // itself knows about a lot of other non-capturing cases already. auto *CB = dyn_cast(UInst); if (!CB) return false; // Direct calls are OK uses. if (CB->isCallee(&U)) return true; // Non-argument uses are scary. if (!CB->isArgOperand(&U)) return false; // TODO: Iterate callees. auto *Fn = dyn_cast(CB->getCalledOperand()); if (!Fn || !A.isFunctionIPOAmendable(*Fn)) return false; unsigned ArgNo = CB->getArgOperandNo(&U); Worklist.push_back(Fn->getArg(ArgNo)); return true; } ChangeStatus updateImpl(Attributor &A) override { unsigned NumUsesBefore = Uses.size(); SmallPtrSet Visited; SmallVector Worklist; Worklist.push_back(&getAnchorValue()); auto UsePred = [&](const Use &U, bool &Follow) -> bool { Uses.insert(&U); switch (DetermineUseCaptureKind(U, nullptr)) { case UseCaptureKind::NO_CAPTURE: return checkUse(A, U, Follow, Worklist); case UseCaptureKind::MAY_CAPTURE: return checkUse(A, U, Follow, Worklist); case UseCaptureKind::PASSTHROUGH: Follow = true; return true; } return true; }; auto EquivalentUseCB = [&](const Use &OldU, const Use &NewU) { Uses.insert(&OldU); return true; }; while (!Worklist.empty()) { const Value *V = Worklist.pop_back_val(); if (!Visited.insert(V).second) continue; if (!A.checkForAllUses(UsePred, *this, *V, /* CheckBBLivenessOnly */ true, DepClassTy::OPTIONAL, /* IgnoreDroppableUses */ true, EquivalentUseCB)) { return indicatePessimisticFixpoint(); } } return Uses.size() == NumUsesBefore ? ChangeStatus::UNCHANGED : ChangeStatus::CHANGED; } bool isPotentialUse(const Use &U) const override { return !isValidState() || Uses.contains(&U); } /// See AbstractAttribute::manifest(...). ChangeStatus manifest(Attributor &A) override { return ChangeStatus::UNCHANGED; } /// See AbstractAttribute::getAsStr(). const std::string getAsStr(Attributor *A) const override { return "[" + std::to_string(Uses.size()) + " uses]"; } void trackStatistics() const override { STATS_DECLTRACK_FLOATING_ATTR(GlobalValuesTracked); } private: /// Set of (transitive) uses of this GlobalValue. SmallPtrSet Uses; }; } // namespace /// ------------------------ Indirect Call Info ------------------------------- namespace { struct AAIndirectCallInfoCallSite : public AAIndirectCallInfo { AAIndirectCallInfoCallSite(const IRPosition &IRP, Attributor &A) : AAIndirectCallInfo(IRP, A) {} /// See AbstractAttribute::initialize(...). void initialize(Attributor &A) override { auto *MD = getCtxI()->getMetadata(LLVMContext::MD_callees); if (!MD && !A.isClosedWorldModule()) return; if (MD) { for (const auto &Op : MD->operands()) if (Function *Callee = mdconst::dyn_extract_or_null(Op)) PotentialCallees.insert(Callee); } else if (A.isClosedWorldModule()) { ArrayRef IndirectlyCallableFunctions = A.getInfoCache().getIndirectlyCallableFunctions(A); PotentialCallees.insert(IndirectlyCallableFunctions.begin(), IndirectlyCallableFunctions.end()); } if (PotentialCallees.empty()) indicateOptimisticFixpoint(); } ChangeStatus updateImpl(Attributor &A) override { CallBase *CB = cast(getCtxI()); const Use &CalleeUse = CB->getCalledOperandUse(); Value *FP = CB->getCalledOperand(); SmallSetVector AssumedCalleesNow; bool AllCalleesKnownNow = AllCalleesKnown; auto CheckPotentialCalleeUse = [&](Function &PotentialCallee, bool &UsedAssumedInformation) { const auto *GIAA = A.getAAFor( *this, IRPosition::value(PotentialCallee), DepClassTy::OPTIONAL); if (!GIAA || GIAA->isPotentialUse(CalleeUse)) return true; UsedAssumedInformation = !GIAA->isAtFixpoint(); return false; }; auto AddPotentialCallees = [&]() { for (auto *PotentialCallee : PotentialCallees) { bool UsedAssumedInformation = false; if (CheckPotentialCalleeUse(*PotentialCallee, UsedAssumedInformation)) AssumedCalleesNow.insert(PotentialCallee); } }; // Use simplification to find potential callees, if !callees was present, // fallback to that set if necessary. bool UsedAssumedInformation = false; SmallVector Values; if (!A.getAssumedSimplifiedValues(IRPosition::value(*FP), this, Values, AA::ValueScope::AnyScope, UsedAssumedInformation)) { if (PotentialCallees.empty()) return indicatePessimisticFixpoint(); AddPotentialCallees(); } // Try to find a reason for \p Fn not to be a potential callee. If none was // found, add it to the assumed callees set. auto CheckPotentialCallee = [&](Function &Fn) { if (!PotentialCallees.empty() && !PotentialCallees.count(&Fn)) return false; auto &CachedResult = FilterResults[&Fn]; if (CachedResult.has_value()) return CachedResult.value(); bool UsedAssumedInformation = false; if (!CheckPotentialCalleeUse(Fn, UsedAssumedInformation)) { if (!UsedAssumedInformation) CachedResult = false; return false; } int NumFnArgs = Fn.arg_size(); int NumCBArgs = CB->arg_size(); // Check if any excess argument (which we fill up with poison) is known to // be UB on undef. for (int I = NumCBArgs; I < NumFnArgs; ++I) { bool IsKnown = false; if (AA::hasAssumedIRAttr( A, this, IRPosition::argument(*Fn.getArg(I)), DepClassTy::OPTIONAL, IsKnown)) { if (IsKnown) CachedResult = false; return false; } } CachedResult = true; return true; }; // Check simplification result, prune known UB callees, also restrict it to // the !callees set, if present. for (auto &VAC : Values) { if (isa(VAC.getValue())) continue; if (isa(VAC.getValue()) && VAC.getValue()->getType()->getPointerAddressSpace() == 0) continue; // TODO: Check for known UB, e.g., poison + noundef. if (auto *VACFn = dyn_cast(VAC.getValue())) { if (CheckPotentialCallee(*VACFn)) AssumedCalleesNow.insert(VACFn); continue; } if (!PotentialCallees.empty()) { AddPotentialCallees(); break; } AllCalleesKnownNow = false; } if (AssumedCalleesNow == AssumedCallees && AllCalleesKnown == AllCalleesKnownNow) return ChangeStatus::UNCHANGED; std::swap(AssumedCallees, AssumedCalleesNow); AllCalleesKnown = AllCalleesKnownNow; return ChangeStatus::CHANGED; } /// See AbstractAttribute::manifest(...). ChangeStatus manifest(Attributor &A) override { // If we can't specialize at all, give up now. if (!AllCalleesKnown && AssumedCallees.empty()) return ChangeStatus::UNCHANGED; CallBase *CB = cast(getCtxI()); bool UsedAssumedInformation = false; if (A.isAssumedDead(*CB, this, /*LivenessAA=*/nullptr, UsedAssumedInformation)) return ChangeStatus::UNCHANGED; ChangeStatus Changed = ChangeStatus::UNCHANGED; Value *FP = CB->getCalledOperand(); if (FP->getType()->getPointerAddressSpace()) FP = new AddrSpaceCastInst(FP, PointerType::get(FP->getType(), 0), FP->getName() + ".as0", CB->getIterator()); bool CBIsVoid = CB->getType()->isVoidTy(); BasicBlock::iterator IP = CB->getIterator(); FunctionType *CSFT = CB->getFunctionType(); SmallVector CSArgs(CB->arg_begin(), CB->arg_end()); // If we know all callees and there are none, the call site is (effectively) // dead (or UB). if (AssumedCallees.empty()) { assert(AllCalleesKnown && "Expected all callees to be known if there are none."); A.changeToUnreachableAfterManifest(CB); return ChangeStatus::CHANGED; } // Special handling for the single callee case. if (AllCalleesKnown && AssumedCallees.size() == 1) { auto *NewCallee = AssumedCallees.front(); if (isLegalToPromote(*CB, NewCallee)) { promoteCall(*CB, NewCallee, nullptr); return ChangeStatus::CHANGED; } Instruction *NewCall = CallInst::Create(FunctionCallee(CSFT, NewCallee), CSArgs, CB->getName(), CB->getIterator()); if (!CBIsVoid) A.changeAfterManifest(IRPosition::callsite_returned(*CB), *NewCall); A.deleteAfterManifest(*CB); return ChangeStatus::CHANGED; } // For each potential value we create a conditional // // ``` // if (ptr == value) value(args); // else ... // ``` // bool SpecializedForAnyCallees = false; bool SpecializedForAllCallees = AllCalleesKnown; ICmpInst *LastCmp = nullptr; SmallVector SkippedAssumedCallees; SmallVector> NewCalls; for (Function *NewCallee : AssumedCallees) { if (!A.shouldSpecializeCallSiteForCallee(*this, *CB, *NewCallee)) { SkippedAssumedCallees.push_back(NewCallee); SpecializedForAllCallees = false; continue; } SpecializedForAnyCallees = true; LastCmp = new ICmpInst(IP, llvm::CmpInst::ICMP_EQ, FP, NewCallee); Instruction *ThenTI = SplitBlockAndInsertIfThen(LastCmp, IP, /* Unreachable */ false); BasicBlock *CBBB = CB->getParent(); A.registerManifestAddedBasicBlock(*ThenTI->getParent()); A.registerManifestAddedBasicBlock(*IP->getParent()); auto *SplitTI = cast(LastCmp->getNextNode()); BasicBlock *ElseBB; if (&*IP == CB) { ElseBB = BasicBlock::Create(ThenTI->getContext(), "", ThenTI->getFunction(), CBBB); A.registerManifestAddedBasicBlock(*ElseBB); IP = BranchInst::Create(CBBB, ElseBB)->getIterator(); SplitTI->replaceUsesOfWith(CBBB, ElseBB); } else { ElseBB = IP->getParent(); ThenTI->replaceUsesOfWith(ElseBB, CBBB); } CastInst *RetBC = nullptr; CallInst *NewCall = nullptr; if (isLegalToPromote(*CB, NewCallee)) { auto *CBClone = cast(CB->clone()); CBClone->insertBefore(ThenTI); NewCall = &cast(promoteCall(*CBClone, NewCallee, &RetBC)); } else { NewCall = CallInst::Create(FunctionCallee(CSFT, NewCallee), CSArgs, CB->getName(), ThenTI->getIterator()); } NewCalls.push_back({NewCall, RetBC}); } auto AttachCalleeMetadata = [&](CallBase &IndirectCB) { if (!AllCalleesKnown) return ChangeStatus::UNCHANGED; MDBuilder MDB(IndirectCB.getContext()); MDNode *Callees = MDB.createCallees(SkippedAssumedCallees); IndirectCB.setMetadata(LLVMContext::MD_callees, Callees); return ChangeStatus::CHANGED; }; if (!SpecializedForAnyCallees) return AttachCalleeMetadata(*CB); // Check if we need the fallback indirect call still. if (SpecializedForAllCallees) { LastCmp->replaceAllUsesWith(ConstantInt::getTrue(LastCmp->getContext())); LastCmp->eraseFromParent(); new UnreachableInst(IP->getContext(), IP); IP->eraseFromParent(); } else { auto *CBClone = cast(CB->clone()); CBClone->setName(CB->getName()); CBClone->insertBefore(*IP->getParent(), IP); NewCalls.push_back({CBClone, nullptr}); AttachCalleeMetadata(*CBClone); } // Check if we need a PHI to merge the results. if (!CBIsVoid) { auto *PHI = PHINode::Create(CB->getType(), NewCalls.size(), CB->getName() + ".phi", CB->getParent()->getFirstInsertionPt()); for (auto &It : NewCalls) { CallBase *NewCall = It.first; Instruction *CallRet = It.second ? It.second : It.first; if (CallRet->getType() == CB->getType()) PHI->addIncoming(CallRet, CallRet->getParent()); else if (NewCall->getType()->isVoidTy()) PHI->addIncoming(PoisonValue::get(CB->getType()), NewCall->getParent()); else llvm_unreachable("Call return should match or be void!"); } A.changeAfterManifest(IRPosition::callsite_returned(*CB), *PHI); } A.deleteAfterManifest(*CB); Changed = ChangeStatus::CHANGED; return Changed; } /// See AbstractAttribute::getAsStr(). const std::string getAsStr(Attributor *A) const override { return std::string(AllCalleesKnown ? "eliminate" : "specialize") + " indirect call site with " + std::to_string(AssumedCallees.size()) + " functions"; } void trackStatistics() const override { if (AllCalleesKnown) { STATS_DECLTRACK( Eliminated, CallSites, "Number of indirect call sites eliminated via specialization") } else { STATS_DECLTRACK(Specialized, CallSites, "Number of indirect call sites specialized") } } bool foreachCallee(function_ref CB) const override { return isValidState() && AllCalleesKnown && all_of(AssumedCallees, CB); } private: /// Map to remember filter results. DenseMap> FilterResults; /// If the !callee metadata was present, this set will contain all potential /// callees (superset). SmallSetVector PotentialCallees; /// This set contains all currently assumed calllees, which might grow over /// time. SmallSetVector AssumedCallees; /// Flag to indicate if all possible callees are in the AssumedCallees set or /// if there could be others. bool AllCalleesKnown = true; }; } // namespace /// ------------------------ Address Space ------------------------------------ namespace { struct AAAddressSpaceImpl : public AAAddressSpace { AAAddressSpaceImpl(const IRPosition &IRP, Attributor &A) : AAAddressSpace(IRP, A) {} int32_t getAddressSpace() const override { assert(isValidState() && "the AA is invalid"); return AssumedAddressSpace; } /// See AbstractAttribute::initialize(...). void initialize(Attributor &A) override { assert(getAssociatedType()->isPtrOrPtrVectorTy() && "Associated value is not a pointer"); } ChangeStatus updateImpl(Attributor &A) override { int32_t OldAddressSpace = AssumedAddressSpace; auto *AUO = A.getOrCreateAAFor(getIRPosition(), this, DepClassTy::REQUIRED); auto Pred = [&](Value &Obj) { if (isa(&Obj)) return true; return takeAddressSpace(Obj.getType()->getPointerAddressSpace()); }; if (!AUO->forallUnderlyingObjects(Pred)) return indicatePessimisticFixpoint(); return OldAddressSpace == AssumedAddressSpace ? ChangeStatus::UNCHANGED : ChangeStatus::CHANGED; } /// See AbstractAttribute::manifest(...). ChangeStatus manifest(Attributor &A) override { Value *AssociatedValue = &getAssociatedValue(); Value *OriginalValue = peelAddrspacecast(AssociatedValue); if (getAddressSpace() == NoAddressSpace || static_cast(getAddressSpace()) == getAssociatedType()->getPointerAddressSpace()) return ChangeStatus::UNCHANGED; Type *NewPtrTy = PointerType::get(getAssociatedType()->getContext(), static_cast(getAddressSpace())); bool UseOriginalValue = OriginalValue->getType()->getPointerAddressSpace() == static_cast(getAddressSpace()); bool Changed = false; auto MakeChange = [&](Instruction *I, Use &U) { Changed = true; if (UseOriginalValue) { A.changeUseAfterManifest(U, *OriginalValue); return; } Instruction *CastInst = new AddrSpaceCastInst(OriginalValue, NewPtrTy); CastInst->insertBefore(cast(I)); A.changeUseAfterManifest(U, *CastInst); }; auto Pred = [&](const Use &U, bool &) { if (U.get() != AssociatedValue) return true; auto *Inst = dyn_cast(U.getUser()); if (!Inst) return true; // This is a WA to make sure we only change uses from the corresponding // CGSCC if the AA is run on CGSCC instead of the entire module. if (!A.isRunOn(Inst->getFunction())) return true; if (isa(Inst)) MakeChange(Inst, const_cast(U)); if (isa(Inst)) { // We only make changes if the use is the pointer operand. if (U.getOperandNo() == 1) MakeChange(Inst, const_cast(U)); } return true; }; // It doesn't matter if we can't check all uses as we can simply // conservatively ignore those that can not be visited. (void)A.checkForAllUses(Pred, *this, getAssociatedValue(), /* CheckBBLivenessOnly */ true); return Changed ? ChangeStatus::CHANGED : ChangeStatus::UNCHANGED; } /// See AbstractAttribute::getAsStr(). const std::string getAsStr(Attributor *A) const override { if (!isValidState()) return "addrspace()"; return "addrspace(" + (AssumedAddressSpace == NoAddressSpace ? "none" : std::to_string(AssumedAddressSpace)) + ")"; } private: int32_t AssumedAddressSpace = NoAddressSpace; bool takeAddressSpace(int32_t AS) { if (AssumedAddressSpace == NoAddressSpace) { AssumedAddressSpace = AS; return true; } return AssumedAddressSpace == AS; } static Value *peelAddrspacecast(Value *V) { if (auto *I = dyn_cast(V)) return peelAddrspacecast(I->getPointerOperand()); if (auto *C = dyn_cast(V)) if (C->getOpcode() == Instruction::AddrSpaceCast) return peelAddrspacecast(C->getOperand(0)); return V; } }; struct AAAddressSpaceFloating final : AAAddressSpaceImpl { AAAddressSpaceFloating(const IRPosition &IRP, Attributor &A) : AAAddressSpaceImpl(IRP, A) {} void trackStatistics() const override { STATS_DECLTRACK_FLOATING_ATTR(addrspace); } }; struct AAAddressSpaceReturned final : AAAddressSpaceImpl { AAAddressSpaceReturned(const IRPosition &IRP, Attributor &A) : AAAddressSpaceImpl(IRP, A) {} /// See AbstractAttribute::initialize(...). void initialize(Attributor &A) override { // TODO: we don't rewrite function argument for now because it will need to // rewrite the function signature and all call sites. (void)indicatePessimisticFixpoint(); } void trackStatistics() const override { STATS_DECLTRACK_FNRET_ATTR(addrspace); } }; struct AAAddressSpaceCallSiteReturned final : AAAddressSpaceImpl { AAAddressSpaceCallSiteReturned(const IRPosition &IRP, Attributor &A) : AAAddressSpaceImpl(IRP, A) {} void trackStatistics() const override { STATS_DECLTRACK_CSRET_ATTR(addrspace); } }; struct AAAddressSpaceArgument final : AAAddressSpaceImpl { AAAddressSpaceArgument(const IRPosition &IRP, Attributor &A) : AAAddressSpaceImpl(IRP, A) {} void trackStatistics() const override { STATS_DECLTRACK_ARG_ATTR(addrspace); } }; struct AAAddressSpaceCallSiteArgument final : AAAddressSpaceImpl { AAAddressSpaceCallSiteArgument(const IRPosition &IRP, Attributor &A) : AAAddressSpaceImpl(IRP, A) {} /// See AbstractAttribute::initialize(...). void initialize(Attributor &A) override { // TODO: we don't rewrite call site argument for now because it will need to // rewrite the function signature of the callee. (void)indicatePessimisticFixpoint(); } void trackStatistics() const override { STATS_DECLTRACK_CSARG_ATTR(addrspace); } }; } // namespace /// ----------- Allocation Info ---------- namespace { struct AAAllocationInfoImpl : public AAAllocationInfo { AAAllocationInfoImpl(const IRPosition &IRP, Attributor &A) : AAAllocationInfo(IRP, A) {} std::optional getAllocatedSize() const override { assert(isValidState() && "the AA is invalid"); return AssumedAllocatedSize; } std::optional findInitialAllocationSize(Instruction *I, const DataLayout &DL) { // TODO: implement case for malloc like instructions switch (I->getOpcode()) { case Instruction::Alloca: { AllocaInst *AI = cast(I); return AI->getAllocationSize(DL); } default: return std::nullopt; } } ChangeStatus updateImpl(Attributor &A) override { const IRPosition &IRP = getIRPosition(); Instruction *I = IRP.getCtxI(); // TODO: update check for malloc like calls if (!isa(I)) return indicatePessimisticFixpoint(); bool IsKnownNoCapture; if (!AA::hasAssumedIRAttr( A, this, IRP, DepClassTy::OPTIONAL, IsKnownNoCapture)) return indicatePessimisticFixpoint(); const AAPointerInfo *PI = A.getOrCreateAAFor(IRP, *this, DepClassTy::REQUIRED); if (!PI) return indicatePessimisticFixpoint(); if (!PI->getState().isValidState()) return indicatePessimisticFixpoint(); const DataLayout &DL = A.getDataLayout(); const auto AllocationSize = findInitialAllocationSize(I, DL); // If allocation size is nullopt, we give up. if (!AllocationSize) return indicatePessimisticFixpoint(); // For zero sized allocations, we give up. // Since we can't reduce further if (*AllocationSize == 0) return indicatePessimisticFixpoint(); int64_t BinSize = PI->numOffsetBins(); // TODO: implement for multiple bins if (BinSize > 1) return indicatePessimisticFixpoint(); if (BinSize == 0) { auto NewAllocationSize = std::optional(TypeSize(0, false)); if (!changeAllocationSize(NewAllocationSize)) return ChangeStatus::UNCHANGED; return ChangeStatus::CHANGED; } // TODO: refactor this to be part of multiple bin case const auto &It = PI->begin(); // TODO: handle if Offset is not zero if (It->first.Offset != 0) return indicatePessimisticFixpoint(); uint64_t SizeOfBin = It->first.Offset + It->first.Size; if (SizeOfBin >= *AllocationSize) return indicatePessimisticFixpoint(); auto NewAllocationSize = std::optional(TypeSize(SizeOfBin * 8, false)); if (!changeAllocationSize(NewAllocationSize)) return ChangeStatus::UNCHANGED; return ChangeStatus::CHANGED; } /// See AbstractAttribute::manifest(...). ChangeStatus manifest(Attributor &A) override { assert(isValidState() && "Manifest should only be called if the state is valid."); Instruction *I = getIRPosition().getCtxI(); auto FixedAllocatedSizeInBits = getAllocatedSize()->getFixedValue(); unsigned long NumBytesToAllocate = (FixedAllocatedSizeInBits + 7) / 8; switch (I->getOpcode()) { // TODO: add case for malloc like calls case Instruction::Alloca: { AllocaInst *AI = cast(I); Type *CharType = Type::getInt8Ty(I->getContext()); auto *NumBytesToValue = ConstantInt::get(I->getContext(), APInt(32, NumBytesToAllocate)); BasicBlock::iterator insertPt = AI->getIterator(); insertPt = std::next(insertPt); AllocaInst *NewAllocaInst = new AllocaInst(CharType, AI->getAddressSpace(), NumBytesToValue, AI->getAlign(), AI->getName(), insertPt); if (A.changeAfterManifest(IRPosition::inst(*AI), *NewAllocaInst)) return ChangeStatus::CHANGED; break; } default: break; } return ChangeStatus::UNCHANGED; } /// See AbstractAttribute::getAsStr(). const std::string getAsStr(Attributor *A) const override { if (!isValidState()) return "allocationinfo()"; return "allocationinfo(" + (AssumedAllocatedSize == HasNoAllocationSize ? "none" : std::to_string(AssumedAllocatedSize->getFixedValue())) + ")"; } private: std::optional AssumedAllocatedSize = HasNoAllocationSize; // Maintain the computed allocation size of the object. // Returns (bool) weather the size of the allocation was modified or not. bool changeAllocationSize(std::optional Size) { if (AssumedAllocatedSize == HasNoAllocationSize || AssumedAllocatedSize != Size) { AssumedAllocatedSize = Size; return true; } return false; } }; struct AAAllocationInfoFloating : AAAllocationInfoImpl { AAAllocationInfoFloating(const IRPosition &IRP, Attributor &A) : AAAllocationInfoImpl(IRP, A) {} void trackStatistics() const override { STATS_DECLTRACK_FLOATING_ATTR(allocationinfo); } }; struct AAAllocationInfoReturned : AAAllocationInfoImpl { AAAllocationInfoReturned(const IRPosition &IRP, Attributor &A) : AAAllocationInfoImpl(IRP, A) {} /// See AbstractAttribute::initialize(...). void initialize(Attributor &A) override { // TODO: we don't rewrite function argument for now because it will need to // rewrite the function signature and all call sites (void)indicatePessimisticFixpoint(); } void trackStatistics() const override { STATS_DECLTRACK_FNRET_ATTR(allocationinfo); } }; struct AAAllocationInfoCallSiteReturned : AAAllocationInfoImpl { AAAllocationInfoCallSiteReturned(const IRPosition &IRP, Attributor &A) : AAAllocationInfoImpl(IRP, A) {} void trackStatistics() const override { STATS_DECLTRACK_CSRET_ATTR(allocationinfo); } }; struct AAAllocationInfoArgument : AAAllocationInfoImpl { AAAllocationInfoArgument(const IRPosition &IRP, Attributor &A) : AAAllocationInfoImpl(IRP, A) {} void trackStatistics() const override { STATS_DECLTRACK_ARG_ATTR(allocationinfo); } }; struct AAAllocationInfoCallSiteArgument : AAAllocationInfoImpl { AAAllocationInfoCallSiteArgument(const IRPosition &IRP, Attributor &A) : AAAllocationInfoImpl(IRP, A) {} /// See AbstractAttribute::initialize(...). void initialize(Attributor &A) override { (void)indicatePessimisticFixpoint(); } void trackStatistics() const override { STATS_DECLTRACK_CSARG_ATTR(allocationinfo); } }; } // namespace const char AANoUnwind::ID = 0; const char AANoSync::ID = 0; const char AANoFree::ID = 0; const char AANonNull::ID = 0; const char AAMustProgress::ID = 0; const char AANoRecurse::ID = 0; const char AANonConvergent::ID = 0; const char AAWillReturn::ID = 0; const char AAUndefinedBehavior::ID = 0; const char AANoAlias::ID = 0; const char AAIntraFnReachability::ID = 0; const char AANoReturn::ID = 0; const char AAIsDead::ID = 0; const char AADereferenceable::ID = 0; const char AAAlign::ID = 0; const char AAInstanceInfo::ID = 0; const char AANoCapture::ID = 0; const char AAValueSimplify::ID = 0; const char AAHeapToStack::ID = 0; const char AAPrivatizablePtr::ID = 0; const char AAMemoryBehavior::ID = 0; const char AAMemoryLocation::ID = 0; const char AAValueConstantRange::ID = 0; const char AAPotentialConstantValues::ID = 0; const char AAPotentialValues::ID = 0; const char AANoUndef::ID = 0; const char AANoFPClass::ID = 0; const char AACallEdges::ID = 0; const char AAInterFnReachability::ID = 0; const char AAPointerInfo::ID = 0; const char AAAssumptionInfo::ID = 0; const char AAUnderlyingObjects::ID = 0; const char AAAddressSpace::ID = 0; const char AAAllocationInfo::ID = 0; const char AAIndirectCallInfo::ID = 0; const char AAGlobalValueInfo::ID = 0; const char AADenormalFPMath::ID = 0; // Macro magic to create the static generator function for attributes that // follow the naming scheme. #define SWITCH_PK_INV(CLASS, PK, POS_NAME) \ case IRPosition::PK: \ llvm_unreachable("Cannot create " #CLASS " for a " POS_NAME " position!"); #define SWITCH_PK_CREATE(CLASS, IRP, PK, SUFFIX) \ case IRPosition::PK: \ AA = new (A.Allocator) CLASS##SUFFIX(IRP, A); \ ++NumAAs; \ break; #define CREATE_FUNCTION_ABSTRACT_ATTRIBUTE_FOR_POSITION(CLASS) \ CLASS &CLASS::createForPosition(const IRPosition &IRP, Attributor &A) { \ CLASS *AA = nullptr; \ switch (IRP.getPositionKind()) { \ SWITCH_PK_INV(CLASS, IRP_INVALID, "invalid") \ SWITCH_PK_INV(CLASS, IRP_FLOAT, "floating") \ SWITCH_PK_INV(CLASS, IRP_ARGUMENT, "argument") \ SWITCH_PK_INV(CLASS, IRP_RETURNED, "returned") \ SWITCH_PK_INV(CLASS, IRP_CALL_SITE_RETURNED, "call site returned") \ SWITCH_PK_INV(CLASS, IRP_CALL_SITE_ARGUMENT, "call site argument") \ SWITCH_PK_CREATE(CLASS, IRP, IRP_FUNCTION, Function) \ SWITCH_PK_CREATE(CLASS, IRP, IRP_CALL_SITE, CallSite) \ } \ return *AA; \ } #define CREATE_VALUE_ABSTRACT_ATTRIBUTE_FOR_POSITION(CLASS) \ CLASS &CLASS::createForPosition(const IRPosition &IRP, Attributor &A) { \ CLASS *AA = nullptr; \ switch (IRP.getPositionKind()) { \ SWITCH_PK_INV(CLASS, IRP_INVALID, "invalid") \ SWITCH_PK_INV(CLASS, IRP_FUNCTION, "function") \ SWITCH_PK_INV(CLASS, IRP_CALL_SITE, "call site") \ SWITCH_PK_CREATE(CLASS, IRP, IRP_FLOAT, Floating) \ SWITCH_PK_CREATE(CLASS, IRP, IRP_ARGUMENT, Argument) \ SWITCH_PK_CREATE(CLASS, IRP, IRP_RETURNED, Returned) \ SWITCH_PK_CREATE(CLASS, IRP, IRP_CALL_SITE_RETURNED, CallSiteReturned) \ SWITCH_PK_CREATE(CLASS, IRP, IRP_CALL_SITE_ARGUMENT, CallSiteArgument) \ } \ return *AA; \ } #define CREATE_ABSTRACT_ATTRIBUTE_FOR_ONE_POSITION(POS, SUFFIX, CLASS) \ CLASS &CLASS::createForPosition(const IRPosition &IRP, Attributor &A) { \ CLASS *AA = nullptr; \ switch (IRP.getPositionKind()) { \ SWITCH_PK_CREATE(CLASS, IRP, POS, SUFFIX) \ default: \ llvm_unreachable("Cannot create " #CLASS " for position otherthan " #POS \ " position!"); \ } \ return *AA; \ } #define CREATE_ALL_ABSTRACT_ATTRIBUTE_FOR_POSITION(CLASS) \ CLASS &CLASS::createForPosition(const IRPosition &IRP, Attributor &A) { \ CLASS *AA = nullptr; \ switch (IRP.getPositionKind()) { \ SWITCH_PK_INV(CLASS, IRP_INVALID, "invalid") \ SWITCH_PK_CREATE(CLASS, IRP, IRP_FUNCTION, Function) \ SWITCH_PK_CREATE(CLASS, IRP, IRP_CALL_SITE, CallSite) \ SWITCH_PK_CREATE(CLASS, IRP, IRP_FLOAT, Floating) \ SWITCH_PK_CREATE(CLASS, IRP, IRP_ARGUMENT, Argument) \ SWITCH_PK_CREATE(CLASS, IRP, IRP_RETURNED, Returned) \ SWITCH_PK_CREATE(CLASS, IRP, IRP_CALL_SITE_RETURNED, CallSiteReturned) \ SWITCH_PK_CREATE(CLASS, IRP, IRP_CALL_SITE_ARGUMENT, CallSiteArgument) \ } \ return *AA; \ } #define CREATE_FUNCTION_ONLY_ABSTRACT_ATTRIBUTE_FOR_POSITION(CLASS) \ CLASS &CLASS::createForPosition(const IRPosition &IRP, Attributor &A) { \ CLASS *AA = nullptr; \ switch (IRP.getPositionKind()) { \ SWITCH_PK_INV(CLASS, IRP_INVALID, "invalid") \ SWITCH_PK_INV(CLASS, IRP_ARGUMENT, "argument") \ SWITCH_PK_INV(CLASS, IRP_FLOAT, "floating") \ SWITCH_PK_INV(CLASS, IRP_RETURNED, "returned") \ SWITCH_PK_INV(CLASS, IRP_CALL_SITE_RETURNED, "call site returned") \ SWITCH_PK_INV(CLASS, IRP_CALL_SITE_ARGUMENT, "call site argument") \ SWITCH_PK_INV(CLASS, IRP_CALL_SITE, "call site") \ SWITCH_PK_CREATE(CLASS, IRP, IRP_FUNCTION, Function) \ } \ return *AA; \ } #define CREATE_NON_RET_ABSTRACT_ATTRIBUTE_FOR_POSITION(CLASS) \ CLASS &CLASS::createForPosition(const IRPosition &IRP, Attributor &A) { \ CLASS *AA = nullptr; \ switch (IRP.getPositionKind()) { \ SWITCH_PK_INV(CLASS, IRP_INVALID, "invalid") \ SWITCH_PK_INV(CLASS, IRP_RETURNED, "returned") \ SWITCH_PK_CREATE(CLASS, IRP, IRP_FUNCTION, Function) \ SWITCH_PK_CREATE(CLASS, IRP, IRP_CALL_SITE, CallSite) \ SWITCH_PK_CREATE(CLASS, IRP, IRP_FLOAT, Floating) \ SWITCH_PK_CREATE(CLASS, IRP, IRP_ARGUMENT, Argument) \ SWITCH_PK_CREATE(CLASS, IRP, IRP_CALL_SITE_RETURNED, CallSiteReturned) \ SWITCH_PK_CREATE(CLASS, IRP, IRP_CALL_SITE_ARGUMENT, CallSiteArgument) \ } \ return *AA; \ } CREATE_FUNCTION_ABSTRACT_ATTRIBUTE_FOR_POSITION(AANoUnwind) CREATE_FUNCTION_ABSTRACT_ATTRIBUTE_FOR_POSITION(AANoSync) CREATE_FUNCTION_ABSTRACT_ATTRIBUTE_FOR_POSITION(AANoRecurse) CREATE_FUNCTION_ABSTRACT_ATTRIBUTE_FOR_POSITION(AAWillReturn) CREATE_FUNCTION_ABSTRACT_ATTRIBUTE_FOR_POSITION(AANoReturn) CREATE_FUNCTION_ABSTRACT_ATTRIBUTE_FOR_POSITION(AAMemoryLocation) CREATE_FUNCTION_ABSTRACT_ATTRIBUTE_FOR_POSITION(AACallEdges) CREATE_FUNCTION_ABSTRACT_ATTRIBUTE_FOR_POSITION(AAAssumptionInfo) CREATE_FUNCTION_ABSTRACT_ATTRIBUTE_FOR_POSITION(AAMustProgress) CREATE_VALUE_ABSTRACT_ATTRIBUTE_FOR_POSITION(AANonNull) CREATE_VALUE_ABSTRACT_ATTRIBUTE_FOR_POSITION(AANoAlias) CREATE_VALUE_ABSTRACT_ATTRIBUTE_FOR_POSITION(AAPrivatizablePtr) CREATE_VALUE_ABSTRACT_ATTRIBUTE_FOR_POSITION(AADereferenceable) CREATE_VALUE_ABSTRACT_ATTRIBUTE_FOR_POSITION(AAAlign) CREATE_VALUE_ABSTRACT_ATTRIBUTE_FOR_POSITION(AAInstanceInfo) CREATE_VALUE_ABSTRACT_ATTRIBUTE_FOR_POSITION(AANoCapture) CREATE_VALUE_ABSTRACT_ATTRIBUTE_FOR_POSITION(AAValueConstantRange) CREATE_VALUE_ABSTRACT_ATTRIBUTE_FOR_POSITION(AAPotentialConstantValues) CREATE_VALUE_ABSTRACT_ATTRIBUTE_FOR_POSITION(AAPotentialValues) CREATE_VALUE_ABSTRACT_ATTRIBUTE_FOR_POSITION(AANoUndef) CREATE_VALUE_ABSTRACT_ATTRIBUTE_FOR_POSITION(AANoFPClass) CREATE_VALUE_ABSTRACT_ATTRIBUTE_FOR_POSITION(AAPointerInfo) CREATE_VALUE_ABSTRACT_ATTRIBUTE_FOR_POSITION(AAAddressSpace) CREATE_VALUE_ABSTRACT_ATTRIBUTE_FOR_POSITION(AAAllocationInfo) CREATE_ALL_ABSTRACT_ATTRIBUTE_FOR_POSITION(AAValueSimplify) CREATE_ALL_ABSTRACT_ATTRIBUTE_FOR_POSITION(AAIsDead) CREATE_ALL_ABSTRACT_ATTRIBUTE_FOR_POSITION(AANoFree) CREATE_ALL_ABSTRACT_ATTRIBUTE_FOR_POSITION(AAUnderlyingObjects) CREATE_ABSTRACT_ATTRIBUTE_FOR_ONE_POSITION(IRP_CALL_SITE, CallSite, AAIndirectCallInfo) CREATE_ABSTRACT_ATTRIBUTE_FOR_ONE_POSITION(IRP_FLOAT, Floating, AAGlobalValueInfo) CREATE_FUNCTION_ONLY_ABSTRACT_ATTRIBUTE_FOR_POSITION(AAHeapToStack) CREATE_FUNCTION_ONLY_ABSTRACT_ATTRIBUTE_FOR_POSITION(AAUndefinedBehavior) CREATE_FUNCTION_ONLY_ABSTRACT_ATTRIBUTE_FOR_POSITION(AANonConvergent) CREATE_FUNCTION_ONLY_ABSTRACT_ATTRIBUTE_FOR_POSITION(AAIntraFnReachability) CREATE_FUNCTION_ONLY_ABSTRACT_ATTRIBUTE_FOR_POSITION(AAInterFnReachability) CREATE_FUNCTION_ONLY_ABSTRACT_ATTRIBUTE_FOR_POSITION(AADenormalFPMath) CREATE_NON_RET_ABSTRACT_ATTRIBUTE_FOR_POSITION(AAMemoryBehavior) #undef CREATE_FUNCTION_ONLY_ABSTRACT_ATTRIBUTE_FOR_POSITION #undef CREATE_FUNCTION_ABSTRACT_ATTRIBUTE_FOR_POSITION #undef CREATE_NON_RET_ABSTRACT_ATTRIBUTE_FOR_POSITION #undef CREATE_VALUE_ABSTRACT_ATTRIBUTE_FOR_POSITION #undef CREATE_ALL_ABSTRACT_ATTRIBUTE_FOR_POSITION #undef CREATE_ABSTRACT_ATTRIBUTE_FOR_ONE_POSITION #undef SWITCH_PK_CREATE #undef SWITCH_PK_INV