diff --git a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp --- a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp +++ b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp @@ -67,9 +67,6 @@ STATISTIC(NumReadNone, "Number of functions marked readnone"); STATISTIC(NumReadOnly, "Number of functions marked readonly"); STATISTIC(NumWriteOnly, "Number of functions marked writeonly"); -STATISTIC(NumNoCapture, "Number of arguments marked nocapture"); -STATISTIC(NumReadNoneArg, "Number of arguments marked readnone"); -STATISTIC(NumReadOnlyArg, "Number of arguments marked readonly"); STATISTIC(NumNoAlias, "Number of function returns marked noalias"); STATISTIC(NumNonNullReturn, "Number of function returns marked nonnull"); STATISTIC(NumNoRecurse, "Number of functions marked as norecurse"); @@ -303,264 +300,6 @@ return MadeChange; } -namespace { - -/// For a given pointer Argument, this retains a list of Arguments of functions -/// in the same SCC that the pointer data flows into. We use this to build an -/// SCC of the arguments. -struct ArgumentGraphNode { - Argument *Definition; - SmallVector Uses; -}; - -class ArgumentGraph { - // We store pointers to ArgumentGraphNode objects, so it's important that - // that they not move around upon insert. - using ArgumentMapTy = std::map; - - ArgumentMapTy ArgumentMap; - - // There is no root node for the argument graph, in fact: - // void f(int *x, int *y) { if (...) f(x, y); } - // is an example where the graph is disconnected. The SCCIterator requires a - // single entry point, so we maintain a fake ("synthetic") root node that - // uses every node. Because the graph is directed and nothing points into - // the root, it will not participate in any SCCs (except for its own). - ArgumentGraphNode SyntheticRoot; - -public: - ArgumentGraph() { SyntheticRoot.Definition = nullptr; } - - using iterator = SmallVectorImpl::iterator; - - iterator begin() { return SyntheticRoot.Uses.begin(); } - iterator end() { return SyntheticRoot.Uses.end(); } - ArgumentGraphNode *getEntryNode() { return &SyntheticRoot; } - - ArgumentGraphNode *operator[](Argument *A) { - ArgumentGraphNode &Node = ArgumentMap[A]; - Node.Definition = A; - SyntheticRoot.Uses.push_back(&Node); - return &Node; - } -}; - -/// This tracker checks whether callees are in the SCC, and if so it does not -/// consider that a capture, instead adding it to the "Uses" list and -/// continuing with the analysis. -struct ArgumentUsesTracker : public CaptureTracker { - ArgumentUsesTracker(const SCCNodeSet &SCCNodes) : SCCNodes(SCCNodes) {} - - void tooManyUses() override { Captured = true; } - - bool captured(const Use *U) override { - CallSite CS(U->getUser()); - if (!CS.getInstruction()) { - Captured = true; - return true; - } - - Function *F = CS.getCalledFunction(); - if (!F || !F->hasExactDefinition() || !SCCNodes.count(F)) { - Captured = true; - return true; - } - - // Note: the callee and the two successor blocks *follow* the argument - // operands. This means there is no need to adjust UseIndex to account for - // these. - - unsigned UseIndex = - std::distance(const_cast(CS.arg_begin()), U); - - assert(UseIndex < CS.data_operands_size() && - "Indirect function calls should have been filtered above!"); - - if (UseIndex >= CS.getNumArgOperands()) { - // Data operand, but not a argument operand -- must be a bundle operand - assert(CS.hasOperandBundles() && "Must be!"); - - // CaptureTracking told us that we're being captured by an operand bundle - // use. In this case it does not matter if the callee is within our SCC - // or not -- we've been captured in some unknown way, and we have to be - // conservative. - Captured = true; - return true; - } - - if (UseIndex >= F->arg_size()) { - assert(F->isVarArg() && "More params than args in non-varargs call"); - Captured = true; - return true; - } - - Uses.push_back(&*std::next(F->arg_begin(), UseIndex)); - return false; - } - - // True only if certainly captured (used outside our SCC). - bool Captured = false; - - // Uses within our SCC. - SmallVector Uses; - - const SCCNodeSet &SCCNodes; -}; - -} // end anonymous namespace - -namespace llvm { - -template <> struct GraphTraits { - using NodeRef = ArgumentGraphNode *; - using ChildIteratorType = SmallVectorImpl::iterator; - - static NodeRef getEntryNode(NodeRef A) { return A; } - static ChildIteratorType child_begin(NodeRef N) { return N->Uses.begin(); } - static ChildIteratorType child_end(NodeRef N) { return N->Uses.end(); } -}; - -template <> -struct GraphTraits : public GraphTraits { - static NodeRef getEntryNode(ArgumentGraph *AG) { return AG->getEntryNode(); } - - static ChildIteratorType nodes_begin(ArgumentGraph *AG) { - return AG->begin(); - } - - static ChildIteratorType nodes_end(ArgumentGraph *AG) { return AG->end(); } -}; - -} // end namespace llvm - -/// Returns Attribute::None, Attribute::ReadOnly or Attribute::ReadNone. -static Attribute::AttrKind -determinePointerReadAttrs(Argument *A, - const SmallPtrSet &SCCNodes) { - SmallVector Worklist; - SmallPtrSet Visited; - - // inalloca arguments are always clobbered by the call. - if (A->hasInAllocaAttr()) - return Attribute::None; - - bool IsRead = false; - // We don't need to track IsWritten. If A is written to, return immediately. - - for (Use &U : A->uses()) { - Visited.insert(&U); - Worklist.push_back(&U); - } - - while (!Worklist.empty()) { - Use *U = Worklist.pop_back_val(); - Instruction *I = cast(U->getUser()); - - switch (I->getOpcode()) { - case Instruction::BitCast: - case Instruction::GetElementPtr: - case Instruction::PHI: - case Instruction::Select: - case Instruction::AddrSpaceCast: - // The original value is not read/written via this if the new value isn't. - for (Use &UU : I->uses()) - if (Visited.insert(&UU).second) - Worklist.push_back(&UU); - break; - - case Instruction::Call: - case Instruction::Invoke: { - bool Captures = true; - - if (I->getType()->isVoidTy()) - Captures = false; - - auto AddUsersToWorklistIfCapturing = [&] { - if (Captures) - for (Use &UU : I->uses()) - if (Visited.insert(&UU).second) - Worklist.push_back(&UU); - }; - - CallSite CS(I); - if (CS.doesNotAccessMemory()) { - AddUsersToWorklistIfCapturing(); - continue; - } - - Function *F = CS.getCalledFunction(); - if (!F) { - if (CS.onlyReadsMemory()) { - IsRead = true; - AddUsersToWorklistIfCapturing(); - continue; - } - return Attribute::None; - } - - // Note: the callee and the two successor blocks *follow* the argument - // operands. This means there is no need to adjust UseIndex to account - // for these. - - unsigned UseIndex = std::distance(CS.arg_begin(), U); - - // U cannot be the callee operand use: since we're exploring the - // transitive uses of an Argument, having such a use be a callee would - // imply the CallSite is an indirect call or invoke; and we'd take the - // early exit above. - assert(UseIndex < CS.data_operands_size() && - "Data operand use expected!"); - - bool IsOperandBundleUse = UseIndex >= CS.getNumArgOperands(); - - if (UseIndex >= F->arg_size() && !IsOperandBundleUse) { - assert(F->isVarArg() && "More params than args in non-varargs call"); - return Attribute::None; - } - - Captures &= !CS.doesNotCapture(UseIndex); - - // Since the optimizer (by design) cannot see the data flow corresponding - // to a operand bundle use, these cannot participate in the optimistic SCC - // analysis. Instead, we model the operand bundle uses as arguments in - // call to a function external to the SCC. - if (IsOperandBundleUse || - !SCCNodes.count(&*std::next(F->arg_begin(), UseIndex))) { - - // The accessors used on CallSite here do the right thing for calls and - // invokes with operand bundles. - - if (!CS.onlyReadsMemory() && !CS.onlyReadsMemory(UseIndex)) - return Attribute::None; - if (!CS.doesNotAccessMemory(UseIndex)) - IsRead = true; - } - - AddUsersToWorklistIfCapturing(); - break; - } - - case Instruction::Load: - // A volatile load has side effects beyond what readonly can be relied - // upon. - if (cast(I)->isVolatile()) - return Attribute::None; - - IsRead = true; - break; - - case Instruction::ICmp: - case Instruction::Ret: - break; - - default: - return Attribute::None; - } - } - - return IsRead ? Attribute::ReadOnly : Attribute::ReadNone; -} - /// If a callsite has arguments that are also arguments to the parent function, /// try to propagate attributes from the callsite's arguments to the parent's /// arguments. This may be important because inlining can cause information loss @@ -604,12 +343,10 @@ return Changed; } -/// Deduce nocapture attributes for the SCC. +/// Deduce nonull attributes for the SCC. static bool addArgumentAttrs(const SCCNodeSet &SCCNodes) { bool Changed = false; - ArgumentGraph AG; - // Check each function in turn, determining which pointer arguments are not // captured. for (Function *F : SCCNodes) { @@ -621,166 +358,6 @@ Changed |= addArgumentAttrsFromCallsites(*F); - // Functions that are readonly (or readnone) and nounwind and don't return - // a value can't capture arguments. Don't analyze them. - if (F->onlyReadsMemory() && F->doesNotThrow() && - F->getReturnType()->isVoidTy()) { - for (Function::arg_iterator A = F->arg_begin(), E = F->arg_end(); A != E; - ++A) { - if (A->getType()->isPointerTy() && !A->hasNoCaptureAttr()) { - A->addAttr(Attribute::NoCapture); - ++NumNoCapture; - Changed = true; - } - } - continue; - } - - for (Function::arg_iterator A = F->arg_begin(), E = F->arg_end(); A != E; - ++A) { - if (!A->getType()->isPointerTy()) - continue; - bool HasNonLocalUses = false; - if (!A->hasNoCaptureAttr()) { - ArgumentUsesTracker Tracker(SCCNodes); - PointerMayBeCaptured(&*A, &Tracker); - if (!Tracker.Captured) { - if (Tracker.Uses.empty()) { - // If it's trivially not captured, mark it nocapture now. - A->addAttr(Attribute::NoCapture); - ++NumNoCapture; - Changed = true; - } else { - // If it's not trivially captured and not trivially not captured, - // then it must be calling into another function in our SCC. Save - // its particulars for Argument-SCC analysis later. - ArgumentGraphNode *Node = AG[&*A]; - for (Argument *Use : Tracker.Uses) { - Node->Uses.push_back(AG[Use]); - if (Use != &*A) - HasNonLocalUses = true; - } - } - } - // Otherwise, it's captured. Don't bother doing SCC analysis on it. - } - if (!HasNonLocalUses && !A->onlyReadsMemory()) { - // Can we determine that it's readonly/readnone without doing an SCC? - // Note that we don't allow any calls at all here, or else our result - // will be dependent on the iteration order through the functions in the - // SCC. - SmallPtrSet Self; - Self.insert(&*A); - Attribute::AttrKind R = determinePointerReadAttrs(&*A, Self); - if (R != Attribute::None) { - A->addAttr(R); - Changed = true; - R == Attribute::ReadOnly ? ++NumReadOnlyArg : ++NumReadNoneArg; - } - } - } - } - - // The graph we've collected is partial because we stopped scanning for - // argument uses once we solved the argument trivially. These partial nodes - // show up as ArgumentGraphNode objects with an empty Uses list, and for - // these nodes the final decision about whether they capture has already been - // made. If the definition doesn't have a 'nocapture' attribute by now, it - // captures. - - for (scc_iterator I = scc_begin(&AG); !I.isAtEnd(); ++I) { - const std::vector &ArgumentSCC = *I; - if (ArgumentSCC.size() == 1) { - if (!ArgumentSCC[0]->Definition) - continue; // synthetic root node - - // eg. "void f(int* x) { if (...) f(x); }" - if (ArgumentSCC[0]->Uses.size() == 1 && - ArgumentSCC[0]->Uses[0] == ArgumentSCC[0]) { - Argument *A = ArgumentSCC[0]->Definition; - A->addAttr(Attribute::NoCapture); - ++NumNoCapture; - Changed = true; - } - continue; - } - - bool SCCCaptured = false; - for (auto I = ArgumentSCC.begin(), E = ArgumentSCC.end(); - I != E && !SCCCaptured; ++I) { - ArgumentGraphNode *Node = *I; - if (Node->Uses.empty()) { - if (!Node->Definition->hasNoCaptureAttr()) - SCCCaptured = true; - } - } - if (SCCCaptured) - continue; - - SmallPtrSet ArgumentSCCNodes; - // Fill ArgumentSCCNodes with the elements of the ArgumentSCC. Used for - // quickly looking up whether a given Argument is in this ArgumentSCC. - for (ArgumentGraphNode *I : ArgumentSCC) { - ArgumentSCCNodes.insert(I->Definition); - } - - for (auto I = ArgumentSCC.begin(), E = ArgumentSCC.end(); - I != E && !SCCCaptured; ++I) { - ArgumentGraphNode *N = *I; - for (ArgumentGraphNode *Use : N->Uses) { - Argument *A = Use->Definition; - if (A->hasNoCaptureAttr() || ArgumentSCCNodes.count(A)) - continue; - SCCCaptured = true; - break; - } - } - if (SCCCaptured) - continue; - - for (unsigned i = 0, e = ArgumentSCC.size(); i != e; ++i) { - Argument *A = ArgumentSCC[i]->Definition; - A->addAttr(Attribute::NoCapture); - ++NumNoCapture; - Changed = true; - } - - // We also want to compute readonly/readnone. With a small number of false - // negatives, we can assume that any pointer which is captured isn't going - // to be provably readonly or readnone, since by definition we can't - // analyze all uses of a captured pointer. - // - // The false negatives happen when the pointer is captured by a function - // that promises readonly/readnone behaviour on the pointer, then the - // pointer's lifetime ends before anything that writes to arbitrary memory. - // Also, a readonly/readnone pointer may be returned, but returning a - // pointer is capturing it. - - Attribute::AttrKind ReadAttr = Attribute::ReadNone; - for (unsigned i = 0, e = ArgumentSCC.size(); i != e; ++i) { - Argument *A = ArgumentSCC[i]->Definition; - Attribute::AttrKind K = determinePointerReadAttrs(A, ArgumentSCCNodes); - if (K == Attribute::ReadNone) - continue; - if (K == Attribute::ReadOnly) { - ReadAttr = Attribute::ReadOnly; - continue; - } - ReadAttr = K; - break; - } - - if (ReadAttr != Attribute::None) { - for (unsigned i = 0, e = ArgumentSCC.size(); i != e; ++i) { - Argument *A = ArgumentSCC[i]->Definition; - // Clear out existing readonly/readnone attributes - A->removeAttr(Attribute::ReadOnly); - A->removeAttr(Attribute::ReadNone); - A->addAttr(ReadAttr); - ReadAttr == Attribute::ReadOnly ? ++NumReadOnlyArg : ++NumReadNoneArg; - Changed = true; - } - } } return Changed; diff --git a/llvm/test/Analysis/TypeBasedAliasAnalysis/functionattrs.ll b/llvm/test/Analysis/TypeBasedAliasAnalysis/functionattrs.ll --- a/llvm/test/Analysis/TypeBasedAliasAnalysis/functionattrs.ll +++ b/llvm/test/Analysis/TypeBasedAliasAnalysis/functionattrs.ll @@ -9,13 +9,13 @@ ; invalid, as it's possible that this only happens after optimization on a ; code path which isn't ever executed. -; CHECK: define void @test0_yes(i32* nocapture %p) #0 { +; CHECK: define void @test0_yes(i32* nocapture readnone %p) #0 { define void @test0_yes(i32* %p) nounwind { store i32 0, i32* %p, !tbaa !1 ret void } -; CHECK: define void @test0_no(i32* nocapture %p) #1 { +; CHECK: define void @test0_no(i32* nocapture writeonly %p) #1 { define void @test0_no(i32* %p) nounwind { store i32 0, i32* %p, !tbaa !2 ret void @@ -24,7 +24,7 @@ ; Add the readonly attribute, since there's just a call to a function which ; TBAA says doesn't modify any memory. -; CHECK: define void @test1_yes(i32* nocapture %p) #2 { +; CHECK: define void @test1_yes(i32* nocapture readonly %p) #2 { define void @test1_yes(i32* %p) nounwind { call void @callee(i32* %p), !tbaa !1 ret void @@ -43,13 +43,13 @@ ; This is unusual, since the function is memcpy, but as above, this ; isn't necessarily invalid. -; CHECK: define void @test2_yes(i8* nocapture %p, i8* nocapture %q, i64 %n) #0 { +; CHECK: define void @test2_yes(i8* nocapture readnone %p, i8* nocapture readnone %q, i64 %n) #0 { define void @test2_yes(i8* %p, i8* %q, i64 %n) nounwind { call void @llvm.memcpy.p0i8.p0i8.i64(i8* %p, i8* %q, i64 %n, i1 false), !tbaa !1 ret void } -; CHECK: define void @test2_no(i8* nocapture %p, i8* nocapture readonly %q, i64 %n) #4 { +; CHECK: define void @test2_no(i8* nocapture writeonly %p, i8* nocapture readonly %q, i64 %n) #4 { define void @test2_no(i8* %p, i8* %q, i64 %n) nounwind { call void @llvm.memcpy.p0i8.p0i8.i64(i8* %p, i8* %q, i64 %n, i1 false), !tbaa !2 ret void @@ -57,7 +57,7 @@ ; Similar to the others, va_arg only accesses memory through its operand. -; CHECK: define i32 @test3_yes(i8* nocapture %p) #0 { +; CHECK: define i32 @test3_yes(i8* nocapture readnone %p) #0 { define i32 @test3_yes(i8* %p) nounwind { %t = va_arg i8* %p, i32, !tbaa !1 ret i32 %t diff --git a/llvm/test/Feature/OperandBundles/function-attrs.ll b/llvm/test/Feature/OperandBundles/function-attrs.ll --- a/llvm/test/Feature/OperandBundles/function-attrs.ll +++ b/llvm/test/Feature/OperandBundles/function-attrs.ll @@ -1,4 +1,4 @@ -; RUN: opt -S -functionattrs < %s | FileCheck %s +; RUN: opt -S -functionattrs -attributor < %s | FileCheck %s declare void @f_readonly() readonly declare void @f_readnone() readnone diff --git a/llvm/test/Other/cgscc-devirt-iteration.ll b/llvm/test/Other/cgscc-devirt-iteration.ll --- a/llvm/test/Other/cgscc-devirt-iteration.ll +++ b/llvm/test/Other/cgscc-devirt-iteration.ll @@ -4,9 +4,9 @@ ; devirtualization here with GVN which forwards a store through a load and to ; an indirect call. ; -; RUN: opt -aa-pipeline=basic-aa -passes='cgscc(function-attrs,function(gvn,instcombine))' -S < %s | FileCheck %s --check-prefix=CHECK --check-prefix=BEFORE -; RUN: opt -aa-pipeline=basic-aa -passes='cgscc(devirt<1>(function-attrs,function(gvn,instcombine)))' -S < %s | FileCheck %s --check-prefix=CHECK --check-prefix=AFTER --check-prefix=AFTER1 -; RUN: opt -aa-pipeline=basic-aa -passes='cgscc(devirt<2>(function-attrs,function(gvn,instcombine)))' -S < %s | FileCheck %s --check-prefix=CHECK --check-prefix=AFTER --check-prefix=AFTER2 +; RUN: opt -aa-pipeline=basic-aa -passes='cgscc(attributor,function-attrs,function(gvn,instcombine))' -S < %s | FileCheck %s --check-prefix=CHECK --check-prefix=BEFORE +; RUN: opt -aa-pipeline=basic-aa -passes='cgscc(devirt<1>(attributor,function-attrs,function(gvn,instcombine)))' -S < %s | FileCheck %s --check-prefix=CHECK --check-prefix=AFTER --check-prefix=AFTER1 +; RUN: opt -aa-pipeline=basic-aa -passes='cgscc(devirt<2>(attributor,function-attrs,function(gvn,instcombine)))' -S < %s | FileCheck %s --check-prefix=CHECK --check-prefix=AFTER --check-prefix=AFTER2 ; ; We also verify that the real O2 pipeline catches these cases. ; RUN: opt -aa-pipeline=basic-aa -passes='default' -S < %s | FileCheck %s --check-prefix=CHECK --check-prefix=AFTER --check-prefix=AFTER2 @@ -65,10 +65,16 @@ ; readonly but not (yet) readnone. call void %f1(void ()** %ignore) ; CHECK: call void @readnone_with_arg(void ()** %ignore) + %cmp = icmp ne void ()** %ignore, null + br i1 %cmp, label %scc, label %end - ; Bogus call to test2_b to make this a cycle. - call void @test2_b() +scc: + ; Bogus call to test2_b to make this a cycle. Conditionally executed to + ; avoid no-return deduction potential consequences. + call void @test2_b() + unreachable +end: ret void } 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 @@ -173,13 +173,14 @@ ; TEST 2 ; ; BOTH: define dso_local double* @ptr_sink_r0(double* readnone returned "no-capture-maybe-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]] +; BOTH: define dso_local double* @ptr_scc_r1(double* %a, double* returned %r, double* nocapture readnone %b) [[NoInlineNoUnwindReadnoneUwtable]] +; BOTH: define dso_local double* @ptr_scc_r2(double* %a, double* %b, double* returned %r) [[NoInlineNoUnwindReadnoneUwtable]] ; -; FNATTR: define dso_local double* @ptr_sink_r0(double* readnone %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]] +; FNATTR: define dso_local double* @ptr_sink_r0(double* %r) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] +; FNATTR: define dso_local double* @ptr_scc_r1(double* %a, double* %r, double* %b) [[NoInlineNoUnwindReadnoneUwtable]] +; FNATTR: define dso_local double* @ptr_scc_r2(double* %a, double* %b, double* %r) [[NoInlineNoUnwindReadnoneUwtable]] ; +; It would be acceptable for this example to mark all pointers as readnone. ; ATTRIBUTOR: define dso_local double* @ptr_sink_r0(double* readnone returned "no-capture-maybe-returned" %r) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] ; ATTRIBUTOR: define dso_local double* @ptr_scc_r1(double* %a, double* returned %r, double* nocapture readnone %b) [[NoInlineNoUnwindReadnoneUwtable]] ; ATTRIBUTOR: define dso_local double* @ptr_scc_r2(double* %a, double* %b, double* returned %r) [[NoInlineNoUnwindReadnoneUwtable]] @@ -267,7 +268,7 @@ ; } ; ; FEW_IT: define dso_local i32* @ret0(i32* %a) -; FNATTR: define dso_local i32* @ret0(i32* readonly %a) [[NoInlineNoUnwindUwtable:#[0-9]*]] +; FNATTR: define dso_local i32* @ret0(i32* %a) [[NoInlineNoUnwindUwtable:#[0-9]*]] ; BOTH: define dso_local i32* @ret0(i32* readonly returned "no-capture-maybe-returned" %a) [[NoInlineNoReturnNoUnwindReadonlyUwtable:#[0-9]*]] define dso_local i32* @ret0(i32* %a) #0 { entry: @@ -308,7 +309,7 @@ ; BOTH: declare void @unknown_fn(i32* (i32*)*) [[NoInlineNoUnwindUwtable:#[0-9]*]] ; ; BOTH: define dso_local i32* @calls_unknown_fn(i32* readnone returned "no-capture-maybe-returned" %r) [[NoInlineNoUnwindUwtable]] -; FNATTR: define dso_local i32* @calls_unknown_fn(i32* readnone %r) [[NoInlineNoUnwindUwtable:#[0-9]*]] +; FNATTR: define dso_local i32* @calls_unknown_fn(i32* %r) [[NoInlineNoUnwindUwtable:#[0-9]*]] ; ATTRIBUTOR: define dso_local i32* @calls_unknown_fn(i32* readnone returned "no-capture-maybe-returned" %r) [[NoInlineNoUnwindUwtable:#[0-9]*]] ; declare void @unknown_fn(i32* (i32*)*) #0 @@ -410,7 +411,7 @@ ; return (double*)b; ; } ; -; FNATTR: define dso_local double* @bitcast(i32* readnone %b) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] +; FNATTR: define dso_local double* @bitcast(i32* %b) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] ; ATTRIBUTOR: define dso_local double* @bitcast(i32* readnone returned "no-capture-maybe-returned" %b) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] ; BOTH: define dso_local double* @bitcast(i32* readnone returned "no-capture-maybe-returned" %b) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] ; @@ -430,7 +431,7 @@ ; return b != 0 ? b : x; ; } ; -; FNATTR: define dso_local double* @bitcasts_select_and_phi(i32* readnone %b) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] +; FNATTR: define dso_local double* @bitcasts_select_and_phi(i32* %b) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] ; ATTRIBUTOR: define dso_local double* @bitcasts_select_and_phi(i32* readnone returned "no-capture-maybe-returned" %b) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] ; BOTH: define dso_local double* @bitcasts_select_and_phi(i32* readnone returned "no-capture-maybe-returned" %b) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] ; @@ -465,7 +466,7 @@ ; /* return undef */ ; } ; -; FNATTR: define dso_local double* @ret_arg_arg_undef(i32* readnone %b) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] +; FNATTR: define dso_local double* @ret_arg_arg_undef(i32* %b) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] ; ATTRIBUTOR: define dso_local double* @ret_arg_arg_undef(i32* readnone returned "no-capture-maybe-returned" %b) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] ; BOTH: define dso_local double* @ret_arg_arg_undef(i32* readnone returned "no-capture-maybe-returned" %b) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] ; @@ -500,7 +501,7 @@ ; /* return undef */ ; } ; -; FNATTR: define dso_local double* @ret_undef_arg_arg(i32* readnone %b) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] +; FNATTR: define dso_local double* @ret_undef_arg_arg(i32* %b) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] ; ATTRIBUTOR: define dso_local double* @ret_undef_arg_arg(i32* readnone returned "no-capture-maybe-returned" %b) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] ; BOTH: define dso_local double* @ret_undef_arg_arg(i32* readnone returned "no-capture-maybe-returned" %b) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] ; @@ -535,7 +536,7 @@ ; /* return undef */ ; } ; -; FNATTR: define dso_local double* @ret_undef_arg_undef(i32* readnone %b) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] +; FNATTR: define dso_local double* @ret_undef_arg_undef(i32* %b) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] ; ATTRIBUTOR: define dso_local double* @ret_undef_arg_undef(i32* readnone returned "no-capture-maybe-returned" %b) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] ; BOTH: define dso_local double* @ret_undef_arg_undef(i32* readnone returned "no-capture-maybe-returned" %b) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] ; diff --git a/llvm/test/Transforms/FunctionAttrs/nocapture.ll b/llvm/test/Transforms/FunctionAttrs/nocapture.ll --- a/llvm/test/Transforms/FunctionAttrs/nocapture.ll +++ b/llvm/test/Transforms/FunctionAttrs/nocapture.ll @@ -48,7 +48,8 @@ declare void @throw_if_bit_set(i8*, i8) readonly -; CHECK: define i1 @c6(i8* readonly %q, i8 %bit) +; FIXME: missing readonly for %q +; CHECK: define i1 @c6(i8* %q, i8 %bit) define i1 @c6(i8* %q, i8 %bit) personality i32 (...)* @__gxx_personality_v0 { invoke void @throw_if_bit_set(i8* %q, i8 %bit) to label %ret0 unwind label %ret1 @@ -70,7 +71,8 @@ ret i1* %lookup } -; CHECK: define i1 @c7(i32* readonly %q, i32 %bitno) +; FIXME: missing readonly for %q +; CHECK: define i1 @c7(i32* %q, i32 %bitno) define i1 @c7(i32* %q, i32 %bitno) { %ptr = call i1* @lookup_bit(i32* %q, i32 %bitno) %val = load i1, i1* %ptr diff --git a/llvm/test/Transforms/FunctionAttrs/nonnull.ll b/llvm/test/Transforms/FunctionAttrs/nonnull.ll --- a/llvm/test/Transforms/FunctionAttrs/nonnull.ll +++ b/llvm/test/Transforms/FunctionAttrs/nonnull.ll @@ -1,5 +1,5 @@ -; RUN: opt -S -functionattrs -enable-nonnull-arg-prop %s | FileCheck %s -; RUN: opt -S -passes=function-attrs -enable-nonnull-arg-prop %s | FileCheck %s +; RUN: opt -S -attributor -functionattrs -enable-nonnull-arg-prop %s | FileCheck %s +; RUN: opt -S -passes='attributor,function-attrs' -enable-nonnull-arg-prop %s | FileCheck %s declare nonnull i8* @ret_nonnull() diff --git a/llvm/test/Transforms/FunctionAttrs/norecurse.ll b/llvm/test/Transforms/FunctionAttrs/norecurse.ll --- a/llvm/test/Transforms/FunctionAttrs/norecurse.ll +++ b/llvm/test/Transforms/FunctionAttrs/norecurse.ll @@ -50,7 +50,7 @@ ; CHECK: Function Attrs ; CHECK-SAME: nounwind ; CHECK-NOT: norecurse -; CHECK-NEXT: define void @intrinsic(i8* nocapture %dest, i8* nocapture readonly %src, i32 %len) +; CHECK-NEXT: define void @intrinsic(i8* nocapture writeonly %dest, i8* nocapture readonly %src, i32 %len) define void @intrinsic(i8* %dest, i8* %src, i32 %len) { call void @llvm.memcpy.p0i8.p0i8.i32(i8* %dest, i8* %src, i32 %len, i1 false) ret void diff --git a/llvm/test/Transforms/FunctionAttrs/out-of-bounds-iterator-bug.ll b/llvm/test/Transforms/FunctionAttrs/out-of-bounds-iterator-bug.ll --- a/llvm/test/Transforms/FunctionAttrs/out-of-bounds-iterator-bug.ll +++ b/llvm/test/Transforms/FunctionAttrs/out-of-bounds-iterator-bug.ll @@ -13,8 +13,9 @@ declare void @llvm.va_start(i8*) declare void @llvm.va_end(i8*) +; FIXME: missing nocapture on %b define void @va_func(i32* readonly %b, ...) readonly nounwind { -; CHECK-LABEL: define void @va_func(i32* nocapture readonly %b, ...) +; CHECK-LABEL: define void @va_func(i32* readonly %b, ...) entry: %valist = alloca i8 call void @llvm.va_start(i8* %valist) diff --git a/llvm/test/Transforms/FunctionAttrs/readattrs.ll b/llvm/test/Transforms/FunctionAttrs/readattrs.ll --- a/llvm/test/Transforms/FunctionAttrs/readattrs.ll +++ b/llvm/test/Transforms/FunctionAttrs/readattrs.ll @@ -6,7 +6,7 @@ ; NOTE: readonly for %y1_2 would be OK here but not for the similar situation in test13. ; -; CHECK: define void @test1_2(i8* %x1_2, i8* readonly %y1_2, i8* %z1_2) +; CHECK: define void @test1_2(i8* %x1_2, i8* %y1_2, i8* %z1_2) define void @test1_2(i8* %x1_2, i8* %y1_2, i8* %z1_2) { call void (i8*, i8*, ...) @test1_1(i8* %x1_2, i8* %y1_2, i8* %z1_2) store i32 0, i32* @x @@ -19,7 +19,9 @@ ret i8* %p } -; CHECK: define i1 @test3(i8* readnone %p, i8* readnone %q) +; We should derive readnone from the function behavior once available in the attributor +; i1 @test3(i8* readnone %p, i8* readnone %q) +; CHECK: define i1 @test3(i8* %p, i8* %q) define i1 @test3(i8* %p, i8* %q) { %A = icmp ult i8* %p, %q ret i1 %A @@ -123,10 +125,8 @@ ; is marked as readnone/only. However, the functions can write the pointer into ; %addr, causing the store to write to %escaped_then_written. ; -; FIXME: This test currently exposes a bug! -; -; BUG: define void @unsound_readnone(i8* %ignored, i8* readnone %escaped_then_written) -; BUG: define void @unsound_readonly(i8* %ignored, i8* readonly %escaped_then_written) +; CHECK: define void @unsound_readnone(i8* %ignored, i8* %escaped_then_written) +; CHECK: define void @unsound_readonly(i8* %ignored, i8* %escaped_then_written) define void @unsound_readnone(i8* %ignored, i8* %escaped_then_written) { %addr = alloca i8* call void @escape_readnone_ptr(i8** %addr, i8* %escaped_then_written) diff --git a/llvm/test/Transforms/FunctionAttrs/readnone.ll b/llvm/test/Transforms/FunctionAttrs/readnone.ll --- a/llvm/test/Transforms/FunctionAttrs/readnone.ll +++ b/llvm/test/Transforms/FunctionAttrs/readnone.ll @@ -1,5 +1,5 @@ -; RUN: opt < %s -functionattrs -S | FileCheck %s -; RUN: opt < %s -passes=function-attrs -S | FileCheck %s +; RUN: opt < %s -attributor -S | FileCheck %s +; RUN: opt < %s -passes=attributor -S | FileCheck %s ; CHECK: define void @bar(i8* nocapture readnone) define void @bar(i8* readonly) {