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 @@ -644,6 +644,27 @@ /// 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) {} + + /// Check \p Pred on all returned values. + /// + /// This method will evaluate \p Pred on returned values and return + /// true if (1) all returned values are known, and (2) \p Pred returned true + /// for all returned values. + virtual bool + checkForallReturnedValues(std::function &Pred) 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 @@ -43,6 +43,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 @@ -86,10 +91,96 @@ if (!Attr.isEnumAttribute()) return; - //switch (Attr.getKindAsEnum()) { - //default: - // 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 InitV 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 +/// a potential origin value is looked at. If no \p FollowValueCB is passed, a +/// default one is used that will make sure we visit every value only once. Once +/// we cannot look through the value any further, the callback \p VisitValueCB +/// is invoked and passed the current value and the \p State. To limit how much +/// effort is invested, we will never visit more than \p MaxValues values. +template +static bool genericValueTraversal( + Value *InitV, StateTy &State, visitValueCB_t &VisitValueCB, + followValueCB_t *FollowValueCB = nullptr, int MaxValues = 8) { + + SmallPtrSet Visited; + followValueCB_t DefaultFollowValueCB = [&](Value *Val, bool &) { + return Visited.insert(Val).second; + }; + + if (!FollowValueCB) + FollowValueCB = &DefaultFollowValueCB; + + SmallVector Worklist; + Worklist.push_back(InitV); + + int Iteration = 0; + do { + Value *V = Worklist.pop_back_val(); + + // Check if we should process the current value. To prevent endless + // recursion keep a record of the values we followed! + if (!(*FollowValueCB)(V, State)) + continue; + + // Make sure we limit the compile time for complex expressions. + if (Iteration++ >= MaxValues) + return false; + + // 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()) { + V = V->stripPointerCasts(); + } else { + CallSite CS(V); + if (CS && CS.getCalledFunction()) { + Value *NewV = nullptr; + for (Argument &Arg : CS.getCalledFunction()->args()) + if (Arg.hasReturnedAttr()) { + NewV = CS.getArgOperand(Arg.getArgNo()); + break; + } + if (NewV) { + Worklist.push_back(NewV); + continue; + } + } + } + + // Look through select instructions, visit both potential values. + if (auto *SI = dyn_cast(V)) { + Worklist.push_back(SI->getTrueValue()); + Worklist.push_back(SI->getFalseValue()); + continue; + } + + // Look through phi nodes, visit all operands. + if (auto *PHI = dyn_cast(V)) { + Worklist.append(PHI->op_begin(), PHI->op_end()); + continue; + } + + // Once a leaf is reached we inform the user through the callback. + VisitValueCB(V, State); + } while (!Worklist.empty()); + + // All values have been visited. + return true; } /// Helper to identify the correct offset into an attribute list. @@ -241,6 +332,330 @@ 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; + + /// State flags + /// + ///{ + bool IsFixed; + bool IsValidState; + bool HasOverdefinedReturnedCalls; + ///} + + /// Collect values that could become \p V in the set \p Values, each mapped to + /// \p ReturnInsts. + void collectValuesRecursively( + Attributor &A, Value *V, SmallPtrSetImpl &ReturnInsts, + DenseMap> &Values) { + + visitValueCB_t VisitValueCB = [&](Value *Val, bool &) { + assert(!isa(Val) || + &getAnchorScope() == cast(Val)->getFunction()); + Values[Val].insert(ReturnInsts.begin(), ReturnInsts.end()); + }; + + bool UnusedBool; + bool Success = genericValueTraversal(V, UnusedBool, VisitValueCB); + + // If we did abort the above traversal we haven't see all the values. + // Consequently, we cannot know if the information we would derive is + // accurate so we give up early. + if (!Success) + indicatePessimisticFixpoint(); + } + +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 { + // Reset the state. + AssociatedVal = nullptr; + IsFixed = false; + IsValidState = true; + HasOverdefinedReturnedCalls = false; + ReturnedValues.clear(); + + 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()) { + + 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(A, 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::updateImpl(Attributor &A). + virtual ChangeStatus updateImpl(Attributor &A) override; + + /// Return the number of potential return values, -1 if unknown. + size_t getNumReturnValues() const { + return isValidState() ? ReturnedValues.size() : -1; + } + + /// 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; + + /// See AbstractState::checkForallReturnedValues(...). + virtual bool + checkForallReturnedValues(std::function &Pred) const override; + + /// 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 { + // If checkForallReturnedValues provides a unique value, 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. + Optional UniqueRV; + + std::function Pred = [&](Value &RV) -> bool { + // 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()))) { + UniqueRV = nullptr; + return false; + } + + // Do not overwrite a value with an undef. + if (!UniqueRV.hasValue() || !isa(RV)) + UniqueRV = &RV; + + return true; + }; + + if (!checkForallReturnedValues(Pred)) + UniqueRV = nullptr; + + return UniqueRV; +} + +bool AAReturnedValuesImpl::checkForallReturnedValues( + std::function &Pred) const { + if (!isValidState()) + return false; + + // Check all returned values but ignore call sites as long as we have not + // encountered an overdefined one during an update. + for (auto &It : ReturnedValues) { + Value *RV = It.first; + + ImmutableCallSite ICS(RV); + if (ICS && !HasOverdefinedReturnedCalls) + continue; + + if (!Pred(*RV)) + return false; + } + + return true; +} + +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. + 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; + + // Try to find a unique assumed return value for the called function. + 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; + } + + LLVM_DEBUG({ + bool UniqueRVIsKnown = RetCSAA->isAtFixpoint(); + 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(A, + RetCS.getArgOperand(AssumedRetArg->getArgNo()), + ReturnInsts, AddRVs); + else + AddRVs[AssumedRetVal].insert(ReturnInsts.begin(), ReturnInsts.end()); + } + + ChangeStatus Changed = ChangeStatus::UNCHANGED; + + for (auto &It : AddRVs) { + assert(!It.second.empty() && "Entry does not add anything."); + auto &ReturnInsts = ReturnedValues[It.first]; + for (ReturnInst *RI : It.second) + if (ReturnInsts.insert(RI).second) { + LLVM_DEBUG(dbgs() << "[AAReturnedValues] Add new returned value " + << *It.first << " => " << *RI << "\n"); + 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 /// ---------------------------------------------------------------------------- @@ -383,6 +798,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. @@ -397,10 +821,12 @@ // to concrete attributes we only cache the ones that are as identified in // the following switch. // Note: There are no concrete attributes now so this is initially empty. - //switch (I.getOpcode()) { - //default: - // break; - //} + switch (I.getOpcode()) { + default: + break; + case Instruction::Ret: // ReturnInst are interesting for AAReturnedValues. + IsInterestingOpcode = true; + } if (IsInterestingOpcode) InstOpcodeMap[I.getOpcode()].push_back(&I); if (I.mayReadOrWriteMemory()) 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 @@ -112,18 +112,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,5 +1,6 @@ -; RUN: opt -functionattrs -attributor -attributor-disable=false -S < %s | FileCheck %s -; RUN: opt -functionattrs -attributor -attributor-disable=false -attributor-verify=true -S < %s | FileCheck %s +; RUN: opt -functionattrs -S < %s | FileCheck %s --check-prefix=FNATTR +; RUN: opt -attributor -attributor-disable=false -S < %s | FileCheck %s --check-prefix=ATTRIBUTOR +; RUN: opt -attributor -attributor-disable=false -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. @@ -7,16 +8,24 @@ ; 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-NEXT: define i32 @sink_r0(i32 returned %r) +; BOTH: Function Attrs: noinline nounwind readnone uwtable +; BOTH-NEXT: define i32 @scc_r1(i32 %a, i32 returned %r, i32 %b) +; BOTH: Function Attrs: noinline nounwind readnone uwtable +; BOTH-NEXT: define i32 @scc_r2(i32 %a, i32 %b, i32 returned %r) +; BOTH: Function Attrs: noinline nounwind readnone uwtable +; BOTH-NEXT: 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); @@ -150,16 +159,20 @@ ; TEST SCC test 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-NEXT: define double* @ptr_sink_r0(double* readnone returned %r) +; BOTH: Function Attrs: noinline nounwind readnone uwtable +; BOTH-NEXT: define double* @ptr_scc_r1(double* %a, double* readnone returned %r, double* nocapture readnone %b) +; BOTH: Function Attrs: noinline nounwind readnone uwtable +; BOTH-NEXT: 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); @@ -237,41 +250,95 @@ } -; TEST a singleton SCC with a lot of recursive calls +; TEST a no-return singleton SCC ; -; int* ret0(int *a) { -; return *a ? a : ret0(ret0(ret0(...ret0(a)...))); +; int* rt0(int *a) { +; return *a ? a : rt0(a); ; } ; -; FIXME: returned on %a missing: -; CHECK: Function Attrs: noinline nounwind readonly uwtable -; CHECK: define i32* @ret0(i32* readonly %a) -define i32* @ret0(i32* %a) #0 { +; FIXME: no-return missing +; FNATTR: define i32* @rt0(i32* readonly %a) +; BOTH: Function Attrs: noinline nounwind readonly uwtable +; BOTH-NEXT: define i32* @rt0(i32* readonly returned %a) +define i32* @rt0(i32* %a) #0 { entry: %v = load i32, i32* %a, align 4 %tobool = icmp ne i32 %v, 0 - %call = call i32* @ret0(i32* %a) - %call1 = call i32* @ret0(i32* %call) - %call2 = call i32* @ret0(i32* %call1) - %call3 = call i32* @ret0(i32* %call2) - %call4 = call i32* @ret0(i32* %call3) - %call5 = call i32* @ret0(i32* %call4) - %call6 = call i32* @ret0(i32* %call5) - %call7 = call i32* @ret0(i32* %call6) - %call8 = call i32* @ret0(i32* %call7) - %call9 = call i32* @ret0(i32* %call8) - %call10 = call i32* @ret0(i32* %call9) - %call11 = call i32* @ret0(i32* %call10) - %call12 = call i32* @ret0(i32* %call11) - %call13 = call i32* @ret0(i32* %call12) - %call14 = call i32* @ret0(i32* %call13) - %call15 = call i32* @ret0(i32* %call14) - %call16 = call i32* @ret0(i32* %call15) - %call17 = call i32* @ret0(i32* %call16) - %sel = select i1 %tobool, i32* %a, i32* %call17 + %call = call i32* @rt0(i32* %a) + %sel = select i1 %tobool, i32* %a, i32* %call ret i32* %sel } +; TEST a no-return singleton SCC +; +; int* rt1(int *a) { +; return *a ? undef : rt1(a); +; } +; +; FIXME: no-return missing +; FNATTR: define noalias i32* @rt1(i32* nocapture readonly %a) +; BOTH: Function Attrs: noinline nounwind readonly uwtable +; BOTH-NEXT: define noalias i32* @rt1(i32* nocapture readonly %a) +define i32* @rt1(i32* %a) #0 { +entry: + %v = load i32, i32* %a, align 4 + %tobool = icmp ne i32 %v, 0 + %call = call i32* @rt1(i32* %a) + %sel = select i1 %tobool, i32* undef, i32* %call + ret i32* %sel +} + +; TEST another SCC test +; +; FNATTR: define i32* @rt2_helper(i32* %a) +; FNATTR: define i32* @rt2(i32* readnone %a, i32* readnone %b) +; BOTH: define i32* @rt2_helper(i32* %a) +; BOTH: define i32* @rt2(i32* readnone %a, i32* readnone %b) +define i32* @rt2_helper(i32* %a) #0 { +entry: + %call = call i32* @rt2(i32* %a, i32* %a) + ret i32* %call +} + +define i32* @rt2(i32* %a, i32 *%b) #0 { +entry: + %cmp = icmp eq i32* %a, null + br i1 %cmp, label %if.then, label %if.end + +if.then: + %call = call i32* @rt2_helper(i32* %a) + br label %if.end + +if.end: + %sel = phi i32* [ %b, %entry], [%call, %if.then] + ret i32* %sel +} + +; TEST another SCC test +; +; FNATTR: define i32* @rt3_helper(i32* %a, i32* %b) +; FNATTR: define i32* @rt3(i32* readnone %a, i32* readnone %b) +; BOTH: define i32* @rt3_helper(i32* %a, i32* returned %b) +; BOTH: define i32* @rt3(i32* readnone %a, i32* readnone returned %b) +define i32* @rt3_helper(i32* %a, i32* %b) #0 { +entry: + %call = call i32* @rt3(i32* %a, i32* %b) + ret i32* %call +} + +define i32* @rt3(i32* %a, i32 *%b) #0 { +entry: + %cmp = icmp eq i32* %a, null + br i1 %cmp, label %if.then, label %if.end + +if.then: + %call = call i32* @rt3_helper(i32* %a, i32* %b) + br label %if.end + +if.end: + %sel = phi i32* [ %b, %entry], [%call, %if.then] + ret i32* %sel +} ; TEST address taken function with call to an external functions ; @@ -282,11 +349,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-NEXT: 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 { @@ -313,6 +381,12 @@ ; ; CHECK: Function Attrs: noinline nounwind uwtable ; CHECK: define i32* @calls_maybe_redefined_fn(i32* returned %r) +; +; BOTH: Function Attrs: noinline nounwind uwtable +; BOTH-NEXT: define linkonce_odr i32* @maybe_redefined_fn(i32* %r) +; +; BOTH: Function Attrs: noinline nounwind uwtable +; BOTH-NEXT: define i32* @calls_maybe_redefined_fn(i32* returned %r) define linkonce_odr i32* @maybe_redefined_fn(i32* %r) #0 { entry: ret i32* %r @@ -324,6 +398,36 @@ ret i32* %r } +; TEST return call to a function that might be redifined at link time +; +; int *maybe_redefined_fn2(int *r) { +; return r; +; } +; +; int *calls_maybe_redefined_fn2(int *r) { +; return maybe_redefined_fn2(r); +; } +; +; Verify the maybe-redefined function is not annotated: +; +; BOTH: Function Attrs: noinline nounwind uwtable +; BOTH-NEXT: define linkonce_odr i32* @maybe_redefined_fn2(i32* %r) +; BOTH: Function Attrs: noinline nounwind uwtable +; BOTH-NEXT: define i32* @calls_maybe_redefined_fn2(i32* %r) +; +; FNATTR: define i32* @calls_maybe_redefined_fn2(i32* %r) +; ATTRIBUTOR: define i32* @calls_maybe_redefined_fn2(i32* %r) +define linkonce_odr i32* @maybe_redefined_fn2(i32* %r) #0 { +entry: + ret i32* %r +} + +define i32* @calls_maybe_redefined_fn2(i32* %r) #0 { +entry: + %call = call i32* @maybe_redefined_fn2(i32* %r) + ret i32* %call +} + ; TEST returned argument goes through select and phi ; @@ -334,9 +438,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-NEXT: 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 @@ -362,9 +468,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-NEXT: 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 @@ -389,9 +497,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-NEXT: 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* @@ -408,9 +518,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-NEXT: 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* @@ -442,8 +554,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-NEXT: 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* @@ -475,8 +590,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-NEXT: 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* @@ -508,8 +626,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-NEXT: 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* @@ -534,13 +655,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 { @@ -573,11 +698,40 @@ ret i32* %phi } +; TEST inconsistent IR in dead code. +; +; FNATTR: define i32 @deadblockcall1(i32 %A) +; FNATTR: define i32 @deadblockcall2(i32 %A) +; ATTRIBUTOR: define i32 @deadblockcall1(i32 returned %A) +; ATTRIBUTOR: define i32 @deadblockcall2(i32 returned %A) +; BOTH: define i32 @deadblockcall1(i32 returned %A) +; BOTH: define i32 @deadblockcall2(i32 returned %A) +define i32 @deadblockcall1(i32 %A) #0 { +entry: + ret i32 %A +unreachableblock: + %B = call i32 @deadblockcall1(i32 %B) + ret i32 %B +} + +declare i32 @deadblockcall_helper(i32 returned %A); + +define i32 @deadblockcall2(i32 %A) #0 { +entry: + ret i32 %A +unreachableblock1: + %B = call i32 @deadblockcall_helper(i32 %B) + ret i32 %B +unreachableblock2: + %C = call i32 @deadblockcall1(i32 %C) + ret i32 %C +} + 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-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-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 @@ -31,7 +31,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) @@ -42,7 +42,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 @@ -71,7 +71,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 @@ -122,7 +122,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 @@ -148,7 +148,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)