diff --git a/llvm/include/llvm/Transforms/IPO/Attributor.h b/llvm/include/llvm/Transforms/IPO/Attributor.h --- a/llvm/include/llvm/Transforms/IPO/Attributor.h +++ b/llvm/include/llvm/Transforms/IPO/Attributor.h @@ -229,6 +229,9 @@ /// are floating values that do not have a corresponding attribute list /// position. struct IRPosition { + // NOTE: In the future this definition can be changed to support recursive + // functions. + using CallBaseContext = CallBase; /// The positions we distinguish in the IR. enum Kind : char { @@ -249,27 +252,34 @@ IRPosition() : Enc(nullptr, ENC_VALUE) { verify(); } /// Create a position describing the value of \p V. - static const IRPosition value(const Value &V) { + static const IRPosition value(const Value &V, + const CallBaseContext *CBContext = nullptr) { if (auto *Arg = dyn_cast(&V)) - return IRPosition::argument(*Arg); + return IRPosition::argument(*Arg, CBContext); if (auto *CB = dyn_cast(&V)) return IRPosition::callsite_returned(*CB); - return IRPosition(const_cast(V), IRP_FLOAT); + return IRPosition(const_cast(V), IRP_FLOAT, CBContext); } /// Create a position describing the function scope of \p F. - static const IRPosition function(const Function &F) { - return IRPosition(const_cast(F), IRP_FUNCTION); + /// \p CBContext is used for call base specific analysis. + static const IRPosition function(const Function &F, + const CallBaseContext *CBContext = nullptr) { + return IRPosition(const_cast(F), IRP_FUNCTION, CBContext); } /// Create a position describing the returned value of \p F. - static const IRPosition returned(const Function &F) { - return IRPosition(const_cast(F), IRP_RETURNED); + /// \p CBContext is used for call base specific analysis. + static const IRPosition returned(const Function &F, + const CallBaseContext *CBContext = nullptr) { + return IRPosition(const_cast(F), IRP_RETURNED, CBContext); } /// Create a position describing the argument \p Arg. - static const IRPosition argument(const Argument &Arg) { - return IRPosition(const_cast(Arg), IRP_ARGUMENT); + /// \p CBContext is used for call base specific analysis. + static const IRPosition argument(const Argument &Arg, + const CallBaseContext *CBContext = nullptr) { + return IRPosition(const_cast(Arg), IRP_ARGUMENT, CBContext); } /// Create a position describing the function scope of \p CB. @@ -305,16 +315,20 @@ /// If \p IRP is a call site (see isAnyCallSitePosition()) then the result /// will be a call site position, otherwise the function position of the /// associated function. - static const IRPosition function_scope(const IRPosition &IRP) { + static const IRPosition + function_scope(const IRPosition &IRP, + const CallBaseContext *CBContext = nullptr) { if (IRP.isAnyCallSitePosition()) { return IRPosition::callsite_function( cast(IRP.getAnchorValue())); } assert(IRP.getAssociatedFunction()); - return IRPosition::function(*IRP.getAssociatedFunction()); + return IRPosition::function(*IRP.getAssociatedFunction(), CBContext); } - bool operator==(const IRPosition &RHS) const { return Enc == RHS.Enc; } + bool operator==(const IRPosition &RHS) const { + return Enc == RHS.Enc && RHS.CBContext == CBContext; + } bool operator!=(const IRPosition &RHS) const { return !(*this == RHS); } /// Return the value this abstract attribute is anchored with. @@ -535,6 +549,19 @@ } } + /// Return the same position without the call base context. + IRPosition stripCallBaseContext() const { + IRPosition Result = *this; + Result.CBContext = nullptr; + return Result; + } + + /// Get the call base context from the position. + const CallBaseContext *getCallBaseContext() const { return CBContext; } + + /// Check if the position has any call base context. + bool hasCallBaseContext() const { return CBContext != nullptr; } + /// Special DenseMap key values. /// ///{ @@ -547,10 +574,15 @@ private: /// Private constructor for special values only! - explicit IRPosition(void *Ptr) { Enc.setFromOpaqueValue(Ptr); } + explicit IRPosition(void *Ptr, const CallBaseContext *CBContext = nullptr) + : CBContext(CBContext) { + Enc.setFromOpaqueValue(Ptr); + } /// IRPosition anchored at \p AnchorVal with kind/argument numbet \p PK. - explicit IRPosition(Value &AnchorVal, Kind PK) { + explicit IRPosition(Value &AnchorVal, Kind PK, + const CallBaseContext *CBContext = nullptr) + : CBContext(CBContext) { switch (PK) { case IRPosition::IRP_INVALID: llvm_unreachable("Cannot create invalid IRP with an anchor value!"); @@ -672,16 +704,27 @@ PointerIntPair Enc; ///} + /// Call base context. Used for callsite specific analysis. + const CallBaseContext *CBContext = nullptr; + /// Return the encoding bits. char getEncodingBits() const { return Enc.getInt(); } }; /// Helper that allows IRPosition as a key in a DenseMap. -template <> struct DenseMapInfo : DenseMapInfo { +template <> struct DenseMapInfo { static inline IRPosition getEmptyKey() { return IRPosition::EmptyKey; } static inline IRPosition getTombstoneKey() { return IRPosition::TombstoneKey; } + static unsigned getHashValue(const IRPosition &IRP) { + return (DenseMapInfo::getHashValue(IRP) << 4) ^ + (DenseMapInfo::getHashValue(IRP.getCallBaseContext())); + } + + static bool isEqual(const IRPosition &a, const IRPosition &b) { + return a == b; + } }; /// A visitor class for IR positions. @@ -1080,8 +1123,22 @@ /// NOTE: ForceUpdate is ignored in any stage other than the update stage. template const AAType & - getOrCreateAAFor(const IRPosition &IRP, const AbstractAttribute *QueryingAA, + getOrCreateAAFor(IRPosition IRP, const AbstractAttribute *QueryingAA, DepClassTy DepClass, bool ForceUpdate = false) { +#ifdef EXPENSIVE_CHECKS + // Don't allow callbase information to leak. + if (auto CBContext = IRP.getCallBaseContext()) { + assert( + ((CBContext->getCalledFunction() == IRP.getAnchorScope() || + QueryingAA || + !QueryingAA.getIRPosition().isAnyCallSitePosition())) && + "non callsite positions are not allowed to propagate CallBaseContext " + "across functions"); + } +#endif + if (!shouldPropagateCallBaseContext(IRP)) + IRP = IRP.stripCallBaseContext(); + if (AAType *AAPtr = lookupAAFor(IRP, QueryingAA, DepClass)) { if (ForceUpdate && Phase == AttributorPhase::UPDATE) updateAA(*AAPtr); @@ -1600,6 +1657,9 @@ const AbstractAttribute *QueryingAA, bool &AllCallSitesKnown); + /// Determine if CallBase context in \p IRP should be propagated. + bool shouldPropagateCallBaseContext(const IRPosition &IRP); + /// Apply all requested function signature rewrites /// (\see registerFunctionSignatureRewrite) and return Changed if the module /// was altered. diff --git a/llvm/lib/Transforms/IPO/Attributor.cpp b/llvm/lib/Transforms/IPO/Attributor.cpp --- a/llvm/lib/Transforms/IPO/Attributor.cpp +++ b/llvm/lib/Transforms/IPO/Attributor.cpp @@ -142,6 +142,11 @@ cl::desc("Print attribute dependencies"), cl::init(false)); +static cl::opt EnableCallSiteSpecific( + "attributor-enable-call-site-specific-deduction", cl::Hidden, + cl::desc("Allow the Attributor to do call site specific analysis"), + cl::init(false)); + /// Logic operators for the change status enum class. /// ///{ @@ -476,6 +481,8 @@ #ifdef EXPENSIVE_CHECKS switch (getPositionKind()) { case IRP_INVALID: + assert((CBContext == nullptr) && + "Invalid position must not have CallBaseContext!"); assert(!Enc.getOpaqueValue() && "Expected a nullptr for an invalid position!"); return; @@ -491,12 +498,16 @@ "Associated value mismatch!"); return; case IRP_CALL_SITE_RETURNED: + assert((CBContext == nullptr) && + "'call site returned' position must not have CallBaseContext!"); assert((isa(getAsValuePtr())) && "Expected call base for 'call site returned' position!"); assert(getAsValuePtr() == &getAssociatedValue() && "Associated value mismatch!"); return; case IRP_CALL_SITE: + assert((CBContext == nullptr) && + "'call site function' position must not have CallBaseContext!"); assert((isa(getAsValuePtr())) && "Expected call base for 'call site function' position!"); assert(getAsValuePtr() == &getAssociatedValue() && @@ -515,6 +526,8 @@ "Associated value mismatch!"); return; case IRP_CALL_SITE_ARGUMENT: { + assert((CBContext == nullptr) && + "'call site argument' position must not have CallBaseContext!"); Use *U = getAsUsePtr(); assert(U && "Expected use for a 'call site argument' position!"); assert(isa(U->getUser()) && @@ -849,6 +862,13 @@ return true; } +bool Attributor::shouldPropagateCallBaseContext(const IRPosition &IRP) { + // TODO: Maintain a cache of Values that are + // on the pathway from a Argument to a Instruction that would effect the + // liveness/return state etc. + return EnableCallSiteSpecific; +} + bool Attributor::checkForAllReturnedValuesAndReturnInsts( function_ref &)> Pred, const AbstractAttribute &QueryingAA) { @@ -1125,6 +1145,9 @@ if (!State.isAtFixpoint()) State.indicateOptimisticFixpoint(); + // We must not manifest Attributes that use Callbase info. + if (AA->hasCallBaseContext()) + continue; // If the state is invalid, we do not try to manifest it. if (!State.isValidState()) continue; @@ -2221,9 +2244,12 @@ raw_ostream &llvm::operator<<(raw_ostream &OS, const IRPosition &Pos) { const Value &AV = Pos.getAssociatedValue(); - return OS << "{" << Pos.getPositionKind() << ":" << AV.getName() << " [" - << Pos.getAnchorValue().getName() << "@" << Pos.getCallSiteArgNo() - << "]}"; + OS << "{" << Pos.getPositionKind() << ":" << AV.getName() << " [" + << Pos.getAnchorValue().getName() << "@" << Pos.getCallSiteArgNo() << "]"; + + if (Pos.hasCallBaseContext()) + OS << "[cb_context:" << *Pos.getCallBaseContext() << "]"; + return OS << "}"; } raw_ostream &llvm::operator<<(raw_ostream &OS, const IntegerRangeState &S) { diff --git a/llvm/lib/Transforms/IPO/AttributorAttributes.cpp b/llvm/lib/Transforms/IPO/AttributorAttributes.cpp --- a/llvm/lib/Transforms/IPO/AttributorAttributes.cpp +++ b/llvm/lib/Transforms/IPO/AttributorAttributes.cpp @@ -431,8 +431,9 @@ /// 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) { +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"); @@ -449,7 +450,7 @@ // Callback for each possibly returned value. auto CheckReturnValue = [&](Value &RV) -> bool { - const IRPosition &RVPos = IRPosition::value(RV); + const IRPosition &RVPos = IRPosition::value(RV, CBContext); const AAType &AA = A.getAAFor(QueryingAA, RVPos, DepClassTy::REQUIRED); LLVM_DEBUG(dbgs() << "[Attributor] RV: " << RV << " AA: " << AA.getAsStr() @@ -472,7 +473,8 @@ /// Helper class for generic deduction: return value -> returned position. template + typename StateType = typename BaseType::StateType, + bool PropagateCallBaseContext = false> struct AAReturnedFromReturnedValues : public BaseType { AAReturnedFromReturnedValues(const IRPosition &IRP, Attributor &A) : BaseType(IRP, A) {} @@ -480,7 +482,9 @@ /// See AbstractAttribute::updateImpl(...). ChangeStatus updateImpl(Attributor &A) override { StateType S(StateType::getBestState(this->getState())); - clampReturnedValueStates(A, *this, S); + 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); @@ -535,17 +539,58 @@ S ^= *T; } -/// Helper class for generic deduction: call site argument -> argument position. +/// 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 auto &AA = A.getAAFor( + QueryingAttribute, IRPosition::callsite_argument(*CBContext, ArgNo), + DepClassTy::REQUIRED); + 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())); + 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); @@ -554,7 +599,8 @@ /// Helper class for generic replication: function returned -> cs returned. template + typename StateType = typename BaseType::StateType, + bool IntroduceCallBaseContext = false> struct AACallSiteReturnedFromReturned : public BaseType { AACallSiteReturnedFromReturned(const IRPosition &IRP, Attributor &A) : BaseType(IRP, A) {} @@ -572,7 +618,13 @@ if (!AssociatedFunction) return S.indicatePessimisticFixpoint(); - IRPosition FnPos = IRPosition::returned(*AssociatedFunction); + CallBase &CBContext = static_cast(this->getAnchorValue()); + if (IntroduceCallBaseContext) + LLVM_DEBUG(dbgs() << "[Attributor] Introducing call base context:" + << CBContext << "\n"); + + IRPosition FnPos = IRPosition::returned( + *AssociatedFunction, IntroduceCallBaseContext ? &CBContext : nullptr); const AAType &AA = A.getAAFor(*this, FnPos, DepClassTy::REQUIRED); return clampStateAndIndicateChange(S, AA.getState()); } @@ -7126,9 +7178,11 @@ struct AAValueConstantRangeArgument final : AAArgumentFromCallSiteArguments< - AAValueConstantRange, AAValueConstantRangeImpl, IntegerRangeState> { + AAValueConstantRange, AAValueConstantRangeImpl, IntegerRangeState, + true /* BridgeCallBaseContext */> { using Base = AAArgumentFromCallSiteArguments< - AAValueConstantRange, AAValueConstantRangeImpl, IntegerRangeState>; + AAValueConstantRange, AAValueConstantRangeImpl, IntegerRangeState, + true /* BridgeCallBaseContext */>; AAValueConstantRangeArgument(const IRPosition &IRP, Attributor &A) : Base(IRP, A) {} @@ -7149,9 +7203,14 @@ struct AAValueConstantRangeReturned : AAReturnedFromReturnedValues { - using Base = AAReturnedFromReturnedValues; + AAValueConstantRangeImpl, + AAValueConstantRangeImpl::StateType, + /* PropogateCallBaseContext */ true> { + using Base = + AAReturnedFromReturnedValues; AAValueConstantRangeReturned(const IRPosition &IRP, Attributor &A) : Base(IRP, A) {} @@ -7221,12 +7280,14 @@ return false; auto &LHSAA = A.getAAFor( - *this, IRPosition::value(*LHS), DepClassTy::REQUIRED); + *this, IRPosition::value(*LHS, getCallBaseContext()), + DepClassTy::REQUIRED); QuerriedAAs.push_back(&LHSAA); auto LHSAARange = LHSAA.getAssumedConstantRange(A, CtxI); auto &RHSAA = A.getAAFor( - *this, IRPosition::value(*RHS), DepClassTy::REQUIRED); + *this, IRPosition::value(*RHS, getCallBaseContext()), + DepClassTy::REQUIRED); QuerriedAAs.push_back(&RHSAA); auto RHSAARange = RHSAA.getAssumedConstantRange(A, CtxI); @@ -7249,8 +7310,9 @@ if (!OpV.getType()->isIntegerTy()) return false; - auto &OpAA = A.getAAFor(*this, IRPosition::value(OpV), - DepClassTy::REQUIRED); + auto &OpAA = A.getAAFor( + *this, IRPosition::value(OpV, getCallBaseContext()), + DepClassTy::REQUIRED); QuerriedAAs.push_back(&OpAA); T.unionAssumed( OpAA.getAssumed().castOp(CastI->getOpcode(), getState().getBitWidth())); @@ -7268,12 +7330,12 @@ return false; auto &LHSAA = A.getAAFor( - *this, IRPosition::value(*LHS), DepClassTy::REQUIRED); + *this, IRPosition::value(*LHS, getCallBaseContext()), + DepClassTy::REQUIRED); QuerriedAAs.push_back(&LHSAA); auto &RHSAA = A.getAAFor( - *this, IRPosition::value(*RHS), DepClassTy::REQUIRED); - QuerriedAAs.push_back(&RHSAA); - + *this, IRPosition::value(*RHS, getCallBaseContext()), + DepClassTy::REQUIRED); auto LHSAARange = LHSAA.getAssumedConstantRange(A, CtxI); auto RHSAARange = RHSAA.getAssumedConstantRange(A, CtxI); @@ -7402,10 +7464,16 @@ struct AAValueConstantRangeCallSiteReturned : AACallSiteReturnedFromReturned { + AAValueConstantRangeImpl, + AAValueConstantRangeImpl::StateType, + /* IntroduceCallBaseContext */ true> { AAValueConstantRangeCallSiteReturned(const IRPosition &IRP, Attributor &A) : AACallSiteReturnedFromReturned(IRP, A) {} + AAValueConstantRangeImpl, + AAValueConstantRangeImpl::StateType, + /* IntroduceCallBaseContext */ true>(IRP, + A) { + } /// See AbstractAttribute::initialize(...). void initialize(Attributor &A) override { diff --git a/llvm/test/Transforms/Attributor/cb_range_disabled.ll b/llvm/test/Transforms/Attributor/cb_range_disabled.ll new file mode 100644 --- /dev/null +++ b/llvm/test/Transforms/Attributor/cb_range_disabled.ll @@ -0,0 +1,132 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function-signature --scrub-attributes +; call site specific analysis is disabled + +; RUN: opt -attributor -enable-new-pm=0 -attributor-manifest-internal -attributor-max-iterations-verify -attributor-annotate-decl-cs -attributor-max-iterations=1 -S < %s | FileCheck %s --check-prefixes=CHECK,NOT_CGSCC_NPM,NOT_CGSCC_OPM,NOT_TUNIT_NPM,IS__TUNIT____,IS________OPM,IS__TUNIT_OPM + +; RUN: opt -aa-pipeline=basic-aa -passes=attributor -attributor-manifest-internal -attributor-max-iterations-verify -attributor-annotate-decl-cs -attributor-max-iterations=1 -S < %s | FileCheck %s --check-prefixes=CHECK,NOT_CGSCC_OPM,NOT_CGSCC_NPM,NOT_TUNIT_OPM,IS__TUNIT____,IS________NPM,IS__TUNIT_NPM + +; RUN: opt -attributor-cgscc -enable-new-pm=0 -attributor-manifest-internal -attributor-annotate-decl-cs -S < %s | FileCheck %s --check-prefixes=CHECK,NOT_TUNIT_NPM,NOT_TUNIT_OPM,NOT_CGSCC_NPM,IS__CGSCC____,IS________OPM,IS__CGSCC_OPM + +; RUN: opt -aa-pipeline=basic-aa -passes=attributor-cgscc -attributor-manifest-internal -attributor-annotate-decl-cs -S < %s | FileCheck %s --check-prefixes=CHECK,NOT_TUNIT_NPM,NOT_TUNIT_OPM,NOT_CGSCC_OPM,IS__CGSCC____,IS________NPM,IS__CGSCC_NPM + +define i32 @test_range(i32 %unknown) { +; CHECK-LABEL: define {{[^@]+}}@test_range +; CHECK-SAME: (i32 [[UNKNOWN:%.*]]) [[ATTR0:#.*]] { +; CHECK-NEXT: [[TMP1:%.*]] = icmp sgt i32 [[UNKNOWN]], 100 +; CHECK-NEXT: [[TMP2:%.*]] = select i1 [[TMP1]], i32 100, i32 0 +; CHECK-NEXT: ret i32 [[TMP2]] +; + %1 = icmp sgt i32 %unknown, 100 + %2 = select i1 %1, i32 100, i32 0 + ret i32 %2 +} + +define i32 @test1(i32 %unknown, i32 %b) { +; IS__TUNIT____-LABEL: define {{[^@]+}}@test1 +; IS__TUNIT____-SAME: (i32 [[UNKNOWN:%.*]], i32 [[B:%.*]]) [[ATTR0:#.*]] { +; IS__TUNIT____-NEXT: [[TMP1:%.*]] = call i32 @test_range(i32 [[UNKNOWN]]) [[ATTR0]], [[RNG0:!range !.*]] +; IS__TUNIT____-NEXT: [[TMP2:%.*]] = sub nsw i32 [[TMP1]], [[B]] +; IS__TUNIT____-NEXT: ret i32 [[TMP2]] +; +; IS__CGSCC____-LABEL: define {{[^@]+}}@test1 +; IS__CGSCC____-SAME: (i32 [[UNKNOWN:%.*]], i32 [[B:%.*]]) [[ATTR0:#.*]] { +; IS__CGSCC____-NEXT: [[TMP1:%.*]] = call i32 @test_range(i32 [[UNKNOWN]]) [[ATTR1:#.*]], [[RNG0:!range !.*]] +; IS__CGSCC____-NEXT: [[TMP2:%.*]] = sub nsw i32 [[TMP1]], [[B]] +; IS__CGSCC____-NEXT: ret i32 [[TMP2]] +; + %1 = call i32 @test_range(i32 %unknown) + %2 = sub nsw i32 %1, %b + ret i32 %2 +} + +define i32 @test2(i32 %unknown, i32 %b) { +; IS__TUNIT____-LABEL: define {{[^@]+}}@test2 +; IS__TUNIT____-SAME: (i32 [[UNKNOWN:%.*]], i32 [[B:%.*]]) [[ATTR0]] { +; IS__TUNIT____-NEXT: [[TMP1:%.*]] = call i32 @test_range(i32 [[UNKNOWN]]) [[ATTR0]], [[RNG0]] +; IS__TUNIT____-NEXT: [[TMP2:%.*]] = add nsw i32 [[TMP1]], [[B]] +; IS__TUNIT____-NEXT: ret i32 [[TMP2]] +; +; IS__CGSCC____-LABEL: define {{[^@]+}}@test2 +; IS__CGSCC____-SAME: (i32 [[UNKNOWN:%.*]], i32 [[B:%.*]]) [[ATTR0]] { +; IS__CGSCC____-NEXT: [[TMP1:%.*]] = call i32 @test_range(i32 [[UNKNOWN]]) [[ATTR1]], [[RNG0]] +; IS__CGSCC____-NEXT: [[TMP2:%.*]] = add nsw i32 [[TMP1]], [[B]] +; IS__CGSCC____-NEXT: ret i32 [[TMP2]] +; + %1 = call i32 @test_range(i32 %unknown) + %2 = add nsw i32 %1, %b + ret i32 %2 +} + +; Positive checks + +define i32 @test1_pcheck(i32 %unknown) { +; NOT_CGSCC_NPM-LABEL: define {{[^@]+}}@test1_pcheck +; NOT_CGSCC_NPM-SAME: (i32 [[UNKNOWN:%.*]]) [[ATTR0:#.*]] { +; NOT_CGSCC_NPM-NEXT: [[TMP1:%.*]] = call i32 @test1(i32 [[UNKNOWN]], i32 noundef 20) +; NOT_CGSCC_NPM-NEXT: [[TMP2:%.*]] = icmp sle i32 [[TMP1]], 90 +; NOT_CGSCC_NPM-NEXT: [[TMP3:%.*]] = zext i1 [[TMP2]] to i32 +; NOT_CGSCC_NPM-NEXT: ret i32 [[TMP3]] +; +; IS__CGSCC_NPM-LABEL: define {{[^@]+}}@test1_pcheck +; IS__CGSCC_NPM-SAME: (i32 [[UNKNOWN:%.*]]) [[ATTR0:#.*]] { +; IS__CGSCC_NPM-NEXT: [[TMP1:%.*]] = call i32 @test1(i32 [[UNKNOWN]], i32 noundef 20) [[ATTR1:#.*]], [[RNG1:!range !.*]] +; IS__CGSCC_NPM-NEXT: [[TMP2:%.*]] = icmp sle i32 [[TMP1]], 90 +; IS__CGSCC_NPM-NEXT: [[TMP3:%.*]] = zext i1 [[TMP2]] to i32 +; IS__CGSCC_NPM-NEXT: ret i32 [[TMP3]] +; + %1 = call i32 @test1(i32 %unknown, i32 20) + %2 = icmp sle i32 %1, 90 + %3 = zext i1 %2 to i32 + ret i32 %3 +} + +define i32 @test2_pcheck(i32 %unknown) { +; CHECK-LABEL: define {{[^@]+}}@test2_pcheck +; CHECK-SAME: (i32 [[UNKNOWN:%.*]]) [[ATTR0]] { +; CHECK-NEXT: [[TMP1:%.*]] = call i32 @test2(i32 [[UNKNOWN]], i32 noundef 20) +; CHECK-NEXT: [[TMP2:%.*]] = icmp sge i32 [[TMP1]], 20 +; CHECK-NEXT: [[TMP3:%.*]] = zext i1 [[TMP2]] to i32 +; CHECK-NEXT: ret i32 [[TMP3]] +; + %1 = call i32 @test2(i32 %unknown, i32 20) + %2 = icmp sge i32 %1, 20 + %3 = zext i1 %2 to i32 + ret i32 %3 +} + +; Negative checks + +define i32 @test1_ncheck(i32 %unknown) { +; NOT_CGSCC_NPM-LABEL: define {{[^@]+}}@test1_ncheck +; NOT_CGSCC_NPM-SAME: (i32 [[UNKNOWN:%.*]]) [[ATTR0]] { +; NOT_CGSCC_NPM-NEXT: [[TMP1:%.*]] = call i32 @test1(i32 [[UNKNOWN]], i32 noundef 20) +; NOT_CGSCC_NPM-NEXT: [[TMP2:%.*]] = icmp sle i32 [[TMP1]], 10 +; NOT_CGSCC_NPM-NEXT: [[TMP3:%.*]] = zext i1 [[TMP2]] to i32 +; NOT_CGSCC_NPM-NEXT: ret i32 [[TMP3]] +; +; IS__CGSCC_NPM-LABEL: define {{[^@]+}}@test1_ncheck +; IS__CGSCC_NPM-SAME: (i32 [[UNKNOWN:%.*]]) [[ATTR0]] { +; IS__CGSCC_NPM-NEXT: [[TMP1:%.*]] = call i32 @test1(i32 [[UNKNOWN]], i32 noundef 20) [[ATTR1]], [[RNG1]] +; IS__CGSCC_NPM-NEXT: [[TMP2:%.*]] = icmp sle i32 [[TMP1]], 10 +; IS__CGSCC_NPM-NEXT: [[TMP3:%.*]] = zext i1 [[TMP2]] to i32 +; IS__CGSCC_NPM-NEXT: ret i32 [[TMP3]] +; + %1 = call i32 @test1(i32 %unknown, i32 20) + %2 = icmp sle i32 %1, 10 + %3 = zext i1 %2 to i32 + ret i32 %3 +} + +define i32 @test2_ncheck(i32 %unknown) { +; CHECK-LABEL: define {{[^@]+}}@test2_ncheck +; CHECK-SAME: (i32 [[UNKNOWN:%.*]]) [[ATTR0]] { +; CHECK-NEXT: [[TMP1:%.*]] = call i32 @test2(i32 [[UNKNOWN]], i32 noundef 20) +; CHECK-NEXT: [[TMP2:%.*]] = icmp sge i32 [[TMP1]], 30 +; CHECK-NEXT: [[TMP3:%.*]] = zext i1 [[TMP2]] to i32 +; CHECK-NEXT: ret i32 [[TMP3]] +; + %1 = call i32 @test2(i32 %unknown, i32 20) + %2 = icmp sge i32 %1, 30 + %3 = zext i1 %2 to i32 + ret i32 %3 +} diff --git a/llvm/test/Transforms/Attributor/cb_range_enabled.ll b/llvm/test/Transforms/Attributor/cb_range_enabled.ll new file mode 100644 --- /dev/null +++ b/llvm/test/Transforms/Attributor/cb_range_enabled.ll @@ -0,0 +1,126 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function-signature --scrub-attributes +; call site specific analysis is enabled + +; RUN: opt -attributor -enable-new-pm=0 -attributor-enable-call-site-specific-deduction=true -attributor-manifest-internal -attributor-max-iterations-verify -attributor-annotate-decl-cs -attributor-max-iterations=1 -S < %s | FileCheck %s --check-prefixes=CHECK,NOT_CGSCC_NPM,NOT_CGSCC_OPM,NOT_TUNIT_NPM,IS__TUNIT____,IS________OPM,IS__TUNIT_OPM + +; RUN: opt -aa-pipeline=basic-aa -passes=attributor -attributor-enable-call-site-specific-deduction=true -attributor-manifest-internal -attributor-max-iterations-verify -attributor-annotate-decl-cs -attributor-max-iterations=1 -S < %s | FileCheck %s --check-prefixes=CHECK,NOT_CGSCC_OPM,NOT_CGSCC_NPM,NOT_TUNIT_OPM,IS__TUNIT____,IS________NPM,IS__TUNIT_NPM + +; RUN: opt -attributor-cgscc -attributor-enable-call-site-specific-deduction=true -enable-new-pm=0 -attributor-manifest-internal -attributor-annotate-decl-cs -S < %s | FileCheck %s --check-prefixes=CHECK,NOT_TUNIT_NPM,NOT_TUNIT_OPM,NOT_CGSCC_NPM,IS__CGSCC____,IS________OPM,IS__CGSCC_OPM + +; RUN: opt -aa-pipeline=basic-aa -passes=attributor-cgscc -attributor-enable-call-site-specific-deduction=true -attributor-manifest-internal -attributor-annotate-decl-cs -S < %s | FileCheck %s --check-prefixes=CHECK,NOT_TUNIT_NPM,NOT_TUNIT_OPM,NOT_CGSCC_OPM,IS__CGSCC____,IS________NPM,IS__CGSCC_NPM + +define i32 @test_range(i32 %unknown) { +; CHECK-LABEL: define {{[^@]+}}@test_range +; CHECK-SAME: (i32 [[UNKNOWN:%.*]]) [[ATTR0:#.*]] { +; CHECK-NEXT: [[TMP1:%.*]] = icmp sgt i32 [[UNKNOWN]], 100 +; CHECK-NEXT: [[TMP2:%.*]] = select i1 [[TMP1]], i32 100, i32 0 +; CHECK-NEXT: ret i32 [[TMP2]] +; + %1 = icmp sgt i32 %unknown, 100 + %2 = select i1 %1, i32 100, i32 0 + ret i32 %2 +} + +define i32 @test1(i32 %unknown, i32 %b) { +; IS__TUNIT____-LABEL: define {{[^@]+}}@test1 +; IS__TUNIT____-SAME: (i32 [[UNKNOWN:%.*]], i32 [[B:%.*]]) [[ATTR0:#.*]] { +; IS__TUNIT____-NEXT: [[TMP1:%.*]] = call i32 @test_range(i32 [[UNKNOWN]]) [[ATTR0]], [[RNG0:!range !.*]] +; IS__TUNIT____-NEXT: [[TMP2:%.*]] = sub nsw i32 [[TMP1]], [[B]] +; IS__TUNIT____-NEXT: ret i32 [[TMP2]] +; +; IS__CGSCC____-LABEL: define {{[^@]+}}@test1 +; IS__CGSCC____-SAME: (i32 [[UNKNOWN:%.*]], i32 [[B:%.*]]) [[ATTR0:#.*]] { +; IS__CGSCC____-NEXT: [[TMP1:%.*]] = call i32 @test_range(i32 [[UNKNOWN]]) [[ATTR1:#.*]], [[RNG0:!range !.*]] +; IS__CGSCC____-NEXT: [[TMP2:%.*]] = sub nsw i32 [[TMP1]], [[B]] +; IS__CGSCC____-NEXT: ret i32 [[TMP2]] +; + %1 = call i32 @test_range(i32 %unknown) + %2 = sub nsw i32 %1, %b + ret i32 %2 +} + +define i32 @test2(i32 %unknown, i32 %b) { +; IS__TUNIT____-LABEL: define {{[^@]+}}@test2 +; IS__TUNIT____-SAME: (i32 [[UNKNOWN:%.*]], i32 [[B:%.*]]) [[ATTR0]] { +; IS__TUNIT____-NEXT: [[TMP1:%.*]] = call i32 @test_range(i32 [[UNKNOWN]]) [[ATTR0]], [[RNG0]] +; IS__TUNIT____-NEXT: [[TMP2:%.*]] = add nsw i32 [[TMP1]], [[B]] +; IS__TUNIT____-NEXT: ret i32 [[TMP2]] +; +; IS__CGSCC____-LABEL: define {{[^@]+}}@test2 +; IS__CGSCC____-SAME: (i32 [[UNKNOWN:%.*]], i32 [[B:%.*]]) [[ATTR0]] { +; IS__CGSCC____-NEXT: [[TMP1:%.*]] = call i32 @test_range(i32 [[UNKNOWN]]) [[ATTR1]], [[RNG0]] +; IS__CGSCC____-NEXT: [[TMP2:%.*]] = add nsw i32 [[TMP1]], [[B]] +; IS__CGSCC____-NEXT: ret i32 [[TMP2]] +; + %1 = call i32 @test_range(i32 %unknown) + %2 = add nsw i32 %1, %b + ret i32 %2 +} + +; Positive checks + +define i32 @test1_pcheck(i32 %unknown) { +; CHECK-LABEL: define {{[^@]+}}@test1_pcheck +; CHECK-SAME: (i32 [[UNKNOWN:%.*]]) [[ATTR0]] { +; CHECK-NEXT: ret i32 1 +; + %1 = call i32 @test1(i32 %unknown, i32 20) + %2 = icmp sle i32 %1, 90 + %3 = zext i1 %2 to i32 + ret i32 %3 +} + +define i32 @test2_pcheck(i32 %unknown) { +; CHECK-LABEL: define {{[^@]+}}@test2_pcheck +; CHECK-SAME: (i32 [[UNKNOWN:%.*]]) [[ATTR0]] { +; CHECK-NEXT: ret i32 1 +; + %1 = call i32 @test2(i32 %unknown, i32 20) + %2 = icmp sge i32 %1, 20 + %3 = zext i1 %2 to i32 + ret i32 %3 +} + +; Negative checks + +define i32 @test1_ncheck(i32 %unknown) { +; IS__TUNIT____-LABEL: define {{[^@]+}}@test1_ncheck +; IS__TUNIT____-SAME: (i32 [[UNKNOWN:%.*]]) [[ATTR0]] { +; IS__TUNIT____-NEXT: [[TMP1:%.*]] = call i32 @test1(i32 [[UNKNOWN]], i32 noundef 20) [[ATTR0]], [[RNG1:!range !.*]] +; IS__TUNIT____-NEXT: [[TMP2:%.*]] = icmp sle i32 [[TMP1]], 10 +; IS__TUNIT____-NEXT: [[TMP3:%.*]] = zext i1 [[TMP2]] to i32 +; IS__TUNIT____-NEXT: ret i32 [[TMP3]] +; +; IS__CGSCC____-LABEL: define {{[^@]+}}@test1_ncheck +; IS__CGSCC____-SAME: (i32 [[UNKNOWN:%.*]]) [[ATTR0]] { +; IS__CGSCC____-NEXT: [[TMP1:%.*]] = call i32 @test1(i32 [[UNKNOWN]], i32 noundef 20) [[ATTR1]], [[RNG1:!range !.*]] +; IS__CGSCC____-NEXT: [[TMP2:%.*]] = icmp sle i32 [[TMP1]], 10 +; IS__CGSCC____-NEXT: [[TMP3:%.*]] = zext i1 [[TMP2]] to i32 +; IS__CGSCC____-NEXT: ret i32 [[TMP3]] +; + %1 = call i32 @test1(i32 %unknown, i32 20) + %2 = icmp sle i32 %1, 10 + %3 = zext i1 %2 to i32 + ret i32 %3 +} + +define i32 @test2_ncheck(i32 %unknown) { +; IS__TUNIT____-LABEL: define {{[^@]+}}@test2_ncheck +; IS__TUNIT____-SAME: (i32 [[UNKNOWN:%.*]]) [[ATTR0]] { +; IS__TUNIT____-NEXT: [[TMP1:%.*]] = call i32 @test2(i32 [[UNKNOWN]], i32 noundef 20) [[ATTR0]], [[RNG2:!range !.*]] +; IS__TUNIT____-NEXT: [[TMP2:%.*]] = icmp sge i32 [[TMP1]], 30 +; IS__TUNIT____-NEXT: [[TMP3:%.*]] = zext i1 [[TMP2]] to i32 +; IS__TUNIT____-NEXT: ret i32 [[TMP3]] +; +; IS__CGSCC____-LABEL: define {{[^@]+}}@test2_ncheck +; IS__CGSCC____-SAME: (i32 [[UNKNOWN:%.*]]) [[ATTR0]] { +; IS__CGSCC____-NEXT: [[TMP1:%.*]] = call i32 @test2(i32 [[UNKNOWN]], i32 noundef 20) [[ATTR1]], [[RNG2:!range !.*]] +; IS__CGSCC____-NEXT: [[TMP2:%.*]] = icmp sge i32 [[TMP1]], 30 +; IS__CGSCC____-NEXT: [[TMP3:%.*]] = zext i1 [[TMP2]] to i32 +; IS__CGSCC____-NEXT: ret i32 [[TMP3]] +; + %1 = call i32 @test2(i32 %unknown, i32 20) + %2 = icmp sge i32 %1, 30 + %3 = zext i1 %2 to i32 + ret i32 %3 +} diff --git a/llvm/unittests/Transforms/IPO/AttributorTest.cpp b/llvm/unittests/Transforms/IPO/AttributorTest.cpp --- a/llvm/unittests/Transforms/IPO/AttributorTest.cpp +++ b/llvm/unittests/Transforms/IPO/AttributorTest.cpp @@ -21,6 +21,22 @@ namespace llvm { +TEST_F(AttributorTestBase, IRPPositionCallBaseContext) { + const char *ModuleString = R"( + define i32 @foo(i32 %a) { + entry: + ret i32 %a + } + )"; + + parseModule(ModuleString); + + Function *F = M->getFunction("foo"); + IRPosition Pos = IRPosition::function(*F, (const llvm::CallBase *)0xDEADBEEF); + EXPECT_TRUE(Pos.hasCallBaseContext()); + EXPECT_FALSE(Pos.stripCallBaseContext().hasCallBaseContext()); +} + TEST_F(AttributorTestBase, TestCast) { const char *ModuleString = R"( define i32 @foo(i32 %a, i32 %b) {