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 @@ -680,6 +680,36 @@ virtual bool isKnownNoUnwind() const = 0; }; +/// An abstract interface for all nocapture attributes. +struct AANoCapture : public AbstractAttribute { + + /// See AbstractAttribute::AbstractAttribute(...). + AANoCapture(Value &V, InformationCache &InfoCache) + : AbstractAttribute(V, InfoCache) {} + + /// Return true if we know that the underlying value is not captured in its + /// respective scope. + virtual bool isKnownNoCapture() const = 0; + + /// Return true if we assume that the underlying value is not captured in its + /// respective scope. + virtual bool isAssumedNoCapture() const = 0; + + /// Return true if we know that the underlying value is not captured in its + /// respective scope but we allow it to escape through a "return". + virtual bool isKnownNoCaptureMaybeReturned() const = 0; + + /// Return true if we assume that the underlying value is not captured in its + /// respective scope but we allow it to escape through a "return". + virtual bool isAssumedNoCaptureMaybeReturned() const = 0; + + /// See AbstractState::getAttrKind(). + Attribute::AttrKind getAttrKind() const override { return ID; } + + /// The identifier used by the Attributor for this class of attributes. + static constexpr Attribute::AttrKind ID = Attribute::NoCapture; +}; + } // end namespace llvm #endif // LLVM_TRANSFORMS_IPO_FUNCTIONATTRS_H 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 @@ -19,6 +19,7 @@ #include "llvm/ADT/SmallPtrSet.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/Statistic.h" +#include "llvm/Analysis/CaptureTracking.h" #include "llvm/Analysis/GlobalsModRef.h" #include "llvm/IR/Argument.h" #include "llvm/IR/Attributes.h" @@ -49,6 +50,9 @@ STATISTIC(NumFnArgumentReturned, "Number of function arguments marked returned"); +STATISTIC(NumFnArgumentNoCapture, + "Number of function arguments marked no-capture"); + // TODO: Determine a good default value. // // In the LLVM-TS and SPEC2006, 32 seems to not induce compile time overheads @@ -99,6 +103,9 @@ case Attribute::Returned: NumFnArgumentReturned++; return; + case Attribute::NoCapture: + NumFnArgumentNoCapture++; + return; default: return; } @@ -719,6 +726,342 @@ return Changed; } +/// ----------------------- Variable Capturing --------------------------------- + +/// A class to hold the state of for no-capture attributes. +struct AANoCaptureImpl : public AANoCapture, IntegerState { + + /// State encoding bits. A set bit in the state means the property holds. + /// NO_CAPTURE is the best possible state, 0 the worst possible state. + enum { + NOT_CAPTURED_IN_MEM = 1 << 0, + NOT_CAPTURED_IN_INT = 1 << 1, + NOT_CAPTURED_IN_RET = 1 << 2, + + /// If we do not capture the value in memory or through integers we can only + /// communicate it back as a derived pointer. + NO_CAPTURE_MAYBE_RETURNED = NOT_CAPTURED_IN_MEM | NOT_CAPTURED_IN_INT, + + /// If we do not capture the value in memory, through integers, or as a + /// derived pointer we know it is not captured. + NO_CAPTURE = + NOT_CAPTURED_IN_MEM | NOT_CAPTURED_IN_INT | NOT_CAPTURED_IN_RET, + }; + + /// Constructor that takes the value this attribute is associated with (\p V) + /// as well as the function this attribute is related to. + AANoCaptureImpl(Value &V, InformationCache &InfoCache) + : AANoCapture(V, InfoCache), IntegerState(NO_CAPTURE) { + assert(getAssumed() == NO_CAPTURE); + } + + /// See AbstractAttribute::initialize(...). + void initialize(Attributor &A) override { + Value &V = *getAssociatedValue(); + + // If the value in questions is unused it is not captured. + if (V.getNumUses() == 0) { + indicateOptimisticFixpoint(); + return; + } + + // Check what state the enclosing function can actually capture state. + Function &F = getAnchorScope(); + determineFunctionCaptureCapabilities(F, *this); + } + + /// See AANoCapture::isKnownNoCapture(). + bool isKnownNoCapture() const override { return getKnown() == NO_CAPTURE; } + + /// See AANoCapture::isAssumedNoCapture(...). + bool isAssumedNoCapture() const override { return isAssumed(NO_CAPTURE); } + + /// See AANoCapture::isKnownNoCaptureMaybeReturned(...). + bool isKnownNoCaptureMaybeReturned() const override { + return isKnown(NO_CAPTURE_MAYBE_RETURNED); + } + + /// See AANoCapture::isAssumedNoCaptureMaybeReturned(...). + bool isAssumedNoCaptureMaybeReturned() const override { + return isAssumed(NO_CAPTURE_MAYBE_RETURNED); + } + + /// see AbstractAttribute::isAssumedNoCaptureMaybeReturned(...). + virtual void + getDeducedAttributes(SmallVectorImpl &Attrs) const override { + if (!isAssumedNoCaptureMaybeReturned()) + return; + + LLVMContext &Ctx = AnchoredVal.getContext(); + if (isAssumedNoCapture()) + Attrs.emplace_back(Attribute::get(Ctx, Attribute::NoCapture)); + else + Attrs.emplace_back(Attribute::get(Ctx, "no-capture-maybe-returned")); + } + + /// Set the NOT_CAPTURED_IN_MEM and NOT_CAPTURED_IN_RET bits in \p Known + /// depending on the ability of the function \p F to capture state in memory + /// and through "returning/throwing", respectively. + static void determineFunctionCaptureCapabilities(Function &F, + IntegerState &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. + if (F.onlyReadsMemory() && F.doesNotThrow() && + F.getReturnType()->isVoidTy()) { + State.addKnownBits(NO_CAPTURE); + return; + } + + // A function cannot capture state in memory if it only reads memory. + if (F.onlyReadsMemory()) + State.addKnownBits(NOT_CAPTURED_IN_MEM); + + // A function cannot communicate state back if it does not through + // exceptions and doesn not return values. + if (F.doesNotThrow() && F.getReturnType()->isVoidTy()) + State.addKnownBits(NOT_CAPTURED_IN_RET); + } + + /// See AbstractAttribute::getState() + ///{ + AbstractState &getState() override { return *this; } + const AbstractState &getState() const override { return *this; } + ///} + + /// See AbstractState::getAsStr(). + const std::string getAsStr() 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"; + } +}; + +/// Attributor-aware capture tracker. +struct AACaptureUseTracker final : public CaptureTracker { + + /// Create a capture tracker that can lookup in-flight abstract attributes + /// through the attributor \p A. + /// + /// If a use leads to a potential capture, \p CapturedInMemory is set and the + /// search is stopped. If a use leads to a return instruction, + /// \p CommunicatedBack is set to true and \p CapturedInMemory is not changed. + /// If a use leads to a ptr2int which may capute the value, + /// \p CapturedInInteger is set. If a use is found that is currently assumed + /// "no-capture-maybe-returned", the user is added to the \p PotentialCopies + /// set. All values in \p PotentialCopies are later tracked aswell. For every + /// explored use we decrement \p RemainingUsesToExplore. Once it reaches 0, + /// the search is stopped with \p CapturedInMemory and \p CapturedInInteger + /// conservatively set to true. + AACaptureUseTracker(Attributor &A, AANoCapture &NoCaptureAA, + IntegerState &State, + SmallVectorImpl &PotentialCopies, + unsigned &RemainingUsesToExplore) + : A(A), NoCaptureAA(NoCaptureAA), State(State), + PotentialCopies(PotentialCopies), + RemainingUsesToExplore(RemainingUsesToExplore) {} + + /// Determine if \p V maybe captured. *Also updates the state!* + bool valueMayBeCaptured(const Value *V) { + if (V->getType()->isPointerTy()) { + PointerMayBeCaptured(V, this); + } else if (isa(V) || V->getNumUses() > 0) + State.removeAssumedBits(AANoCaptureImpl::NOT_CAPTURED_IN_INT); + return State.isAssumed(AANoCaptureImpl::NO_CAPTURE_MAYBE_RETURNED); + } + + /// See CaptureTracker::tooManyUses(). + void tooManyUses() override { + State.removeAssumedBits(AANoCaptureImpl::NO_CAPTURE); + } + + /// See CaptureTracker::captured(...). + bool captured(const Use *U) override { + LLVM_DEBUG(errs() << "Check use: " << *U->get() << " in " << *U->getUser() + << "\n"); + + // Because we may reuse the tracker multiple times we keep track of the + // number of explored uses ourselves as well. + if (RemainingUsesToExplore-- == 0) { + LLVM_DEBUG(errs() << " - too many uses to explore\n"); + return isCapturedIn(/* Memory */ true, /* Integer */ true, + /* Return */ true); + } + + // Deal with ptr2int by following uses. + if (isa(U->getUser())) { + LLVM_DEBUG(errs() << " - delegate to ptr2int users!\n"); + return valueMayBeCaptured(U->getUser()); + } + + // Explicitly catch return instructions. + if (isa(U->getUser())) + return isCapturedIn(/* Memory */ false, /* Integer */ 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. + CallSite CS(U->getUser()); + if (!CS || !CS.isArgOperand(U)) + return isCapturedIn(/* Memory */ true, /* Integer */ true, + /* Return */ true); + + // If we do not know the called function we have to assume the use captures. + if (!CS.getCalledFunction()) + return isCapturedIn(/* Memory */ true, /* Integer */ true, + /* Return */ true); + + // If the called function cannot capture state, nothing is captured. + Function &F = *CS.getCalledFunction(); + + // Check what we know about the callee already from the IR. If that suffices + // to justify no-caputre(-in-memory) we take it. Note that a similar + // reasoning is applied for assumed capture capabilities but implicitly + // through the recursive use of a AANoCapture attribute below. + IntegerState KnownCaptureInfo; + AANoCaptureImpl::determineFunctionCaptureCapabilities(F, KnownCaptureInfo); + if (KnownCaptureInfo.isKnown(AANoCaptureImpl::NO_CAPTURE)) + return isCapturedIn(/* Memory */ false, /* Integer */ false, + /* Return */ false); + if (KnownCaptureInfo.isKnown(AANoCaptureImpl::NO_CAPTURE_MAYBE_RETURNED)) { + addPotentialCopyIfNecessary(CS); + return isCapturedIn(/* Memory */ false, /* Integer */ false, + /* Return */ false); + } + + unsigned ArgNo = CS.getArgumentNo(U); + // Exclude var-arg arguments. + if (F.arg_size() > 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! + auto *ArgNoCaptureAA = A.getAAFor(NoCaptureAA, F, ArgNo); + if (ArgNoCaptureAA && ArgNoCaptureAA->getState().isValidState()) { + if (ArgNoCaptureAA->isAssumedNoCapture()) + return isCapturedIn(/* Memory */ false, /* Integer */ false, + /* Return */ false); + if (ArgNoCaptureAA->isAssumedNoCaptureMaybeReturned()) { + addPotentialCopyIfNecessary(CS); + return isCapturedIn(/* Memory */ false, /* Integer */ false, + /* Return */ false); + } + } + + // Check for an existing attribute to justify no-capture for this use. + if (F.getAttributes().hasParamAttr(ArgNo, "no-capture-maybe-returned")) { + addPotentialCopyIfNecessary(CS); + return isCapturedIn(/* Memory */ false, /* Integer */ false, + /* Return */ false); + } + } + + // Lastly, we could not find a reason no-capture can be assumed so we don't. + return isCapturedIn(/* Memory */ true, /* Integer */ true, + /* Return */ true); + } + + /// Register \p CS as potential copy of the value we are checking. + void addPotentialCopyIfNecessary(CallSite CS) { + PotentialCopies.push_back(CS.getInstruction()); + } + + /// See CaptureTracker::shouldExplore(...). + bool shouldExplore(const Use *U) override { return true; } + + /// Update the state according to \p CapturedInMem, \p CapturedInInt, and + /// \p CapturedInRet, then return the appropriate value for use in the + /// CaptureTracker::captured() interface. + bool isCapturedIn(bool CapturedInMem, bool CapturedInInt, + bool CapturedInRet) { + LLVM_DEBUG(dbgs() << " - captures [Mem " << CapturedInMem << "|Int " + << CapturedInInt << "|Ret " << CapturedInRet << "]\n"); + if (CapturedInMem) + State.removeAssumedBits(AANoCaptureImpl::NOT_CAPTURED_IN_MEM); + if (CapturedInInt) + State.removeAssumedBits(AANoCaptureImpl::NOT_CAPTURED_IN_INT); + if (CapturedInRet) + State.removeAssumedBits(AANoCaptureImpl::NOT_CAPTURED_IN_RET); + return !State.isAssumed(AANoCaptureImpl::NO_CAPTURE_MAYBE_RETURNED); + } + +private: + /// The attributor providing in-flight abstract attributes. + Attributor &A; + + /// The abstract attribute currently updated. + AANoCapture &NoCaptureAA; + + /// The state currently updated. + IntegerState &State; + + /// Set of potential copies of the tracked value. + SmallVectorImpl &PotentialCopies; + + /// Global counter to limit the number of explored uses. + unsigned &RemainingUsesToExplore; +}; + +/// An AA to represent the no-capture argument attribute. +struct AANoCaptureArgument final : public AANoCaptureImpl { + + /// See AANoCaptureImpl::AANoCaptureImpl(...). + AANoCaptureArgument(Argument &Arg, InformationCache &InfoCache) + : AANoCaptureImpl(Arg, InfoCache) {} + + /// See AbstractAttribute::initialize(...). + void initialize(Attributor &A) override { + Argument &Arg = cast(*getAssociatedValue()); + if (Arg.hasAttribute(Attribute::NoCapture)) + indicateOptimisticFixpoint(); + } + + /// See AbstractAttribute::updateImpl(Attributor &A). + virtual ChangeStatus updateImpl(Attributor &A) override; + + /// See AbstractAttribute::getManifestPosition(). + virtual ManifestPosition getManifestPosition() const override { + return MP_ARGUMENT; + } +}; + +ChangeStatus AANoCaptureArgument::updateImpl(Attributor &A) { + Argument &Arg = cast(*getAssociatedValue()); + + // The current assumed state used to determine a change. + auto AssumedState = getAssumed(); + + // TODO: Once we have memory behavior attributes we should use them here + // similar to the reasoning in + // AANoCaptureImpl::determineFunctionCaptureCapabilities(...). + + // TODO: Use the AAReturnedValues to learn if the argument can return or not. + + SmallVector PotentialCopies; + unsigned RemainingUsesToExplore = DefaultMaxUsesToExplore; + + // Use the CaptureTracker interface and logic with the specialized tracker, + // defined in AACaptureUseTracker, that can look at in-flight abstract + // attributes and directly updates the assumed state. + AACaptureUseTracker Tracker(A, *this, *this, PotentialCopies, + RemainingUsesToExplore); + + // Check all potential copies of the associated value until we can assume none + // will be captured or we have to assume at least one might be. + unsigned Idx = 0; + PotentialCopies.push_back(&Arg); + while (isAssumedNoCaptureMaybeReturned() && Idx < PotentialCopies.size()) + Tracker.valueMayBeCaptured(PotentialCopies[Idx++]); + + return (AssumedState == getAssumed()) ? ChangeStatus::UNCHANGED + : ChangeStatus::CHANGED; +} + /// ---------------------------------------------------------------------------- /// Attributor /// ---------------------------------------------------------------------------- @@ -873,6 +1216,17 @@ registerAA(*new AAReturnedValuesImpl(F, InfoCache)); } + // For each argument we check if we can derive attributes. + for (Argument &Arg : F.args()) { + + // So far only pointer arguments are interesting. However, "returned" + // is also derived but as a "function return attribute" (see above). + if (Arg.getType()->isPointerTy()) { + if (!Whitelist || Whitelist->count(AANoCaptureArgument::ID)) + registerAA(*new AANoCaptureArgument(Arg, InfoCache)); + } + } + // Walk all instructions to find more attribute opportunities and also // interesting instructions that might be queried by abstract attributes // during their initialization or update. diff --git a/llvm/test/Transforms/FunctionAttrs/2009-01-02-LocalStores.ll b/llvm/test/Transforms/FunctionAttrs/2009-01-02-LocalStores.ll --- a/llvm/test/Transforms/FunctionAttrs/2009-01-02-LocalStores.ll +++ b/llvm/test/Transforms/FunctionAttrs/2009-01-02-LocalStores.ll @@ -1,5 +1,5 @@ -; RUN: opt < %s -functionattrs -S | FileCheck %s -; RUN: opt < %s -passes=function-attrs -S | FileCheck %s +; RUN: opt < %s -attributor-disable=false -attributor -functionattrs -S | FileCheck %s +; RUN: opt < %s -attributor-disable=false -passes="attributor,cgscc(function-attrs)" -S | FileCheck %s ; CHECK: define i32* @a(i32** nocapture readonly %p) define i32* @a(i32** %p) { diff --git a/llvm/test/Transforms/FunctionAttrs/arg_nocapture.ll b/llvm/test/Transforms/FunctionAttrs/arg_nocapture.ll --- a/llvm/test/Transforms/FunctionAttrs/arg_nocapture.ll +++ b/llvm/test/Transforms/FunctionAttrs/arg_nocapture.ll @@ -12,9 +12,8 @@ ; return p == 0; ; } ; -; FIXME: no-capture missing for %p -; CHECK: define i32 @is_null_return(i32* readnone %p) -define i32 @is_null_return(i32* %p) #0 { +; CHECK: define i32 @is_null_return(i32* nocapture readnone dereferenceable(1) %p) +define i32 @is_null_return(i32* dereferenceable(1) %p) #0 { entry: %cmp = icmp eq i32* %p, null %conv = zext i1 %cmp to i32 @@ -31,9 +30,8 @@ ; return 0; ; } ; -; FIXME: no-capture missing for %p -; CHECK: define i32 @is_null_control(i32* readnone %p) -define i32 @is_null_control(i32* %p) #0 { +; CHECK: define i32 @is_null_control(i32* nocapture readnone dereferenceable_or_null(1) %p) +define i32 @is_null_control(i32* dereferenceable_or_null(1) %p) #0 { entry: %retval = alloca i32, align 4 %cmp = icmp eq i32* %p, null @@ -44,7 +42,7 @@ br label %return if.end: ; preds = %entry - %cmp1 = icmp eq i32* null, %p + %cmp1 = icmp eq i32* %p, null br i1 %cmp1, label %if.then2, label %if.end3 if.then2: ; preds = %if.end @@ -112,15 +110,15 @@ ; TEST SCC with various calls, casts, and comparisons agains NULL ; -; FIXME: no-capture missing for %a -; CHECK: define float* @scc_A(i32* readnone returned %a) +; FIXME: "no-capture-maybe-returned" missing for %a in all functions because +; dereferenceable_or_null is not propagated to the return value of scc_C. ; -; FIXME: no-capture missing for %a -; CHECK: define i64* @scc_B(double* readnone returned %a) +; CHECK: define float* @scc_A(i32* readnone returned dereferenceable_or_null(1) %a) +; +; CHECK: define i64* @scc_B(double* readnone returned dereferenceable_or_null(1) %a) ; ; FIXME: readnone missing for %s -; FIXME: no-capture missing for %a -; CHECK: define i8* @scc_C(i16* returned %a) +; CHECK: define i8* @scc_C(i16* returned dereferenceable_or_null(1) %a) ; ; float *scc_A(int *a) { ; return (float*)(a ? (int*)scc_A((int*)scc_B((double*)scc_C((short*)a))) : a); @@ -133,7 +131,7 @@ ; void *scc_C(short *a) { ; return scc_A((int*)(scc_C(a) ? scc_B((double*)a) : scc_C(a))); ; } -define float* @scc_A(i32* %a) { +define float* @scc_A(i32* dereferenceable_or_null(1) %a) { entry: %tobool = icmp ne i32* %a, null br i1 %tobool, label %cond.true, label %cond.false @@ -157,7 +155,7 @@ ret float* %4 } -define i64* @scc_B(double* %a) { +define i64* @scc_B(double* dereferenceable_or_null(1) %a) { entry: %tobool = icmp ne double* %a, null br i1 %tobool, label %cond.true, label %cond.false @@ -181,7 +179,7 @@ ret i64* %4 } -define i8* @scc_C(i16* %a) { +define i8* @scc_C(i16* dereferenceable_or_null(1) %a) { entry: %call = call i8* @scc_C(i16* %a) %tobool = icmp ne i8* %call, null @@ -246,7 +244,7 @@ ; } ; ; There should *not* be a no-capture attribute on %a -; CHECK: define i64* @not_captured_but_returned_0(i64* returned %a) +; CHECK: define i64* @not_captured_but_returned_0(i64* returned "no-capture-maybe-returned" %a) define i64* @not_captured_but_returned_0(i64* %a) #0 { entry: store i64 0, i64* %a, align 8 @@ -260,8 +258,9 @@ ; return a + 1; ; } ; -; There should *not* be a no-capture attribute on %a -; CHECK: define nonnull i64* @not_captured_but_returned_1(i64* %a) +; There should *not* be a no-capture attribute on %a, no-capture-maybe-returned is fine though. +; +; CHECK: define nonnull i64* @not_captured_but_returned_1(i64* "no-capture-maybe-returned" %a) define i64* @not_captured_but_returned_1(i64* %a) #0 { entry: %add.ptr = getelementptr inbounds i64, i64* %a, i64 1 @@ -276,8 +275,7 @@ ; not_captured_but_returned_1(a); ; } ; -; FIXME: no-capture missing for %a -; CHECK: define void @test_not_captured_but_returned_calls(i64* %a) +; CHECK: define void @test_not_captured_but_returned_calls(i64* nocapture %a) define void @test_not_captured_but_returned_calls(i64* %a) #0 { entry: %call = call i64* @not_captured_but_returned_0(i64* %a) @@ -291,8 +289,8 @@ ; return not_captured_but_returned_0(a); ; } ; -; There should *not* be a no-capture attribute on %a -; CHECK: define i64* @negative_test_not_captured_but_returned_call_0a(i64* returned %a) +; There should *not* be a no-capture attribute on %a, no-capture-maybe-returned is fine though. +; CHECK: define i64* @negative_test_not_captured_but_returned_call_0a(i64* returned "no-capture-maybe-returned" %a) define i64* @negative_test_not_captured_but_returned_call_0a(i64* %a) #0 { entry: %call = call i64* @not_captured_but_returned_0(i64* %a) @@ -321,8 +319,8 @@ ; return not_captured_but_returned_1(a); ; } ; -; There should *not* be a no-capture attribute on %a -; CHECK: define nonnull i64* @negative_test_not_captured_but_returned_call_1a(i64* %a) +; There should *not* be a no-capture attribute on %a, no-capture-maybe-returned is fine though. +; CHECK: define nonnull i64* @negative_test_not_captured_but_returned_call_1a(i64* "no-capture-maybe-returned" %a) define i64* @negative_test_not_captured_but_returned_call_1a(i64* %a) #0 { entry: %call = call i64* @not_captured_but_returned_1(i64* %a) @@ -353,13 +351,18 @@ ; return unknown(); ; } ; -; Verify we do *not* assume b is returned or not captured. +; Verify we do *not* assume b is returned or not captured, no-capture-maybe-returned is fine though. +; +; FNATTR: define i32* @ret_arg_or_unknown(i32* dereferenceable_or_null(1) %b) +; FNATTR: define i32* @ret_arg_or_unknown_through_phi(i32* dereferenceable_or_null(1) %b) +; ATTRIBUTOR: define i32* @ret_arg_or_unknown(i32* dereferenceable_or_null(1) "no-capture-maybe-returned" %b) +; ATTRIBUTOR: define i32* @ret_arg_or_unknown_through_phi(i32* dereferenceable_or_null(1) "no-capture-maybe-returned" %b) +; BOTH: define i32* @ret_arg_or_unknown(i32* dereferenceable_or_null(1) "no-capture-maybe-returned" %b) +; BOTH: define i32* @ret_arg_or_unknown_through_phi(i32* dereferenceable_or_null(1) "no-capture-maybe-returned" %b) ; -; CHECK: define i32* @ret_arg_or_unknown(i32* readnone %b) -; CHECK: define i32* @ret_arg_or_unknown_through_phi(i32* readnone %b) declare i32* @unknown() -define i32* @ret_arg_or_unknown(i32* %b) #0 { +define i32* @ret_arg_or_unknown(i32* dereferenceable_or_null(1) %b) #0 { entry: %cmp = icmp eq i32* %b, null br i1 %cmp, label %ret_arg, label %ret_unknown @@ -392,7 +395,9 @@ ; TEST not captured by readonly external function ; -; CHECK: define void @not_captured_by_readonly_call(i32* nocapture %b) +; ATTRIBUTOR: define i32* @readonly(i32* "no-capture-maybe-returned") +; ATTRIBUTOR: define void @not_captured_by_readonly_call(i32* nocapture %b) +; declare i32* @readonly_unknown(i32*, i32*) readonly define void @not_captured_by_readonly_call(i32* %b) #0 { @@ -407,13 +412,13 @@ ; Make sure the returned flag on %r is strong enough to justify nocapture on %b but **not** on %r. ; ; FIXME: The "returned" information is not propagated to the fullest extend causing us to miss "nocapture" on %b in the following: -; CHECK: define i32* @not_captured_by_readonly_call_not_returned_either1(i32* readonly %b, i32* readonly returned %r) +; ATTRIBUTOR: define i32* @not_captured_by_readonly_call_not_returned_either1(i32* "no-capture-maybe-returned" %b, i32* returned "no-capture-maybe-returned" %r) #0 { ; -; CHECK: define i32* @not_captured_by_readonly_call_not_returned_either2(i32* readonly %b, i32* readonly returned %r) -; CHECK: define i32* @not_captured_by_readonly_call_not_returned_either3(i32* readonly %b, i32* readonly returned %r) +; ATTRIBUTOR: define i32* @not_captured_by_readonly_call_not_returned_either2(i32* nocapture %b, i32* returned "no-capture-maybe-returned" %r) #0 { +; ATTRIBUTOR: define i32* @not_captured_by_readonly_call_not_returned_either3(i32* nocapture %b, i32* returned "no-capture-maybe-returned" %r) #0 { ; ; FIXME: The "nounwind" information is not derived to the fullest extend causing us to miss "nocapture" on %b in the following: -; CHECK: define i32* @not_captured_by_readonly_call_not_returned_either4(i32* readonly %b, i32* readonly returned %r) +; ATTRIBUTOR: define i32* @not_captured_by_readonly_call_not_returned_either4(i32* "no-capture-maybe-returned" %b, i32* returned "no-capture-maybe-returned" %r) #0 { define i32* @not_captured_by_readonly_call_not_returned_either1(i32* %b, i32* returned %r) #0 { entry: %call = call i32* @readonly_unknown(i32* %b, i32* %r) nounwind diff --git a/llvm/test/Transforms/FunctionAttrs/arg_returned.ll b/llvm/test/Transforms/FunctionAttrs/arg_returned.ll --- a/llvm/test/Transforms/FunctionAttrs/arg_returned.ll +++ b/llvm/test/Transforms/FunctionAttrs/arg_returned.ll @@ -160,7 +160,7 @@ ; TEST SCC test returning a pointer value argument ; ; BOTH: Function Attrs: noinline norecurse nounwind readnone uwtable -; BOTH-NEXT: define double* @ptr_sink_r0(double* readnone returned %r) +; BOTH-NEXT: define double* @ptr_sink_r0(double* readnone returned "no-capture-maybe-returned" %r) ; BOTH: Function Attrs: noinline nounwind readnone uwtable ; BOTH-NEXT: define double* @ptr_scc_r1(double* %a, double* readnone returned %r, double* nocapture readnone %b) ; BOTH: Function Attrs: noinline nounwind readnone uwtable @@ -170,8 +170,8 @@ ; FNATTR: define double* @ptr_scc_r1(double* %a, double* readnone %r, double* nocapture readnone %b) ; FNATTR: define double* @ptr_scc_r2(double* readnone %a, double* readnone %b, double* readnone %r) ; -; ATTRIBUTOR: define double* @ptr_sink_r0(double* returned %r) -; ATTRIBUTOR: define double* @ptr_scc_r1(double* %a, double* returned %r, double* %b) +; ATTRIBUTOR: define double* @ptr_sink_r0(double* returned "no-capture-maybe-returned" %r) +; ATTRIBUTOR: define double* @ptr_scc_r1(double* %a, double* returned %r, double* nocapture %b) ; ATTRIBUTOR: define double* @ptr_scc_r2(double* %a, double* %b, double* returned %r) ; ; double* ptr_scc_r1(double* a, double* b, double* r); @@ -259,7 +259,7 @@ ; FIXME: no-return missing ; FNATTR: define i32* @rt0(i32* readonly %a) ; BOTH: Function Attrs: noinline nounwind readonly uwtable -; BOTH-NEXT: define i32* @rt0(i32* readonly returned %a) +; BOTH-NEXT: define i32* @rt0(i32* readonly returned "no-capture-maybe-returned" %a) define i32* @rt0(i32* %a) #0 { entry: %v = load i32, i32* %a, align 4 @@ -293,7 +293,7 @@ ; FNATTR: define i32* @rt2_helper(i32* %a) ; FNATTR: define i32* @rt2(i32* readnone %a, i32* readnone %b) ; BOTH: define i32* @rt2_helper(i32* %a) -; BOTH: define i32* @rt2(i32* readnone %a, i32* readnone %b) +; BOTH: define i32* @rt2(i32* readnone %a, i32* readnone "no-capture-maybe-returned" %b) define i32* @rt2_helper(i32* %a) #0 { entry: %call = call i32* @rt2(i32* %a, i32* %a) @@ -316,17 +316,17 @@ ; TEST another SCC test ; -; FNATTR: define i32* @rt3_helper(i32* %a, i32* %b) -; FNATTR: define i32* @rt3(i32* readnone %a, i32* readnone %b) -; BOTH: define i32* @rt3_helper(i32* %a, i32* returned %b) -; BOTH: define i32* @rt3(i32* readnone %a, i32* readnone returned %b) +; FNATTR: define i32* @rt3_helper(i32* nocapture readnone %a, i32* %b) +; FNATTR: define i32* @rt3(i32* nocapture readnone dereferenceable(1) %a, i32* readnone %b) +; BOTH: define i32* @rt3_helper(i32* nocapture readnone %a, i32* returned "no-capture-maybe-returned" %b) +; BOTH: define i32* @rt3(i32* nocapture readnone dereferenceable(1) %a, i32* readnone returned "no-capture-maybe-returned" %b) define i32* @rt3_helper(i32* %a, i32* %b) #0 { entry: %call = call i32* @rt3(i32* %a, i32* %b) ret i32* %call } -define i32* @rt3(i32* %a, i32 *%b) #0 { +define i32* @rt3(i32* dereferenceable(1) %a, i32 *%b) #0 { entry: %cmp = icmp eq i32* %a, null br i1 %cmp, label %if.then, label %if.end @@ -352,9 +352,9 @@ ; BOTH: declare void @unknown_fn(i32* (i32*)*) ; ; BOTH: Function Attrs: noinline nounwind uwtable -; BOTH-NEXT: define i32* @calls_unknown_fn(i32* readnone returned %r) +; BOTH-NEXT: define i32* @calls_unknown_fn(i32* readnone returned "no-capture-maybe-returned" %r) ; FNATTR: define i32* @calls_unknown_fn(i32* readnone returned %r) -; ATTRIBUTOR: define i32* @calls_unknown_fn(i32* returned %r) +; ATTRIBUTOR: define i32* @calls_unknown_fn(i32* returned "no-capture-maybe-returned" %r) declare void @unknown_fn(i32* (i32*)*) #0 define i32* @calls_unknown_fn(i32* %r) #0 { @@ -498,10 +498,9 @@ ; } ; ; BOTH: Function Attrs: noinline norecurse nounwind readnone uwtable -; BOTH-NEXT: define double* @bitcast(i32* readnone returned %b) -; +; BOTH-NEXT: define double* @bitcast(i32* readnone returned "no-capture-maybe-returned" %b) ; FNATTR: define double* @bitcast(i32* readnone %b) -; ATTRIBUTOR: define double* @bitcast(i32* returned %b) +; ATTRIBUTOR: define double* @bitcast(i32* returned "no-capture-maybe-returned" %b) define double* @bitcast(i32* %b) #0 { entry: %bc0 = bitcast i32* %b to double* @@ -555,7 +554,7 @@ ; } ; ; BOTH: Function Attrs: noinline norecurse nounwind readnone uwtable -; BOTH-NEXT: define double* @ret_arg_arg_undef(i32* readnone returned %b) +; BOTH-NEXT: define double* @ret_arg_arg_undef(i32* readnone returned %b) ; ; FNATTR: define double* @ret_arg_arg_undef(i32* readnone %b) ; ATTRIBUTOR: define double* @ret_arg_arg_undef(i32* returned %b) @@ -655,7 +654,7 @@ ; int* ret_arg_or_unknown(int* b) { ; if (b == 0) ; return b; -; return unknown(); +; return unknown(b); ; } ; ; Verify we do not assume b is returned diff --git a/llvm/test/Transforms/FunctionAttrs/convergent.ll b/llvm/test/Transforms/FunctionAttrs/convergent.ll --- a/llvm/test/Transforms/FunctionAttrs/convergent.ll +++ b/llvm/test/Transforms/FunctionAttrs/convergent.ll @@ -1,8 +1,8 @@ ; FIXME: convert CHECK-INDIRECT into CHECK (and remove -check-prefixes) as soon ; FIXME: as new-pass-manager's handling of indirect_non_convergent_call is fixed ; -; RUN: opt -functionattrs -S < %s | FileCheck %s --check-prefixes=CHECK,CHECK-INDIRECT -; RUN: opt -passes=function-attrs -S < %s | FileCheck %s +; RUN: opt -attributor-disable=false -attributor -functionattrs -S < %s | FileCheck %s --check-prefixes=CHECK,CHECK-INDIRECT +; RUN: opt -attributor-disable=false -passes="attributor,cgscc(function-attrs)" -S < %s | FileCheck %s ; CHECK: Function Attrs ; CHECK-NOT: convergent diff --git a/llvm/test/Transforms/FunctionAttrs/incompatible_fn_attrs.ll b/llvm/test/Transforms/FunctionAttrs/incompatible_fn_attrs.ll --- a/llvm/test/Transforms/FunctionAttrs/incompatible_fn_attrs.ll +++ b/llvm/test/Transforms/FunctionAttrs/incompatible_fn_attrs.ll @@ -1,26 +1,26 @@ -; RUN: opt -S -o - -functionattrs %s | FileCheck %s -; RUN: opt -S -o - -passes=function-attrs %s | FileCheck %s +; RUN: opt -S -o - -attributor -attributor-disable=false -functionattrs %s | FileCheck %s +; RUN: opt -S -o - -attributor-disable=false -passes="attributor,cgscc(function-attrs)" %s | FileCheck %s ; Verify we remove argmemonly/inaccessiblememonly/inaccessiblemem_or_argmemonly ; function attributes when we derive readnone. ; Function Attrs: argmemonly define i32* @given_argmem_infer_readnone(i32* %p) #0 { -; CHECK: define i32* @given_argmem_infer_readnone(i32* readnone returned %p) #0 { +; CHECK: define i32* @given_argmem_infer_readnone(i32* readnone returned "no-capture-maybe-returned" %p) #0 { entry: ret i32* %p } ; Function Attrs: inaccessiblememonly define i32* @given_inaccessible_infer_readnone(i32* %p) #1 { -; CHECK: define i32* @given_inaccessible_infer_readnone(i32* readnone returned %p) #0 { +; CHECK: define i32* @given_inaccessible_infer_readnone(i32* readnone returned "no-capture-maybe-returned" %p) #0 { entry: ret i32* %p } ; Function Attrs: inaccessiblemem_or_argmemonly define i32* @given_inaccessible_or_argmem_infer_readnone(i32* %p) #2 { -; CHECK: define i32* @given_inaccessible_or_argmem_infer_readnone(i32* readnone returned %p) #0 { +; CHECK: define i32* @given_inaccessible_or_argmem_infer_readnone(i32* readnone returned "no-capture-maybe-returned" %p) #0 { entry: ret i32* %p } diff --git a/llvm/test/Transforms/FunctionAttrs/nocapture.ll b/llvm/test/Transforms/FunctionAttrs/nocapture.ll --- a/llvm/test/Transforms/FunctionAttrs/nocapture.ll +++ b/llvm/test/Transforms/FunctionAttrs/nocapture.ll @@ -1,9 +1,11 @@ -; RUN: opt < %s -functionattrs -S | FileCheck %s -; RUN: opt < %s -passes=function-attrs -S | FileCheck %s +; RUN: opt < %s -attributor-disable=false -attributor -S | FileCheck %s --check-prefix=ATTRIBUTOR +; RUN: opt < %s -attributor-disable=false -attributor -functionattrs -S | FileCheck %s +; RUN: opt < %s -attributor-disable=false -passes='attributor,cgscc(function-attrs)' -S | FileCheck %s --check-prefix=NPM @g = global i32* null ; [#uses=1] -; CHECK: define i32* @c1(i32* readnone returned %q) +; CHECK: define i32* @c1(i32* readnone returned "no-capture-maybe-returned" %q) +; ATTRIBUTOR: define i32* @c1(i32* returned "no-capture-maybe-returned" %q) define i32* @c1(i32* %q) { ret i32* %q } @@ -94,6 +96,7 @@ } ; CHECK: define i32 @nc1_addrspace(i32* %q, i32 addrspace(1)* nocapture %p, i1 %b) +; ATTRIBUTOR: define i32 @nc1_addrspace(i32* %q, i32 addrspace(1)* nocapture %p, i1 %b) define i32 @nc1_addrspace(i32* %q, i32 addrspace(1)* %p, i1 %b) { e: br label %l @@ -109,32 +112,42 @@ } ; CHECK: define void @nc2(i32* nocapture %p, i32* %q) +; ATTRIBUTOR: define void @nc2(i32* nocapture %p, i32* %q) define void @nc2(i32* %p, i32* %q) { %1 = call i32 @nc1(i32* %q, i32* %p, i1 0) ; [#uses=0] ret void } ; CHECK: define void @nc3(void ()* nocapture %p) +; ATTRIBUTOR: define void @nc3(void ()* nocapture %p) define void @nc3(void ()* %p) { call void %p() ret void } declare void @external(i8*) readonly nounwind -; CHECK: define void @nc4(i8* nocapture readonly %p) + +; FIXME: Because nocapture and readonly deduction in the functionattrs pass are interleaved, it doesn't +; trigger when nocapture is already present. Once the Attributor derives memory behavior, +; this should be fixed. +; CHECK: define void @nc4(i8* nocapture %p) +; ATTRIBUTOR: define void @nc4(i8* nocapture %p) +; NPM: define void @nc4(i8* nocapture readonly %p) define void @nc4(i8* %p) { call void @external(i8* %p) ret void } ; CHECK: define void @nc5(void (i8*)* nocapture %f, i8* nocapture %p) +; ATTRIBUTOR: define void @nc5(void (i8*)* nocapture %f, i8* nocapture %p) define void @nc5(void (i8*)* %f, i8* %p) { call void %f(i8* %p) readonly nounwind call void %f(i8* nocapture %p) ret void } -; CHECK: define void @test1_1(i8* nocapture readnone %x1_1, i8* %y1_1) +; CHECK: define void @test1_1(i8* nocapture %x1_1, i8* nocapture %y1_1) +; ATTRIBUTOR: define void @test1_1(i8* nocapture %x1_1, i8* nocapture %y1_1) ; It would be acceptable to add readnone to %y1_1 and %y1_2. define void @test1_1(i8* %x1_1, i8* %y1_1) { call i8* @test1_2(i8* %x1_1, i8* %y1_1) @@ -142,7 +155,8 @@ ret void } -; CHECK: define i8* @test1_2(i8* nocapture readnone %x1_2, i8* returned %y1_2) +; CHECK: define i8* @test1_2(i8* nocapture %x1_2, i8* returned "no-capture-maybe-returned" %y1_2) +; ATTRIBUTOR: define i8* @test1_2(i8* nocapture %x1_2, i8* returned "no-capture-maybe-returned" %y1_2) define i8* @test1_2(i8* %x1_2, i8* %y1_2) { call void @test1_1(i8* %x1_2, i8* %y1_2) store i32* null, i32** @g @@ -150,27 +164,31 @@ } ; CHECK: define void @test2(i8* nocapture readnone %x2) +; ATTRIBUTOR: define void @test2(i8* nocapture %x2) define void @test2(i8* %x2) { call void @test2(i8* %x2) store i32* null, i32** @g ret void } -; CHECK: define void @test3(i8* nocapture readnone %x3, i8* nocapture readnone %y3, i8* nocapture readnone %z3) +; CHECK: define void @test3(i8* nocapture %x3, i8* nocapture readnone %y3, i8* nocapture %z3) +; ATTRIBUTOR: define void @test3(i8* nocapture %x3, i8* nocapture %y3, i8* nocapture %z3) define void @test3(i8* %x3, i8* %y3, i8* %z3) { call void @test3(i8* %z3, i8* %y3, i8* %x3) store i32* null, i32** @g ret void } -; CHECK: define void @test4_1(i8* %x4_1) +; CHECK: define void @test4_1(i8* nocapture readnone %x4_1) +; ATTRIBUTOR: define void @test4_1(i8* nocapture %x4_1) define void @test4_1(i8* %x4_1) { call i8* @test4_2(i8* %x4_1, i8* %x4_1, i8* %x4_1) store i32* null, i32** @g ret void } -; CHECK: define i8* @test4_2(i8* nocapture readnone %x4_2, i8* readnone returned %y4_2, i8* nocapture readnone %z4_2) +; CHECK: define i8* @test4_2(i8* nocapture readnone %x4_2, i8* readnone returned "no-capture-maybe-returned" %y4_2, i8* nocapture readnone %z4_2) +; ATTRIBUTOR: define i8* @test4_2(i8* nocapture %x4_2, i8* returned "no-capture-maybe-returned" %y4_2, i8* nocapture %z4_2) define i8* @test4_2(i8* %x4_2, i8* %y4_2, i8* %z4_2) { call void @test4_1(i8* null) store i32* null, i32** @g @@ -189,6 +207,7 @@ declare void @test6_1(i8* %x6_1, i8* nocapture %y6_1, ...) ; CHECK: define void @test6_2(i8* %x6_2, i8* nocapture %y6_2, i8* %z6_2) +; ATTRIBUTOR: define void @test6_2(i8* %x6_2, i8* nocapture %y6_2, i8* %z6_2) define void @test6_2(i8* %x6_2, i8* %y6_2, i8* %z6_2) { call void (i8*, i8*, ...) @test6_1(i8* %x6_2, i8* %y6_2, i8* %z6_2) store i32* null, i32** @g @@ -196,18 +215,21 @@ } ; CHECK: define void @test_cmpxchg(i32* nocapture %p) +; ATTRIBUTOR: define void @test_cmpxchg(i32* nocapture %p) define void @test_cmpxchg(i32* %p) { cmpxchg i32* %p, i32 0, i32 1 acquire monotonic ret void } ; CHECK: define void @test_cmpxchg_ptr(i32** nocapture %p, i32* %q) +; ATTRIBUTOR: define void @test_cmpxchg_ptr(i32** nocapture %p, i32* %q) define void @test_cmpxchg_ptr(i32** %p, i32* %q) { cmpxchg i32** %p, i32* null, i32* %q acquire monotonic ret void } ; CHECK: define void @test_atomicrmw(i32* nocapture %p) +; ATTRIBUTOR: define void @test_atomicrmw(i32* nocapture %p) define void @test_atomicrmw(i32* %p) { atomicrmw add i32* %p, i32 1 seq_cst ret void @@ -222,6 +244,7 @@ } ; CHECK: nocaptureLaunder(i8* nocapture %p) +; ATTRIBUTOR: nocaptureLaunder(i8* nocapture %p) define void @nocaptureLaunder(i8* %p) { entry: %b = call i8* @llvm.launder.invariant.group.p0i8(i8* %p) @@ -238,6 +261,7 @@ } ; CHECK: @nocaptureStrip(i8* nocapture %p) +; ATTRIBUTOR: @nocaptureStrip(i8* nocapture %p) define void @nocaptureStrip(i8* %p) { entry: %b = call i8* @llvm.strip.invariant.group.p0i8(i8* %p) @@ -260,6 +284,7 @@ } ; CHECK: define i1 @nocaptureInboundsGEPICmp(i32* nocapture readnone %x) +; ATTRIBUTOR: define i1 @nocaptureInboundsGEPICmp(i32* nocapture %x) define i1 @nocaptureInboundsGEPICmp(i32* %x) { %1 = getelementptr inbounds i32, i32* %x, i32 5 %2 = bitcast i32* %1 to i8* @@ -268,6 +293,7 @@ } ; CHECK: define i1 @nocaptureDereferenceableOrNullICmp(i32* nocapture readnone dereferenceable_or_null(4) %x) +; ATTRIBUTOR: define i1 @nocaptureDereferenceableOrNullICmp(i32* nocapture dereferenceable_or_null(4) %x) define i1 @nocaptureDereferenceableOrNullICmp(i32* dereferenceable_or_null(4) %x) { %1 = bitcast i32* %x to i8* %2 = icmp eq i8* %1, null diff --git a/llvm/test/Transforms/FunctionAttrs/nonnull.ll b/llvm/test/Transforms/FunctionAttrs/nonnull.ll --- a/llvm/test/Transforms/FunctionAttrs/nonnull.ll +++ b/llvm/test/Transforms/FunctionAttrs/nonnull.ll @@ -1,5 +1,5 @@ -; RUN: opt -S -functionattrs -enable-nonnull-arg-prop %s | FileCheck %s -; RUN: opt -S -passes=function-attrs -enable-nonnull-arg-prop %s | FileCheck %s +; RUN: opt -S -attributor-disable=false -attributor -functionattrs -enable-nonnull-arg-prop %s | FileCheck %s +; RUN: opt -S -attributor-disable=false -passes="attributor,cgscc(function-attrs)" -enable-nonnull-arg-prop %s | FileCheck %s declare nonnull i8* @ret_nonnull() diff --git a/llvm/test/Transforms/FunctionAttrs/out-of-bounds-iterator-bug.ll b/llvm/test/Transforms/FunctionAttrs/out-of-bounds-iterator-bug.ll --- a/llvm/test/Transforms/FunctionAttrs/out-of-bounds-iterator-bug.ll +++ b/llvm/test/Transforms/FunctionAttrs/out-of-bounds-iterator-bug.ll @@ -1,5 +1,5 @@ -; RUN: opt -functionattrs -S < %s | FileCheck %s -; RUN: opt -passes=function-attrs -S < %s | FileCheck %s +; RUN: opt < %s -attributor-disable=false -attributor -functionattrs -S | FileCheck %s +; RUN: opt < %s -attributor-disable=false -passes="attributor,cgscc(function-attrs)" -S | FileCheck %s ; This checks for an iterator wraparound bug in FunctionAttrs. The previous ; "incorrect" behavior was inferring readonly for the %x argument in @caller. diff --git a/llvm/test/Transforms/FunctionAttrs/read_write_returned_arguments_scc.ll b/llvm/test/Transforms/FunctionAttrs/read_write_returned_arguments_scc.ll --- a/llvm/test/Transforms/FunctionAttrs/read_write_returned_arguments_scc.ll +++ b/llvm/test/Transforms/FunctionAttrs/read_write_returned_arguments_scc.ll @@ -1,5 +1,5 @@ -; RUN: opt -functionattrs -enable-nonnull-arg-prop -attributor -attributor-disable=false -S < %s | FileCheck %s -; RUN: opt -functionattrs -enable-nonnull-arg-prop -attributor -attributor-disable=false -attributor-verify=true -S < %s | FileCheck %s +; RUN: opt -attributor -functionattrs -enable-nonnull-arg-prop -attributor-disable=false -S < %s | FileCheck %s +; RUN: opt -attributor -functionattrs -enable-nonnull-arg-prop -attributor-disable=false -attributor-verify=true -S < %s | FileCheck %s ; ; This is an evolved example to stress test SCC parameter attribute propagation. ; The SCC in this test is made up of the following six function, three of which @@ -18,20 +18,23 @@ ; as well as how the parameters are (transitively) used (n = readnone, ; r = readonly, w = writeonly). ; -; What we should see is something along the lines of: -; 1 - Number of functions marked as norecurse -; 6 - Number of functions marked argmemonly -; 6 - Number of functions marked as nounwind -; 16 - Number of arguments marked nocapture -; 4 - Number of arguments marked readnone -; 6 - Number of arguments marked writeonly -; 6 - Number of arguments marked readonly -; 6 - Number of arguments marked returned +; What we should get is something along the lines of: +; 1 functionattrs - Number of functions marked as norecurse +; 6 functionattrs - Number of functions marked argmemonly +; 6 functionattrs - Number of functions marked as nounwind +; 10 functionattrs - Number of arguments marked nocapture +; 6 functionattrs - Number of arguments marked nocapture-maybe-returned +; 4 functionattrs - Number of arguments marked readnone +; 6 functionattrs - Number of arguments marked writeonly +; 6 functionattrs - Number of arguments marked readonly +; 6 functionattrs - Number of arguments marked returned +; -- +; 51 ; target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" ; CHECK: Function Attrs: nofree nounwind -; CHECK-NEXT: define i32* @external_ret2_nrw(i32* %n0, i32* %r0, i32* returned %w0) +; CHECK-NEXT: define i32* @external_ret2_nrw(i32* nocapture nonnull %n0, i32* nocapture %r0, i32* returned "no-capture-maybe-returned" %w0) define i32* @external_ret2_nrw(i32* %n0, i32* %r0, i32* %w0) { entry: %call = call i32* @internal_ret0_nw(i32* %n0, i32* %w0) @@ -42,8 +45,8 @@ } ; CHECK: Function Attrs: nofree nounwind -; CHECK-NEXT: define internal i32* @internal_ret0_nw(i32* returned %n0, i32* %w0) -define internal i32* @internal_ret0_nw(i32* %n0, i32* %w0) { +; CHECK-NEXT: define internal nonnull i32* @internal_ret0_nw(i32* returned dereferenceable(1) "no-capture-maybe-returned" %n0, i32* nocapture %w0) +define internal i32* @internal_ret0_nw(i32* dereferenceable(1) %n0, i32* %w0) { entry: %r0 = alloca i32, align 4 %r1 = alloca i32, align 4 @@ -71,7 +74,7 @@ } ; CHECK: Function Attrs: nofree nounwind -; CHECK-NEXT: define internal i32* @internal_ret1_rrw(i32* %r0, i32* returned %r1, i32* %w0) +; CHECK-NEXT: define internal i32* @internal_ret1_rrw(i32* nocapture %r0, i32* returned "no-capture-maybe-returned" %r1, i32* nocapture %w0) define internal i32* @internal_ret1_rrw(i32* %r0, i32* %r1, i32* %w0) { entry: %0 = load i32, i32* %r0, align 4 @@ -103,8 +106,8 @@ } ; CHECK: Function Attrs: nofree norecurse nounwind -; CHECK-NEXT: define i32* @external_sink_ret2_nrw(i32* readnone %n0, i32* nocapture readonly %r0, i32* returned %w0) -define i32* @external_sink_ret2_nrw(i32* %n0, i32* %r0, i32* %w0) { +; CHECK-NEXT: define i32* @external_sink_ret2_nrw(i32* nocapture readnone dereferenceable(1) %n0, i32* nocapture readonly %r0, i32* returned "no-capture-maybe-returned" %w0) +define i32* @external_sink_ret2_nrw(i32* dereferenceable(1) %n0, i32* %r0, i32* %w0) { entry: %tobool = icmp ne i32* %n0, null br i1 %tobool, label %if.end, label %if.then @@ -122,8 +125,8 @@ } ; CHECK: Function Attrs: nofree nounwind -; CHECK-NEXT: define internal i32* @internal_ret1_rw(i32* %r0, i32* returned %w0) -define internal i32* @internal_ret1_rw(i32* %r0, i32* %w0) { +; CHECK-NEXT: define internal i32* @internal_ret1_rw(i32* nocapture dereferenceable(1) %r0, i32* returned "no-capture-maybe-returned" %w0) +define internal i32* @internal_ret1_rw(i32* dereferenceable(1) %r0, i32* %w0) { entry: %0 = load i32, i32* %r0, align 4 %tobool = icmp ne i32 %0, 0 @@ -148,7 +151,7 @@ } ; CHECK: Function Attrs: nofree nounwind -; CHECK-NEXT: define i32* @external_source_ret2_nrw(i32* %n0, i32* %r0, i32* returned %w0) +; CHECK-NEXT: define i32* @external_source_ret2_nrw(i32* nocapture nonnull %n0, i32* nocapture %r0, i32* returned "no-capture-maybe-returned" %w0) define i32* @external_source_ret2_nrw(i32* %n0, i32* %r0, i32* %w0) { entry: %call = call i32* @external_sink_ret2_nrw(i32* %n0, i32* %r0, i32* %w0) diff --git a/llvm/test/Transforms/FunctionAttrs/readattrs.ll b/llvm/test/Transforms/FunctionAttrs/readattrs.ll --- a/llvm/test/Transforms/FunctionAttrs/readattrs.ll +++ b/llvm/test/Transforms/FunctionAttrs/readattrs.ll @@ -1,5 +1,5 @@ -; RUN: opt < %s -functionattrs -S | FileCheck %s -; RUN: opt < %s -aa-pipeline=basic-aa -passes='cgscc(function-attrs)' -S | FileCheck %s +; RUN: opt < %s -attributor -attributor-disable=false -functionattrs -S | FileCheck %s +; RUN: opt < %s -attributor-disable=false -aa-pipeline=basic-aa -passes='attributor,cgscc(function-attrs)' -S | FileCheck %s @x = global i32 0 declare void @test1_1(i8* %x1_1, i8* readonly %y1_1, ...) @@ -13,7 +13,7 @@ ret void } -; CHECK: define i8* @test2(i8* readnone returned %p) +; CHECK: define i8* @test2(i8* readnone returned "no-capture-maybe-returned" %p) define i8* @test2(i8* %p) { store i32 0, i32* @x ret i8* %p @@ -55,13 +55,13 @@ ret void } -; CHECK: define i32* @test8_1(i32* readnone returned %p) +; CHECK: define i32* @test8_1(i32* readnone returned "no-capture-maybe-returned" %p) define i32* @test8_1(i32* %p) { entry: ret i32* %p } -; CHECK: define void @test8_2(i32* %p) +; CHECK: define void @test8_2(i32* nocapture %p) define void @test8_2(i32* %p) { entry: %call = call i32* @test8_1(i32* %p) diff --git a/llvm/test/Transforms/FunctionAttrs/readnone.ll b/llvm/test/Transforms/FunctionAttrs/readnone.ll --- a/llvm/test/Transforms/FunctionAttrs/readnone.ll +++ b/llvm/test/Transforms/FunctionAttrs/readnone.ll @@ -1,13 +1,17 @@ -; RUN: opt < %s -functionattrs -S | FileCheck %s -; RUN: opt < %s -passes=function-attrs -S | FileCheck %s +; RUN: opt < %s -attributor-disable=false -attributor -functionattrs -S | FileCheck %s +; RUN: opt < %s -attributor-disable=false -passes="attributor,cgscc(function-attrs)" -S | FileCheck %s -; CHECK: define void @bar(i8* nocapture readnone) +; FIXME: Because nocapture and readnone deduction in the functionattrs pass are interleaved, it doesn't +; trigger when nocapture is already present. Once the Attributor derives memory behavior, +; this should be fixed. +; FIXME: readnone missing for %0 two times +; CHECK: define void @bar(i8* nocapture readonly) define void @bar(i8* readonly) { call void @foo(i8* %0) ret void } -; CHECK: define void @foo(i8* nocapture readnone) +; CHECK: define void @foo(i8* nocapture readonly) define void @foo(i8* readonly) { call void @bar(i8* %0) ret void