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 @@ -560,6 +560,28 @@ /// Abstract Attribute Classes /// ---------------------------------------------------------------------------- +/// An abstract attribute for the returned values of a function. +struct AAReturnedValues : public AbstractAttribute { + + /// See AbstractAttribute::AbstractAttribute(...). + AAReturnedValues(Function &F, InformationCache &InfoCache) + : AbstractAttribute(F, InfoCache) {} + + /// 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; +}; + } // 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 @@ -45,6 +45,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 @@ -167,11 +172,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) { @@ -324,6 +385,314 @@ 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, InformationCache &InfoCache) + : AAReturnedValues(F, InfoCache) { + // 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 = InfoCache.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)); + + indicateOptimisticFixpoint(); + 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::indicateOptimisticFixpoint(...). + void indicateOptimisticFixpoint() override { + IsFixed = true; + IsValidState &= true; + } + void indicatePessimisticFixpoint() override { + IsFixed = true; + IsValidState = false; + } +}; + +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) { + indicateOptimisticFixpoint(); + 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) { + indicateOptimisticFixpoint(); + return ChangeStatus::CHANGED; + } + + return Changed; +} + /// ---------------------------------------------------------------------------- /// Attributor /// ---------------------------------------------------------------------------- @@ -447,6 +816,15 @@ Function &F, InformationCache &InfoCache, DenseSet *Whitelist) { + // 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. + if (!Whitelist || Whitelist->count(AAReturnedValues::ID)) + registerAA(*new AAReturnedValuesImpl(F, InfoCache)); + } + // Walk all instructions to find more attribute opportunities and also // interesting instructions that might be queried by abstract attributes // during their initialization or update. @@ -459,6 +837,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/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 @@ -111,18 +111,15 @@ ; TEST SCC with various calls, casts, and comparisons agains NULL ; -; FIXME: returned missing for %a ; FIXME: no-capture missing for %a -; CHECK: define float* @scc_A(i32* readnone %a) +; CHECK: define float* @scc_A(i32* readnone returned %a) ; -; FIXME: returned missing for %a ; FIXME: no-capture missing for %a -; CHECK: define i64* @scc_B(double* readnone %a) +; CHECK: define 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 i8* @scc_C(i16* %a) +; CHECK: define 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. @@ -6,16 +10,26 @@ ; TEST SCC test returning an integer value argument ; -; CHECK: Function Attrs: noinline norecurse nounwind readnone uwtable -; CHECK: define i32 @sink_r0(i32 returned %r) -; -; FIXME: returned on %r missing: -; CHECK: Function Attrs: noinline nounwind readnone uwtable -; CHECK: define i32 @scc_r1(i32 %a, i32 %r, i32 %b) -; -; FIXME: returned on %r missing: -; CHECK: Function Attrs: noinline nounwind readnone uwtable -; CHECK: define i32 @scc_r2(i32 %a, i32 %b, i32 %r) +; BOTH: Function Attrs: noinline norecurse nounwind readnone uwtable +; BOTH: define i32 @sink_r0(i32 returned %r) +; BOTH: Function Attrs: noinline norecurse nounwind readnone uwtable +; BOTH: define i32 @sink_r0(i32 returned %r) +; BOTH: Function Attrs: noinline norecurse nounwind readnone uwtable +; BOTH: define i32 @scc_r1(i32 %a, i32 returned %r, i32 %b) +; BOTH: Function Attrs: noinline norecurse nounwind readnone uwtable +; BOTH: define i32 @scc_r2(i32 %a, i32 %b, i32 returned %r) +; BOTH: Function Attrs: noinline norecurse nounwind readnone uwtable +; BOTH: define i32 @scc_rX(i32 %a, i32 %b, i32 %r) +; +; FNATTR: define i32 @sink_r0(i32 returned %r) +; FNATTR: define i32 @scc_r1(i32 %a, i32 %r, i32 %b) +; FNATTR: define i32 @scc_r2(i32 %a, i32 %b, i32 %r) +; FNATTR: define i32 @scc_rX(i32 %a, i32 %b, i32 %r) +; +; ATTRIBUTOR: define i32 @sink_r0(i32 returned %r) +; ATTRIBUTOR: define i32 @scc_r1(i32 %a, i32 returned %r, i32 %b) +; ATTRIBUTOR: define i32 @scc_r2(i32 %a, i32 %b, i32 returned %r) +; ATTRIBUTOR: define i32 @scc_rX(i32 %a, i32 %b, i32 %r) ; ; int scc_r1(int a, int b, int r); ; int scc_r2(int a, int b, int r); @@ -149,16 +163,20 @@ ; TEST the same SCC as in SCC test returning an integer value argument returning a pointer value argument ; -; CHECK: Function Attrs: noinline norecurse nounwind readnone uwtable -; CHECK: define double* @ptr_sink_r0(double* readnone returned %r) +; BOTH: Function Attrs: noinline norecurse nounwind readnone uwtable +; BOTH: define double* @ptr_sink_r0(double* readnone returned %r) +; BOTH: Function Attrs: noinline norecurse nounwind readnone uwtable +; BOTH: define double* @ptr_scc_r1(double* %a, double* readnone returned %r, double* nocapture readnone %b) +; BOTH: Function Attrs: noinline norecurse nounwind readnone uwtable +; BOTH: define double* @ptr_scc_r2(double* readnone %a, double* readnone %b, double* readnone returned %r) ; -; FIXME: returned on %r missing: -; CHECK: Function Attrs: noinline nounwind readnone uwtable -; CHECK: define double* @ptr_scc_r1(double* %a, double* readnone %r, double* nocapture readnone %b) +; FNATTR: define double* @ptr_sink_r0(double* readnone returned %r) +; FNATTR: define double* @ptr_scc_r1(double* %a, double* readnone %r, double* nocapture readnone %b) +; FNATTR: define double* @ptr_scc_r2(double* readnone %a, double* readnone %b, double* readnone %r) ; -; FIXME: returned on %r missing: -; CHECK: Function Attrs: noinline nounwind readnone uwtable -; CHECK: define double* @ptr_scc_r2(double* readnone %a, double* readnone %b, double* readnone %r) +; ATTRIBUTOR: define double* @ptr_sink_r0(double* returned %r) +; ATTRIBUTOR: define double* @ptr_scc_r1(double* %a, double* returned %r, double* %b) +; ATTRIBUTOR: define double* @ptr_scc_r2(double* %a, double* %b, double* returned %r) ; ; double* ptr_scc_r1(double* a, double* b, double* r); ; double* ptr_scc_r2(double* a, double* b, double* r); @@ -242,9 +260,10 @@ ; return *a ? a : ret0(ret0(ret0(...ret0(a)...))); ; } ; -; FIXME: returned on %a missing: -; CHECK: Function Attrs: noinline nounwind readonly uwtable -; CHECK: define i32* @ret0(i32* readonly %a) +; FEW_IT: define i32* @ret0(i32* %a) +; FNATTR: define i32* @ret0(i32* readonly %a) +; BOTH: Function Attrs: noinline nounwind readonly uwtable +; BOTH: define i32* @ret0(i32* readonly returned %a) define i32* @ret0(i32* %a) #0 { entry: %v = load i32, i32* %a, align 4 @@ -281,11 +300,12 @@ ; return r; ; } ; -; CHECK: Function Attrs: noinline nounwind uwtable -; CHECK: declare void @unknown_fn(i32* (i32*)*) +; BOTH: declare void @unknown_fn(i32* (i32*)*) ; -; CHECK: Function Attrs: noinline nounwind uwtable -; CHECK: define i32* @calls_unknown_fn(i32* readnone returned %r) +; BOTH: Function Attrs: noinline nounwind uwtable +; BOTH: define i32* @calls_unknown_fn(i32* readnone returned %r) +; FNATTR: define i32* @calls_unknown_fn(i32* readnone returned %r) +; ATTRIBUTOR: define i32* @calls_unknown_fn(i32* returned %r) declare void @unknown_fn(i32* (i32*)*) #0 define i32* @calls_unknown_fn(i32* %r) #0 { @@ -307,17 +327,14 @@ ; ; Verify the maybe-redefined function is not annotated: ; -; CHECK: Function Attrs: noinline norecurse nounwind uwtable -; CHECK: define linkonce_odr i32* @maybe_redefined_fn(i32* %r) [[NoInlineNoRecurseNoUnwindUwtable:#[0-9]*]] +; BOTH: Function Attrs: noinline norecurse nounwind uwtable +; BOTH: define linkonce_odr i32* @maybe_redefined_fn(i32* %r) ; FIXME: We should not derive norecurse for potentially redefined functions! ; Function Attrs: noinline nounwind uwtable ; define linkonce_odr i32* @maybe_redefined_fn(i32* %r) ; -; CHECK: Function Attrs: noinline norecurse nounwind uwtable -; CHECK: define i32* @calls_maybe_redefined_fn(i32* returned %r) [[NoInlineNoRecurseNoUnwindUwtable]] -; FIXME: We should not derive norecurse for potentially redefined functions! -; Function Attrs: noinline nounwind uwtable -; define i32* @calls_maybe_redefined_fn(i32* returned %r) +; FNATTR: define i32* @calls_maybe_redefined_fn(i32* returned %r) +; ATTRIBUTOR: define i32* @calls_maybe_redefined_fn(i32* returned %r) define linkonce_odr i32* @maybe_redefined_fn(i32* %r) #0 { entry: ret i32* %r @@ -339,9 +356,11 @@ ; return b == 0? b : x; ; } ; -; FIXME: returned on %b missing: -; CHECK: Function Attrs: noinline norecurse nounwind readnone uwtable -; CHECK: define double @select_and_phi(double %b) +; BOTH: Function Attrs: noinline norecurse nounwind readnone uwtable +; BOTH: define double @select_and_phi(double returned %b) +; +; FNATTR: define double @select_and_phi(double %b) +; ATTRIBUTOR: define double @select_and_phi(double returned %b) define double @select_and_phi(double %b) #0 { entry: %cmp = fcmp ogt double %b, 0.000000e+00 @@ -367,9 +386,11 @@ ; return b == 0? b : x; ; } ; -; FIXME: returned on %b missing: -; CHECK: Function Attrs: noinline nounwind readnone uwtable -; CHECK: define double @recursion_select_and_phi(i32 %a, double %b) +; BOTH: Function Attrs: noinline nounwind readnone uwtable +; BOTH: define double @recursion_select_and_phi(i32 %a, double returned %b) +; +; FNATTR: define double @recursion_select_and_phi(i32 %a, double %b) +; ATTRIBUTOR: define double @recursion_select_and_phi(i32 %a, double returned %b) define double @recursion_select_and_phi(i32 %a, double %b) #0 { entry: %dec = add nsw i32 %a, -1 @@ -394,9 +415,11 @@ ; return (double*)b; ; } ; -; FIXME: returned on %b missing: -; CHECK: Function Attrs: noinline norecurse nounwind readnone uwtable -; CHECK: define double* @bitcast(i32* readnone %b) +; BOTH: Function Attrs: noinline norecurse nounwind readnone uwtable +; BOTH: define double* @bitcast(i32* readnone returned %b) +; +; FNATTR: define double* @bitcast(i32* readnone %b) +; ATTRIBUTOR: define double* @bitcast(i32* returned %b) define double* @bitcast(i32* %b) #0 { entry: %bc0 = bitcast i32* %b to double* @@ -413,9 +436,11 @@ ; return b != 0 ? b : x; ; } ; -; FIXME: returned on %b missing: -; CHECK: Function Attrs: noinline norecurse nounwind readnone uwtable -; CHECK: define double* @bitcasts_select_and_phi(i32* readnone %b) +; BOTH: Function Attrs: noinline norecurse nounwind readnone uwtable +; BOTH: define double* @bitcasts_select_and_phi(i32* readnone returned %b) +; +; FNATTR: define double* @bitcasts_select_and_phi(i32* readnone %b) +; ATTRIBUTOR: define double* @bitcasts_select_and_phi(i32* returned %b) define double* @bitcasts_select_and_phi(i32* %b) #0 { entry: %bc0 = bitcast i32* %b to double* @@ -447,8 +472,11 @@ ; /* return undef */ ; } ; -; CHECK: Function Attrs: noinline norecurse nounwind readnone uwtable -; CHECK: define double* @ret_arg_arg_undef(i32* readnone %b) +; BOTH: Function Attrs: noinline norecurse nounwind readnone uwtable +; BOTH: define double* @ret_arg_arg_undef(i32* readnone returned %b) +; +; FNATTR: define double* @ret_arg_arg_undef(i32* readnone %b) +; ATTRIBUTOR: define double* @ret_arg_arg_undef(i32* returned %b) define double* @ret_arg_arg_undef(i32* %b) #0 { entry: %bc0 = bitcast i32* %b to double* @@ -480,8 +508,11 @@ ; /* return undef */ ; } ; -; CHECK: Function Attrs: noinline norecurse nounwind readnone uwtable -; CHECK: define double* @ret_undef_arg_arg(i32* readnone %b) +; BOTH: Function Attrs: noinline norecurse nounwind readnone uwtable +; BOTH: define double* @ret_undef_arg_arg(i32* readnone returned %b) +; +; FNATTR: define double* @ret_undef_arg_arg(i32* readnone %b) +; ATTRIBUTOR: define double* @ret_undef_arg_arg(i32* returned %b) define double* @ret_undef_arg_arg(i32* %b) #0 { entry: %bc0 = bitcast i32* %b to double* @@ -513,8 +544,11 @@ ; /* return undef */ ; } ; -; CHECK: Function Attrs: noinline norecurse nounwind readnone uwtable -; CHECK: define double* @ret_undef_arg_undef(i32* readnone %b) +; BOTH: Function Attrs: noinline norecurse nounwind readnone uwtable +; BOTH: define double* @ret_undef_arg_undef(i32* readnone returned %b) +; +; FNATTR: define double* @ret_undef_arg_undef(i32* readnone %b) +; ATTRIBUTOR: define double* @ret_undef_arg_undef(i32* returned %b) define double* @ret_undef_arg_undef(i32* %b) #0 { entry: %bc0 = bitcast i32* %b to double* @@ -539,13 +573,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> +; Verify we do not assume b is returned ; -; CHECK: define i32* @ret_arg_or_unknown(i32* %b) -; CHECK: define i32* @ret_arg_or_unknown_through_phi(i32* %b) +; FNATTR: define i32* @ret_arg_or_unknown(i32* %b) +; FNATTR: define i32* @ret_arg_or_unknown_through_phi(i32* %b) +; ATTRIBUTOR: define i32* @ret_arg_or_unknown(i32* %b) +; ATTRIBUTOR: define i32* @ret_arg_or_unknown_through_phi(i32* %b) +; BOTH: define i32* @ret_arg_or_unknown(i32* %b) +; BOTH: define i32* @ret_arg_or_unknown_through_phi(i32* %b) declare i32* @unknown(i32*) define i32* @ret_arg_or_unknown(i32* %b) #0 { @@ -578,12 +616,13 @@ ret i32* %phi } + attributes #0 = { noinline nounwind uwtable } -; CHECK-NOT: attributes # -; CHECK-DAG: attributes #{{[0-9]*}} = { noinline norecurse nounwind readnone uwtable } -; CHECK-DAG: attributes #{{[0-9]*}} = { noinline nounwind readnone uwtable } -; CHECK-DAG: attributes #{{[0-9]*}} = { noinline nounwind readonly uwtable } -; CHECK-DAG: attributes #{{[0-9]*}} = { noinline nounwind uwtable } -; CHECK-DAG: attributes #{{[0-9]*}} = { noinline norecurse nounwind uwtable } -; CHECK-NOT: attributes # +; BOTH-NOT: attributes # +; BOTH-DAG: attributes #{{[0-9]*}} = { noinline norecurse nounwind readnone uwtable } +; BOTH-DAG: attributes #{{[0-9]*}} = { noinline nounwind readnone uwtable } +; BOTH-DAG: attributes #{{[0-9]*}} = { noinline nounwind readonly uwtable } +; BOTH-DAG: attributes #{{[0-9]*}} = { noinline nounwind uwtable } +; BOTH-DAG: attributes #{{[0-9]*}} = { noinline norecurse nounwind uwtable } +; BOTH-NOT: attributes # 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 @@ -30,7 +30,7 @@ target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" ; CHECK: Function Attrs: nounwind -; CHECK-NEXT: define i32* @external_ret2_nrw(i32* %n0, i32* %r0, i32* %w0) +; CHECK-NEXT: define i32* @external_ret2_nrw(i32* %n0, i32* %r0, i32* returned %w0) define i32* @external_ret2_nrw(i32* %n0, i32* %r0, i32* %w0) { entry: %call = call i32* @internal_ret0_nw(i32* %n0, i32* %w0) @@ -41,7 +41,7 @@ } ; CHECK: Function Attrs: nounwind -; CHECK-NEXT: define internal i32* @internal_ret0_nw(i32* %n0, i32* %w0) +; CHECK-NEXT: define internal i32* @internal_ret0_nw(i32* returned %n0, i32* %w0) define internal i32* @internal_ret0_nw(i32* %n0, i32* %w0) { entry: %r0 = alloca i32, align 4 @@ -70,7 +70,7 @@ } ; CHECK: Function Attrs: nounwind -; CHECK-NEXT: define internal i32* @internal_ret1_rrw(i32* %r0, i32* %r1, i32* %w0) +; CHECK-NEXT: define internal i32* @internal_ret1_rrw(i32* %r0, i32* returned %r1, i32* %w0) define internal i32* @internal_ret1_rrw(i32* %r0, i32* %r1, i32* %w0) { entry: %0 = load i32, i32* %r0, align 4 @@ -121,7 +121,7 @@ } ; CHECK: Function Attrs: nounwind -; CHECK-NEXT: define internal i32* @internal_ret1_rw(i32* %r0, i32* %w0) +; CHECK-NEXT: define internal i32* @internal_ret1_rw(i32* %r0, i32* returned %w0) define internal i32* @internal_ret1_rw(i32* %r0, i32* %w0) { entry: %0 = load i32, i32* %r0, align 4 @@ -147,7 +147,7 @@ } ; CHECK: Function Attrs: nounwind -; CHECK-NEXT: define i32* @external_source_ret2_nrw(i32* %n0, i32* %r0, i32* %w0) +; CHECK-NEXT: define i32* @external_source_ret2_nrw(i32* %n0, i32* %r0, i32* returned %w0) define i32* @external_source_ret2_nrw(i32* %n0, i32* %r0, i32* %w0) { entry: %call = call i32* @external_sink_ret2_nrw(i32* %n0, i32* %r0, i32* %w0)