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 @@ -269,6 +269,25 @@ } ///} + /// Return the associated argument, if any. + /// + ///{ + Argument *getAssociatedArgument() { + if (auto *Arg = dyn_cast<Argument>(&getAnchorValue())) + return Arg; + int ArgNo = getArgNo(); + if (ArgNo < 0) + return nullptr; + Function *AssociatedFn = getAssociatedFunction(); + if (!AssociatedFn || AssociatedFn->arg_size() <= ArgNo) + return nullptr; + return AssociatedFn->arg_begin() + ArgNo; + } + const Argument *getAssociatedArgument() const { + return const_cast<IRPosition *>(this)->getAssociatedArgument(); + } + ///} + /// Return the Function surrounding the anchor value. /// ///{ @@ -1390,6 +1409,56 @@ static const char ID; }; +/// An abstract interface for all nocapture attributes. +struct AANoCapture + : public IRAttribute<Attribute::NoCapture, + StateWrapper<IntegerState, AbstractAttribute>> { + AANoCapture(const IRPosition &IRP) : IRAttribute(IRP) {} + + /// 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, + }; + + /// Return true if we know that the underlying value is not captured in its + /// respective scope. + bool isKnownNoCapture() const { return isKnown(NO_CAPTURE); } + + /// Return true if we assume that the underlying value is not captured in its + /// respective scope. + bool isAssumedNoCapture() const { return isAssumed(NO_CAPTURE); } + + /// 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". + bool isKnownNoCaptureMaybeReturned() const { + return isKnown(NO_CAPTURE_MAYBE_RETURNED); + } + + /// 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". + bool isAssumedNoCaptureMaybeReturned() const { + return isAssumed(NO_CAPTURE_MAYBE_RETURNED); + } + + /// Create an abstract attribute view for the position \p IRP. + static AANoCapture &createForPosition(const IRPosition &IRP, Attributor &A); + + /// Unique ID (due to the unique address) + static const char ID; +}; + } // 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 @@ -1277,8 +1277,8 @@ const auto &AA = A.getAAFor<AANonNull>(*this, IRPosition::value(V)); if (!Stripped && this == &AA) { if (!isKnownNonZero(&V, DL, 0, /* TODO: AC */ nullptr, - /* TODO: CtxI */ nullptr, - /* TODO: DT */ nullptr)) + /* TODO: CtxI */ nullptr, + /* TODO: DT */ nullptr)) T.indicatePessimisticFixpoint(); } else { // Use abstract attribute information. @@ -1524,19 +1524,13 @@ if (!ICS) return false; - const auto &NoAliasAA = - A.getAAFor<AANoAlias>(*this, IRPosition::callsite_returned(ICS)); + const IRPosition &RVPos = IRPosition::value(RV); + const auto &NoAliasAA = A.getAAFor<AANoAlias>(*this, RVPos); if (!NoAliasAA.isAssumedNoAlias()) return false; - /// FIXME: We can improve capture check in two ways: - /// 1. Use the AANoCapture facilities. - /// 2. Use the location of return insts for escape queries. - if (PointerMayBeCaptured(&RV, /* ReturnCaptures */ false, - /* StoreCaptures */ true)) - return false; - - return true; + const auto &NoCaptureAA = A.getAAFor<AANoCapture>(*this, RVPos); + return NoCaptureAA.isAssumedNoCaptureMaybeReturned(); }; if (!A.checkForAllReturnedValues(CheckReturnValue, *this)) @@ -2226,6 +2220,343 @@ /// NoReturn attribute deduction for a call sites. using AANoReturnCallSite = AANoReturnFunction; +/// ----------------------- Variable Capturing --------------------------------- + +/// A class to hold the state of for no-capture attributes. +struct AANoCaptureImpl : public AANoCapture { + AANoCaptureImpl(const IRPosition &IRP) : AANoCapture(IRP) {} + + /// See AbstractAttribute::initialize(...). + void initialize(Attributor &A) override { + if (hasAttr(Attribute::NoCapture)) { + indicateOptimisticFixpoint(); + return; + } + + const IRPosition &IRP = getIRPosition(); + const Function *F = + getArgNo() >= 0 ? IRP.getAssociatedFunction() : IRP.getAnchorScope(); + + // Check what state the associated function can actually capture. + if (F) + determineFunctionCaptureCapabilities(*F, *this); + + if (!F || !F->hasExactDefinition()) + indicatePessimisticFixpoint(); + } + + /// See AbstractAttribute::updateImpl(...). + ChangeStatus updateImpl(Attributor &A) override; + + /// see AbstractAttribute::isAssumedNoCaptureMaybeReturned(...). + virtual void + getDeducedAttributes(LLVMContext &Ctx, + SmallVectorImpl<Attribute> &Attrs) const override { + if (!isAssumedNoCaptureMaybeReturned()) + return; + + 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 associated with \p IRP to capture + /// state in memory and through "returning/throwing", respectively. + static void determineFunctionCaptureCapabilities(const 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, it can + // however return/throw state and the state might be influenced by the pointer + // value, e.g., loading from a returned pointer might reveal a bit. + if (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 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, + const AAIsDead &IsDeadAA, IntegerState &State, + SmallVectorImpl<const Value *> &PotentialCopies, + unsigned &RemainingUsesToExplore) + : A(A), NoCaptureAA(NoCaptureAA), IsDeadAA(IsDeadAA), 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 { + State.indicatePessimisticFixpoint(); + } + return State.isAssumed(AANoCapture::NO_CAPTURE_MAYBE_RETURNED); + } + + /// See CaptureTracker::tooManyUses(). + void tooManyUses() override { + State.removeAssumedBits(AANoCapture::NO_CAPTURE); + } + + bool isDereferenceableOrNull(Value *O, const DataLayout &DL) override { + if (CaptureTracker::isDereferenceableOrNull(O, DL)) + return true; + const auto &DerefAA = A.getAAFor<AADereferenceable>(NoCaptureAA, IRPosition::value(*O)); + return DerefAA.getAssumedDereferenceableBytes(); + } + + /// See CaptureTracker::captured(...). + bool captured(const Use *U) override { + Instruction *UInst = cast<Instruction>(U->getUser()); + LLVM_DEBUG(dbgs() << "Check use: " << *U->get() << " in " << *UInst + << "\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(dbgs() << " - too many uses to explore!\n"); + return isCapturedIn(/* Memory */ true, /* Integer */ true, + /* Return */ true); + } + + // Deal with ptr2int by following uses. + if (isa<PtrToIntInst>(UInst)) { + LLVM_DEBUG(dbgs() << " - ptr2int assume the worst!\n"); + return valueMayBeCaptured(UInst); + } + + // Explicitly catch return instructions. + if (isa<ReturnInst>(UInst)) + 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(UInst); + if (!CS || !CS.isArgOperand(U)) + return isCapturedIn(/* Memory */ true, /* Integer */ true, + /* Return */ true); + + unsigned ArgNo = CS.getArgumentNo(U); + const IRPosition &CSArgPos = IRPosition::callsite_argument(CS, 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<AANoCapture>(NoCaptureAA, CSArgPos); + if (ArgNoCaptureAA.isAssumedNoCapture()) + return isCapturedIn(/* Memory */ false, /* Integer */ false, + /* Return */ false); + if (ArgNoCaptureAA.isAssumedNoCaptureMaybeReturned()) { + 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 { + // Check liveness. + return !IsDeadAA.isAssumedDead(cast<Instruction>(U->getUser())); + } + + /// 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(AANoCapture::NOT_CAPTURED_IN_MEM); + if (CapturedInInt) + State.removeAssumedBits(AANoCapture::NOT_CAPTURED_IN_INT); + if (CapturedInRet) + State.removeAssumedBits(AANoCapture::NOT_CAPTURED_IN_RET); + return !State.isAssumed(AANoCapture::NO_CAPTURE_MAYBE_RETURNED); + } + +private: + /// The attributor providing in-flight abstract attributes. + Attributor &A; + + /// The abstract attribute currently updated. + AANoCapture &NoCaptureAA; + + /// The abstract liveness state. + const AAIsDead &IsDeadAA; + + /// The state currently updated. + IntegerState &State; + + /// Set of potential copies of the tracked value. + SmallVectorImpl<const Value *> &PotentialCopies; + + /// Global counter to limit the number of explored uses. + unsigned &RemainingUsesToExplore; +}; + +ChangeStatus AANoCaptureImpl::updateImpl(Attributor &A) { + const IRPosition &IRP = getIRPosition(); + const Value *V = + getArgNo() >= 0 ? IRP.getAssociatedArgument() : &IRP.getAssociatedValue(); + if (!V) + return indicatePessimisticFixpoint(); + + const Function *F = + getArgNo() >= 0 ? IRP.getAssociatedFunction() : IRP.getAnchorScope(); + assert(F && "Expected a function!"); + const auto &IsDeadAA = A.getAAFor<AAIsDead>(*this, IRPosition::function(*F)); + + AAAlign::StateType T; + // 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. + + // 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. + SmallVector<const Value *, 4> PotentialCopies; + unsigned RemainingUsesToExplore = DefaultMaxUsesToExplore; + AACaptureUseTracker Tracker(A, *this, IsDeadAA, T, 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(V); + while (T.isAssumed(NO_CAPTURE_MAYBE_RETURNED) && Idx < PotentialCopies.size()) + Tracker.valueMayBeCaptured(PotentialCopies[Idx++]); + + AAAlign::StateType &S = getState(); + auto Assumed = S.getAssumed(); + S.intersectAssumedBits(T.getAssumed()); + return Assumed == S.getAssumed() ? ChangeStatus::UNCHANGED + : ChangeStatus::CHANGED; +} + +/// NoCapture attribute for function arguments. +struct AANoCaptureArgument final : AANoCaptureImpl { + AANoCaptureArgument(const IRPosition &IRP) + : AANoCaptureImpl(IRP) {} + + /// See AbstractAttribute::trackStatistics() + void trackStatistics() const override { + STATS_DECLTRACK_ARG_ATTR(nocapture) + } +}; + +/// NoCapture attribute for call site arguments. +struct AANoCaptureCallSiteArgument final : AANoCaptureImpl { + AANoCaptureCallSiteArgument(const IRPosition &IRP) : AANoCaptureImpl(IRP) {} + + /// See AbstractAttribute::updateImpl(...). + ChangeStatus updateImpl(Attributor &A) override { + // TODO: Once we have call site specific value information we can provide + // call site specific liveness liveness information and then it makes + // sense to specialize attributes for call sites arguments instead of + // redirecting requests to the callee argument. + Argument *Arg = getAssociatedArgument(); + if (!Arg) + return indicatePessimisticFixpoint(); + const IRPosition &ArgPos = IRPosition::argument(*Arg); + auto &ArgAA = A.getAAFor<AANoCapture>(*this, ArgPos); + return clampStateAndIndicateChange( + getState(), + static_cast<const AANoCapture::StateType &>(ArgAA.getState())); + } + + /// See AbstractAttribute::trackStatistics() + void trackStatistics() const override{STATS_DECLTRACK_CSARG_ATTR(nocapture)}; +}; + +/// NoCapture attribute for floating values. +struct AANoCaptureFloating final : AANoCaptureImpl { + AANoCaptureFloating(const IRPosition &IRP) + : AANoCaptureImpl(IRP) {} + + /// See AbstractAttribute::trackStatistics() + void trackStatistics() const override { + STATS_DECLTRACK_FLOATING_ATTR(nocapture) + } +}; + +/// NoCapture attribute for function return value. +struct AANoCaptureReturned final : AANoCaptureImpl { + AANoCaptureReturned(const IRPosition &IRP) : AANoCaptureImpl(IRP) { + llvm_unreachable("NoCapture is not applicable to function returns!"); + } + + /// See AbstractAttribute::initialize(...). + void initialize(Attributor &A) override { + llvm_unreachable("NoCapture is not applicable to function returns!"); + } + + /// See AbstractAttribute::updateImpl(...). + ChangeStatus updateImpl(Attributor &A) override { + llvm_unreachable("NoCapture is not applicable to function returns!"); + } + + /// See AbstractAttribute::trackStatistics() + void trackStatistics() const override {} +}; + +/// NoCapture attribute deduction for a call site return value. +using AANoCaptureCallSiteReturned = AANoCaptureFloating; + /// ---------------------------------------------------------------------------- /// Attributor /// ---------------------------------------------------------------------------- @@ -2626,6 +2957,9 @@ // Every argument with pointer type might be marked align. checkAndRegisterAA<AAAlignArgument>(ArgPos, *this, Whitelist); + + // Every argument with pointer type might be marked nocapture. + checkAndRegisterAA<AANoCaptureArgument>(ArgPos, *this, Whitelist); } } @@ -2862,6 +3196,7 @@ const char AAIsDead::ID = 0; const char AADereferenceable::ID = 0; const char AAAlign::ID = 0; +const char AANoCapture::ID = 0; // Macro magic to create the static generator function for attributes that // follow the naming scheme. @@ -2922,6 +3257,7 @@ CREATE_VALUE_ABSTRACT_ATTRIBUTE_FOR_POSITION(AANoAlias) CREATE_VALUE_ABSTRACT_ATTRIBUTE_FOR_POSITION(AADereferenceable) CREATE_VALUE_ABSTRACT_ATTRIBUTE_FOR_POSITION(AAAlign) +CREATE_VALUE_ABSTRACT_ATTRIBUTE_FOR_POSITION(AANoCapture) #undef CREATE_FUNCTION_ABSTRACT_ATTRIBUTE_FOR_POSITION #undef CREATE_VALUE_ABSTRACT_ATTRIBUTE_FOR_POSITION diff --git a/llvm/test/Transforms/FunctionAttrs/align.ll b/llvm/test/Transforms/FunctionAttrs/align.ll --- a/llvm/test/Transforms/FunctionAttrs/align.ll +++ b/llvm/test/Transforms/FunctionAttrs/align.ll @@ -7,26 +7,26 @@ ; TEST 1 -; ATTRIBUTOR: define align 8 i32* @test1(i32* returned align 8 %0) +; ATTRIBUTOR: define align 8 i32* @test1(i32* returned align 8 "no-capture-maybe-returned" %0) define i32* @test1(i32* align 8 %0) #0 { ret i32* %0 } ; TEST 2 -; ATTRIBUTOR: define i32* @test2(i32* returned %0) +; ATTRIBUTOR: define i32* @test2(i32* returned "no-capture-maybe-returned" %0) define i32* @test2(i32* %0) #0 { ret i32* %0 } ; TEST 3 -; ATTRIBUTOR: define align 4 i32* @test3(i32* align 8 %0, i32* align 4 %1, i1 %2) +; ATTRIBUTOR: define align 4 i32* @test3(i32* align 8 "no-capture-maybe-returned" %0, i32* align 4 "no-capture-maybe-returned" %1, i1 %2) define i32* @test3(i32* align 8 %0, i32* align 4 %1, i1 %2) #0 { %ret = select i1 %2, i32* %0, i32* %1 ret i32* %ret } ; TEST 4 -; ATTRIBUTOR: define align 32 i32* @test4(i32* align 32 %0, i32* align 32 %1, i1 %2) +; ATTRIBUTOR: define align 32 i32* @test4(i32* align 32 "no-capture-maybe-returned" %0, i32* align 32 "no-capture-maybe-returned" %1, i1 %2) define i32* @test4(i32* align 32 %0, i32* align 32 %1, i1 %2) #0 { %ret = select i1 %2, i32* %0, i32* %1 ret i32* %ret @@ -87,7 +87,7 @@ ; Function Attrs: nounwind readnone ssp uwtable define internal i8* @f1(i8* readnone %0) local_unnamed_addr #0 { -; ATTRIBUTOR: define internal nonnull align 8 dereferenceable(1) i8* @f1(i8* nonnull readnone align 8 dereferenceable(1) %0) +; ATTRIBUTOR: define internal nonnull align 8 dereferenceable(1) i8* @f1(i8* nonnull readnone align 8 dereferenceable(1) "no-capture-maybe-returned" %0) %2 = icmp eq i8* %0, null br i1 %2, label %3, label %5 @@ -103,13 +103,13 @@ ; Function Attrs: nounwind readnone ssp uwtable define internal i8* @f2(i8* readnone %0) local_unnamed_addr #0 { -; ATTRIBUTOR: define internal nonnull align 8 dereferenceable(1) i8* @f2(i8* nonnull readnone align 8 dereferenceable(1) %0) +; ATTRIBUTOR: define internal nonnull align 8 dereferenceable(1) i8* @f2(i8* nonnull readnone align 8 dereferenceable(1) "no-capture-maybe-returned" %0) %2 = icmp eq i8* %0, null br i1 %2, label %5, label %3 ; <label>:3: ; preds = %1 -; ATTRIBUTOR: %4 = tail call nonnull align 8 dereferenceable(1) i8* @f1(i8* nonnull align 8 dereferenceable(1) %0) +; ATTRIBUTOR: %4 = tail call nonnull align 8 dereferenceable(1) i8* @f1(i8* nonnull align 8 dereferenceable(1) "no-capture-maybe-returned" %0) %4 = tail call i8* @f1(i8* nonnull %0) br label %7 @@ -125,7 +125,7 @@ ; Function Attrs: nounwind readnone ssp uwtable define internal i8* @f3(i8* readnone %0) local_unnamed_addr #0 { -; ATTRIBUTOR: define internal nonnull align 8 dereferenceable(1) i8* @f3(i8* nonnull readnone align 16 dereferenceable(1) %0) +; ATTRIBUTOR: define internal nonnull align 8 dereferenceable(1) i8* @f3(i8* nocapture nonnull readnone align 16 dereferenceable(1) %0) %2 = icmp eq i8* %0, null br i1 %2, label %3, label %5 @@ -141,7 +141,7 @@ ; TEST 7 ; Better than IR information -; ATTRIBUTOR: define align 32 i32* @test7(i32* returned align 32 %p) +; ATTRIBUTOR: define align 32 i32* @test7(i32* returned align 32 "no-capture-maybe-returned" %p) define align 4 i32* @test7(i32* align 32 %p) #0 { ret i32* %p } @@ -163,7 +163,7 @@ } define internal void @test8(i32* %a, i32* %b, i32* %c) { -; ATTRIBUTOR: define internal void @test8(i32* align 4 %a, i32* align 4 %b, i32* %c) +; ATTRIBUTOR: define internal void @test8(i32* nocapture align 4 %a, i32* nocapture align 4 %b, i32* nocapture %c) ret void } 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 @@ -5,122 +5,14 @@ ; target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" -; TEST comparison against NULL -; -; int is_null_return(int *p) { -; 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 { -entry: - %cmp = icmp eq i32* %p, null - %conv = zext i1 %cmp to i32 - ret i32 %conv -} - -; TEST comparison against NULL in control flow -; -; int is_null_control(int *p) { -; if (p == 0) -; return 1; -; if (0 == p) -; return 1; -; 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 { -entry: - %retval = alloca i32, align 4 - %cmp = icmp eq i32* %p, null - br i1 %cmp, label %if.then, label %if.end - -if.then: ; preds = %entry - store i32 1, i32* %retval, align 4 - br label %return - -if.end: ; preds = %entry - %cmp1 = icmp eq i32* null, %p - br i1 %cmp1, label %if.then2, label %if.end3 - -if.then2: ; preds = %if.end - store i32 1, i32* %retval, align 4 - br label %return - -if.end3: ; preds = %if.end - store i32 0, i32* %retval, align 4 - br label %return - -return: ; preds = %if.end3, %if.then2, %if.then - %0 = load i32, i32* %retval, align 4 - ret i32 %0 -} - -; TEST singleton SCC -; -; double *srec0(double *a) { -; srec0(a); -; return 0; -; } -; -; CHECK: define noalias nonnull align 536870912 dereferenceable(4294967295) double* @srec0(double* nocapture readnone %a) -define double* @srec0(double* %a) #0 { -entry: - %call = call double* @srec0(double* %a) - ret double* null -} - -; TEST singleton SCC with lots of nested recursive calls -; -; int* srec16(int* a) { -; return srec16(srec16(srec16(srec16( -; srec16(srec16(srec16(srec16( -; srec16(srec16(srec16(srec16( -; srec16(srec16(srec16(srec16( -; a -; )))))))))))))))); -; } -; -; Other arguments are possible here due to the no-return behavior. -; -; CHECK: define noalias nonnull align 536870912 dereferenceable(4294967295) i32* @srec16(i32* nocapture readnone %a) -define i32* @srec16(i32* %a) #0 { -entry: - %call = call i32* @srec16(i32* %a) -; CHECK: %call = call noalias nonnull align 536870912 dereferenceable(4294967295) i32* @srec16(i32* %a) -; CHECK-NEXT: unreachable - %call1 = call i32* @srec16(i32* %call) - %call2 = call i32* @srec16(i32* %call1) - %call3 = call i32* @srec16(i32* %call2) - %call4 = call i32* @srec16(i32* %call3) - %call5 = call i32* @srec16(i32* %call4) - %call6 = call i32* @srec16(i32* %call5) - %call7 = call i32* @srec16(i32* %call6) - %call8 = call i32* @srec16(i32* %call7) - %call9 = call i32* @srec16(i32* %call8) - %call10 = call i32* @srec16(i32* %call9) - %call11 = call i32* @srec16(i32* %call10) - %call12 = call i32* @srec16(i32* %call11) - %call13 = call i32* @srec16(i32* %call12) - %call14 = call i32* @srec16(i32* %call13) - %call15 = call i32* @srec16(i32* %call14) - ret i32* %call15 -} - ; 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) +; CHECK: define dereferenceable_or_null(4) float* @scc_A(i32* readnone returned dereferenceable_or_null(4) "no-capture-maybe-returned" %a) ; -; FIXME: no-capture missing for %a -; CHECK: define i64* @scc_B(double* readnone returned %a) +; CHECK: define dereferenceable_or_null(8) i64* @scc_B(double* readnone returned dereferenceable_or_null(8) "no-capture-maybe-returned" %a) ; ; FIXME: readnone missing for %s -; FIXME: no-capture missing for %a -; CHECK: define i8* @scc_C(i16* returned %a) +; CHECK: define dereferenceable_or_null(2) i8* @scc_C(i16* returned dereferenceable_or_null(2) "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); @@ -133,7 +25,7 @@ ; void *scc_C(short *a) { ; return scc_A((int*)(scc_A(a) ? scc_B((double*)a) : scc_C(a))); ; } -define float* @scc_A(i32* %a) { +define float* @scc_A(i32* dereferenceable_or_null(4) %a) { entry: %tobool = icmp ne i32* %a, null br i1 %tobool, label %cond.true, label %cond.false @@ -157,7 +49,7 @@ ret float* %4 } -define i64* @scc_B(double* %a) { +define i64* @scc_B(double* dereferenceable_or_null(8) %a) { entry: %tobool = icmp ne double* %a, null br i1 %tobool, label %cond.true, label %cond.false @@ -181,7 +73,7 @@ ret i64* %4 } -define i8* @scc_C(i16* %a) { +define i8* @scc_C(i16* dereferenceable_or_null(2) %a) { entry: %bc = bitcast i16* %a to i32* %call = call float* @scc_A(i32* %bc) @@ -208,238 +100,5 @@ } -; TEST call to external function, marked no-capture -; -; void external_no_capture(int /* no-capture */ *p); -; void test_external_no_capture(int *p) { -; external_no_capture(p); -; } -; -; CHECK: define void @test_external_no_capture(i32* nocapture %p) -declare void @external_no_capture(i32* nocapture) - -define void @test_external_no_capture(i32* %p) #0 { -entry: - call void @external_no_capture(i32* %p) - ret void -} - -; TEST call to external var-args function, marked no-capture -; -; void test_var_arg_call(char *p, int a) { -; printf(p, a); -; } -; -; CHECK: define void @test_var_arg_call(i8* nocapture %p, i32 %a) -define void @test_var_arg_call(i8* %p, i32 %a) #0 { -entry: - %call = call i32 (i8*, ...) @printf(i8* %p, i32 %a) - ret void -} - -declare i32 @printf(i8* nocapture, ...) - - -; TEST "captured" only through return -; -; long *not_captured_but_returned_0(long *a) { -; *a1 = 0; -; return a; -; } -; -; There should *not* be a no-capture attribute on %a -; CHECK: define i64* @not_captured_but_returned_0(i64* returned %a) -define i64* @not_captured_but_returned_0(i64* %a) #0 { -entry: - store i64 0, i64* %a, align 8 - ret i64* %a -} - -; TEST "captured" only through return -; -; long *not_captured_but_returned_1(long *a) { -; *(a+1) = 1; -; return a + 1; -; } -; -; There should *not* be a no-capture attribute on %a -; CHECK: define nonnull i64* @not_captured_but_returned_1(i64* %a) -define i64* @not_captured_but_returned_1(i64* %a) #0 { -entry: - %add.ptr = getelementptr inbounds i64, i64* %a, i64 1 - store i64 1, i64* %add.ptr, align 8 - ret i64* %add.ptr -} - -; TEST calls to "captured" only through return functions -; -; void test_not_captured_but_returned_calls(long *a) { -; not_captured_but_returned_0(a); -; not_captured_but_returned_1(a); -; } -; -; FIXME: no-capture missing for %a -; CHECK: define void @test_not_captured_but_returned_calls(i64* %a) -define void @test_not_captured_but_returned_calls(i64* %a) #0 { -entry: - %call = call i64* @not_captured_but_returned_0(i64* %a) - %call1 = call i64* @not_captured_but_returned_1(i64* %a) - ret void -} - -; TEST "captured" only through transitive return -; -; long* negative_test_not_captured_but_returned_call_0a(long *a) { -; 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) -define i64* @negative_test_not_captured_but_returned_call_0a(i64* %a) #0 { -entry: - %call = call i64* @not_captured_but_returned_0(i64* %a) - ret i64* %call -} - -; TEST captured through write -; -; void negative_test_not_captured_but_returned_call_0b(long *a) { -; *a = (long)not_captured_but_returned_0(a); -; } -; -; There should *not* be a no-capture attribute on %a -; CHECK: define void @negative_test_not_captured_but_returned_call_0b(i64* %a) -define void @negative_test_not_captured_but_returned_call_0b(i64* %a) #0 { -entry: - %call = call i64* @not_captured_but_returned_0(i64* %a) - %0 = ptrtoint i64* %call to i64 - store i64 %0, i64* %a, align 8 - ret void -} - -; TEST "captured" only through transitive return -; -; long* negative_test_not_captured_but_returned_call_1a(long *a) { -; 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) -define i64* @negative_test_not_captured_but_returned_call_1a(i64* %a) #0 { -entry: - %call = call i64* @not_captured_but_returned_1(i64* %a) - ret i64* %call -} - -; TEST captured through write -; -; void negative_test_not_captured_but_returned_call_1b(long *a) { -; *a = (long)not_captured_but_returned_1(a); -; } -; -; There should *not* be a no-capture attribute on %a -; CHECK: define void @negative_test_not_captured_but_returned_call_1b(i64* %a) -define void @negative_test_not_captured_but_returned_call_1b(i64* %a) #0 { -entry: - %call = call i64* @not_captured_but_returned_1(i64* %a) - %0 = ptrtoint i64* %call to i64 - store i64 %0, i64* %call, align 8 - ret void -} - -; TEST return argument or unknown call result -; -; int* ret_arg_or_unknown(int* b) { -; if (b == 0) -; return b; -; return unknown(); -; } -; -; Verify we do *not* assume b is returned or not captured. -; -; 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 { -entry: - %cmp = icmp eq i32* %b, null - br i1 %cmp, label %ret_arg, label %ret_unknown - -ret_arg: - ret i32* %b - -ret_unknown: - %call = call i32* @unknown() - ret i32* %call -} - -define i32* @ret_arg_or_unknown_through_phi(i32* %b) #0 { -entry: - %cmp = icmp eq i32* %b, null - br i1 %cmp, label %ret_arg, label %ret_unknown - -ret_arg: - br label %r - -ret_unknown: - %call = call i32* @unknown() - br label %r - -r: - %phi = phi i32* [ %b, %ret_arg ], [ %call, %ret_unknown ] - ret i32* %phi -} - - -; TEST not captured by readonly external function -; -; CHECK: 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 { -entry: - %call = call i32* @readonly_unknown(i32* %b, i32* %b) - ret void -} - - -; TEST not captured by readonly external function if return chain is known -; -; 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) -; -; 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) -; -; 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) -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 - ret i32* %call -} - -declare i32* @readonly_unknown_r1a(i32*, i32* returned) readonly -define i32* @not_captured_by_readonly_call_not_returned_either2(i32* %b, i32* %r) #0 { -entry: - %call = call i32* @readonly_unknown_r1a(i32* %b, i32* %r) nounwind - ret i32* %call -} - -declare i32* @readonly_unknown_r1b(i32*, i32* returned) readonly nounwind -define i32* @not_captured_by_readonly_call_not_returned_either3(i32* %b, i32* %r) #0 { -entry: - %call = call i32* @readonly_unknown_r1b(i32* %b, i32* %r) - ret i32* %call -} - -define i32* @not_captured_by_readonly_call_not_returned_either4(i32* %b, i32* %r) #0 { -entry: - %call = call i32* @readonly_unknown_r1a(i32* %b, i32* %r) - ret i32* %call -} attributes #0 = { noinline nounwind uwtable } 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: nofree noinline norecurse nosync 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: nofree noinline nosync nounwind readnone uwtable ; BOTH-NEXT: define double* @ptr_scc_r1(double* %a, double* readnone returned %r, double* nocapture readnone %b) ; BOTH: Function Attrs: nofree noinline nosync nounwind readnone uwtable @@ -171,9 +171,9 @@ ; FNATTR: define double* @ptr_scc_r2(double* readnone %a, double* readnone %b, double* readnone %r) ; ; ATTRIBUTOR: Function Attrs: nofree noinline nosync nounwind uwtable -; ATTRIBUTOR-NEXT: define double* @ptr_sink_r0(double* returned %r) +; ATTRIBUTOR-NEXT: define double* @ptr_sink_r0(double* returned "no-capture-maybe-returned" %r) ; ATTRIBUTOR: Function Attrs: nofree noinline nosync nounwind uwtable -; ATTRIBUTOR-NEXT: define double* @ptr_scc_r1(double* %a, double* returned %r, double* %b) +; ATTRIBUTOR-NEXT: define double* @ptr_scc_r1(double* %a, double* returned %r, double* nocapture %b) ; ATTRIBUTOR: Function Attrs: nofree noinline nosync nounwind uwtable ; ATTRIBUTOR-NEXT: define double* @ptr_scc_r2(double* %a, double* %b, double* returned %r) ; @@ -261,7 +261,7 @@ ; ; FNATTR: define i32* @rt0(i32* readonly %a) ; BOTH: Function Attrs: nofree noinline noreturn nosync nounwind readonly uwtable -; BOTH-NEXT: define noalias nonnull align 536870912 dereferenceable(4294967295) i32* @rt0(i32* readonly %a) +; BOTH-NEXT: define noalias nonnull align 536870912 dereferenceable(4294967295) i32* @rt0(i32* nocapture readonly %a) define i32* @rt0(i32* %a) #0 { entry: %v = load i32, i32* %a, align 4 @@ -294,7 +294,7 @@ ; FNATTR: define i32* @rt2_helper(i32* %a) ; FNATTR: define i32* @rt2(i32* readnone %a, i32* readnone %b) ; BOTH: define i32* @rt2_helper(i32* returned %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) @@ -319,8 +319,8 @@ ; ; 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) +; BOTH: define i32* @rt3_helper(i32* %a, i32* returned "no-capture-maybe-returned" %b) +; BOTH: define i32* @rt3(i32* readnone %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) @@ -353,9 +353,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 { @@ -502,12 +502,12 @@ ; } ; ; BOTH: Function Attrs: nofree noinline norecurse nosync 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: Function Attrs: nofree noinline nosync nounwind uwtable -; ATTRIBUTOR-NEXT: define double* @bitcast(i32* returned %b) +; ATTRIBUTOR-NEXT: define double* @bitcast(i32* returned "no-capture-maybe-returned" %b) define double* @bitcast(i32* %b) #0 { entry: %bc0 = bitcast i32* %b to double* diff --git a/llvm/test/Transforms/FunctionAttrs/dereferenceable.ll b/llvm/test/Transforms/FunctionAttrs/dereferenceable.ll --- a/llvm/test/Transforms/FunctionAttrs/dereferenceable.ll +++ b/llvm/test/Transforms/FunctionAttrs/dereferenceable.ll @@ -5,7 +5,7 @@ ; take mininimum of return values ; define i32* @test1(i32* dereferenceable(4) %0, double* dereferenceable(8) %1, i1 zeroext %2) local_unnamed_addr { -; ATTRIBUTOR: define nonnull dereferenceable(4) i32* @test1(i32* nonnull dereferenceable(4) %0, double* nonnull dereferenceable(8) %1, i1 zeroext %2) +; ATTRIBUTOR: define nonnull dereferenceable(4) i32* @test1(i32* nonnull dereferenceable(4) "no-capture-maybe-returned" %0, double* nonnull dereferenceable(8) "no-capture-maybe-returned" %1, i1 zeroext %2) %4 = bitcast double* %1 to i32* %5 = select i1 %2, i32* %0, i32* %4 ret i32* %5 @@ -13,7 +13,7 @@ ; TEST 2 define i32* @test2(i32* dereferenceable_or_null(4) %0, double* dereferenceable(8) %1, i1 zeroext %2) local_unnamed_addr { -; ATTRIBUTOR: define dereferenceable_or_null(4) i32* @test2(i32* dereferenceable_or_null(4) %0, double* nonnull dereferenceable(8) %1, i1 zeroext %2) +; ATTRIBUTOR: define dereferenceable_or_null(4) i32* @test2(i32* dereferenceable_or_null(4) "no-capture-maybe-returned" %0, double* nonnull dereferenceable(8) "no-capture-maybe-returned" %1, i1 zeroext %2) %4 = bitcast double* %1 to i32* %5 = select i1 %2, i32* %0, i32* %4 ret i32* %5 @@ -22,20 +22,20 @@ ; TEST 3 ; GEP inbounds define i32* @test3_1(i32* dereferenceable(8) %0) local_unnamed_addr { -; ATTRIBUTOR: define nonnull dereferenceable(4) i32* @test3_1(i32* nonnull dereferenceable(8) %0) +; ATTRIBUTOR: define nonnull dereferenceable(4) i32* @test3_1(i32* nonnull dereferenceable(8) "no-capture-maybe-returned" %0) %ret = getelementptr inbounds i32, i32* %0, i64 1 ret i32* %ret } define i32* @test3_2(i32* dereferenceable_or_null(32) %0) local_unnamed_addr { ; FIXME: Argument should be mark dereferenceable because of GEP `inbounds`. -; ATTRIBUTOR: define nonnull dereferenceable(16) i32* @test3_2(i32* dereferenceable_or_null(32) %0) +; ATTRIBUTOR: define nonnull dereferenceable(16) i32* @test3_2(i32* dereferenceable_or_null(32) "no-capture-maybe-returned" %0) %ret = getelementptr inbounds i32, i32* %0, i64 4 ret i32* %ret } define i32* @test3_3(i32* dereferenceable(8) %0, i32* dereferenceable(16) %1, i1 %2) local_unnamed_addr { -; ATTRIBUTOR: define nonnull dereferenceable(4) i32* @test3_3(i32* nonnull dereferenceable(8) %0, i32* nonnull dereferenceable(16) %1, i1 %2) local_unnamed_addr +; ATTRIBUTOR: define nonnull dereferenceable(4) i32* @test3_3(i32* nonnull dereferenceable(8) "no-capture-maybe-returned" %0, i32* nonnull dereferenceable(16) "no-capture-maybe-returned" %1, i1 %2) local_unnamed_addr %ret1 = getelementptr inbounds i32, i32* %0, i64 1 %ret2 = getelementptr inbounds i32, i32* %1, i64 2 %ret = select i1 %2, i32* %ret1, i32* %ret2 @@ -46,7 +46,7 @@ ; Better than known in IR. define dereferenceable(4) i32* @test4(i32* dereferenceable(8) %0) local_unnamed_addr { -; ATTRIBUTOR: define nonnull dereferenceable(8) i32* @test4(i32* nonnull returned dereferenceable(8) %0) +; ATTRIBUTOR: define nonnull dereferenceable(8) i32* @test4(i32* nonnull returned dereferenceable(8) "no-capture-maybe-returned" %0) ret i32* %0 } diff --git a/llvm/test/Transforms/FunctionAttrs/noalias_returned.ll b/llvm/test/Transforms/FunctionAttrs/noalias_returned.ll --- a/llvm/test/Transforms/FunctionAttrs/noalias_returned.ll +++ b/llvm/test/Transforms/FunctionAttrs/noalias_returned.ll @@ -29,6 +29,17 @@ ret i8* %1 } +define void @nocapture(i8* %a){ + ret void +} + +; CHECK: define noalias i8* @return_noalias_looks_like_capture() +define i8* @return_noalias_looks_like_capture(){ + %1 = tail call noalias i8* @malloc(i64 4) + call void @nocapture(i8* %1) + ret i8* %1 +} + declare i8* @alias() ; TEST 3 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,27 +1,30 @@ -; RUN: opt < %s -functionattrs -S | FileCheck %s -; RUN: opt < %s -passes=function-attrs -S | FileCheck %s +; RUN: opt -functionattrs -S < %s | FileCheck %s --check-prefixes=FNATTR,EITHER +; RUN: opt -passes=function-attrs -S < %s | FileCheck %s --check-prefixes=FNATTR,EITHER +; RUN: opt -attributor -attributor-disable=false -S < %s | FileCheck %s --check-prefixes=ATTRIBUTOR,EITHER +; RUN: opt -passes=attributor -attributor-disable=false -S < %s | FileCheck %s --check-prefixes=ATTRIBUTOR,EITHER @g = global i32* null ; <i32**> [#uses=1] -; CHECK: define i32* @c1(i32* readnone returned %q) +; FNATTR: define i32* @c1(i32* readnone returned %q) +; ATTRIBUTOR: define i32* @c1(i32* returned "no-capture-maybe-returned" %q) define i32* @c1(i32* %q) { ret i32* %q } -; CHECK: define void @c2(i32* %q) +; EITHER: define void @c2(i32* %q) ; It would also be acceptable to mark %q as readnone. Update @c3 too. define void @c2(i32* %q) { store i32* %q, i32** @g ret void } -; CHECK: define void @c3(i32* %q) +; EITHER: define void @c3(i32* %q) define void @c3(i32* %q) { call void @c2(i32* %q) ret void } -; CHECK: define i1 @c4(i32* %q, i32 %bitno) +; EITHER: define i1 @c4(i32* %q, i32 %bitno) define i1 @c4(i32* %q, i32 %bitno) { %tmp = ptrtoint i32* %q to i32 %tmp2 = lshr i32 %tmp, %bitno @@ -35,7 +38,7 @@ @lookup_table = global [2 x i1] [ i1 0, i1 1 ] -; CHECK: define i1 @c5(i32* %q, i32 %bitno) +; EITHER: define i1 @c5(i32* %q, i32 %bitno) define i1 @c5(i32* %q, i32 %bitno) { %tmp = ptrtoint i32* %q to i32 %tmp2 = lshr i32 %tmp, %bitno @@ -48,7 +51,8 @@ declare void @throw_if_bit_set(i8*, i8) readonly -; CHECK: define i1 @c6(i8* readonly %q, i8 %bit) +; FNATTR: define i1 @c6(i8* readonly %q, i8 %bit) +; ATTRIBUTOR: define i1 @c6(i8* %q, i8 %bit) define i1 @c6(i8* %q, i8 %bit) personality i32 (...)* @__gxx_personality_v0 { invoke void @throw_if_bit_set(i8* %q, i8 %bit) to label %ret0 unwind label %ret1 @@ -70,7 +74,8 @@ ret i1* %lookup } -; CHECK: define i1 @c7(i32* readonly %q, i32 %bitno) +; FNATTR: define i1 @c7(i32* readonly %q, i32 %bitno) +; ATTRIBUTOR: define i1 @c7(i32* %q, i32 %bitno) define i1 @c7(i32* %q, i32 %bitno) { %ptr = call i1* @lookup_bit(i32* %q, i32 %bitno) %val = load i1, i1* %ptr @@ -78,7 +83,7 @@ } -; CHECK: define i32 @nc1(i32* %q, i32* nocapture %p, i1 %b) +; EITHER: define i32 @nc1(i32* %q, i32* nocapture %p, i1 %b) define i32 @nc1(i32* %q, i32* %p, i1 %b) { e: br label %l @@ -93,7 +98,7 @@ ret i32 %val } -; CHECK: define i32 @nc1_addrspace(i32* %q, i32 addrspace(1)* nocapture %p, i1 %b) +; EITHER: 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 @@ -108,78 +113,93 @@ ret i32 %val } -; CHECK: define void @nc2(i32* nocapture %p, i32* %q) +; EITHER: 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) ; <i32> [#uses=0] ret void } -; CHECK: define void @nc3(void ()* nocapture %p) +; EITHER: 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) +; FNATTR: define void @nc4(i8* nocapture readonly %p) +; ATTRIBUTOR: define void @nc4(i8* nocapture %p) define void @nc4(i8* %p) { call void @external(i8* %p) ret void } -; CHECK: define void @nc5(void (i8*)* nocapture %f, i8* nocapture %p) +; EITHER: 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) +; FNATTR: define void @test1_1(i8* nocapture readnone %x1_1, i8* %y1_1, i1 %c) +; ATTRIBUTOR: define void @test1_1(i8* nocapture %x1_1, i8* nocapture %y1_1, i1 %c) ; 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) +define void @test1_1(i8* %x1_1, i8* %y1_1, i1 %c) { + call i8* @test1_2(i8* %x1_1, i8* %y1_1, i1 %c) store i32* null, i32** @g ret void } -; CHECK: define i8* @test1_2(i8* nocapture readnone %x1_2, i8* returned %y1_2) -define i8* @test1_2(i8* %x1_2, i8* %y1_2) { - call void @test1_1(i8* %x1_2, i8* %y1_2) +; FNATTR: define i8* @test1_2(i8* nocapture readnone %x1_2, i8* returned %y1_2, i1 %c) +; ATTRIBUTOR: define i8* @test1_2(i8* nocapture %x1_2, i8* returned "no-capture-maybe-returned" %y1_2, i1 %c) +define i8* @test1_2(i8* %x1_2, i8* %y1_2, i1 %c) { + br i1 %c, label %t, label %f +t: + call void @test1_1(i8* %x1_2, i8* %y1_2, i1 %c) store i32* null, i32** @g + br label %f +f: ret i8* %y1_2 } -; CHECK: define void @test2(i8* nocapture readnone %x2) +; FNATTR: 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) +; FNATTR: define void @test3(i8* nocapture readnone %x3, i8* nocapture readnone %y3, i8* nocapture readnone %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) -define void @test4_1(i8* %x4_1) { - call i8* @test4_2(i8* %x4_1, i8* %x4_1, i8* %x4_1) +; FNATTR: define void @test4_1(i8* %x4_1, i1 %c) +; ATTRIBUTOR: define void @test4_1(i8* nocapture %x4_1, i1 %c) +define void @test4_1(i8* %x4_1, i1 %c) { + call i8* @test4_2(i8* %x4_1, i8* %x4_1, i8* %x4_1, i1 %c) 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) -define i8* @test4_2(i8* %x4_2, i8* %y4_2, i8* %z4_2) { - call void @test4_1(i8* null) +; FNATTR: define i8* @test4_2(i8* nocapture readnone %x4_2, i8* readnone returned %y4_2, i8* nocapture readnone %z4_2, i1 %c) +; ATTRIBUTOR: define i8* @test4_2(i8* nocapture %x4_2, i8* returned "no-capture-maybe-returned" %y4_2, i8* nocapture %z4_2, i1 %c) +define i8* @test4_2(i8* %x4_2, i8* %y4_2, i8* %z4_2, i1 %c) { + br i1 %c, label %t, label %f +t: + call void @test4_1(i8* null, i1 %c) store i32* null, i32** @g + br label %f +f: ret i8* %y4_2 } declare i8* @test5_1(i8* %x5_1) -; CHECK: define void @test5_2(i8* %x5_2) +; EITHER: define void @test5_2(i8* %x5_2) define void @test5_2(i8* %x5_2) { call i8* @test5_1(i8* %x5_2) store i32* null, i32** @g @@ -188,32 +208,32 @@ 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) +; EITHER: 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 ret void } -; CHECK: define void @test_cmpxchg(i32* nocapture %p) +; EITHER: 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) +; EITHER: 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) +; EITHER: define void @test_atomicrmw(i32* nocapture %p) define void @test_atomicrmw(i32* %p) { atomicrmw add i32* %p, i32 1 seq_cst ret void } -; CHECK: define void @test_volatile(i32* %x) +; EITHER: define void @test_volatile(i32* %x) define void @test_volatile(i32* %x) { entry: %gep = getelementptr i32, i32* %x, i64 1 @@ -221,7 +241,7 @@ ret void } -; CHECK: nocaptureLaunder(i8* nocapture %p) +; EITHER: nocaptureLaunder(i8* nocapture %p) define void @nocaptureLaunder(i8* %p) { entry: %b = call i8* @llvm.launder.invariant.group.p0i8(i8* %p) @@ -230,14 +250,14 @@ } @g2 = global i8* null -; CHECK: define void @captureLaunder(i8* %p) +; EITHER: define void @captureLaunder(i8* %p) define void @captureLaunder(i8* %p) { %b = call i8* @llvm.launder.invariant.group.p0i8(i8* %p) store i8* %b, i8** @g2 ret void } -; CHECK: @nocaptureStrip(i8* nocapture %p) +; EITHER: @nocaptureStrip(i8* nocapture %p) define void @nocaptureStrip(i8* %p) { entry: %b = call i8* @llvm.strip.invariant.group.p0i8(i8* %p) @@ -246,26 +266,29 @@ } @g3 = global i8* null -; CHECK: define void @captureStrip(i8* %p) +; EITHER: define void @captureStrip(i8* %p) define void @captureStrip(i8* %p) { %b = call i8* @llvm.strip.invariant.group.p0i8(i8* %p) store i8* %b, i8** @g3 ret void } -; CHECK: define i1 @captureICmp(i32* readnone %x) +; FNATTR: define i1 @captureICmp(i32* readnone %x) +; ATTRIBUTOR: define i1 @captureICmp(i32* %x) define i1 @captureICmp(i32* %x) { %1 = icmp eq i32* %x, null ret i1 %1 } -; CHECK: define i1 @captureICmpRev(i32* readnone %x) +; FNATTR: define i1 @captureICmpRev(i32* readnone %x) +; ATTRIBUTOR: define i1 @captureICmpRev(i32* %x) define i1 @captureICmpRev(i32* %x) { %1 = icmp eq i32* null, %x ret i1 %1 } -; CHECK: define i1 @nocaptureInboundsGEPICmp(i32* nocapture readnone %x) +; FNATTR: 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* @@ -273,7 +296,8 @@ ret i1 %3 } -; CHECK: define i1 @nocaptureInboundsGEPICmpRev(i32* nocapture readnone %x) +; FNATTR: define i1 @nocaptureInboundsGEPICmpRev(i32* nocapture readnone %x) +; ATTRIBUTOR: define i1 @nocaptureInboundsGEPICmpRev(i32* nocapture %x) define i1 @nocaptureInboundsGEPICmpRev(i32* %x) { %1 = getelementptr inbounds i32, i32* %x, i32 5 %2 = bitcast i32* %1 to i8* @@ -281,14 +305,16 @@ ret i1 %3 } -; CHECK: define i1 @nocaptureDereferenceableOrNullICmp(i32* nocapture readnone dereferenceable_or_null(4) %x) +; FNATTR: 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 ret i1 %2 } -; CHECK: define i1 @captureDereferenceableOrNullICmp(i32* readnone dereferenceable_or_null(4) %x) +; FNATTR: define i1 @captureDereferenceableOrNullICmp(i32* readnone dereferenceable_or_null(4) %x) +; ATTRIBUTOR: define i1 @captureDereferenceableOrNullICmp(i32* dereferenceable_or_null(4) %x) define i1 @captureDereferenceableOrNullICmp(i32* dereferenceable_or_null(4) %x) "null-pointer-is-valid"="true" { %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 @@ -159,7 +159,7 @@ ret void } define internal void @test13(i8* %a, i8* %b, i8* %c) { -; ATTRIBUTOR: define internal void @test13(i8* nonnull %a, i8* %b, i8* %c) +; ATTRIBUTOR: define internal void @test13(i8* nocapture nonnull %a, i8* nocapture %b, i8* nocapture %c) ret void } @@ -402,7 +402,7 @@ define i1 @parent8(i8* %a, i8* %bogus1, i8* %b) personality i8* bitcast (i32 (...)* @esfp to i8*){ ; FNATTR-LABEL: @parent8(i8* nonnull %a, i8* nocapture readnone %bogus1, i8* nonnull %b) ; FIXME : missing "nonnull", it should be @parent8(i8* nonnull %a, i8* %bogus1, i8* nonnull %b) -; ATTRIBUTOR-LABEL: @parent8(i8* %a, i8* %bogus1, i8* %b) +; ATTRIBUTOR-LABEL: @parent8(i8* %a, i8* nocapture %bogus1, i8* %b) ; BOTH-NEXT: entry: ; FNATTR-NEXT: invoke void @use2nonnull(i8* %a, i8* %b) ; ATTRIBUTOR-NEXT: invoke void @use2nonnull(i8* nonnull %a, i8* nonnull %b) diff --git a/llvm/test/Transforms/FunctionAttrs/nosync.ll b/llvm/test/Transforms/FunctionAttrs/nosync.ll --- a/llvm/test/Transforms/FunctionAttrs/nosync.ll +++ b/llvm/test/Transforms/FunctionAttrs/nosync.ll @@ -28,7 +28,7 @@ ; FNATTR: Function Attrs: norecurse nounwind optsize readnone ssp uwtable ; FNATTR-NEXT: define nonnull i32* @foo(%struct.ST* readnone %s) ; ATTRIBUTOR: Function Attrs: nofree nosync nounwind optsize readnone ssp uwtable -; ATTRIBUTOR-NEXT: define nonnull i32* @foo(%struct.ST* %s) +; ATTRIBUTOR-NEXT: define nonnull i32* @foo(%struct.ST* "no-capture-maybe-returned" %s) define i32* @foo(%struct.ST* %s) nounwind uwtable readnone optsize ssp { entry: %arrayidx = getelementptr inbounds %struct.ST, %struct.ST* %s, i64 1, i32 2, i32 1, i64 5, i64 13 @@ -186,7 +186,7 @@ ; FNATTR: Function Attrs: nofree noinline nounwind uwtable ; FNATTR-NEXT: define i32 @scc1(i32* %0) ; ATTRIBUTOR: Function Attrs: nofree noinline noreturn nosync nounwind uwtable -; ATTRIBUTOR-NEXT: define i32 @scc1(i32* %0) +; ATTRIBUTOR-NEXT: define i32 @scc1(i32* nocapture %0) define i32 @scc1(i32* %0) noinline nounwind uwtable { tail call void @scc2(i32* %0); %val = tail call i32 @volatile_load(i32* %0); @@ -196,7 +196,7 @@ ; FNATTR: Function Attrs: nofree noinline nounwind uwtable ; FNATTR-NEXT: define void @scc2(i32* %0) ; ATTRIBUTOR: Function Attrs: nofree noinline noreturn nosync nounwind uwtable -; ATTRIBUTOR-NEXT: define void @scc2(i32* %0) +; ATTRIBUTOR-NEXT: define void @scc2(i32* nocapture %0) define void @scc2(i32* %0) noinline nounwind uwtable { tail call i32 @scc1(i32* %0); ret void; @@ -224,7 +224,7 @@ ; FNATTR: Function Attrs: nofree norecurse nounwind ; FNATTR-NEXT: define void @foo1(i32* nocapture %0, %"struct.std::atomic"* nocapture %1) ; ATTRIBUTOR-NOT: nosync -; ATTRIBUTOR: define void @foo1(i32* %0, %"struct.std::atomic"* %1) +; ATTRIBUTOR: define void @foo1(i32* nocapture %0, %"struct.std::atomic"* nocapture %1) define void @foo1(i32* %0, %"struct.std::atomic"* %1) { store i32 100, i32* %0, align 4 fence release @@ -236,7 +236,7 @@ ; FNATTR: Function Attrs: nofree norecurse nounwind ; FNATTR-NEXT: define void @bar(i32* nocapture readnone %0, %"struct.std::atomic"* nocapture readonly %1) ; ATTRIBUTOR-NOT: nosync -; ATTRIBUTOR: define void @bar(i32* %0, %"struct.std::atomic"* %1) +; ATTRIBUTOR: define void @bar(i32* nocapture %0, %"struct.std::atomic"* nocapture %1) define void @bar(i32* %0, %"struct.std::atomic"* %1) { %3 = getelementptr inbounds %"struct.std::atomic", %"struct.std::atomic"* %1, i64 0, i32 0, i32 0 br label %4 @@ -256,7 +256,7 @@ ; FNATTR: Function Attrs: nofree norecurse nounwind ; FNATTR-NEXT: define void @foo1_singlethread(i32* nocapture %0, %"struct.std::atomic"* nocapture %1) ; ATTRIBUTOR: Function Attrs: nofree nosync -; ATTRIBUTOR: define void @foo1_singlethread(i32* %0, %"struct.std::atomic"* %1) +; ATTRIBUTOR: define void @foo1_singlethread(i32* nocapture %0, %"struct.std::atomic"* nocapture %1) define void @foo1_singlethread(i32* %0, %"struct.std::atomic"* %1) { store i32 100, i32* %0, align 4 fence syncscope("singlethread") release @@ -268,7 +268,7 @@ ; FNATTR: Function Attrs: nofree norecurse nounwind ; FNATTR-NEXT: define void @bar_singlethread(i32* nocapture readnone %0, %"struct.std::atomic"* nocapture readonly %1) ; ATTRIBUTOR: Function Attrs: nofree nosync -; ATTRIBUTOR: define void @bar_singlethread(i32* %0, %"struct.std::atomic"* %1) +; ATTRIBUTOR: define void @bar_singlethread(i32* nocapture %0, %"struct.std::atomic"* nocapture %1) define void @bar_singlethread(i32* %0, %"struct.std::atomic"* %1) { %3 = getelementptr inbounds %"struct.std::atomic", %"struct.std::atomic"* %1, i64 0, i32 0, i32 0 br label %4 @@ -289,9 +289,11 @@ ; TEST 14 - negative, checking volatile intrinsics. +; It is odd to add nocapture but a result of the llvm.memcpy nocapture. +; ; ATTRIBUTOR: Function Attrs: nounwind ; ATTRIBUTOR-NOT: nosync -; ATTRIBUTOR-NEXT: define i32 @memcpy_volatile(i8* %ptr1, i8* %ptr2) +; ATTRIBUTOR-NEXT: define i32 @memcpy_volatile(i8* nocapture %ptr1, i8* nocapture %ptr2) define i32 @memcpy_volatile(i8* %ptr1, i8* %ptr2) { call void @llvm.memcpy(i8* %ptr1, i8* %ptr2, i32 8, i1 1) ret i32 4 @@ -299,8 +301,10 @@ ; TEST 15 - positive, non-volatile intrinsic. +; It is odd to add nocapture but a result of the llvm.memset nocapture. +; ; ATTRIBUTOR: Function Attrs: nosync -; ATTRIBUTOR-NEXT: define i32 @memset_non_volatile(i8* %ptr1, i8 %val) +; ATTRIBUTOR-NEXT: define i32 @memset_non_volatile(i8* nocapture %ptr1, i8 %val) define i32 @memset_non_volatile(i8* %ptr1, i8 %val) { call void @llvm.memset(i8* %ptr1, i8 %val, i32 8, i1 0) ret i32 4 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 @@ -103,7 +103,7 @@ } ; CHECK: Function Attrs: nofree norecurse nosync nounwind -; CHECK-NEXT: define i32* @external_sink_ret2_nrw(i32* readnone %n0, i32* nocapture readonly %r0, i32* returned %w0) +; CHECK-NEXT: define i32* @external_sink_ret2_nrw(i32* readnone %n0, i32* nocapture readonly %r0, i32* returned "no-capture-maybe-returned" %w0) define i32* @external_sink_ret2_nrw(i32* %n0, i32* %r0, i32* %w0) { entry: %tobool = icmp ne i32* %n0, null