diff --git a/clang/test/CodeGenObjC/os_log.m b/clang/test/CodeGenObjC/os_log.m --- a/clang/test/CodeGenObjC/os_log.m +++ b/clang/test/CodeGenObjC/os_log.m @@ -14,7 +14,7 @@ #ifdef __x86_64__ // CHECK-LABEL: define i8* @test_builtin_os_log // CHECK-O0-LABEL: define i8* @test_builtin_os_log -// CHECK: (i8* returned %[[BUF:.*]]) +// CHECK: (i8* returned "no-capture-maybe-returned" %[[BUF:.*]]) // CHECK-O0: (i8* %[[BUF:.*]]) void *test_builtin_os_log(void *buf) { return __builtin_os_log_format(buf, "capabilities: %@", GenString()); diff --git a/clang/test/CodeGenOpenCL/as_type.cl b/clang/test/CodeGenOpenCL/as_type.cl --- a/clang/test/CodeGenOpenCL/as_type.cl +++ b/clang/test/CodeGenOpenCL/as_type.cl @@ -67,7 +67,7 @@ return __builtin_astype(x, int3); } -//CHECK: define spir_func i32 addrspace(1)* @addr_cast(i32* readnone returned %[[x:.*]]) +//CHECK: define spir_func i32 addrspace(1)* @addr_cast(i32* readnone returned "no-capture-maybe-returned" %[[x:.*]]) //CHECK: %[[cast:.*]] = addrspacecast i32* %[[x]] to i32 addrspace(1)* //CHECK: ret i32 addrspace(1)* %[[cast]] global int* addr_cast(int *x) { @@ -81,7 +81,7 @@ return __builtin_astype(x, global int*); } -//CHECK: define spir_func i32 @ptr_to_int(i32* %[[x:.*]]) +//CHECK: define spir_func i32 @ptr_to_int(i32* "no-capture-maybe-returned" %[[x:.*]]) //CHECK: %[[cast:.*]] = ptrtoint i32* %[[x]] to i32 //CHECK: ret i32 %[[cast]] int ptr_to_int(int *x) { 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 @@ -266,6 +266,35 @@ static constexpr Attribute::AttrKind ID = Attribute::Returned; }; +/// An abstract interface for all nocapture attributes. +struct AANoCapture : public AbstractAttribute { + + /// See AbstractAttribute::AbstractAttribute(...). + AANoCapture(Value &V) : AbstractAttribute(V) {} + + /// 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; +}; + /// ---------------------------------------------------------------------------- /// Pass (Manager) Boilerplate /// ---------------------------------------------------------------------------- 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 @@ -20,6 +20,7 @@ #include "llvm/ADT/Statistic.h" #include "llvm/Analysis/CallGraph.h" #include "llvm/Analysis/CallGraphSCCPass.h" +#include "llvm/Analysis/CaptureTracking.h" #include "llvm/Analysis/GlobalsModRef.h" #include "llvm/IR/Argument.h" #include "llvm/IR/Attributes.h" @@ -52,6 +53,9 @@ STATISTIC(NumFnNoReturn, "Number of functions marked noreturn"); +STATISTIC(NumFnArgumentNoCapture, + "Number of function arguments marked no-capture"); + // TODO: Determine a good default value. static cl::opt MaxFixpointIterations("attributor-max-iterations", cl::Hidden, @@ -103,7 +107,6 @@ /// Return the assumed (integer) state. BaseTy getAssumed() const { return Assumed; } -protected: /// Copy the bits set in \p BitsEncoding from \p From to \p To. static void copyBits(BaseTy &To, const BaseTy From, BaseTy BitsEncoding) { BaseTy UnaffectedBits = To & (~BitsEncoding); @@ -133,6 +136,7 @@ return (Source & BitsEncoding) == BitsEncoding; } +protected: /// The known state encoding in an integer of type BaseTy. BaseTy Known; @@ -155,6 +159,9 @@ case Attribute::Returned: NumFnArgumentReturned++; return; + case Attribute::NoCapture: + NumFnArgumentNoCapture++; + return; case Attribute::NoReturn: NumFnNoReturn++; return; @@ -257,7 +264,6 @@ llvm_unreachable("Expected enum or string attribute!"); } - ChangeStatus AbstractAttribute::update(Attributor &A) { ChangeStatus Changed = ChangeStatus::UNCHANGED; if (getState().isAtFixpoint()) @@ -783,6 +789,396 @@ } }; +/// ----------------------- 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_COMMUNICATED_BACK = 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_COMMUNICATED_BACK, + }; + + /// 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) : AANoCapture(V) { + 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) { + indicateFixpoint(/* Optimistic */ true); + return; + } + + // Check what state the enclosing function can actually capture state. + Function &F = getAnchorScope(); + determineFunctionCaptureCapabilities(F, Known); + } + + /// See AANoCapture::isKnownNoCapture(). + bool isKnownNoCapture() const override { return getKnown() == NO_CAPTURE; } + + /// See AANoCapture::isAssumedNoCapture(...). + bool isAssumedNoCapture() const override { + return testBits(getAssumed(), NO_CAPTURE); + } + + /// See AANoCapture::isKnownNoCaptureMaybeReturned(...). + bool isKnownNoCaptureMaybeReturned() const override { + return testBits(getKnown(), NO_CAPTURE_MAYBE_RETURNED); + } + + /// See AANoCapture::isAssumedNoCaptureMaybeReturned(...). + bool isAssumedNoCaptureMaybeReturned() const override { + return testBits(getAssumed(), 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_COMMUNICATED_BACK 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, + decltype(Known) &Known) { + // 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()) { + setBits(Known, NO_CAPTURE); + return; + } + + // A function cannot capture state in memory if it only reads memory. + if (F.onlyReadsMemory()) + setBits(Known, 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()) + setBits(Known, NOT_COMMUNICATED_BACK); + } + + /// 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, AbstractAttribute &QueryingAA, + bool &CapturedInMemory, bool &CapturedInInteger, + bool &CommunicatedBack, + SmallVectorImpl &PotentialCopies, + unsigned &RemainingUsesToExplore) + : A(A), QueryingAA(QueryingAA), CapturedInMemory(CapturedInMemory), + CapturedInInteger(CapturedInInteger), + CommunicatedBack(CommunicatedBack), PotentialCopies(PotentialCopies), + RemainingUsesToExplore(RemainingUsesToExplore) {} + + /// Determine if \p V maybe captured. *Also updated CapturedInMemory or + /// CapturedInInteger!* + bool valueMayBeCaptured(const Value *V) { + if (V->getType()->isPointerTy()) { + PointerMayBeCaptured(V, this); + } else { + for (const Use &U : V->uses()) { + if (!captured(&U)) + continue; + CapturedInInteger = true; + } + if (isa(V)) + CapturedInInteger = true; + } + + return CapturedInMemory | CapturedInInteger; + } + + /// See CaptureTracker::tooManyUses(). + void tooManyUses() override { CapturedInMemory = CapturedInInteger = true; } + + /// 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 shouldContinueTraversal(false); + } + + // 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 shouldContinueTraversal(true, /* Maybe handed back */ true); + + // Special case for comparisons agains null. We consider them as + // non-capturing. + // TODO: This should only be valid if 0 is not a valid address. + if (ICmpInst *ICmp = dyn_cast(U->getUser())) { + unsigned Idx = (ICmp->getOperand(0) == U->get()); + if (isa(ICmp->getOperand(Idx))) + if (cast(ICmp->getOperand(Idx))->isNullValue()) + return shouldContinueTraversal(true); + return shouldContinueTraversal(false); + } + + // 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 shouldContinueTraversal(false); + + unsigned ArgNo = CS.getArgumentNo(U); + // TODO: This might be checked in the tracker already. + if (CS.paramHasAttr(ArgNo, Attribute::NoCapture)) + return shouldContinueTraversal(true); + + // If we do not know the called function we have to assume the use captures. + if (!CS.getCalledFunction()) + return shouldContinueTraversal(false); + + // 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. + char KnownCaptureInfo = 0; + AANoCaptureImpl::determineFunctionCaptureCapabilities(F, KnownCaptureInfo); + if (AANoCaptureImpl::testBits(KnownCaptureInfo, + AANoCaptureImpl::NO_CAPTURE)) + return shouldContinueTraversal(true); + if (AANoCaptureImpl::testBits(KnownCaptureInfo, + AANoCaptureImpl::NO_CAPTURE_MAYBE_RETURNED)) { + addPotentialCopyIfNecessary(U, CS); + return shouldContinueTraversal(true); + } + + // 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(QueryingAA, F, ArgNo); + if (ArgNoCaptureAA) { + if (ArgNoCaptureAA->isAssumedNoCapture()) + return shouldContinueTraversal(true); + if (ArgNoCaptureAA->isAssumedNoCaptureMaybeReturned()) { + addPotentialCopyIfNecessary(U, CS); + return shouldContinueTraversal(true); + } + } + + // Check for an existing attribute to justify no-capture for this use. + if (F.getAttributes().hasParamAttr(ArgNo, "no-capture-maybe-returned")) { + addPotentialCopyIfNecessary(U, CS); + return shouldContinueTraversal(true); + } + } + + // Lastly, we could not find a reason no-capture can be assumed so we don't. + return shouldContinueTraversal(false); + } + + /// Register \p CS as potential copy if the returned/thrown value might be + /// derived from the use value \p U. + void addPotentialCopyIfNecessary(const Use *U, CallSite CS) { + Function *F = CS.getCalledFunction(); + + // If "returned" is present in the IR we use it. + if (Value *RetArgOp = CS.getReturnedArgOperand()) { + if (RetArgOp == U->get()) + PotentialCopies.push_back(CS.getInstruction()); + return; + } + + // If this is a var-args argument we have to be conservative. + unsigned ArgNo = CS.getArgumentNo(U); + if (!F || F->arg_size() <= ArgNo) { + PotentialCopies.push_back(CS.getInstruction()); + return; + } + + // Use the returned values abstract attribute to determine if the + // assocated argument might be returned or not. + const AAReturnedValues *RVAA = A.getAAFor(QueryingAA, *F); + if (RVAA && RVAA->getState().isValidState()) + if (!RVAA->maybeReturned(F->arg_begin() + ArgNo)) + return; + + // By default we look at the call site users as well. + PotentialCopies.push_back(CS.getInstruction()); + } + + /// See CaptureTracker::shouldExplore(...). + bool shouldExplore(const Use *U) override { return true; } + + /// Update the state according to \p ShouldContinue and return the appropriate + /// value for use in the CaptureTracker::captured() interface. + /// The \p MaybeCommunicated flag indicate if the use allows the value to be + /// "returned/thrown" out of the current scope. + bool shouldContinueTraversal(bool ShouldContinue, + bool MaybeCommunicated = false) { + LLVM_DEBUG(dbgs() << " - " << (ShouldContinue ? "won't" : "might") + << " capture" + << (MaybeCommunicated ? ", but maybe communicated back\n" + : "\n")); + CapturedInMemory = !ShouldContinue; + CommunicatedBack |= MaybeCommunicated; + return CapturedInMemory | CapturedInInteger; + } + +private: + /// The attributor providing in-flight abstract attributes. + Attributor &A; + + /// The abstract attribute currently updated. + AbstractAttribute &QueryingAA; + + /// Flag indicating if capturing through memory is possible. + bool &CapturedInMemory; + + /// Flag indicating if capturing through integers is possible. + bool &CapturedInInteger; + + /// Flag indicating if capturing through communication (return/throw) is + /// possible. + bool &CommunicatedBack; + + /// 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) : AANoCaptureImpl(Arg) { + if (Arg.hasAttribute(Attribute::NoCapture)) + indicateFixpoint(/* Optimistic */ true); + } + + /// 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) { + // 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(...). + + bool CapturedInInteger = false; + bool CapturedInMemory = false; + bool CommunicatedBack = false; + 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, CapturedInMemory, CapturedInInteger, + CommunicatedBack, 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(getAssociatedValue()); + while (!CapturedInMemory && !CapturedInInteger && + Idx < PotentialCopies.size()) + Tracker.valueMayBeCaptured(PotentialCopies[Idx++]); + + // We cannot derive useful information if the value may have escaped into + // memory or an integer (we couldn't track furthr). + if (CapturedInMemory | CapturedInInteger) { + indicateFixpoint(/* Optimistic */ false); + return ChangeStatus::CHANGED; + } + + // If the uses indicate that value might be communicated back, have to give up + // on the "not-communicated-back" bit. + if (CommunicatedBack) + setBits(unsetBits(Assumed, NOT_COMMUNICATED_BACK), Known); + + return (AssumedState == getAssumed()) ? ChangeStatus::UNCHANGED + : ChangeStatus::CHANGED; +} + /// ---------------------------------------------------------------------------- /// Attributor /// ---------------------------------------------------------------------------- @@ -975,6 +1371,16 @@ // Every function might be "no-return". registerAA(*new AANoReturnFunction(F)); + // 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()) { + registerAA(*new AANoCaptureArgument(Arg)); + } + } + // Walk all instructions to find more attribute opportunities and also // interesting instructions that might be querried by abstract attributes // during their initialziation 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 @@ -15,7 +15,7 @@ ret i32* %tmp } -; CHECK: define i32* @c(i32* readnone returned %r) +; CHECK: define i32* @c(i32* readnone returned "no-capture-maybe-returned" %r) @g = global i32 0 define i32* @c(i32 *%r) { %a = icmp eq i32* %r, null diff --git a/llvm/test/Transforms/FunctionAttrs/SCC1.ll b/llvm/test/Transforms/FunctionAttrs/SCC1.ll --- a/llvm/test/Transforms/FunctionAttrs/SCC1.ll +++ b/llvm/test/Transforms/FunctionAttrs/SCC1.ll @@ -35,7 +35,8 @@ ; 1 functionattrs - Number of functions marked as norecurse ; 6 functionattrs - Number of functions marked argmemonly ; 6 functionattrs - Number of functions marked as nounwind -; 16 functionattrs - Number of arguments marked nocapture +; 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 @@ -45,7 +46,7 @@ ; target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" -; CHECK: define dso_local i32* @external_ret2_nrw(i32* %n0, i32* %r0, i32* returned %w0) #[[NOUNWIND:[0-9]*]] +; CHECK: define dso_local i32* @external_ret2_nrw(i32* nocapture %n0, i32* nocapture %r0, i32* returned "no-capture-maybe-returned" %w0) #[[NOUNWIND:[0-9]*]] define dso_local i32* @external_ret2_nrw(i32* %n0, i32* %r0, i32* %w0) { entry: %call = call i32* @internal_ret0_nw(i32* %n0, i32* %w0) @@ -55,7 +56,7 @@ ret i32* %call3 } -; CHECK: define internal i32* @internal_ret0_nw(i32* returned %n0, i32* %w0) #[[NOUNWIND]] +; CHECK: define internal i32* @internal_ret0_nw(i32* returned "no-capture-maybe-returned" %n0, i32* nocapture %w0) #[[NOUNWIND]] define internal i32* @internal_ret0_nw(i32* %n0, i32* %w0) { entry: %r0 = alloca i32, align 4 @@ -83,7 +84,7 @@ ret i32* %retval.0 } -; CHECK: define internal i32* @internal_ret1_rrw(i32* %r0, i32* returned %r1, i32* %w0) #[[NOUNWIND]] +; CHECK: define internal i32* @internal_ret1_rrw(i32* nocapture %r0, i32* returned "no-capture-maybe-returned" %r1, i32* nocapture %w0) #[[NOUNWIND]] define internal i32* @internal_ret1_rrw(i32* %r0, i32* %r1, i32* %w0) { entry: %0 = load i32, i32* %r0, align 4 @@ -114,7 +115,7 @@ ret i32* %retval.0 } -; CHECK: define dso_local i32* @external_sink_ret2_nrw(i32* readnone %n0, i32* nocapture readonly %r0, i32* returned %w0) #[[NOREC_NOUNWIND:[0-9]*]] +; CHECK: define dso_local i32* @external_sink_ret2_nrw(i32* nocapture readnone %n0, i32* nocapture readonly %r0, i32* returned "no-capture-maybe-returned" %w0) #[[NOREC_NOUNWIND:[0-9]*]] define dso_local i32* @external_sink_ret2_nrw(i32* %n0, i32* %r0, i32* %w0) { entry: %tobool = icmp ne i32* %n0, null @@ -132,7 +133,7 @@ ret i32* %w0 } -; CHECK: define internal i32* @internal_ret1_rw(i32* %r0, i32* returned %w0) #[[NOUNWIND]] +; CHECK: define internal i32* @internal_ret1_rw(i32* nocapture %r0, i32* returned "no-capture-maybe-returned" %w0) #[[NOUNWIND]] define internal i32* @internal_ret1_rw(i32* %r0, i32* %w0) { entry: %0 = load i32, i32* %r0, align 4 @@ -157,7 +158,7 @@ ret i32* %retval.0 } -; CHECK: define dso_local i32* @external_source_ret2_nrw(i32* %n0, i32* %r0, i32* returned %w0) #[[NOUNWIND]] +; CHECK: define dso_local i32* @external_source_ret2_nrw(i32* nocapture %n0, i32* nocapture %r0, i32* returned "no-capture-maybe-returned" %w0) #[[NOUNWIND]] define dso_local 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/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 @@ -29,8 +29,7 @@ ; return p == 0; ; } ; -; FIXME: no-capture missing for %p -; CHECK: define dso_local i32 @is_null_return(i32* readnone %p) +; CHECK: define dso_local i32 @is_null_return(i32* nocapture readnone %p) ; define dso_local i32 @is_null_return(i32* %p) #0 { entry: @@ -49,8 +48,7 @@ ; return 0; ; } ; -; FIXME: no-capture missing for %p -; CHECK: define dso_local i32 @is_null_control(i32* readnone %p) +; CHECK: define dso_local i32 @is_null_control(i32* nocapture readnone %p) ; define dso_local i32 @is_null_control(i32* %p) #0 { entry: @@ -127,15 +125,12 @@ ; TEST 5: ; -; FIXME: no-capture missing for %a -; CHECK: define dso_local float* @scc_A(i32* readnone returned %a) +; CHECK: define dso_local float* @scc_A(i32* readnone returned "no-capture-maybe-returned" %a) ; -; FIXME: no-capture missing for %a -; CHECK: define dso_local i64* @scc_B(double* readnone returned %a) +; CHECK: define dso_local i64* @scc_B(double* readnone returned "no-capture-maybe-returned" %a) ; ; FIXME: readnone missing for %s -; FIXME: no-capture missing for %a -; CHECK: define dso_local i8* @scc_C(i16* returned %a) +; CHECK: define dso_local i8* @scc_C(i16* returned "no-capture-maybe-returned" %a) ; ; float *scc_A(int *a) { ; return (float*)(a ? (int*)scc_A((int*)scc_B((double*)scc_C((short*)a))) : a); @@ -264,7 +259,7 @@ ; } ; ; There should *not* be a no-capture attribute on %a -; CHECK: define dso_local i64* @not_captured_but_returned_0(i64* returned %a) +; CHECK: define dso_local i64* @not_captured_but_returned_0(i64* returned "no-capture-maybe-returned" %a) ; define dso_local i64* @not_captured_but_returned_0(i64* %a) #0 { entry: @@ -280,7 +275,7 @@ ; } ; ; There should *not* be a no-capture attribute on %a -; CHECK: define dso_local nonnull i64* @not_captured_but_returned_1(i64* %a) +; CHECK: define dso_local nonnull i64* @not_captured_but_returned_1(i64* "no-capture-maybe-returned" %a) ; define dso_local i64* @not_captured_but_returned_1(i64* %a) #0 { entry: @@ -296,8 +291,7 @@ ; not_captured_but_returned_1(a); ; } ; -; FIXME: no-capture missing for %a -; CHECK: define dso_local void @test_not_captured_but_returned_calls(i64* %a) +; CHECK: define dso_local void @test_not_captured_but_returned_calls(i64* nocapture %a) ; define dso_local void @test_not_captured_but_returned_calls(i64* %a) #0 { entry: @@ -313,7 +307,7 @@ ; } ; ; There should *not* be a no-capture attribute on %a -; CHECK: define dso_local i64* @negative_test_not_captured_but_returned_call_0a(i64* returned %a) +; CHECK: define dso_local i64* @negative_test_not_captured_but_returned_call_0a(i64* returned "no-capture-maybe-returned" %a) ; define dso_local i64* @negative_test_not_captured_but_returned_call_0a(i64* %a) #0 { entry: @@ -345,7 +339,7 @@ ; } ; ; There should *not* be a no-capture attribute on %a -; CHECK: define dso_local nonnull i64* @negative_test_not_captured_but_returned_call_1a(i64* %a) +; CHECK: define dso_local nonnull i64* @negative_test_not_captured_but_returned_call_1a(i64* "no-capture-maybe-returned" %a) ; define dso_local i64* @negative_test_not_captured_but_returned_call_1a(i64* %a) #0 { entry: @@ -378,10 +372,14 @@ ; return unknown(); ; } ; -; Verify we do *not* assume b is returned or not captured. +; Verify we do not assume b is returned> ; -; CHECK: define dso_local i32* @ret_arg_or_unknown(i32* readnone %b) -; CHECK: define dso_local i32* @ret_arg_or_unknown_through_phi(i32* readnone %b) +; FNATTR: define dso_local i32* @ret_arg_or_unknown(i32* %b) +; FNATTR: define dso_local i32* @ret_arg_or_unknown_through_phi(i32* %b) +; ATTRIBUTOR: define dso_local i32* @ret_arg_or_unknown(i32* "no-capture-maybe-returned" %b) +; ATTRIBUTOR: define dso_local i32* @ret_arg_or_unknown_through_phi(i32* "no-capture-maybe-returned" %b) +; BOTH: define dso_local i32* @ret_arg_or_unknown(i32* "no-capture-maybe-returned" %b) +; BOTH: define dso_local i32* @ret_arg_or_unknown_through_phi(i32* "no-capture-maybe-returned" %b) ; declare dso_local i32* @unknown() @@ -418,7 +416,8 @@ ; TEST 16: ; -; CHECK: define dso_local void @not_captured_by_readonly_call(i32* nocapture %b) +; ATTRIBUTOR: define dso_local i32* @readonly(i32* "no-capture-maybe-returned") +; ATTRIBUTOR: define dso_local void @not_captured_by_readonly_call(i32* nocapture %b) ; declare dso_local i32* @readonly_unknown(i32*, i32*) readonly @@ -434,13 +433,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 dso_local i32* @not_captured_by_readonly_call_not_returned_either1(i32* readonly %b, i32* readonly returned %r) +; ATTRIBUTOR: define dso_local 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 dso_local i32* @not_captured_by_readonly_call_not_returned_either2(i32* readonly %b, i32* readonly returned %r) -; CHECK: define dso_local i32* @not_captured_by_readonly_call_not_returned_either3(i32* readonly %b, i32* readonly returned %r) +; ATTRIBUTOR: define dso_local i32* @not_captured_by_readonly_call_not_returned_either2(i32* nocapture %b, i32* returned "no-capture-maybe-returned" %r) #0 { +; ATTRIBUTOR: define dso_local 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 dso_local i32* @not_captured_by_readonly_call_not_returned_either4(i32* readonly %b, i32* readonly returned %r) +; ATTRIBUTOR: define dso_local i32* @not_captured_by_readonly_call_not_returned_either4(i32* "no-capture-maybe-returned" %b, i32* returned "no-capture-maybe-returned" %r) #0 { ; define dso_local i32* @not_captured_by_readonly_call_not_returned_either1(i32* %b, i32* returned %r) #0 { entry: 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 @@ -172,7 +172,7 @@ ; TEST 2 ; -; BOTH: define dso_local double* @ptr_sink_r0(double* readnone returned %r) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] +; BOTH: define dso_local double* @ptr_sink_r0(double* readnone returned "no-capture-maybe-returned" %r) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] ; BOTH: define dso_local double* @ptr_scc_r1(double* %a, double* readnone returned %r, double* nocapture readnone %b) [[NoInlineNoUnwindReadnoneUwtable]] ; BOTH: define dso_local double* @ptr_scc_r2(double* readnone %a, double* readnone %b, double* readnone returned %r) [[NoInlineNoUnwindReadnoneUwtable]] ; @@ -180,8 +180,8 @@ ; FNATTR: define dso_local double* @ptr_scc_r1(double* %a, double* readnone %r, double* nocapture readnone %b) [[NoInlineNoUnwindReadnoneUwtable]] ; FNATTR: define dso_local double* @ptr_scc_r2(double* readnone %a, double* readnone %b, double* readnone %r) [[NoInlineNoUnwindReadnoneUwtable]] ; -; ATTRIBUTOR: define dso_local double* @ptr_sink_r0(double* returned %r) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] -; ATTRIBUTOR: define dso_local double* @ptr_scc_r1(double* %a, double* returned %r, double* %b) [[NoInlineNoUnwindReadnoneUwtable]] +; ATTRIBUTOR: define dso_local double* @ptr_sink_r0(double* returned "no-capture-maybe-returned" %r) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] +; ATTRIBUTOR: define dso_local double* @ptr_scc_r1(double* %a, double* returned %r, double* nocapture %b) [[NoInlineNoUnwindReadnoneUwtable]] ; ATTRIBUTOR: define dso_local double* @ptr_scc_r2(double* %a, double* %b, double* returned %r) [[NoInlineNoUnwindReadnoneUwtable]] ; ; double* ptr_scc_r1(double* a, double* b, double* r); @@ -268,7 +268,7 @@ ; ; FEW_IT: define dso_local i32* @ret0(i32* %a) ; FNATTR: define dso_local i32* @ret0(i32* readonly %a) [[NoInlineNoUnwindUwtable:#[0-9]*]] -; BOTH: define dso_local i32* @ret0(i32* readonly returned %a) [[NoInlineNoReturnNoUnwindReadonlyUwtable:#[0-9]*]] +; BOTH: define dso_local i32* @ret0(i32* readonly returned "no-capture-maybe-returned" %a) [[NoInlineNoReturnNoUnwindReadonlyUwtable:#[0-9]*]] define dso_local i32* @ret0(i32* %a) #0 { entry: %v = load i32, i32* %a, align 4 @@ -307,9 +307,9 @@ ; ; BOTH: declare void @unknown_fn(i32* (i32*)*) [[NoInlineNoUnwindUwtable:#[0-9]*]] ; -; BOTH: define dso_local i32* @calls_unknown_fn(i32* readnone returned %r) [[NoInlineNoUnwindUwtable]] +; BOTH: define dso_local i32* @calls_unknown_fn(i32* readnone returned "no-capture-maybe-returned" %r) [[NoInlineNoUnwindUwtable]] ; FNATTR: define dso_local i32* @calls_unknown_fn(i32* readnone %r) [[NoInlineNoUnwindUwtable:#[0-9]*]] -; ATTRIBUTOR: define dso_local i32* @calls_unknown_fn(i32* returned %r) [[NoInlineNoUnwindUwtable:#[0-9]*]] +; ATTRIBUTOR: define dso_local i32* @calls_unknown_fn(i32* returned "no-capture-maybe-returned" %r) [[NoInlineNoUnwindUwtable:#[0-9]*]] ; declare void @unknown_fn(i32* (i32*)*) #0 @@ -417,8 +417,8 @@ ; } ; ; FNATTR: define dso_local double* @bitcast(i32* readnone %b) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] -; ATTRIBUTOR: define dso_local double* @bitcast(i32* returned %b) [[NoInlineNoUnwindUwtable]] -; BOTH: define dso_local double* @bitcast(i32* readnone returned %b) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] +; ATTRIBUTOR: define dso_local double* @bitcast(i32* returned "no-capture-maybe-returned" %b) [[NoInlineNoUnwindUwtable]] +; BOTH: define dso_local double* @bitcast(i32* readnone returned "no-capture-maybe-returned" %b) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] ; define dso_local double* @bitcast(i32* %b) #0 { entry: @@ -437,8 +437,8 @@ ; } ; ; FNATTR: define dso_local double* @bitcasts_select_and_phi(i32* readnone %b) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] -; ATTRIBUTOR: define dso_local double* @bitcasts_select_and_phi(i32* returned %b) [[NoInlineNoUnwindUwtable]] -; BOTH: define dso_local double* @bitcasts_select_and_phi(i32* readnone returned %b) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] +; ATTRIBUTOR: define dso_local double* @bitcasts_select_and_phi(i32* returned "no-capture-maybe-returned" %b) [[NoInlineNoUnwindUwtable]] +; BOTH: define dso_local double* @bitcasts_select_and_phi(i32* readnone returned "no-capture-maybe-returned" %b) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] ; define dso_local double* @bitcasts_select_and_phi(i32* %b) #0 { entry: @@ -472,8 +472,8 @@ ; } ; ; FNATTR: define dso_local double* @ret_arg_arg_undef(i32* readnone %b) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] -; ATTRIBUTOR: define dso_local double* @ret_arg_arg_undef(i32* returned %b) [[NoInlineNoUnwindUwtable]] -; BOTH: define dso_local double* @ret_arg_arg_undef(i32* readnone returned %b) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] +; ATTRIBUTOR: define dso_local double* @ret_arg_arg_undef(i32* returned "no-capture-maybe-returned" %b) [[NoInlineNoUnwindUwtable]] +; BOTH: define dso_local double* @ret_arg_arg_undef(i32* readnone returned "no-capture-maybe-returned" %b) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] ; define dso_local double* @ret_arg_arg_undef(i32* %b) #0 { entry: @@ -507,8 +507,8 @@ ; } ; ; FNATTR: define dso_local double* @ret_undef_arg_arg(i32* readnone %b) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] -; ATTRIBUTOR: define dso_local double* @ret_undef_arg_arg(i32* returned %b) [[NoInlineNoUnwindUwtable]] -; BOTH: define dso_local double* @ret_undef_arg_arg(i32* readnone returned %b) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] +; ATTRIBUTOR: define dso_local double* @ret_undef_arg_arg(i32* returned "no-capture-maybe-returned" %b) [[NoInlineNoUnwindUwtable]] +; BOTH: define dso_local double* @ret_undef_arg_arg(i32* readnone returned "no-capture-maybe-returned" %b) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] ; define dso_local double* @ret_undef_arg_arg(i32* %b) #0 { entry: @@ -542,8 +542,8 @@ ; } ; ; FNATTR: define dso_local double* @ret_undef_arg_undef(i32* readnone %b) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] -; ATTRIBUTOR: define dso_local double* @ret_undef_arg_undef(i32* returned %b) [[NoInlineNoUnwindUwtable]] -; BOTH: define dso_local double* @ret_undef_arg_undef(i32* readnone returned %b) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] +; ATTRIBUTOR: define dso_local double* @ret_undef_arg_undef(i32* returned "no-capture-maybe-returned" %b) [[NoInlineNoUnwindUwtable]] +; BOTH: define dso_local double* @ret_undef_arg_undef(i32* readnone returned "no-capture-maybe-returned" %b) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] ; define dso_local double* @ret_undef_arg_undef(i32* %b) #0 { entry: @@ -569,7 +569,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/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 @@ -6,21 +6,21 @@ ; 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 @@ -3,7 +3,7 @@ @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) define i32* @c1(i32* %q) { ret i32* %q } @@ -134,7 +134,7 @@ 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) ; 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 +142,7 @@ 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) 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 @@ -156,21 +156,21 @@ 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) 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) 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) define i8* @test4_2(i8* %x4_2, i8* %y4_2, i8* %z4_2) { call void @test4_1(i8* null) store i32* null, i32** @g 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 @@ -12,7 +12,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 @@ -54,13 +54,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)