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 @@ -245,6 +245,26 @@ /// 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 @@ -38,6 +38,11 @@ STATISTIC(NumFnWithoutExactDefinition, "Number of function without exact definitions"); +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. static cl::opt MaxFixpointIterations("attributor-max-iterations", cl::Hidden, @@ -136,11 +141,54 @@ return; switch (Attr.getKindAsEnum()) { + case Attribute::Returned: + NumFnArgumentReturned++; + return; default: return; } } +/// Recursively collect returned values, starting from \p V but look through +/// through select/phi/casts. +static void collectValuesRecursively( + Value *V, SmallPtrSetImpl &ReturnInsts, + DenseMap> &Values, + SmallPtrSetImpl &Visited) { + // TODO: This (looking through casts/phis/selects should be a general + // functionality weith a callback. + V = V->stripPointerCasts(); + + // Explicitly look through calls with a "returned" attribute. + CallSite CS(V); + if (CS && CS.getCalledFunction()) + for (Argument &Arg : CS.getCalledFunction()->args()) + if (Arg.hasReturnedAttr()) + return collectValuesRecursively(CS.getArgOperand(Arg.getArgNo()), + ReturnInsts, Values, Visited); + + if (!Visited.insert(V).second) + return; + + // Look through select instructions. + if (auto *SI = dyn_cast(V)) { + collectValuesRecursively(SI->getTrueValue(), ReturnInsts, Values, Visited); + collectValuesRecursively(SI->getFalseValue(), ReturnInsts, Values, Visited); + return; + } + + // Look through phi nodes. Recursion is not a problem as we keep the PHI + // node in the container as well (see above). + if (auto *PHI = dyn_cast(V)) { + for (Value *PHIOp : PHI->operands()) + collectValuesRecursively(PHIOp, ReturnInsts, Values, Visited); + return; + } + + // Remember that V can be returned by all return instructions in ReturnInsts. + Values[V].insert(ReturnInsts.begin(), ReturnInsts.end()); +} + /// Helper to identify the correct offset into an attribute list. static unsigned getAttrIndex(AbstractAttribute::ManifestPosition MP, unsigned ArgNo = 0) { @@ -195,6 +243,7 @@ llvm_unreachable("Expected enum or string attribute!"); } + ChangeStatus AbstractAttribute::update(Attributor &A) { ChangeStatus Changed = ChangeStatus::UNCHANGED; if (getState().isAtFixpoint()) @@ -293,6 +342,294 @@ 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(); + + /// State flags + /// + ///{ + bool IsFixed = false; + bool IsValidState = true; + bool HasOverdefinedReturnedCalls = false; + ///} + +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)}); + SmallPtrSet Visited; + collectValuesRecursively(cast(RI)->getReturnValue(), RISet, + ReturnedValues, Visited); + } + } + + /// 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 { + if (!isValidState()) + return true; + return 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 iterator = decltype(ReturnedValues)::iterator; + iterator begin() { return ReturnedValues.begin(); } + iterator end() { 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() { + 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 Visited; + 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, Visited); + 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 /// ---------------------------------------------------------------------------- @@ -466,6 +803,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. @@ -475,6 +820,8 @@ switch (I.getOpcode()) { default: break; + case Instruction::Ret: // ReturnInst are interesting for AAReturnedValues. + IsInteresting = true; } if (IsInteresting) 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 }