summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--llvm/include/llvm/Transforms/IPO/Attributor.h33
-rw-r--r--llvm/lib/Transforms/IPO/Attributor.cpp352
-rw-r--r--llvm/test/Transforms/FunctionAttrs/align.ll119
-rw-r--r--llvm/test/Transforms/FunctionAttrs/arg_nocapture.ll4
-rw-r--r--llvm/test/Transforms/FunctionAttrs/arg_returned.ll18
-rw-r--r--llvm/test/Transforms/FunctionAttrs/liveness.ll39
-rw-r--r--llvm/test/Transforms/FunctionAttrs/misc.ll61
-rw-r--r--llvm/test/Transforms/FunctionAttrs/new_attributes.ll2
-rw-r--r--llvm/test/Transforms/FunctionAttrs/noalias_returned.ll2
-rw-r--r--llvm/test/Transforms/FunctionAttrs/nonnull.ll6
10 files changed, 565 insertions, 71 deletions
diff --git a/llvm/include/llvm/Transforms/IPO/Attributor.h b/llvm/include/llvm/Transforms/IPO/Attributor.h
index 17cc11491fd..d3c16acb1a1 100644
--- a/llvm/include/llvm/Transforms/IPO/Attributor.h
+++ b/llvm/include/llvm/Transforms/IPO/Attributor.h
@@ -789,10 +789,25 @@ struct Attributor {
identifyDefaultAbstractAttributes(const_cast<Function &>(F));
}
- /// Record that \p I is deleted after information was manifested.
+ /// Record that \p U is to be replaces with \p NV after information was
+ /// manifested. This also triggers deletion of trivially dead istructions.
+ bool changeUseAfterManifest(Use &U, Value &NV) {
+ Value *&V = ToBeChangedUses[&U];
+ if (V && (V->stripPointerCasts() == NV.stripPointerCasts() ||
+ isa_and_nonnull<UndefValue>(V)))
+ return false;
+ assert((!V || V == &NV || isa<UndefValue>(NV)) &&
+ "Use was registered twice for replacement with different values!");
+ V = &NV;
+ return true;
+ }
+
+ /// Record that \p I is deleted after information was manifested. This also
+ /// triggers deletion of trivially dead istructions.
void deleteAfterManifest(Instruction &I) { ToBeDeletedInsts.insert(&I); }
- /// Record that \p BB is deleted after information was manifested.
+ /// Record that \p BB is deleted after information was manifested. This also
+ /// triggers deletion of trivially dead istructions.
void deleteAfterManifest(BasicBlock &BB) { ToBeDeletedBlocks.insert(&BB); }
/// Record that \p F is deleted after information was manifested.
@@ -803,6 +818,13 @@ struct Attributor {
/// If \p LivenessAA is not provided it is queried.
bool isAssumedDead(const AbstractAttribute &AA, const AAIsDead *LivenessAA);
+ /// Check \p Pred on all (transitive) uses of \p V.
+ ///
+ /// This method will evaluate \p Pred on all (transitive) uses of the
+ /// associated value and return true if \p Pred holds every time.
+ bool checkForAllUses(const function_ref<bool(const Use &, bool &)> &Pred,
+ const AbstractAttribute &QueryingAA, const Value &V);
+
/// Check \p Pred on all function call sites.
///
/// This method will evaluate \p Pred on call sites and return
@@ -972,6 +994,10 @@ private:
/// A set to remember the functions we already assume to be live and visited.
DenseSet<const Function *> VisitedFunctions;
+ /// Uses we replace with a new value after manifest is done. We will remove
+ /// then trivially dead instructions as well.
+ DenseMap<Use *, Value *> ToBeChangedUses;
+
/// Functions, blocks, and instructions we delete after manifest is done.
///
///{
@@ -1683,6 +1709,9 @@ struct AAIsDead : public StateWrapper<BooleanState, AbstractAttribute>,
public IRPosition {
AAIsDead(const IRPosition &IRP) : IRPosition(IRP) {}
+ /// Returns true if the underlying value is assumed dead.
+ virtual bool isAssumedDead() const = 0;
+
/// Returns true if \p BB is assumed dead.
virtual bool isAssumedDead(const BasicBlock *BB) const = 0;
diff --git a/llvm/lib/Transforms/IPO/Attributor.cpp b/llvm/lib/Transforms/IPO/Attributor.cpp
index 129eb197674..e18e4ccefc7 100644
--- a/llvm/lib/Transforms/IPO/Attributor.cpp
+++ b/llvm/lib/Transforms/IPO/Attributor.cpp
@@ -2095,9 +2095,204 @@ struct AANoAliasCallSiteReturned final : AANoAliasImpl {
/// -------------------AAIsDead Function Attribute-----------------------
-struct AAIsDeadImpl : public AAIsDead {
- AAIsDeadImpl(const IRPosition &IRP) : AAIsDead(IRP) {}
+struct AAIsDeadValueImpl : public AAIsDead {
+ AAIsDeadValueImpl(const IRPosition &IRP) : AAIsDead(IRP) {}
+ /// See AAIsDead::isAssumedDead().
+ bool isAssumedDead() const override { return getAssumed(); }
+
+ /// See AAIsDead::isAssumedDead(BasicBlock *).
+ bool isAssumedDead(const BasicBlock *BB) const override { return false; }
+
+ /// See AAIsDead::isKnownDead(BasicBlock *).
+ bool isKnownDead(const BasicBlock *BB) const override { return false; }
+
+ /// See AAIsDead::isAssumedDead(Instruction *I).
+ bool isAssumedDead(const Instruction *I) const override {
+ return I == getCtxI() && isAssumedDead();
+ }
+
+ /// See AAIsDead::isKnownDead(Instruction *I).
+ bool isKnownDead(const Instruction *I) const override {
+ return I == getCtxI() && getKnown();
+ }
+
+ /// See AbstractAttribute::getAsStr().
+ const std::string getAsStr() const override {
+ return isAssumedDead() ? "assumed-dead" : "assumed-live";
+ }
+};
+
+struct AAIsDeadFloating : public AAIsDeadValueImpl {
+ AAIsDeadFloating(const IRPosition &IRP) : AAIsDeadValueImpl(IRP) {}
+
+ /// See AbstractAttribute::initialize(...).
+ void initialize(Attributor &A) override {
+ if (Instruction *I = dyn_cast<Instruction>(&getAssociatedValue()))
+ if (!wouldInstructionBeTriviallyDead(I))
+ indicatePessimisticFixpoint();
+ if (isa<UndefValue>(getAssociatedValue()))
+ indicatePessimisticFixpoint();
+ }
+
+ /// See AbstractAttribute::updateImpl(...).
+ ChangeStatus updateImpl(Attributor &A) override {
+ auto UsePred = [&](const Use &U, bool &Follow) {
+ Instruction *UserI = cast<Instruction>(U.getUser());
+ if (CallSite CS = CallSite(UserI)) {
+ if (!CS.isArgOperand(&U))
+ return false;
+ const IRPosition &CSArgPos =
+ IRPosition::callsite_argument(CS, CS.getArgumentNo(&U));
+ const auto &CSArgIsDead = A.getAAFor<AAIsDead>(*this, CSArgPos);
+ return CSArgIsDead.isAssumedDead();
+ }
+ if (ReturnInst *RI = dyn_cast<ReturnInst>(UserI)) {
+ const IRPosition &RetPos = IRPosition::returned(*RI->getFunction());
+ const auto &RetIsDeadAA = A.getAAFor<AAIsDead>(*this, RetPos);
+ return RetIsDeadAA.isAssumedDead();
+ }
+ Follow = true;
+ return wouldInstructionBeTriviallyDead(UserI);
+ };
+
+ if (!A.checkForAllUses(UsePred, *this, getAssociatedValue()))
+ return indicatePessimisticFixpoint();
+ return ChangeStatus::UNCHANGED;
+ }
+
+ /// See AbstractAttribute::manifest(...).
+ ChangeStatus manifest(Attributor &A) override {
+ Value &V = getAssociatedValue();
+ if (auto *I = dyn_cast<Instruction>(&V))
+ if (wouldInstructionBeTriviallyDead(I)) {
+ A.deleteAfterManifest(*I);
+ return ChangeStatus::CHANGED;
+ }
+
+ if (V.use_empty())
+ return ChangeStatus::UNCHANGED;
+
+ UndefValue &UV = *UndefValue::get(V.getType());
+ bool AnyChange = false;
+ for (Use &U : V.uses())
+ AnyChange |= A.changeUseAfterManifest(U, UV);
+ return AnyChange ? ChangeStatus::CHANGED : ChangeStatus::UNCHANGED;
+ }
+
+ /// See AbstractAttribute::trackStatistics()
+ void trackStatistics() const override {
+ STATS_DECLTRACK_FLOATING_ATTR(IsDead)
+ }
+};
+
+struct AAIsDeadArgument : public AAIsDeadFloating {
+ AAIsDeadArgument(const IRPosition &IRP) : AAIsDeadFloating(IRP) {}
+
+ /// See AbstractAttribute::initialize(...).
+ void initialize(Attributor &A) override {
+ if (!getAssociatedFunction()->hasExactDefinition())
+ indicatePessimisticFixpoint();
+ }
+
+ /// See AbstractAttribute::trackStatistics()
+ void trackStatistics() const override { STATS_DECLTRACK_ARG_ATTR(IsDead) }
+};
+
+struct AAIsDeadCallSiteArgument : public AAIsDeadValueImpl {
+ AAIsDeadCallSiteArgument(const IRPosition &IRP) : AAIsDeadValueImpl(IRP) {}
+
+ /// See AbstractAttribute::initialize(...).
+ void initialize(Attributor &A) override {
+ if (isa<UndefValue>(getAssociatedValue()))
+ indicatePessimisticFixpoint();
+ }
+
+ /// See AbstractAttribute::updateImpl(...).
+ ChangeStatus updateImpl(Attributor &A) override {
+ // TODO: Once we have call site specific value information we can provide
+ // call site specific liveness information and then it makes
+ // sense to specialize attributes for call sites arguments instead of
+ // redirecting requests to the callee argument.
+ Argument *Arg = getAssociatedArgument();
+ if (!Arg)
+ return indicatePessimisticFixpoint();
+ const IRPosition &ArgPos = IRPosition::argument(*Arg);
+ auto &ArgAA = A.getAAFor<AAIsDead>(*this, ArgPos);
+ return clampStateAndIndicateChange(
+ getState(), static_cast<const AAIsDead::StateType &>(ArgAA.getState()));
+ }
+
+ /// See AbstractAttribute::manifest(...).
+ ChangeStatus manifest(Attributor &A) override {
+ CallBase &CB = cast<CallBase>(getAnchorValue());
+ Use &U = CB.getArgOperandUse(getArgNo());
+ assert(!isa<UndefValue>(U.get()) &&
+ "Expected undef values to be filtered out!");
+ UndefValue &UV = *UndefValue::get(U->getType());
+ if (A.changeUseAfterManifest(U, UV))
+ return ChangeStatus::CHANGED;
+ return ChangeStatus::UNCHANGED;
+ }
+
+ /// See AbstractAttribute::trackStatistics()
+ void trackStatistics() const override { STATS_DECLTRACK_CSARG_ATTR(IsDead) }
+};
+
+struct AAIsDeadReturned : public AAIsDeadValueImpl {
+ AAIsDeadReturned(const IRPosition &IRP) : AAIsDeadValueImpl(IRP) {}
+
+ /// See AbstractAttribute::updateImpl(...).
+ ChangeStatus updateImpl(Attributor &A) override {
+
+ auto PredForCallSite = [&](AbstractCallSite ACS) {
+ if (ACS.isCallbackCall())
+ return false;
+ const IRPosition &CSRetPos =
+ IRPosition::callsite_returned(ACS.getCallSite());
+ const auto &RetIsDeadAA = A.getAAFor<AAIsDead>(*this, CSRetPos);
+ return RetIsDeadAA.isAssumedDead();
+ };
+
+ if (!A.checkForAllCallSites(PredForCallSite, *this, true))
+ return indicatePessimisticFixpoint();
+
+ return ChangeStatus::UNCHANGED;
+ }
+
+ /// See AbstractAttribute::manifest(...).
+ ChangeStatus manifest(Attributor &A) override {
+ // TODO: Rewrite the signature to return void?
+ bool AnyChange = false;
+ UndefValue &UV = *UndefValue::get(getAssociatedFunction()->getReturnType());
+ auto RetInstPred = [&](Instruction &I) {
+ ReturnInst &RI = cast<ReturnInst>(I);
+ if (!isa<UndefValue>(RI.getReturnValue()))
+ AnyChange |= A.changeUseAfterManifest(RI.getOperandUse(0), UV);
+ return true;
+ };
+ A.checkForAllInstructions(RetInstPred, *this, {Instruction::Ret});
+ return AnyChange ? ChangeStatus::CHANGED : ChangeStatus::UNCHANGED;
+ }
+
+ /// See AbstractAttribute::trackStatistics()
+ void trackStatistics() const override { STATS_DECLTRACK_FNRET_ATTR(IsDead) }
+};
+
+struct AAIsDeadCallSiteReturned : public AAIsDeadFloating {
+ AAIsDeadCallSiteReturned(const IRPosition &IRP) : AAIsDeadFloating(IRP) {}
+
+ /// See AbstractAttribute::initialize(...).
+ void initialize(Attributor &A) override {}
+
+ /// See AbstractAttribute::trackStatistics()
+ void trackStatistics() const override { STATS_DECLTRACK_CSRET_ATTR(IsDead) }
+};
+
+struct AAIsDeadFunction : public AAIsDead {
+ AAIsDeadFunction(const IRPosition &IRP) : AAIsDead(IRP) {}
+
+ /// See AbstractAttribute::initialize(...).
void initialize(Attributor &A) override {
const Function *F = getAssociatedFunction();
if (F && !F->isDeclaration())
@@ -2231,6 +2426,12 @@ struct AAIsDeadImpl : public AAIsDead {
/// See AbstractAttribute::updateImpl(...).
ChangeStatus updateImpl(Attributor &A) override;
+ /// See AbstractAttribute::trackStatistics()
+ void trackStatistics() const override {}
+
+ /// Returns true if the function is assumed dead.
+ bool isAssumedDead() const override { return false; }
+
/// See AAIsDead::isAssumedDead(BasicBlock *).
bool isAssumedDead(const BasicBlock *BB) const override {
assert(BB->getParent() == getAssociatedFunction() &&
@@ -2303,18 +2504,7 @@ struct AAIsDeadImpl : public AAIsDead {
SmallSetVector<const Instruction *, 4> NoReturnCalls;
};
-struct AAIsDeadFunction final : public AAIsDeadImpl {
- AAIsDeadFunction(const IRPosition &IRP) : AAIsDeadImpl(IRP) {}
-
- /// See AbstractAttribute::trackStatistics()
- void trackStatistics() const override {
- STATS_DECL(PartiallyDeadBlocks, Function,
- "Number of basic blocks classified as partially dead");
- BUILD_STAT_NAME(PartiallyDeadBlocks, Function) += NoReturnCalls.size();
- }
-};
-
-bool AAIsDeadImpl::isAfterNoReturn(const Instruction *I) const {
+bool AAIsDeadFunction::isAfterNoReturn(const Instruction *I) const {
const Instruction *PrevI = I->getPrevNode();
while (PrevI) {
if (NoReturnCalls.count(PrevI))
@@ -2324,8 +2514,8 @@ bool AAIsDeadImpl::isAfterNoReturn(const Instruction *I) const {
return false;
}
-const Instruction *AAIsDeadImpl::findNextNoReturn(Attributor &A,
- const Instruction *I) {
+const Instruction *AAIsDeadFunction::findNextNoReturn(Attributor &A,
+ const Instruction *I) {
const BasicBlock *BB = I->getParent();
const Function &F = *BB->getParent();
@@ -2374,7 +2564,7 @@ const Instruction *AAIsDeadImpl::findNextNoReturn(Attributor &A,
return nullptr;
}
-ChangeStatus AAIsDeadImpl::updateImpl(Attributor &A) {
+ChangeStatus AAIsDeadFunction::updateImpl(Attributor &A) {
ChangeStatus Status = ChangeStatus::UNCHANGED;
// Temporary collection to iterate over existing noreturn instructions. This
@@ -2423,8 +2613,8 @@ ChangeStatus AAIsDeadImpl::updateImpl(Attributor &A) {
}
/// Liveness information for a call sites.
-struct AAIsDeadCallSite final : AAIsDeadImpl {
- AAIsDeadCallSite(const IRPosition &IRP) : AAIsDeadImpl(IRP) {}
+struct AAIsDeadCallSite final : AAIsDeadFunction {
+ AAIsDeadCallSite(const IRPosition &IRP) : AAIsDeadFunction(IRP) {}
/// See AbstractAttribute::initialize(...).
void initialize(Attributor &A) override {
@@ -4249,6 +4439,8 @@ bool Attributor::isAssumedDead(const AbstractAttribute &AA,
if (!CtxI)
return false;
+ // TODO: Find a good way to utilize fine and coarse grained liveness
+ // information.
if (!LivenessAA)
LivenessAA =
&getAAFor<AAIsDead>(AA, IRPosition::function(*CtxI->getFunction()),
@@ -4267,6 +4459,58 @@ bool Attributor::isAssumedDead(const AbstractAttribute &AA,
return true;
}
+bool Attributor::checkForAllUses(
+ const function_ref<bool(const Use &, bool &)> &Pred,
+ const AbstractAttribute &QueryingAA, const Value &V) {
+ const IRPosition &IRP = QueryingAA.getIRPosition();
+ SmallVector<const Use *, 16> Worklist;
+ SmallPtrSet<const Use *, 16> Visited;
+
+ for (const Use &U : V.uses())
+ Worklist.push_back(&U);
+
+ LLVM_DEBUG(dbgs() << "[Attributor] Got " << Worklist.size()
+ << " initial uses to check\n");
+
+ if (Worklist.empty())
+ return true;
+
+ bool AnyDead = false;
+ const Function *ScopeFn = IRP.getAnchorScope();
+ const auto *LivenessAA =
+ ScopeFn ? &getAAFor<AAIsDead>(QueryingAA, IRPosition::function(*ScopeFn),
+ /* TrackDependence */ false)
+ : nullptr;
+
+ while (!Worklist.empty()) {
+ const Use *U = Worklist.pop_back_val();
+ if (!Visited.insert(U).second)
+ continue;
+ LLVM_DEBUG(dbgs() << "[Attributor] Check use: " << **U << "\n");
+ if (Instruction *UserI = dyn_cast<Instruction>(U->getUser()))
+ if (LivenessAA && LivenessAA->isAssumedDead(UserI)) {
+ LLVM_DEBUG(dbgs() << "[Attributor] Dead user: " << *UserI << ": "
+ << static_cast<const AbstractAttribute &>(*LivenessAA)
+ << "\n");
+ AnyDead = true;
+ continue;
+ }
+
+ bool Follow = false;
+ if (!Pred(*U, Follow))
+ return false;
+ if (!Follow)
+ continue;
+ for (const Use &UU : U->getUser()->uses())
+ Worklist.push_back(&UU);
+ }
+
+ if (AnyDead)
+ recordDependence(*LivenessAA, QueryingAA);
+
+ return true;
+}
+
bool Attributor::checkForAllCallSites(
const function_ref<bool(AbstractCallSite)> &Pred,
const AbstractAttribute &QueryingAA, bool RequireAllCallSites) {
@@ -4633,13 +4877,48 @@ ChangeStatus Attributor::run(Module &M) {
LLVM_DEBUG(dbgs() << "\n[Attributor] Delete at least "
<< ToBeDeletedFunctions.size() << " functions and "
<< ToBeDeletedBlocks.size() << " blocks and "
- << ToBeDeletedInsts.size() << " instructions\n");
+ << ToBeDeletedInsts.size() << " instructions and "
+ << ToBeChangedUses.size() << " uses\n");
+
+ SmallVector<Instruction *, 32> DeadInsts;
+ SmallVector<Instruction *, 32> TerminatorsToFold;
+ SmallVector<Instruction *, 32> UnreachablesToInsert;
+
+ for (auto &It : ToBeChangedUses) {
+ Use *U = It.first;
+ Value *NewV = It.second;
+ Value *OldV = U->get();
+ LLVM_DEBUG(dbgs() << "Use " << *NewV << " in " << *U->getUser()
+ << " instead of " << *OldV << "\n");
+ U->set(NewV);
+ if (Instruction *I = dyn_cast<Instruction>(OldV))
+ if (!isa<PHINode>(I) && !ToBeDeletedInsts.count(I) && isInstructionTriviallyDead(I)) {
+ DeadInsts.push_back(I);
+ }
+ if (isa<Constant>(NewV) && isa<BranchInst>(U->getUser())) {
+ Instruction *UserI = cast<Instruction>(U->getUser());
+ if (isa<UndefValue>(NewV)) {
+ UnreachablesToInsert.push_back(UserI);
+ } else {
+ TerminatorsToFold.push_back(UserI);
+ }
+ }
+ }
+ for (Instruction *I : UnreachablesToInsert)
+ changeToUnreachable(I, /* UseLLVMTrap */ false);
+ for (Instruction *I : TerminatorsToFold)
+ ConstantFoldTerminator(I->getParent());
+
for (Instruction *I : ToBeDeletedInsts) {
- if (!I->use_empty())
- I->replaceAllUsesWith(UndefValue::get(I->getType()));
- I->eraseFromParent();
+ I->replaceAllUsesWith(UndefValue::get(I->getType()));
+ if (!isa<PHINode>(I) && isInstructionTriviallyDead(I))
+ DeadInsts.push_back(I);
+ else
+ I->eraseFromParent();
}
+ RecursivelyDeleteTriviallyDeadInstructions(DeadInsts);
+
if (unsigned NumDeadBlocks = ToBeDeletedBlocks.size()) {
SmallVector<BasicBlock *, 8> ToBeDeletedBBs;
ToBeDeletedBBs.reserve(NumDeadBlocks);
@@ -4676,14 +4955,12 @@ ChangeStatus Attributor::run(Module &M) {
if (!F)
continue;
- const auto *LivenessAA =
- lookupAAFor<AAIsDead>(IRPosition::function(*F));
- if (LivenessAA &&
- !checkForAllCallSites([](AbstractCallSite ACS) { return false; },
- *LivenessAA, true))
+ if (!checkForAllCallSites([](AbstractCallSite ACS) { return false; },
+ *F, true, nullptr))
continue;
STATS_TRACK(AAIsDead, Function);
+ ToBeDeletedFunctions.insert(F);
F->replaceAllUsesWith(UndefValue::get(F->getType()));
F->eraseFromParent();
InternalFns[u] = nullptr;
@@ -4800,6 +5077,9 @@ void Attributor::identifyDefaultAbstractAttributes(Function &F) {
IRPosition RetPos = IRPosition::returned(F);
+ // Every returned value might be dead.
+ getOrCreateAAFor<AAIsDead>(RetPos);
+
// Every function might be simplified.
getOrCreateAAFor<AAValueSimplify>(RetPos);
@@ -4850,11 +5130,21 @@ void Attributor::identifyDefaultAbstractAttributes(Function &F) {
auto CallSitePred = [&](Instruction &I) -> bool {
CallSite CS(&I);
- if (CS.getCalledFunction()) {
- for (int i = 0, e = CS.getCalledFunction()->arg_size(); i < e; i++) {
+ if (Function *Callee = CS.getCalledFunction()) {
+ if (!Callee->getReturnType()->isVoidTy()) {
+ IRPosition CSRetPos = IRPosition::callsite_returned(CS);
+
+ // Call site return values might be dead.
+ getOrCreateAAFor<AAIsDead>(CSRetPos);
+ }
+
+ for (int i = 0, e = Callee->arg_size(); i < e; i++) {
IRPosition CSArgPos = IRPosition::callsite_argument(CS, i);
+ // Every call site argument might be dead.
+ getOrCreateAAFor<AAIsDead>(CSArgPos);
+
// Call site argument might be simplified.
getOrCreateAAFor<AAValueSimplify>(CSArgPos);
@@ -5156,7 +5446,6 @@ CREATE_FUNCTION_ABSTRACT_ATTRIBUTE_FOR_POSITION(AANoFree)
CREATE_FUNCTION_ABSTRACT_ATTRIBUTE_FOR_POSITION(AANoRecurse)
CREATE_FUNCTION_ABSTRACT_ATTRIBUTE_FOR_POSITION(AAWillReturn)
CREATE_FUNCTION_ABSTRACT_ATTRIBUTE_FOR_POSITION(AANoReturn)
-CREATE_FUNCTION_ABSTRACT_ATTRIBUTE_FOR_POSITION(AAIsDead)
CREATE_FUNCTION_ABSTRACT_ATTRIBUTE_FOR_POSITION(AAReturnedValues)
CREATE_VALUE_ABSTRACT_ATTRIBUTE_FOR_POSITION(AANonNull)
@@ -5166,6 +5455,7 @@ CREATE_VALUE_ABSTRACT_ATTRIBUTE_FOR_POSITION(AAAlign)
CREATE_VALUE_ABSTRACT_ATTRIBUTE_FOR_POSITION(AANoCapture)
CREATE_ALL_ABSTRACT_ATTRIBUTE_FOR_POSITION(AAValueSimplify)
+CREATE_ALL_ABSTRACT_ATTRIBUTE_FOR_POSITION(AAIsDead)
CREATE_FUNCTION_ONLY_ABSTRACT_ATTRIBUTE_FOR_POSITION(AAHeapToStack)
diff --git a/llvm/test/Transforms/FunctionAttrs/align.ll b/llvm/test/Transforms/FunctionAttrs/align.ll
index f986d31af0a..f4cb54289ca 100644
--- a/llvm/test/Transforms/FunctionAttrs/align.ll
+++ b/llvm/test/Transforms/FunctionAttrs/align.ll
@@ -1,3 +1,4 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
; RUN: opt -attributor -attributor-manifest-internal -attributor-disable=false -attributor-max-iterations-verify -attributor-max-iterations=6 -S < %s | FileCheck %s --check-prefix=ATTRIBUTOR
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
@@ -83,14 +84,11 @@ define i32* @test6_2() #0 {
; Function Attrs: nounwind readnone ssp uwtable
define internal i8* @f1(i8* readnone %0) local_unnamed_addr #0 {
-; ATTRIBUTOR: define internal nonnull align 8 dereferenceable(1) i8* @f1(i8* nonnull readnone align 8 dereferenceable(1) "no-capture-maybe-returned" %0)
%2 = icmp eq i8* %0, null
br i1 %2, label %3, label %5
; <label>:3: ; preds = %1
-; ATTRIBUTOR: %4 = tail call align 8 i8* @f2(i8* nonnull align 8 dereferenceable(1) @a1)
%4 = tail call i8* @f2(i8* nonnull @a1)
-; ATTRIBUTOR: %l = load i8, i8* %4, align 8
%l = load i8, i8* %4
br label %5
@@ -101,18 +99,15 @@ define internal i8* @f1(i8* readnone %0) local_unnamed_addr #0 {
; Function Attrs: nounwind readnone ssp uwtable
define internal i8* @f2(i8* readnone %0) local_unnamed_addr #0 {
-; ATTRIBUTOR: define internal nonnull align 8 dereferenceable(1) i8* @f2(i8* nonnull readnone align 8 dereferenceable(1) "no-capture-maybe-returned" %0)
%2 = icmp eq i8* %0, null
br i1 %2, label %5, label %3
; <label>:3: ; preds = %1
-; ATTRIBUTOR: %4 = tail call i8* @f1(i8* nonnull align 8 dereferenceable(1) "no-capture-maybe-returned" @a1)
%4 = tail call i8* @f1(i8* nonnull %0)
br label %7
; <label>:5: ; preds = %1
-; ATTRIBUTOR: %6 = tail call i8* @f3(i8* nonnull align 16 dereferenceable(1) @a2)
%6 = tail call i8* @f3(i8* nonnull @a2)
br label %7
@@ -123,12 +118,10 @@ define internal i8* @f2(i8* readnone %0) local_unnamed_addr #0 {
; Function Attrs: nounwind readnone ssp uwtable
define internal i8* @f3(i8* readnone %0) local_unnamed_addr #0 {
-; ATTRIBUTOR: define internal nonnull align 8 dereferenceable(1) i8* @f3(i8* nocapture nonnull readnone align 16 dereferenceable(1) %0)
%2 = icmp eq i8* %0, null
br i1 %2, label %3, label %5
; <label>:3: ; preds = %1
-; ATTRIBUTOR: %4 = tail call i8* @f1(i8* nonnull align 16 dereferenceable(1) @a2)
%4 = tail call i8* @f1(i8* nonnull @a2)
br label %5
@@ -139,12 +132,116 @@ define internal i8* @f3(i8* readnone %0) local_unnamed_addr #0 {
; TEST 7
; Better than IR information
-; ATTRIBUTOR: define align 32 i32* @test7(i32* readnone returned align 32 "no-capture-maybe-returned" %p)
define align 4 i32* @test7(i32* align 32 %p) #0 {
+; ATTRIBUTOR-LABEL: define {{[^@]+}}@test7
+; ATTRIBUTOR-SAME: (i32* readnone returned align 32 "no-capture-maybe-returned" [[P:%.*]])
+; ATTRIBUTOR-NEXT: ret i32* [[P:%.*]]
+;
tail call i8* @f1(i8* align 8 dereferenceable(1) @a1)
ret i32* %p
}
+; TEST 7b
+; Function Attrs: nounwind readnone ssp uwtable
+define internal i8* @f1b(i8* readnone %0) local_unnamed_addr #0 {
+;
+; ATTRIBUTOR-LABEL: define {{[^@]+}}@f1b
+; ATTRIBUTOR-SAME: (i8* nonnull readnone align 8 dereferenceable(1) "no-capture-maybe-returned" [[TMP0:%.*]]) local_unnamed_addr
+; ATTRIBUTOR-NEXT: [[TMP2:%.*]] = icmp eq i8* [[TMP0:%.*]], null
+; ATTRIBUTOR-NEXT: br i1 [[TMP2]], label [[TMP3:%.*]], label [[TMP5:%.*]]
+; ATTRIBUTOR: 3:
+; ATTRIBUTOR-NEXT: [[TMP4:%.*]] = tail call align 8 i8* @f2b(i8* nonnull align 8 dereferenceable(1) @a1)
+; ATTRIBUTOR-NEXT: [[L:%.*]] = load i8, i8* [[TMP4]], align 8
+; ATTRIBUTOR-NEXT: store i8 [[L]], i8* @a1, align 8
+; ATTRIBUTOR-NEXT: br label [[TMP5]]
+; ATTRIBUTOR: 5:
+; ATTRIBUTOR-NEXT: [[TMP6:%.*]] = phi i8* [ [[TMP4]], [[TMP3]] ], [ [[TMP0]], [[TMP1:%.*]] ]
+; ATTRIBUTOR-NEXT: ret i8* [[TMP6]]
+;
+ %2 = icmp eq i8* %0, null
+ br i1 %2, label %3, label %5
+
+; <label>:3: ; preds = %1
+ %4 = tail call i8* @f2b(i8* nonnull @a1)
+ %l = load i8, i8* %4
+ store i8 %l, i8* @a1
+ br label %5
+
+; <label>:5: ; preds = %1, %3
+ %6 = phi i8* [ %4, %3 ], [ %0, %1 ]
+ ret i8* %6
+}
+
+; Function Attrs: nounwind readnone ssp uwtable
+define internal i8* @f2b(i8* readnone %0) local_unnamed_addr #0 {
+;
+; ATTRIBUTOR-LABEL: define {{[^@]+}}@f2b
+; ATTRIBUTOR-SAME: (i8* nonnull readnone align 8 dereferenceable(1) "no-capture-maybe-returned" [[TMP0:%.*]]) local_unnamed_addr
+; ATTRIBUTOR-NEXT: [[TMP2:%.*]] = icmp eq i8* @a1, null
+; ATTRIBUTOR-NEXT: br i1 [[TMP2]], label [[TMP5:%.*]], label [[TMP3:%.*]]
+; ATTRIBUTOR: 3:
+; ATTRIBUTOR-NEXT: [[TMP4:%.*]] = tail call i8* @f1b(i8* nonnull align 8 dereferenceable(1) "no-capture-maybe-returned" @a1)
+; ATTRIBUTOR-NEXT: br label [[TMP7:%.*]]
+; ATTRIBUTOR: 5:
+; ATTRIBUTOR-NEXT: [[TMP6:%.*]] = tail call i8* @f3b(i8* nonnull align 16 dereferenceable(1) @a2)
+; ATTRIBUTOR-NEXT: br label [[TMP7]]
+; ATTRIBUTOR: 7:
+; ATTRIBUTOR-NEXT: [[TMP8:%.*]] = phi i8* [ [[TMP4]], [[TMP3]] ], [ [[TMP6]], [[TMP5]] ]
+; ATTRIBUTOR-NEXT: ret i8* [[TMP8]]
+;
+ %2 = icmp eq i8* %0, null
+ br i1 %2, label %5, label %3
+
+; <label>:3: ; preds = %1
+
+ %4 = tail call i8* @f1b(i8* nonnull %0)
+ br label %7
+
+; <label>:5: ; preds = %1
+ %6 = tail call i8* @f3b(i8* nonnull @a2)
+ br label %7
+
+; <label>:7: ; preds = %5, %3
+ %8 = phi i8* [ %4, %3 ], [ %6, %5 ]
+ ret i8* %8
+}
+
+; Function Attrs: nounwind readnone ssp uwtable
+define internal i8* @f3b(i8* readnone %0) local_unnamed_addr #0 {
+;
+; ATTRIBUTOR-LABEL: define {{[^@]+}}@f3b
+; ATTRIBUTOR-SAME: (i8* nocapture nonnull readnone align 16 dereferenceable(1) [[TMP0:%.*]]) local_unnamed_addr
+; ATTRIBUTOR-NEXT: [[TMP2:%.*]] = icmp eq i8* @a2, null
+; ATTRIBUTOR-NEXT: br i1 [[TMP2]], label [[TMP3:%.*]], label [[TMP5:%.*]]
+; ATTRIBUTOR: 3:
+; ATTRIBUTOR-NEXT: [[TMP4:%.*]] = tail call i8* @f1b(i8* nonnull align 16 dereferenceable(1) @a2)
+; ATTRIBUTOR-NEXT: br label [[TMP5]]
+; ATTRIBUTOR: 5:
+; ATTRIBUTOR-NEXT: [[TMP6:%.*]] = phi i8* [ [[TMP4]], [[TMP3]] ], [ @a1, [[TMP1:%.*]] ]
+; ATTRIBUTOR-NEXT: ret i8* [[TMP6]]
+;
+ %2 = icmp eq i8* %0, null
+ br i1 %2, label %3, label %5
+
+; <label>:3: ; preds = %1
+ %4 = tail call i8* @f1b(i8* nonnull @a2)
+ br label %5
+
+; <label>:5: ; preds = %1, %3
+ %6 = phi i8* [ %4, %3 ], [ @a1, %1 ]
+ ret i8* %6
+}
+
+define align 4 i32* @test7b(i32* align 32 %p) #0 {
+; ATTRIBUTOR-LABEL: define {{[^@]+}}@test7b
+; ATTRIBUTOR-SAME: (i32* readnone returned align 32 "no-capture-maybe-returned" [[P:%.*]])
+; ATTRIBUTOR-NEXT: [[TMP1:%.*]] = tail call i8* @f1b(i8* nonnull align 8 dereferenceable(1) @a1)
+; ATTRIBUTOR-NEXT: ret i32* [[P:%.*]]
+;
+ tail call i8* @f1b(i8* align 8 dereferenceable(1) @a1)
+ ret i32* %p
+}
+
; TEST 8
define void @test8_helper() {
@@ -161,8 +258,12 @@ define void @test8_helper() {
ret void
}
+declare void @user_i32_ptr(i32*) readnone nounwind
define internal void @test8(i32* %a, i32* %b, i32* %c) {
; ATTRIBUTOR: define internal void @test8(i32* nocapture readnone align 4 %a, i32* nocapture readnone align 4 %b, i32* nocapture readnone %c)
+ call void @user_i32_ptr(i32* %a)
+ call void @user_i32_ptr(i32* %b)
+ call void @user_i32_ptr(i32* %c)
ret void
}
diff --git a/llvm/test/Transforms/FunctionAttrs/arg_nocapture.ll b/llvm/test/Transforms/FunctionAttrs/arg_nocapture.ll
index 7afaab6637e..cb598180bc2 100644
--- a/llvm/test/Transforms/FunctionAttrs/arg_nocapture.ll
+++ b/llvm/test/Transforms/FunctionAttrs/arg_nocapture.ll
@@ -90,8 +90,8 @@ entry:
define i32* @srec16(i32* %a) #0 {
entry:
%call = call i32* @srec16(i32* %a)
-; CHECK: %call
-; CHECK-NEXT: unreachable
+; CHECK-NOT: %call
+; CHECK: unreachable
%call1 = call i32* @srec16(i32* %call)
%call2 = call i32* @srec16(i32* %call1)
%call3 = call i32* @srec16(i32* %call2)
diff --git a/llvm/test/Transforms/FunctionAttrs/arg_returned.ll b/llvm/test/Transforms/FunctionAttrs/arg_returned.ll
index d927cdf7927..514438eaa24 100644
--- a/llvm/test/Transforms/FunctionAttrs/arg_returned.ll
+++ b/llvm/test/Transforms/FunctionAttrs/arg_returned.ll
@@ -253,8 +253,8 @@ return: ; preds = %cond.end, %if.then3
; }
;
; FNATTR: define i32* @rt0(i32* readonly %a)
-; BOTH: Function Attrs: nofree noinline noreturn nosync nounwind readonly uwtable
-; BOTH-NEXT: define noalias nonnull align 536870912 dereferenceable(4294967295) i32* @rt0(i32* nocapture nonnull readonly dereferenceable(4) %a)
+; BOTH: Function Attrs: nofree noinline norecurse noreturn nosync nounwind readonly uwtable
+; BOTH-NEXT: define noalias nonnull align 536870912 dereferenceable(4294967295) i32* @rt0(i32* nocapture nonnull readonly dereferenceable(4) %a)
define i32* @rt0(i32* %a) #0 {
entry:
%v = load i32, i32* %a, align 4
@@ -271,7 +271,7 @@ entry:
; }
;
; FNATTR: define noalias i32* @rt1(i32* nocapture readonly %a)
-; BOTH: Function Attrs: nofree noinline noreturn nosync nounwind readonly uwtable
+; BOTH: Function Attrs: nofree noinline norecurse noreturn nosync nounwind readonly uwtable
; BOTH-NEXT: define noalias nonnull align 536870912 dereferenceable(4294967295) i32* @rt1(i32* nocapture nonnull readonly dereferenceable(4) %a)
define i32* @rt1(i32* %a) #0 {
entry:
@@ -837,15 +837,3 @@ define i32* @dont_use_const() #0 {
}
attributes #0 = { noinline nounwind uwtable }
-
-; BOTH-NOT: attributes #
-; BOTH-DAG: attributes #{{[0-9]*}} = { nofree noinline norecurse nosync nounwind readnone uwtable willreturn }
-; BOTH-DAG: attributes #{{[0-9]*}} = { nofree noinline nosync nounwind readnone uwtable }
-; BOTH-DAG: attributes #{{[0-9]*}} = { nofree noinline noreturn nosync nounwind readonly uwtable }
-; BOTH-DAG: attributes #{{[0-9]*}} = { noinline nounwind uwtable }
-; BOTH-DAG: attributes #{{[0-9]*}} = { noreturn }
-; BOTH-DAG: attributes #{{[0-9]*}} = { nofree noinline norecurse nosync nounwind readnone uwtable }
-; BOTH-DAG: attributes #{{[0-9]*}} = { nofree nosync readnone willreturn }
-; BOTH-DAG: attributes #{{[0-9]*}} = { nofree nosync readnone }
-; BOTH-DAG: attributes #{{[0-9]*}} = { nofree noreturn nosync readonly }
-; BOTH-NOT: attributes #
diff --git a/llvm/test/Transforms/FunctionAttrs/liveness.ll b/llvm/test/Transforms/FunctionAttrs/liveness.ll
index f029f9476b7..cde910f2c2e 100644
--- a/llvm/test/Transforms/FunctionAttrs/liveness.ll
+++ b/llvm/test/Transforms/FunctionAttrs/liveness.ll
@@ -1,4 +1,4 @@
-; RUN: opt -attributor --attributor-disable=false -attributor-max-iterations-verify -attributor-max-iterations=3 -S < %s | FileCheck %s
+; RUN: opt -attributor --attributor-disable=false -attributor-max-iterations-verify -attributor-max-iterations=5 -S < %s | FileCheck %s
declare void @no_return_call() nofree noreturn nounwind readnone
@@ -39,8 +39,7 @@ define i32 @volatile_load(i32*) norecurse nounwind uwtable {
ret i32 %2
}
-; CHECK: Function Attrs: nofree norecurse nosync nounwind readonly uwtable willreturn
-; CHECK-NEXT: define internal i32 @internal_load(i32* nocapture nonnull readonly dereferenceable(4) %0)
+; CHECK-NOT: internal_load
define internal i32 @internal_load(i32*) norecurse nounwind uwtable {
%2 = load i32, i32* %0, align 4
ret i32 %2
@@ -52,7 +51,6 @@ define internal i32 @internal_load(i32*) norecurse nounwind uwtable {
define i32 @first_block_no_return(i32 %a, i32* nonnull %ptr1, i32* %ptr2) #0 {
entry:
call i32 @internal_load(i32* %ptr1)
- ; CHECK: call i32 @internal_load(i32* nocapture nonnull readonly %ptr1)
call void @no_return_call()
; CHECK: call void @no_return_call()
; CHECK-NEXT: unreachable
@@ -716,3 +714,36 @@ lp2:
live_with_dead_entry:
ret void
}
+
+; CHECK: define internal void @useless_arg_sink(i32* nocapture readnone %a)
+define internal void @useless_arg_sink(i32* %a) {
+ ret void
+}
+
+; CHECK: define internal void @useless_arg_almost_sink(i32* nocapture readnone %a)
+define internal void @useless_arg_almost_sink(i32* %a) {
+; CHECK: call void @useless_arg_sink(i32* undef)
+ call void @useless_arg_sink(i32* %a)
+ ret void
+}
+
+; Check we do not annotate the function interface of this weak function.
+; CHECK: define weak_odr void @useless_arg_ext(i32* %a)
+define weak_odr void @useless_arg_ext(i32* %a) {
+; CHECK: call void @useless_arg_almost_sink(i32* undef)
+ call void @useless_arg_almost_sink(i32* %a)
+ ret void
+}
+
+; CHECK: define internal void @useless_arg_ext_int(i32* %a)
+define internal void @useless_arg_ext_int(i32* %a) {
+; CHECK: call void @useless_arg_ext(i32* %a)
+ call void @useless_arg_ext(i32* %a)
+ ret void
+}
+
+define void @useless_arg_ext_int_ext(i32* %a) {
+; CHECK: call void @useless_arg_ext_int(i32* %a)
+ call void @useless_arg_ext_int(i32* %a)
+ ret void
+}
diff --git a/llvm/test/Transforms/FunctionAttrs/misc.ll b/llvm/test/Transforms/FunctionAttrs/misc.ll
index 96ceb8743fd..ea4a5046fd0 100644
--- a/llvm/test/Transforms/FunctionAttrs/misc.ll
+++ b/llvm/test/Transforms/FunctionAttrs/misc.ll
@@ -1,20 +1,73 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
; RUN: opt -S -attributor -attributor-disable=false < %s | FileCheck %s
+; RUN: opt -S -aa-pipeline='basic-aa' -passes=attributor -attributor-disable=false < %s | FileCheck %s
+;
+; Mostly check we do not crash on these uses
-define void @external() {
+define internal void @internal(void (i8*)* %fp) {
+; CHECK-LABEL: define {{[^@]+}}@internal
+; CHECK-SAME: (void (i8*)* [[FP:%.*]])
+; CHECK-NEXT: entry:
+; CHECK-NEXT: [[A:%.*]] = alloca i32, align 4
+; CHECK-NEXT: [[TMP:%.*]] = bitcast i32* [[A]] to i8*
+; CHECK-NEXT: call void @foo(i32* nocapture nonnull align 4 dereferenceable(4) undef)
+; CHECK-NEXT: call void [[FP:%.*]](i8* bitcast (void (i32*)* @foo to i8*))
+; CHECK-NEXT: call void @callback1(void (i32*)* nonnull @foo)
+; CHECK-NEXT: call void @callback2(void (i8*)* nonnull bitcast (void (i32*)* @foo to void (i8*)*))
+; CHECK-NEXT: call void @callback2(void (i8*)* [[FP]])
+; CHECK-NEXT: [[TMP1:%.*]] = bitcast i32* [[A]] to i8*
+; CHECK-NEXT: call void [[FP]](i8* [[TMP1]])
+; CHECK-NEXT: ret void
+;
entry:
%a = alloca i32, align 4
%tmp = bitcast i32* %a to i8*
call void @foo(i32* nonnull %a)
-; Check we do not crash on these uses
-; CHECK: call void @callback1(void (i32*)* nonnull @foo)
+ call void %fp(i8* bitcast (void (i32*)* @foo to i8*))
call void @callback1(void (i32*)* nonnull @foo)
-; CHECK: call void @callback2(void (i8*)* nonnull bitcast (void (i32*)* @foo to void (i8*)*))
call void @callback2(void (i8*)* bitcast (void (i32*)* @foo to void (i8*)*))
+ call void @callback2(void (i8*)* %fp)
%tmp1 = bitcast i32* %a to i8*
+ call void %fp(i8* %tmp1)
+ ret void
+}
+
+define void @external(void (i8*)* %fp) {
+; CHECK-LABEL: define {{[^@]+}}@external
+; CHECK-SAME: (void (i8*)* [[FP:%.*]])
+; CHECK-NEXT: entry:
+; CHECK-NEXT: [[A:%.*]] = alloca i32, align 4
+; CHECK-NEXT: [[TMP:%.*]] = bitcast i32* [[A]] to i8*
+; CHECK-NEXT: call void @foo(i32* nocapture nonnull align 4 dereferenceable(4) undef)
+; CHECK-NEXT: call void @callback1(void (i32*)* nonnull @foo)
+; CHECK-NEXT: call void @callback2(void (i8*)* nonnull bitcast (void (i32*)* @foo to void (i8*)*))
+; CHECK-NEXT: call void @callback2(void (i8*)* [[FP:%.*]])
+; CHECK-NEXT: call void [[FP]](i8* bitcast (void (i32*)* @foo to i8*))
+; CHECK-NEXT: [[TMP1:%.*]] = bitcast i32* [[A]] to i8*
+; CHECK-NEXT: call void [[FP]](i8* [[TMP1]])
+; CHECK-NEXT: call void @internal(void (i8*)* [[FP]])
+; CHECK-NEXT: ret void
+;
+entry:
+ %a = alloca i32, align 4
+ %tmp = bitcast i32* %a to i8*
+ call void @foo(i32* nonnull %a)
+ call void @callback1(void (i32*)* nonnull @foo)
+ call void @callback2(void (i8*)* bitcast (void (i32*)* @foo to void (i8*)*))
+ call void @callback2(void (i8*)* %fp)
+ call void %fp(i8* bitcast (void (i32*)* @foo to i8*))
+ %tmp1 = bitcast i32* %a to i8*
+ call void %fp(i8* %tmp1)
+ call void @internal(void (i8*)* %fp)
ret void
}
define internal void @foo(i32* %a) {
+; CHECK-LABEL: define {{[^@]+}}@foo
+; CHECK-SAME: (i32* nocapture readnone [[A:%.*]])
+; CHECK-NEXT: entry:
+; CHECK-NEXT: ret void
+;
entry:
ret void
}
diff --git a/llvm/test/Transforms/FunctionAttrs/new_attributes.ll b/llvm/test/Transforms/FunctionAttrs/new_attributes.ll
index d2f6854bd3c..aa71b64b982 100644
--- a/llvm/test/Transforms/FunctionAttrs/new_attributes.ll
+++ b/llvm/test/Transforms/FunctionAttrs/new_attributes.ll
@@ -20,7 +20,7 @@ declare i32 @foo3()
; CHECK-NEXT: %1 = call i32 @foo1()
; CHECK-NEXT: %2 = call i32 @foo2()
; CHECK-NEXT: %3 = call i32 @foo3()
-; CHECK-NEXT: ret i32 1
+; CHECK-NEXT: ret i32 undef
; CHECK-NEXT: }
define internal i32 @bar() {
%1 = call i32 @foo1()
diff --git a/llvm/test/Transforms/FunctionAttrs/noalias_returned.ll b/llvm/test/Transforms/FunctionAttrs/noalias_returned.ll
index 8cb1ec2eb4e..7c65a4350d0 100644
--- a/llvm/test/Transforms/FunctionAttrs/noalias_returned.ll
+++ b/llvm/test/Transforms/FunctionAttrs/noalias_returned.ll
@@ -168,6 +168,8 @@ define void @test9_helper(i8* %a, i8* %b) {
declare void @test10_helper_1(i8* %a)
define void @test10_helper_2(i8* noalias %a) {
+; CHECK: tail call void @test10_helper_1(i8* %a)
+ tail call void @test10_helper_1(i8* %a)
ret void
}
define void @test10(i8* noalias %a) {
diff --git a/llvm/test/Transforms/FunctionAttrs/nonnull.ll b/llvm/test/Transforms/FunctionAttrs/nonnull.ll
index 657ef7152eb..7e766da9d07 100644
--- a/llvm/test/Transforms/FunctionAttrs/nonnull.ll
+++ b/llvm/test/Transforms/FunctionAttrs/nonnull.ll
@@ -221,12 +221,12 @@ bb:
define dso_local noalias i32* @f3(i32* %arg) {
; FIXME: missing nonnull. It should be nonnull @f3(i32* nonnull readonly %arg)
-; ATTRIBUTOR: define dso_local noalias i32* @f3(i32* nocapture readonly %arg)
+; ATTRIBUTOR: define dso_local noalias nonnull i32* @f3(i32* readonly %arg)
bb:
; FIXME: missing nonnull. It should be @f1(i32* nonnull readonly %arg)
-; ATTRIBUTOR: %tmp = call i32* @f1(i32* readonly %arg)
+; ATTRIBUTOR: %tmp = call nonnull i32* @f1(i32* readonly %arg)
%tmp = call i32* @f1(i32* %arg)
- ret i32* null
+ ret i32* %tmp
}
; TEST 15
OpenPOWER on IntegriCloud