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 %[[x:.*]]) +//CHECK: define spir_func i32 addrspace(1)* @addr_cast(i32* readnone returned %[[x:.*]]) //CHECK: %[[cast:.*]] = addrspacecast i32* %[[x]] to i32 addrspace(1)* //CHECK: ret i32 addrspace(1)* %[[cast]] global int* addr_cast(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 @@ -256,6 +256,27 @@ /// Abstract Attribute Classes /// ---------------------------------------------------------------------------- +/// An abstract attribute for the returned values of a function. +struct AAReturnedValues : public AbstractAttribute { + + /// See AbstractAttribute::AbstractAttribute(...). + AAReturnedValues(Function &F) : AbstractAttribute(F) {} + + /// Return the number of potential return values, -1 if unknown. + virtual size_t getNumReturnValues() const = 0; + + /// Return true if \p V maybe returned by the underlying function. + /// + /// This method will return false until it cannot justify that answer anymore. + virtual bool maybeReturned(Value *V) const = 0; + + /// See AbstractAttribute::getAttrKind() + virtual Attribute::AttrKind getAttrKind() const override { return ID; } + + /// The identifier used by the Attributor for this class of attributes. + static constexpr Attribute::AttrKind ID = Attribute::Returned; +}; + /// ---------------------------------------------------------------------------- /// 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 @@ -44,6 +44,11 @@ STATISTIC(NumAttributesManifested, "Number of abstract attributes manifested in IR"); +STATISTIC(NumFnUniqueReturned, "Number of function with unique return"); +STATISTIC(NumFnKnownReturns, "Number of function with known return values"); +STATISTIC(NumFnArgumentReturned, + "Number of function arguments marked returned"); + // TODO: Determine a good default value. // // In the LLVM-TS and SPEC2006, 32 seems to not induce compile time overheads @@ -168,11 +173,67 @@ if (!Attr.isEnumAttribute()) return; switch (Attr.getKindAsEnum()) { + case Attribute::Returned: + NumFnArgumentReturned++; + return; default: return; } } +template +using followValueCB_t = std::function; +template +using visitValueCB_t = std::function; + +/// Recursively visit all values that might become \p V at some point. This will +/// be done by looking through cast instructions, selects, phis, and calls with +/// the "returned" attribute. The callback \p followValueCB is asked before +/// multiple potential origina values are tracked. If it returns false, the +/// tracking of the passed value is stopped right away. Once we cannot look +/// through the value any further, the callback \p visitValueCB is invoked. +template +static void gernericValueTraversal(Value *V, StateTy &State, + followValueCB_t &followValueCB, + visitValueCB_t &visitValueCB) { + V = V->stripPointerCasts(); + + // Explicitly look through calls with a "returned" attribute if we do not have + // a pointer as stripPointerCasts only works on them. + if (!V->getType()->isPointerTy()) { + CallSite CS(V); + if (CS && CS.getCalledFunction()) + for (Argument &Arg : CS.getCalledFunction()->args()) + if (Arg.hasReturnedAttr()) + return gernericValueTraversal(CS.getArgOperand(Arg.getArgNo()), State, + followValueCB, visitValueCB); + } + + // Check if we should start to follow multiple values. To prevent endless + // recursion keep a record of the values we followed! + if (!followValueCB(V, State)) + return; + + // Look through select instructions, visit both potential values. + if (auto *SI = dyn_cast(V)) { + gernericValueTraversal(SI->getTrueValue(), State, followValueCB, + visitValueCB); + gernericValueTraversal(SI->getFalseValue(), State, followValueCB, + visitValueCB); + return; + } + + // Look through phi nodes, visit all operands. + if (auto *PHI = dyn_cast(V)) { + for (Value *PHIOp : PHI->operands()) + gernericValueTraversal(PHIOp, State, followValueCB, visitValueCB); + return; + } + + // Once a leaf is reached we inform the user through the callback. + visitValueCB(V, State); +} + /// Helper to identify the correct offset into an attribute list. static unsigned getAttrIndex(AbstractAttribute::ManifestPosition MP, unsigned ArgNo = 0) { @@ -325,6 +386,309 @@ return const_cast(this)->getAnchorScope(); } +/// --------------------- Function Return Values ------------------------------- + +/// "Attribute" that collects all potential returned values and the return +/// instructions that they arise from. +/// +/// If there is a unique returned value R, the manifest method will: +/// - mark R with the "returned" attribute, if R is an argument. +class AAReturnedValuesImpl final : public AAReturnedValues, AbstractState { + + /// Mapping of values potentially returned by the associated function to the + /// return instructions that might return them. + DenseMap> ReturnedValues; + + /// Return an assumed unique return value if a single candidate is found. If + /// there cannot be one, return a nullptr. If it is not clear yet, return the + /// Optional::NoneType. + Optional getUniqueReturnValue() const; + + /// State flags + /// + ///{ + bool IsFixed = false; + bool IsValidState = true; + bool HasOverdefinedReturnedCalls = false; + ///} + + /// Collect values that could become \p V in the set \p Values, each mapped to + /// \p ReturnInsts. + static void collectValuesRecursively( + Value *V, SmallPtrSetImpl &ReturnInsts, + DenseMap> &Values) { + + SmallPtrSet Visited; + followValueCB_t FollowValueCB = [&](Value *Val, bool &) { + return Visited.insert(Val).second; + }; + visitValueCB_t VisitValueCB = [&](Value *Val, bool &) { + Values[Val].insert(ReturnInsts.begin(), ReturnInsts.end()); + }; + + bool UnusedBool; + gernericValueTraversal(V, UnusedBool, FollowValueCB, VisitValueCB); + } + +public: + /// See AbstractAttribute::AbstractAttribute(...). + AAReturnedValuesImpl(Function &F) : AAReturnedValues(F) { + // We do not have an associated argument yet. + AssociatedVal = nullptr; + } + + /// See AbstractAttribute::initialize(...). + void initialize(Attributor &A) override { + Function &F = cast(getAnchoredValue()); + + // The map from instruction opcodes to those instructions in the function. + auto &OpcodeInstMap = A.getOpcodeInstMapForFunction(F); + + // Look through all arguments, if one is marked as returned we are done. + for (Argument &Arg : F.args()) { + if (Arg.hasReturnedAttr()) { + assert(ReturnedValues.empty()); + + auto &ReturnInstSet = ReturnedValues[&Arg]; + for (Instruction *RI : OpcodeInstMap[Instruction::Ret]) + ReturnInstSet.insert(cast(RI)); + + indicateFixpoint(/* Optimistic */ true); + return; + } + } + + // If no argument was marked as returned we look at all return instructions + // and collect potentially returned values. + for (Instruction *RI : OpcodeInstMap[Instruction::Ret]) { + SmallPtrSet RISet({cast(RI)}); + collectValuesRecursively(cast(RI)->getReturnValue(), RISet, + ReturnedValues); + } + } + + /// See AbstractAttribute::manifest(...). + virtual ChangeStatus manifest(Attributor &A) override; + + /// See AbstractAttribute::getState(...). + virtual AbstractState &getState() override { return *this; } + + /// See AbstractAttribute::getState(...). + virtual const AbstractState &getState() const override { return *this; } + + /// See AbstractAttribute::getManifestPosition(). + virtual ManifestPosition getManifestPosition() const override { + return MP_ARGUMENT; + } + + /// See AbstractAttribute::maybeReturned(...). + virtual bool maybeReturned(Value *V) const override { + return (!isValidState() || HasOverdefinedReturnedCalls || + ReturnedValues.count(V)); + } + + /// See AbstractAttribute::updateImpl(Attributor &A). + virtual ChangeStatus updateImpl(Attributor &A) override; + + /// See AAReturnedValues::getNumReturnValues(). + virtual size_t getNumReturnValues() const override { + return isValidState() ? ReturnedValues.size() : -1; + } + + /// Iterators to walk through all possibly returned values. + ///{ + using const_iterator = decltype(ReturnedValues)::const_iterator; + const_iterator begin() const { return ReturnedValues.begin(); } + const_iterator end() const { return ReturnedValues.end(); } + ///} + + /// Pretty print the attribute similar to the IR representation. + virtual const std::string getAsStr() const override; + + /// See AbstractState::isAtFixpoint(). + bool isAtFixpoint() const override { return IsFixed; } + + /// See AbstractState::isValidState(). + bool isValidState() const override { return IsValidState; } + + /// See AbstractState::indicateFixpoint(...). + void indicateFixpoint(bool Optimistic) override { + IsFixed = true; + IsValidState &= Optimistic; + } +}; + +ChangeStatus AAReturnedValuesImpl::manifest(Attributor &A) { + ChangeStatus Changed = ChangeStatus::UNCHANGED; + + // Bookkeeping. + assert(isValidState()); + NumFnKnownReturns++; + + // Check if we have an assumed unique return value that we could manifest. + Optional UniqueRV = getUniqueReturnValue(); + + if (!UniqueRV.hasValue() || !UniqueRV.getValue()) + return Changed; + + // Bookkeeping. + NumFnUniqueReturned++; + + // If the assumed unique return value is an argument, annotate it. + if (auto *UniqueRVArg = dyn_cast(UniqueRV.getValue())) { + AssociatedVal = UniqueRVArg; + Changed = AbstractAttribute::manifest(A) | Changed; + } + + return Changed; +} + +const std::string AAReturnedValuesImpl::getAsStr() const { + return (isAtFixpoint() ? "returns(#" : "may-return(#") + + (isValidState() ? std::to_string(getNumReturnValues()) : "?") + ")"; +} + +Optional AAReturnedValuesImpl::getUniqueReturnValue() const { + Optional UniqueRV; + + // Check all returned values but ignore call sites as long as we have not + // encountered an overdefined one during an update. If there is a unique value + // always returned, ignoring potential undef values that can also be present, + // it is assumed to be the actual return value and forwarded to the caller of + // this method. If there are multiple, a nullptr is returned indicating there + // cannot be a unique returned value. + for (auto &It : ReturnedValues) { + Value *RV = It.first; + + ImmutableCallSite ICS(RV); + if (ICS && !HasOverdefinedReturnedCalls) + continue; + + // If we found a second returned value and neither the current nor the saved + // one is an undef, there is no unique returned value. Undefs are special + // since we can pretend they have any value. + if (UniqueRV.hasValue() && UniqueRV != RV && + !(isa(RV) || isa(UniqueRV.getValue()))) + return Optional(nullptr); + + // Do not overwrite a value with an undef. + if (UniqueRV.hasValue() && isa(RV)) + continue; + + UniqueRV = RV; + } + + return UniqueRV; +} + +ChangeStatus AAReturnedValuesImpl::updateImpl(Attributor &A) { + + // Check if we know of any values returned by the associated function, + // if not, we are done. + if (getNumReturnValues() == 0) { + indicateFixpoint(/* Optimistic */ true); + return ChangeStatus::UNCHANGED; + } + + // Check if any of the returned values is a call site we can refine. + SmallPtrSet DelRVs; + decltype(ReturnedValues) AddRVs; + bool HasCallSite = false; + + // Look at all returned call sites. + for (auto &It : ReturnedValues) { + SmallPtrSet &ReturnInsts = It.second; + Value *RV = It.first; + LLVM_DEBUG(dbgs() << "[AAReturnedValues] Potentially returned value " << *RV + << "\n"); + + // Only call sites can change during an update, ignore the rest. + CallSite RetCS(RV); + if (!RetCS) + continue; + + // For now, any call site we see will prevent us from directly fixing the + // state. However, if the information on the callees is fixed, the call + // sites will be removed and we will fix the information for this state. + HasCallSite = true; + + // If we do not have information on the + auto *RetCSAA = A.getAAFor(*this, *RV); + if (!RetCSAA || !RetCSAA->isValidState()) { + HasOverdefinedReturnedCalls = true; + LLVM_DEBUG(dbgs() << "[AAReturnedValues] Returned call site (" << *RV + << ") with " << (RetCSAA ? "invalid" : "no") + << " associated state\n"); + continue; + } + + // Try to find a unique assumed return value for the called function. + Optional AssumedUniqueRV = RetCSAA->getUniqueReturnValue(); + + // If no unique assumed return value was found due to the lack of + // candidates, we may need to resolve more calls (through more update + // iterations) or the called function will not return. Either way, we simply + // stick with the call site as return value. Because there were not multiple + // possibilities, we do not treat it as overdefined. + if (!AssumedUniqueRV.hasValue()) + continue; + + // If multiple, non-refinable values were found, there cannot be a unique + // return value for the called function. The returned call is overdefined! + if (!AssumedUniqueRV.getValue()) { + HasOverdefinedReturnedCalls = true; + LLVM_DEBUG(dbgs() << "[AAReturnedValues] Returned call site has multiple " + "potentially returned values\n"); + continue; + } + + bool UniqueRVIsKnown = RetCSAA->isAtFixpoint(); + LLVM_DEBUG(dbgs() << "[AAReturnedValues] Returned call site " + << (UniqueRVIsKnown ? "known" : "assumed") + << " unique return value: " << *AssumedUniqueRV << "\n"); + + // The unique assumed return value. + Value *AssumedRetVal = AssumedUniqueRV.getValue(); + + // If the unique assumed return value is an argument, lookup the matching + // call site operand and recursively collect new returned values. + // If it is not an argument, it is just put into the set of returned values + // as we would have already looked through casts, phis, and similar values. + if (Argument *AssumedRetArg = dyn_cast(AssumedRetVal)) + collectValuesRecursively(RetCS.getArgument(AssumedRetArg->getArgNo()), + ReturnInsts, AddRVs); + else + AddRVs[AssumedRetVal].insert(ReturnInsts.begin(), ReturnInsts.end()); + + // If the information for the called function is known to hold we drop the + // call from the set of returned values. + if (UniqueRVIsKnown) + DelRVs.insert(RV); + } + + ChangeStatus Changed = + DelRVs.empty() ? ChangeStatus::UNCHANGED : ChangeStatus::CHANGED; + + // Update the return values set after we stopped iterating over it. + for (Value *RV : DelRVs) + ReturnedValues.erase(RV); + + for (auto &It : AddRVs) { + auto &ReturnInsts = ReturnedValues[It.first]; + for (ReturnInst *RI : It.second) + if (ReturnInsts.insert(RI).second) + Changed = ChangeStatus::CHANGED; + } + + // If there is no call site in the returned values we are done. + if (!HasCallSite) { + indicateFixpoint(/* Optimistic */ true); + return ChangeStatus::CHANGED; + } + + return Changed; +} + /// ---------------------------------------------------------------------------- /// Attributor /// ---------------------------------------------------------------------------- @@ -506,6 +870,14 @@ } void Attributor::identifyAbstractAttributes(Function &F) { + // Return attributes are only appropriate if the return type is non void. + Type *ReturnType = F.getReturnType(); + if (!ReturnType->isVoidTy()) { + // Argument attribute "returned" --- Create only one per function even + // though it is an argument attribute. + registerAA(*new AAReturnedValuesImpl(F)); + } + // Walk all instructions to find more attribute opportunities and also // interesting instructions that might be querried by abstract attributes // during their initialziation or update. @@ -518,6 +890,8 @@ switch (I.getOpcode()) { default: break; + case Instruction::Ret: // ReturnInst are interesting for AAReturnedValues. + IsInterestingOpcode = true; } if (IsInterestingOpcode) InstOpcodeMap[I.getOpcode()].push_back(&I); 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 @@ -45,7 +45,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* %w0) #[[NOUNWIND:[0-9]*]] +; CHECK: define dso_local i32* @external_ret2_nrw(i32* %n0, i32* %r0, i32* 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 +55,7 @@ ret i32* %call3 } -; CHECK: define internal i32* @internal_ret0_nw(i32* %n0, i32* %w0) #[[NOUNWIND]] +; CHECK: define internal i32* @internal_ret0_nw(i32* returned %n0, i32* %w0) #[[NOUNWIND]] define internal i32* @internal_ret0_nw(i32* %n0, i32* %w0) { entry: %r0 = alloca i32, align 4 @@ -83,7 +83,7 @@ ret i32* %retval.0 } -; CHECK: define internal i32* @internal_ret1_rrw(i32* %r0, i32* %r1, i32* %w0) #[[NOUNWIND]] +; CHECK: define internal i32* @internal_ret1_rrw(i32* %r0, i32* returned %r1, i32* %w0) #[[NOUNWIND]] define internal i32* @internal_ret1_rrw(i32* %r0, i32* %r1, i32* %w0) { entry: %0 = load i32, i32* %r0, align 4 @@ -132,7 +132,7 @@ ret i32* %w0 } -; CHECK: define internal i32* @internal_ret1_rw(i32* %r0, i32* %w0) #[[NOUNWIND]] +; CHECK: define internal i32* @internal_ret1_rw(i32* %r0, i32* returned %w0) #[[NOUNWIND]] define internal i32* @internal_ret1_rw(i32* %r0, i32* %w0) { entry: %0 = load i32, i32* %r0, align 4 @@ -157,7 +157,7 @@ ret i32* %retval.0 } -; CHECK: define dso_local i32* @external_source_ret2_nrw(i32* %n0, i32* %r0, i32* %w0) #[[NOUNWIND]] +; CHECK: define dso_local i32* @external_source_ret2_nrw(i32* %n0, i32* %r0, i32* 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 @@ -128,18 +128,15 @@ ; TEST 5: ; -; FIXME: returned missing for %a ; FIXME: no-capture missing for %a -; CHECK: define dso_local float* @scc_A(i32* readnone %a) +; CHECK: define dso_local float* @scc_A(i32* readnone returned %a) ; -; FIXME: returned missing for %a ; FIXME: no-capture missing for %a -; CHECK: define dso_local i64* @scc_B(double* readnone %a) +; CHECK: define dso_local i64* @scc_B(double* readnone returned %a) ; -; FIXME: returned missing for %a ; FIXME: readnone missing for %s ; FIXME: no-capture missing for %a -; CHECK: define dso_local i8* @scc_C(i16* %a) +; CHECK: define dso_local i8* @scc_C(i16* returned %a) ; ; float *scc_A(int *a) { ; return (float*)(a ? (int*)scc_A((int*)scc_B((double*)scc_C((short*)a))) : a); 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 @@ -1,4 +1,8 @@ -; RUN: opt -functionattrs -attributor -S < %s | FileCheck %s +; RUN: opt -functionattrs -S < %s | FileCheck %s --check-prefix=FNATTR +; RUN: opt -attributor -S < %s | FileCheck %s --check-prefix=ATTRIBUTOR +; RUN: opt -attributor -functionattrs -S < %s | FileCheck %s --check-prefix=BOTH +; RUN: opt -attributor -attributor-max-iterations=18 -S < %s | FileCheck %s --check-prefix=FEW_IT +; RUN: opt -attributor -attributor-max-iterations=19 -functionattrs -S < %s | FileCheck %s --check-prefix=BOTH ; ; Test cases specifically designed for the "returned" argument attribute. ; We use FIXME's to indicate problems and missing attributes. @@ -20,13 +24,20 @@ ; TEST 1 ; -; CHECK: define dso_local i32 @sink_r0(i32 returned %r) [[NoInlineNoRecurseNoUnwindReadnoneUwtable:#[0-9]]] +; BOTH: define dso_local i32 @sink_r0(i32 returned %r) [[NoInlineNoRecurseNoUnwindReadnoneUwtable:#[0-9]]] +; BOTH: define dso_local i32 @scc_r1(i32 %a, i32 returned %r, i32 %b) [[NoInlineNoUnwindReadnoneUwtable:#[0-9]]] +; BOTH: define dso_local i32 @scc_r2(i32 %a, i32 %b, i32 returned %r) [[NoInlineNoUnwindReadnoneUwtable]] +; BOTH: define dso_local i32 @scc_rX(i32 %a, i32 %b, i32 %r) [[NoInlineNoUnwindReadnoneUwtable]] ; -; FIXME: returned on %r missing: -; CHECK: define dso_local i32 @scc_r1(i32 %a, i32 %r, i32 %b) [[NoInlineNoUnwindReadnoneUwtable:#[0-9]]] +; FNATTR: define dso_local i32 @sink_r0(i32 returned %r) [[NoInlineNoRecurseNoUnwindReadnoneUwtable:#[0-9]]] +; FNATTR: define dso_local i32 @scc_r1(i32 %a, i32 %r, i32 %b) [[NoInlineNoUnwindReadnoneUwtable:#[0-9]]] +; FNATTR: define dso_local i32 @scc_r2(i32 %a, i32 %b, i32 %r) [[NoInlineNoUnwindReadnoneUwtable]] +; FNATTR: define dso_local i32 @scc_rX(i32 %a, i32 %b, i32 %r) [[NoInlineNoUnwindReadnoneUwtable]] ; -; FIXME: returned on %r missing: -; CHECK: define dso_local i32 @scc_r2(i32 %a, i32 %b, i32 %r) [[NoInlineNoUnwindReadnoneUwtable]] +; ATTRIBUTOR: define dso_local i32 @sink_r0(i32 returned %r) [[NoInlineNoRecurseNoUnwindReadnoneUwtable:#[0-9]]] +; ATTRIBUTOR: define dso_local i32 @scc_r1(i32 %a, i32 returned %r, i32 %b) [[NoInlineNoUnwindReadnoneUwtable:#[0-9]]] +; ATTRIBUTOR: define dso_local i32 @scc_r2(i32 %a, i32 %b, i32 returned %r) [[NoInlineNoUnwindReadnoneUwtable]] +; ATTRIBUTOR: define dso_local i32 @scc_rX(i32 %a, i32 %b, i32 %r) [[NoInlineNoUnwindReadnoneUwtable]] ; ; int scc_r1(int a, int b, int r); ; int scc_r2(int a, int b, int r); @@ -161,13 +172,17 @@ ; TEST 2 ; -; CHECK: define dso_local double* @ptr_sink_r0(double* readnone returned %r) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] +; BOTH: define dso_local double* @ptr_sink_r0(double* readnone 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]] ; -; FIXME: returned on %r missing: -; CHECK: define dso_local double* @ptr_scc_r1(double* %a, double* readnone %r, double* nocapture readnone %b) [[NoInlineNoUnwindReadnoneUwtable]] +; FNATTR: define dso_local double* @ptr_sink_r0(double* readnone returned %r) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] +; 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]] ; -; FIXME: returned on %r missing: -; CHECK: 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_scc_r2(double* %a, double* %b, double* returned %r) [[NoInlineNoUnwindReadnoneUwtable]] ; ; double* ptr_scc_r1(double* a, double* b, double* r); ; double* ptr_scc_r2(double* a, double* b, double* r); @@ -251,8 +266,9 @@ ; return *a ? a : ret0(ret0(ret0(...ret0(a)...))); ; } ; -; FIXME: returned on %a missing: -; CHECK: define dso_local i32* @ret0(i32* readonly %a) [[NoInlineNoUnwindReadonlyUwtable:#[0-9]*]] +; FEW_IT: define dso_local i32* @ret0(i32* %a) +; FNATTR: define dso_local i32* @ret0(i32* readonly %a) [[NoInlineNoUnwindReadonlyUwtable:#[0-9]*]] +; BOTH: define dso_local i32* @ret0(i32* readonly returned %a) [[NoInlineNoUnwindReadonlyUwtable:#[0-9]*]] define dso_local i32* @ret0(i32* %a) #0 { entry: %v = load i32, i32* %a, align 4 @@ -289,9 +305,11 @@ ; return r; ; } ; -; CHECK: declare void @unknown_fn(i32* (i32*)*) [[NoInlineNoUnwindUwtable:#[0-9]*]] +; BOTH: declare void @unknown_fn(i32* (i32*)*) [[NoInlineNoUnwindUwtable:#[0-9]*]] ; -; CHECK: define dso_local i32* @calls_unknown_fn(i32* readnone returned %r) [[NoInlineNoUnwindUwtable]] +; BOTH: define dso_local i32* @calls_unknown_fn(i32* readnone returned %r) [[NoInlineNoUnwindUwtable]] +; FNATTR: define dso_local i32* @calls_unknown_fn(i32* readnone returned %r) [[NoInlineNoUnwindUwtable:#[0-9]*]] +; ATTRIBUTOR: define dso_local i32* @calls_unknown_fn(i32* returned %r) [[NoInlineNoUnwindUwtable:#[0-9]*]] ; declare void @unknown_fn(i32* (i32*)*) #0 @@ -313,11 +331,12 @@ ; } ; ; Verify the maybe-redefined function is not annotated: -; CHECK: define linkonce_odr i32* @maybe_redefined_fn(i32* %r) [[NoInlineNoRecurseNoUnwindUwtable:#[0-9]*]] +; BOTH: define linkonce_odr i32* @maybe_redefined_fn(i32* %r) [[NoInlineNoRecurseNoUnwindUwtable:#[0-9]*]] ; FIXME: We should not derive norecurse for potentially redefined functions! ; define linkonce_odr i32* @maybe_redefined_fn(i32* %r) [[NoInlineNoUnwindUwtable]] ; -; CHECK: define dso_local i32* @calls_maybe_redefined_fn(i32* returned %r) [[NoInlineNoRecurseNoUnwindUwtable]] +; FNATTR: define dso_local i32* @calls_maybe_redefined_fn(i32* returned %r) [[NoInlineNoRecurseNoUnwindUwtable:#[0-9]*]] +; ATTRIBUTOR: define dso_local i32* @calls_maybe_redefined_fn(i32* returned %r) [[NoInlineNoUnwindUwtable]] ; FIXME: We should not derive norecurse for potentially redefined functions! ; define dso_local i32* @calls_maybe_redefined_fn(i32* returned %r) [[NoInlineNoUnwindUwtable]] ; @@ -342,8 +361,8 @@ ; return b == 0? b : x; ; } ; -; FIXME: returned on %b missing: -; CHECK: define dso_local double @select_and_phi(double %b) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] +; FNATTR: define dso_local double @select_and_phi(double %b) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] +; ATTRIBUTOR: define dso_local double @select_and_phi(double returned %b) [[NoInlineNoUnwindUwtable]] ; define dso_local double @select_and_phi(double %b) #0 { entry: @@ -370,8 +389,8 @@ ; return b == 0? b : x; ; } ; -; FIXME: returned on %b missing: -; CHECK: define dso_local double @recursion_select_and_phi(i32 %a, double %b) [[NoInlineNoUnwindReadnoneUwtable]] +; FNATTR: define dso_local double @recursion_select_and_phi(i32 %a, double %b) [[NoInlineNoUnwindReadnoneUwtable]] +; ATTRIBUTOR: define dso_local double @recursion_select_and_phi(i32 %a, double returned %b) [[NoInlineNoUnwindUwtable]] ; define dso_local double @recursion_select_and_phi(i32 %a, double %b) #0 { entry: @@ -397,8 +416,9 @@ ; return (double*)b; ; } ; -; FIXME: returned on %b missing: -; CHECK: define dso_local double* @bitcast(i32* readnone %b) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] +; 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]] ; define dso_local double* @bitcast(i32* %b) #0 { entry: @@ -416,8 +436,9 @@ ; return b != 0 ? b : x; ; } ; -; FIXME: returned on %b missing: -; CHECK: define dso_local double* @bitcasts_select_and_phi(i32* readnone %b) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] +; 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]] ; define dso_local double* @bitcasts_select_and_phi(i32* %b) #0 { entry: @@ -450,7 +471,9 @@ ; /* return undef */ ; } ; -; CHECK: define dso_local double* @ret_arg_arg_undef(i32* readnone %b) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] +; 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]] ; define dso_local double* @ret_arg_arg_undef(i32* %b) #0 { entry: @@ -483,7 +506,9 @@ ; /* return undef */ ; } ; -; CHECK: define dso_local double* @ret_undef_arg_arg(i32* readnone %b) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] +; 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]] ; define dso_local double* @ret_undef_arg_arg(i32* %b) #0 { entry: @@ -516,7 +541,9 @@ ; /* return undef */ ; } ; -; CHECK: define dso_local double* @ret_undef_arg_undef(i32* readnone %b) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] +; 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]] ; define dso_local double* @ret_undef_arg_undef(i32* %b) #0 { entry: @@ -542,13 +569,17 @@ ; int* ret_arg_or_unknown(int* b) { ; if (b == 0) ; return b; -; return unknown(b); +; return unknown(); ; } ; ; Verify we do not assume b is returned> ; -; CHECK: define dso_local i32* @ret_arg_or_unknown(i32* %b) -; CHECK: define dso_local i32* @ret_arg_or_unknown_through_phi(i32* %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* %b) +; ATTRIBUTOR: define dso_local i32* @ret_arg_or_unknown_through_phi(i32* %b) +; BOTH: define dso_local i32* @ret_arg_or_unknown(i32* %b) +; BOTH: define dso_local i32* @ret_arg_or_unknown_through_phi(i32* %b) ; declare dso_local i32* @unknown(i32*) @@ -582,10 +613,10 @@ ret i32* %phi } + attributes #0 = { noinline nounwind uwtable } -; CHECK-DAG: attributes [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] = { noinline norecurse nounwind readnone uwtable } -; CHECK-DAG: attributes [[NoInlineNoUnwindReadnoneUwtable]] = { noinline nounwind readnone uwtable } -; CHECK-DAG: attributes [[NoInlineNoUnwindReadonlyUwtable]] = { noinline nounwind readonly uwtable } -; CHECK-DAG: attributes [[NoInlineNoUnwindUwtable]] = { noinline nounwind uwtable } -; CHECK-DAG: attributes [[NoInlineNoRecurseNoUnwindUwtable]] = { noinline norecurse nounwind uwtable } +; BOTH-DAG: attributes [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] = { noinline norecurse nounwind readnone uwtable } +; BOTH-DAG: attributes [[NoInlineNoUnwindReadnoneUwtable]] = { noinline nounwind readnone uwtable } +; BOTH-DAG: attributes [[NoInlineNoUnwindReadonlyUwtable]] = { noinline nounwind readonly uwtable } +; BOTH-DAG: attributes [[NoInlineNoRecurseNoUnwindUwtable]] = { noinline norecurse nounwind uwtable }