summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--llvm/include/llvm/Analysis/MustExecute.h31
-rw-r--r--llvm/lib/Analysis/MustExecute.cpp188
-rw-r--r--llvm/test/Analysis/MustExecute/must_be_executed_context.ll121
-rw-r--r--llvm/test/Transforms/FunctionAttrs/nonnull.ll232
4 files changed, 556 insertions, 16 deletions
diff --git a/llvm/include/llvm/Analysis/MustExecute.h b/llvm/include/llvm/Analysis/MustExecute.h
index 045171b706f..d88cdf40ed8 100644
--- a/llvm/include/llvm/Analysis/MustExecute.h
+++ b/llvm/include/llvm/Analysis/MustExecute.h
@@ -33,8 +33,13 @@
namespace llvm {
+namespace {
+template <typename T> using GetterTy = std::function<T *(const Function &F)>;
+}
+
class Instruction;
class DominatorTree;
+class PostDominatorTree;
class Loop;
/// Captures loop safety information.
@@ -374,8 +379,14 @@ struct MustBeExecutedContextExplorer {
/// \param ExploreInterBlock Flag to indicate if instructions in blocks
/// other than the parent of PP should be
/// explored.
- MustBeExecutedContextExplorer(bool ExploreInterBlock)
- : ExploreInterBlock(ExploreInterBlock), EndIterator(*this, nullptr) {}
+ MustBeExecutedContextExplorer(
+ bool ExploreInterBlock,
+ GetterTy<const LoopInfo> LIGetter =
+ [](const Function &) { return nullptr; },
+ GetterTy<const PostDominatorTree> PDTGetter =
+ [](const Function &) { return nullptr; })
+ : ExploreInterBlock(ExploreInterBlock), LIGetter(LIGetter),
+ PDTGetter(PDTGetter), EndIterator(*this, nullptr) {}
/// Clean up the dynamically allocated iterators.
~MustBeExecutedContextExplorer() {
@@ -454,6 +465,9 @@ struct MustBeExecutedContextExplorer {
getMustBeExecutedNextInstruction(MustBeExecutedIterator &It,
const Instruction *PP);
+ /// Find the next join point from \p InitBB in forward direction.
+ const BasicBlock *findForwardJoinPoint(const BasicBlock *InitBB);
+
/// Parameter that limit the performed exploration. See the constructor for
/// their meaning.
///{
@@ -461,6 +475,19 @@ struct MustBeExecutedContextExplorer {
///}
private:
+ /// Getters for common CFG analyses: LoopInfo, DominatorTree, and
+ /// PostDominatorTree.
+ ///{
+ GetterTy<const LoopInfo> LIGetter;
+ GetterTy<const PostDominatorTree> PDTGetter;
+ ///}
+
+ /// Map to cache isGuaranteedToTransferExecutionToSuccessor results.
+ DenseMap<const BasicBlock *, Optional<bool>> BlockTransferMap;
+
+ /// Map to cache containsIrreducibleCFG results.
+ DenseMap<const Function*, Optional<bool>> IrreducibleControlMap;
+
/// Map from instructions to associated must be executed iterators.
DenseMap<const Instruction *, MustBeExecutedIterator *>
InstructionIteratorMap;
diff --git a/llvm/lib/Analysis/MustExecute.cpp b/llvm/lib/Analysis/MustExecute.cpp
index 44527773115..a796cc79ad8 100644
--- a/llvm/lib/Analysis/MustExecute.cpp
+++ b/llvm/lib/Analysis/MustExecute.cpp
@@ -13,6 +13,7 @@
#include "llvm/Analysis/LoopInfo.h"
#include "llvm/Analysis/Passes.h"
#include "llvm/Analysis/ValueTracking.h"
+#include "llvm/Analysis/PostDominators.h"
#include "llvm/IR/AssemblyAnnotationWriter.h"
#include "llvm/IR/DataLayout.h"
#include "llvm/IR/InstIterator.h"
@@ -353,7 +354,19 @@ ModulePass *llvm::createMustBeExecutedContextPrinter() {
}
bool MustBeExecutedContextPrinter::runOnModule(Module &M) {
- MustBeExecutedContextExplorer Explorer(true);
+ // We provide non-PM analysis here because the old PM doesn't like to query
+ // function passes from a module pass. Given that this is a printer, we don't
+ // care much about memory leaks.
+ GetterTy<LoopInfo> LIGetter = [this](const Function &F) {
+ DominatorTree *DT = new DominatorTree(const_cast<Function &>(F));
+ LoopInfo *LI = new LoopInfo(*DT);
+ return LI;
+ };
+ GetterTy<PostDominatorTree> PDTGetter = [this](const Function &F) {
+ PostDominatorTree *PDT = new PostDominatorTree(const_cast<Function &>(F));
+ return PDT;
+ };
+ MustBeExecutedContextExplorer Explorer(true, LIGetter, PDTGetter);
for (Function &F : M) {
for (Instruction &I : instructions(F)) {
dbgs() << "-- Explore context of: " << I << "\n";
@@ -443,6 +456,173 @@ bool MustExecutePrinter::runOnFunction(Function &F) {
return false;
}
+/// Return true if \p L might be an endless loop.
+static bool maybeEndlessLoop(const Loop &L) {
+ if (L.getHeader()->getParent()->hasFnAttribute(Attribute::WillReturn))
+ return false;
+ // TODO: Actually try to prove it is not.
+ // TODO: If maybeEndlessLoop is going to be expensive, cache it.
+ return true;
+}
+
+static bool mayContainIrreducibleControl(const Function &F, const LoopInfo *LI) {
+ if (!LI)
+ return false;
+ using RPOTraversal = ReversePostOrderTraversal<const Function *>;
+ RPOTraversal FuncRPOT(&F);
+ return !containsIrreducibleCFG<const BasicBlock *, const RPOTraversal,
+ const LoopInfo>(FuncRPOT, *LI);
+}
+
+/// Lookup \p Key in \p Map and return the result, potentially after
+/// initializing the optional through \p Fn(\p args).
+template <typename K, typename V, typename FnTy, typename... ArgsTy>
+static V getOrCreateCachedOptional(K Key, DenseMap<K, Optional<V>> &Map,
+ FnTy &&Fn, ArgsTy&&... args) {
+ Optional<V> &OptVal = Map[Key];
+ if (!OptVal.hasValue())
+ OptVal = Fn(std::forward<ArgsTy>(args)...);
+ return OptVal.getValue();
+}
+
+const BasicBlock *
+MustBeExecutedContextExplorer::findForwardJoinPoint(const BasicBlock *InitBB) {
+ const LoopInfo *LI = LIGetter(*InitBB->getParent());
+ const PostDominatorTree *PDT = PDTGetter(*InitBB->getParent());
+
+ LLVM_DEBUG(dbgs() << "\tFind forward join point for " << InitBB->getName()
+ << (LI ? " [LI]" : "") << (PDT ? " [PDT]" : ""));
+
+ const Function &F = *InitBB->getParent();
+ const Loop *L = LI ? LI->getLoopFor(InitBB) : nullptr;
+ const BasicBlock *HeaderBB = L ? L->getHeader() : InitBB;
+ bool WillReturnAndNoThrow = (F.hasFnAttribute(Attribute::WillReturn) ||
+ (L && !maybeEndlessLoop(*L))) &&
+ F.doesNotThrow();
+ LLVM_DEBUG(dbgs() << (L ? " [in loop]" : "")
+ << (WillReturnAndNoThrow ? " [WillReturn] [NoUnwind]" : "")
+ << "\n");
+
+ // Determine the adjacent blocks in the given direction but exclude (self)
+ // loops under certain circumstances.
+ SmallVector<const BasicBlock *, 8> Worklist;
+ for (const BasicBlock *SuccBB : successors(InitBB)) {
+ bool IsLatch = SuccBB == HeaderBB;
+ // Loop latches are ignored in forward propagation if the loop cannot be
+ // endless and may not throw: control has to go somewhere.
+ if (!WillReturnAndNoThrow || !IsLatch)
+ Worklist.push_back(SuccBB);
+ }
+ LLVM_DEBUG(dbgs() << "\t\t#Worklist: " << Worklist.size() << "\n");
+
+ // If there are no other adjacent blocks, there is no join point.
+ if (Worklist.empty())
+ return nullptr;
+
+ // If there is one adjacent block, it is the join point.
+ if (Worklist.size() == 1)
+ return Worklist[0];
+
+ // Try to determine a join block through the help of the post-dominance
+ // tree. If no tree was provided, we perform simple pattern matching for one
+ // block conditionals and one block loops only.
+ const BasicBlock *JoinBB = nullptr;
+ if (PDT)
+ if (const auto *InitNode = PDT->getNode(InitBB))
+ if (const auto *IDomNode = InitNode->getIDom())
+ JoinBB = IDomNode->getBlock();
+
+ if (!JoinBB && Worklist.size() == 2) {
+ const BasicBlock *Succ0 = Worklist[0];
+ const BasicBlock *Succ1 = Worklist[1];
+ const BasicBlock *Succ0UniqueSucc = Succ0->getUniqueSuccessor();
+ const BasicBlock *Succ1UniqueSucc = Succ1->getUniqueSuccessor();
+ if (Succ0UniqueSucc == InitBB) {
+ // InitBB -> Succ0 -> InitBB
+ // InitBB -> Succ1 = JoinBB
+ JoinBB = Succ1;
+ } else if (Succ1UniqueSucc == InitBB) {
+ // InitBB -> Succ1 -> InitBB
+ // InitBB -> Succ0 = JoinBB
+ JoinBB = Succ0;
+ } else if (Succ0 == Succ1UniqueSucc) {
+ // InitBB -> Succ0 = JoinBB
+ // InitBB -> Succ1 -> Succ0 = JoinBB
+ JoinBB = Succ0;
+ } else if (Succ1 == Succ0UniqueSucc) {
+ // InitBB -> Succ0 -> Succ1 = JoinBB
+ // InitBB -> Succ1 = JoinBB
+ JoinBB = Succ1;
+ } else if (Succ0UniqueSucc == Succ1UniqueSucc) {
+ // InitBB -> Succ0 -> JoinBB
+ // InitBB -> Succ1 -> JoinBB
+ JoinBB = Succ0UniqueSucc;
+ }
+ }
+
+ if (!JoinBB && L)
+ JoinBB = L->getUniqueExitBlock();
+
+ if (!JoinBB)
+ return nullptr;
+
+ LLVM_DEBUG(dbgs() << "\t\tJoin block candidate: " << JoinBB->getName() << "\n");
+
+ // In forward direction we check if control will for sure reach JoinBB from
+ // InitBB, thus it can not be "stopped" along the way. Ways to "stop" control
+ // are: infinite loops and instructions that do not necessarily transfer
+ // execution to their successor. To check for them we traverse the CFG from
+ // the adjacent blocks to the JoinBB, looking at all intermediate blocks.
+
+ // If we know the function is "will-return" and "no-throw" there is no need
+ // for futher checks.
+ if (!F.hasFnAttribute(Attribute::WillReturn) || !F.doesNotThrow()) {
+
+ auto BlockTransfersExecutionToSuccessor = [](const BasicBlock *BB) {
+ return isGuaranteedToTransferExecutionToSuccessor(BB);
+ };
+
+ SmallPtrSet<const BasicBlock *, 16> Visited;
+ while (!Worklist.empty()) {
+ const BasicBlock *ToBB = Worklist.pop_back_val();
+ if (ToBB == JoinBB)
+ continue;
+
+ // Make sure all loops in-between are finite.
+ if (!Visited.insert(ToBB).second) {
+ if (!F.hasFnAttribute(Attribute::WillReturn)) {
+ if (!LI)
+ return nullptr;
+
+ bool MayContainIrreducibleControl = getOrCreateCachedOptional(
+ &F, IrreducibleControlMap, mayContainIrreducibleControl, F, LI);
+ if (MayContainIrreducibleControl)
+ return nullptr;
+
+ const Loop *L = LI->getLoopFor(ToBB);
+ if (L && maybeEndlessLoop(*L))
+ return nullptr;
+ }
+
+ continue;
+ }
+
+ // Make sure the block has no instructions that could stop control
+ // transfer.
+ bool TransfersExecution = getOrCreateCachedOptional(
+ ToBB, BlockTransferMap, BlockTransfersExecutionToSuccessor, ToBB);
+ if (!TransfersExecution)
+ return nullptr;
+
+ for (const BasicBlock *AdjacentBB : successors(ToBB))
+ Worklist.push_back(AdjacentBB);
+ }
+ }
+
+ LLVM_DEBUG(dbgs() << "\tJoin block: " << JoinBB->getName() << "\n");
+ return JoinBB;
+}
+
const Instruction *
MustBeExecutedContextExplorer::getMustBeExecutedNextInstruction(
MustBeExecutedIterator &It, const Instruction *PP) {
@@ -490,6 +670,12 @@ MustBeExecutedContextExplorer::getMustBeExecutedNextInstruction(
return &PP->getSuccessor(0)->front();
}
+ // Multiple successors mean we need to find the join point where control flow
+ // converges again. We use the findForwardJoinPoint helper function with
+ // information about the function and helper analyses, if available.
+ if (const BasicBlock *JoinBB = findForwardJoinPoint(PP->getParent()))
+ return &JoinBB->front();
+
LLVM_DEBUG(dbgs() << "\tNo join point found\n");
return nullptr;
}
diff --git a/llvm/test/Analysis/MustExecute/must_be_executed_context.ll b/llvm/test/Analysis/MustExecute/must_be_executed_context.ll
index da1aa7280b0..fd650872dd9 100644
--- a/llvm/test/Analysis/MustExecute/must_be_executed_context.ll
+++ b/llvm/test/Analysis/MustExecute/must_be_executed_context.ll
@@ -1,5 +1,6 @@
-; RUN: opt -print-mustexecute -analyze 2>&1 < %s | FileCheck %s --check-prefix=ME
-; RUN: opt -print-must-be-executed-contexts -analyze 2>&1 < %s | FileCheck %s --check-prefix=MBEC
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt < %s -print-mustexecute -analyze 2>&1 | FileCheck %s --check-prefix=ME
+; RUN: opt < %s -print-must-be-executed-contexts -analyze 2>&1 | FileCheck %s --check-prefix=MBEC
;
; void simple_conditional(int c) {
; A();
@@ -36,6 +37,8 @@ bb:
; MBEC-NEXT: [F: simple_conditional] call void @B()
; MBEC-NEXT: [F: simple_conditional] %tmp = icmp eq i32 %arg, 0
; MBEC-NEXT: [F: simple_conditional] br i1 %tmp, label %bb2, label %bb1
+; MBEC-NEXT: [F: simple_conditional] call void @E()
+; MBEC-NEXT: [F: simple_conditional] call void @F()
; MBEC-NOT: call
call void @B()
@@ -43,6 +46,8 @@ bb:
; MBEC-NEXT: [F: simple_conditional] call void @B()
; MBEC-NEXT: [F: simple_conditional] %tmp = icmp eq i32 %arg, 0
; MBEC-NEXT: [F: simple_conditional] br i1 %tmp, label %bb2, label %bb1
+; MBEC-NEXT: [F: simple_conditional] call void @E()
+; MBEC-NEXT: [F: simple_conditional] call void @F()
; MBEC-NOT: call
; MBEC: -- Explore context of: %tmp
@@ -280,3 +285,115 @@ declare void @E() nounwind willreturn
declare void @F() nounwind
declare void @G() nounwind willreturn
+
+declare i32 @g(i32*) nounwind willreturn
+
+declare void @h(i32*) nounwind willreturn
+
+define i32 @nonnull_exec_ctx_1(i32* %a, i32 %b) {
+; MBEC: -- Explore context of: %tmp3 = icmp eq i32 %b, 0
+; MBEC-NEXT: [F: nonnull_exec_ctx_1] %tmp3 = icmp eq i32 %b, 0
+; MBEC-NEXT: [F: nonnull_exec_ctx_1] br i1 %tmp3, label %ex, label %hd
+; MBEC-NEXT: -- Explore context of: br i1 %tmp3, label %ex, label %hd
+; MBEC-NEXT: [F: nonnull_exec_ctx_1] br i1 %tmp3, label %ex, label %hd
+; MBEC-NEXT: -- Explore context of: %tmp5 = tail call i32 @g(i32* nonnull %a)
+; MBEC-NEXT: [F: nonnull_exec_ctx_1] %tmp5 = tail call i32 @g(i32* nonnull %a)
+; MBEC-NEXT: [F: nonnull_exec_ctx_1] ret i32 %tmp5
+; MBEC-NEXT: -- Explore context of: ret i32 %tmp5
+; MBEC-NEXT: [F: nonnull_exec_ctx_1] ret i32 %tmp5
+; MBEC-NEXT: -- Explore context of: %tmp7 = phi i32 [ %tmp8, %hd ], [ 0, %en ]
+; MBEC-NEXT: [F: nonnull_exec_ctx_1] %tmp7 = phi i32 [ %tmp8, %hd ], [ 0, %en ]
+; MBEC-NEXT: [F: nonnull_exec_ctx_1] tail call void @h(i32* %a)
+; MBEC-NEXT: [F: nonnull_exec_ctx_1] %tmp8 = add nuw i32 %tmp7, 1
+; MBEC-NEXT: [F: nonnull_exec_ctx_1] %tmp9 = icmp eq i32 %tmp8, %b
+; MBEC-NEXT: [F: nonnull_exec_ctx_1] br i1 %tmp9, label %ex, label %hd
+; MBEC-NEXT: -- Explore context of: tail call void @h(i32* %a)
+; MBEC-NEXT: [F: nonnull_exec_ctx_1] tail call void @h(i32* %a)
+; MBEC-NEXT: [F: nonnull_exec_ctx_1] %tmp8 = add nuw i32 %tmp7, 1
+; MBEC-NEXT: [F: nonnull_exec_ctx_1] %tmp9 = icmp eq i32 %tmp8, %b
+; MBEC-NEXT: [F: nonnull_exec_ctx_1] br i1 %tmp9, label %ex, label %hd
+; MBEC-NEXT: -- Explore context of: %tmp8 = add nuw i32 %tmp7, 1
+; MBEC-NEXT: [F: nonnull_exec_ctx_1] %tmp8 = add nuw i32 %tmp7, 1
+; MBEC-NEXT: [F: nonnull_exec_ctx_1] %tmp9 = icmp eq i32 %tmp8, %b
+; MBEC-NEXT: [F: nonnull_exec_ctx_1] br i1 %tmp9, label %ex, label %hd
+; MBEC-NEXT: -- Explore context of: %tmp9 = icmp eq i32 %tmp8, %b
+; MBEC-NEXT: [F: nonnull_exec_ctx_1] %tmp9 = icmp eq i32 %tmp8, %b
+; MBEC-NEXT: [F: nonnull_exec_ctx_1] br i1 %tmp9, label %ex, label %hd
+; MBEC-NEXT: -- Explore context of: br i1 %tmp9, label %ex, label %hd
+; MBEC-NEXT: [F: nonnull_exec_ctx_1] br i1 %tmp9, label %ex, label %hd
+en:
+ %tmp3 = icmp eq i32 %b, 0
+ br i1 %tmp3, label %ex, label %hd
+
+ex:
+ %tmp5 = tail call i32 @g(i32* nonnull %a)
+ ret i32 %tmp5
+
+hd:
+ %tmp7 = phi i32 [ %tmp8, %hd ], [ 0, %en ]
+ tail call void @h(i32* %a)
+ %tmp8 = add nuw i32 %tmp7, 1
+ %tmp9 = icmp eq i32 %tmp8, %b
+ br i1 %tmp9, label %ex, label %hd
+}
+
+define i32 @nonnull_exec_ctx_2(i32* %a, i32 %b) nounwind willreturn {
+; MBEC: -- Explore context of: %tmp3 = icmp eq i32 %b, 0
+; MBEC-NEXT: [F: nonnull_exec_ctx_2] %tmp3 = icmp eq i32 %b, 0
+; MBEC-NEXT: [F: nonnull_exec_ctx_2] br i1 %tmp3, label %ex, label %hd
+; MBEC-NEXT: [F: nonnull_exec_ctx_2] %tmp5 = tail call i32 @g(i32* nonnull %a)
+; MBEC-NEXT: [F: nonnull_exec_ctx_2] ret i32 %tmp5
+; MBEC-NEXT: -- Explore context of: br i1 %tmp3, label %ex, label %hd
+; MBEC-NEXT: [F: nonnull_exec_ctx_2] br i1 %tmp3, label %ex, label %hd
+; MBEC-NEXT: [F: nonnull_exec_ctx_2] %tmp5 = tail call i32 @g(i32* nonnull %a)
+; MBEC-NEXT: [F: nonnull_exec_ctx_2] ret i32 %tmp5
+; MBEC-NEXT: -- Explore context of: %tmp5 = tail call i32 @g(i32* nonnull %a)
+; MBEC-NEXT: [F: nonnull_exec_ctx_2] %tmp5 = tail call i32 @g(i32* nonnull %a)
+; MBEC-NEXT: [F: nonnull_exec_ctx_2] ret i32 %tmp5
+; MBEC-NEXT: -- Explore context of: ret i32 %tmp5
+; MBEC-NEXT: [F: nonnull_exec_ctx_2] ret i32 %tmp5
+; MBEC-NEXT: -- Explore context of: %tmp7 = phi i32 [ %tmp8, %hd ], [ 0, %en ]
+; MBEC-NEXT: [F: nonnull_exec_ctx_2] %tmp7 = phi i32 [ %tmp8, %hd ], [ 0, %en ]
+; MBEC-NEXT: [F: nonnull_exec_ctx_2] tail call void @h(i32* %a)
+; MBEC-NEXT: [F: nonnull_exec_ctx_2] %tmp8 = add nuw i32 %tmp7, 1
+; MBEC-NEXT: [F: nonnull_exec_ctx_2] %tmp9 = icmp eq i32 %tmp8, %b
+; MBEC-NEXT: [F: nonnull_exec_ctx_2] br i1 %tmp9, label %ex, label %hd
+; MBEC-NEXT: [F: nonnull_exec_ctx_2] %tmp5 = tail call i32 @g(i32* nonnull %a)
+; MBEC-NEXT: [F: nonnull_exec_ctx_2] ret i32 %tmp5
+; MBEC-NEXT: -- Explore context of: tail call void @h(i32* %a)
+; MBEC-NEXT: [F: nonnull_exec_ctx_2] tail call void @h(i32* %a)
+; MBEC-NEXT: [F: nonnull_exec_ctx_2] %tmp8 = add nuw i32 %tmp7, 1
+; MBEC-NEXT: [F: nonnull_exec_ctx_2] %tmp9 = icmp eq i32 %tmp8, %b
+; MBEC-NEXT: [F: nonnull_exec_ctx_2] br i1 %tmp9, label %ex, label %hd
+; MBEC-NEXT: [F: nonnull_exec_ctx_2] %tmp5 = tail call i32 @g(i32* nonnull %a)
+; MBEC-NEXT: [F: nonnull_exec_ctx_2] ret i32 %tmp5
+; MBEC-NEXT: -- Explore context of: %tmp8 = add nuw i32 %tmp7, 1
+; MBEC-NEXT: [F: nonnull_exec_ctx_2] %tmp8 = add nuw i32 %tmp7, 1
+; MBEC-NEXT: [F: nonnull_exec_ctx_2] %tmp9 = icmp eq i32 %tmp8, %b
+; MBEC-NEXT: [F: nonnull_exec_ctx_2] br i1 %tmp9, label %ex, label %hd
+; MBEC-NEXT: [F: nonnull_exec_ctx_2] %tmp5 = tail call i32 @g(i32* nonnull %a)
+; MBEC-NEXT: [F: nonnull_exec_ctx_2] ret i32 %tmp5
+; MBEC-NEXT: -- Explore context of: %tmp9 = icmp eq i32 %tmp8, %b
+; MBEC-NEXT: [F: nonnull_exec_ctx_2] %tmp9 = icmp eq i32 %tmp8, %b
+; MBEC-NEXT: [F: nonnull_exec_ctx_2] br i1 %tmp9, label %ex, label %hd
+; MBEC-NEXT: [F: nonnull_exec_ctx_2] %tmp5 = tail call i32 @g(i32* nonnull %a)
+; MBEC-NEXT: [F: nonnull_exec_ctx_2] ret i32 %tmp5
+; MBEC-NEXT: -- Explore context of: br i1 %tmp9, label %ex, label %hd
+; MBEC-NEXT: [F: nonnull_exec_ctx_2] br i1 %tmp9, label %ex, label %hd
+; MBEC-NEXT: [F: nonnull_exec_ctx_2] %tmp5 = tail call i32 @g(i32* nonnull %a)
+; MBEC-NEXT: [F: nonnull_exec_ctx_2] ret i32 %tmp5
+en:
+ %tmp3 = icmp eq i32 %b, 0
+ br i1 %tmp3, label %ex, label %hd
+
+ex:
+ %tmp5 = tail call i32 @g(i32* nonnull %a)
+ ret i32 %tmp5
+
+hd:
+ %tmp7 = phi i32 [ %tmp8, %hd ], [ 0, %en ]
+ tail call void @h(i32* %a)
+ %tmp8 = add nuw i32 %tmp7, 1
+ %tmp9 = icmp eq i32 %tmp8, %b
+ br i1 %tmp9, label %ex, label %hd
+}
diff --git a/llvm/test/Transforms/FunctionAttrs/nonnull.ll b/llvm/test/Transforms/FunctionAttrs/nonnull.ll
index 9a7eb114eae..657ef7152eb 100644
--- a/llvm/test/Transforms/FunctionAttrs/nonnull.ll
+++ b/llvm/test/Transforms/FunctionAttrs/nonnull.ll
@@ -1,3 +1,4 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
; RUN: opt -S -functionattrs -enable-nonnull-arg-prop %s | FileCheck %s --check-prefixes=BOTH,FNATTR
; RUN: opt -S -passes=function-attrs -enable-nonnull-arg-prop %s | FileCheck %s --check-prefixes=BOTH,FNATTR
; RUN: opt -attributor --attributor-disable=false -attributor-max-iterations-verify -attributor-max-iterations=8 -S < %s | FileCheck %s --check-prefixes=BOTH,ATTRIBUTOR
@@ -159,7 +160,7 @@ define void @test13_helper() {
ret void
}
define internal void @test13(i8* %a, i8* %b, i8* %c) {
-; ATTRIBUTOR: define internal void @test13(i8* nocapture nonnull readnone %a, i8* nocapture readnone %b, i8* nocapture readnone %c)
+; ATTRIBUTOR: define internal void @test13(i8* nocapture nonnull readnone %a, i8* nocapture readnone %b, i8* nocapture readnone %c)
ret void
}
@@ -172,7 +173,7 @@ declare nonnull i8* @nonnull()
; * Argument
; 1. In f1:bb6, %arg can be marked with nonnull because of the comparison in bb1
; 2. Because f2 is internal function, f2(i32* %arg) -> @f2(i32* nonnull %arg)
-; 3. In f1:bb4 %tmp5 is nonnull and f3 is internal function.
+; 3. In f1:bb4 %tmp5 is nonnull and f3 is internal function.
; Then, f3(i32* %arg) -> @f3(i32* nonnull %arg)
; 4. We get nonnull in whole f1 call sites so f1(i32* %arg) -> @f1(i32* nonnull %arg)
@@ -208,21 +209,21 @@ bb9: ; preds = %bb4, %bb
}
define internal i32* @f2(i32* %arg) {
-; FIXME: missing nonnull. It should be nonnull @f2(i32* nonnull %arg)
+; FIXME: missing nonnull. It should be nonnull @f2(i32* nonnull %arg)
; ATTRIBUTOR: define internal nonnull i32* @f2(i32* readonly %arg)
bb:
-; FIXME: missing nonnull. It should be @f1(i32* nonnull readonly %arg)
+; FIXME: missing nonnull. It should be @f1(i32* nonnull readonly %arg)
; ATTRIBUTOR: %tmp = tail call nonnull i32* @f1(i32* readonly %arg)
%tmp = tail call i32* @f1(i32* %arg)
ret i32* %tmp
}
define dso_local noalias i32* @f3(i32* %arg) {
-; FIXME: missing nonnull. It should be nonnull @f3(i32* nonnull readonly %arg)
+; FIXME: missing nonnull. It should be nonnull @f3(i32* nonnull readonly %arg)
; ATTRIBUTOR: define dso_local noalias i32* @f3(i32* nocapture readonly %arg)
bb:
-; FIXME: missing nonnull. It should be @f1(i32* nonnull readonly %arg)
+; FIXME: missing nonnull. It should be @f1(i32* nonnull readonly %arg)
; ATTRIBUTOR: %tmp = call i32* @f1(i32* readonly %arg)
%tmp = call i32* @f1(i32* %arg)
ret i32* null
@@ -266,8 +267,7 @@ if.else:
; fun1(nonnull %a)
; We can say that %a is nonnull
define void @f17(i8* %a, i8 %c) {
-; FIXME: missing nonnull on %a
-; ATTRIBUTOR: define void @f17(i8* %a, i8 %c)
+; ATTRIBUTOR: define void @f17(i8* nonnull %a, i8 %c)
%cmp = icmp eq i8 %c, 0
br i1 %cmp, label %if.then, label %if.else
if.then:
@@ -292,8 +292,7 @@ cont:
; fun1(nonnull %a)
define void @f18(i8* %a, i8* %b, i8 %c) {
-; FIXME: missing nonnull on %a
-; ATTRIBUTOR: define void @f18(i8* %a, i8* %b, i8 %c)
+; ATTRIBUTOR: define void @f18(i8* nonnull %a, i8* %b, i8 %c)
%cmp1 = icmp eq i8 %c, 0
br i1 %cmp1, label %if.then, label %if.else
if.then:
@@ -477,7 +476,7 @@ define i8 @parent7(i8* %a) {
declare i32 @esfp(...)
define i1 @parent8(i8* %a, i8* %bogus1, i8* %b) personality i8* bitcast (i32 (...)* @esfp to i8*){
-; BOTH-LABEL: @parent8(i8* nonnull %a, i8* nocapture readnone %bogus1, i8* nonnull %b)
+; BOTH-LABEL: @parent8(i8* nonnull %a, i8* nocapture readnone %bogus1, i8* nonnull %b)
; BOTH-NEXT: entry:
; FNATTR-NEXT: invoke void @use2nonnull(i8* %a, i8* %b)
; ATTRIBUTOR-NEXT: invoke void @use2nonnull(i8* nonnull %a, i8* nonnull %b)
@@ -579,5 +578,216 @@ define void @make_live(i32* nonnull dereferenceable(8) %a) {
ret void
}
+
+;int f(int *u, int n){
+; for(int i = 0;i<n;i++){
+; h(u);
+; }
+; return g(nonnull u);
+;}
+declare void @h(i32*) willreturn nounwind
+declare i32 @g(i32*) willreturn nounwind
+define i32 @nonnull_exec_ctx_1(i32* %a, i32 %b) {
+; FNATTR-LABEL: define {{[^@]+}}@nonnull_exec_ctx_1
+; FNATTR-SAME: (i32* [[A:%.*]], i32 [[B:%.*]]) #3
+; FNATTR-NEXT: en:
+; FNATTR-NEXT: [[TMP3:%.*]] = icmp eq i32 [[B:%.*]], 0
+; FNATTR-NEXT: br i1 [[TMP3]], label [[EX:%.*]], label [[HD:%.*]]
+; FNATTR: ex:
+; FNATTR-NEXT: [[TMP5:%.*]] = tail call i32 @g(i32* nonnull [[A:%.*]])
+; FNATTR-NEXT: ret i32 [[TMP5]]
+; FNATTR: hd:
+; FNATTR-NEXT: [[TMP7:%.*]] = phi i32 [ [[TMP8:%.*]], [[HD]] ], [ 0, [[EN:%.*]] ]
+; FNATTR-NEXT: tail call void @h(i32* [[A]])
+; FNATTR-NEXT: [[TMP8]] = add nuw i32 [[TMP7]], 1
+; FNATTR-NEXT: [[TMP9:%.*]] = icmp eq i32 [[TMP8]], [[B]]
+; FNATTR-NEXT: br i1 [[TMP9]], label [[EX]], label [[HD]]
+;
+; ATTRIBUTOR-LABEL: define {{[^@]+}}@nonnull_exec_ctx_1
+; ATTRIBUTOR-SAME: (i32* [[A:%.*]], i32 [[B:%.*]]) #5
+; ATTRIBUTOR-NEXT: en:
+; ATTRIBUTOR-NEXT: [[TMP3:%.*]] = icmp eq i32 [[B:%.*]], 0
+; ATTRIBUTOR-NEXT: br i1 [[TMP3]], label [[EX:%.*]], label [[HD:%.*]]
+; ATTRIBUTOR: ex:
+; ATTRIBUTOR-NEXT: [[TMP5:%.*]] = tail call i32 @g(i32* nonnull [[A:%.*]])
+; ATTRIBUTOR-NEXT: ret i32 [[TMP5]]
+; ATTRIBUTOR: hd:
+; ATTRIBUTOR-NEXT: [[TMP7:%.*]] = phi i32 [ [[TMP8:%.*]], [[HD]] ], [ 0, [[EN:%.*]] ]
+; ATTRIBUTOR-NEXT: tail call void @h(i32* [[A]])
+; ATTRIBUTOR-NEXT: [[TMP8]] = add nuw i32 [[TMP7]], 1
+; ATTRIBUTOR-NEXT: [[TMP9:%.*]] = icmp eq i32 [[TMP8]], [[B]]
+; ATTRIBUTOR-NEXT: br i1 [[TMP9]], label [[EX]], label [[HD]]
+;
+en:
+ %tmp3 = icmp eq i32 %b, 0
+ br i1 %tmp3, label %ex, label %hd
+
+ex:
+ %tmp5 = tail call i32 @g(i32* nonnull %a)
+ ret i32 %tmp5
+
+hd:
+ %tmp7 = phi i32 [ %tmp8, %hd ], [ 0, %en ]
+ tail call void @h(i32* %a)
+ %tmp8 = add nuw i32 %tmp7, 1
+ %tmp9 = icmp eq i32 %tmp8, %b
+ br i1 %tmp9, label %ex, label %hd
+}
+
+define i32 @nonnull_exec_ctx_1b(i32* %a, i32 %b) {
+; FNATTR-LABEL: define {{[^@]+}}@nonnull_exec_ctx_1b
+; FNATTR-SAME: (i32* [[A:%.*]], i32 [[B:%.*]]) #3
+; FNATTR-NEXT: en:
+; FNATTR-NEXT: [[TMP3:%.*]] = icmp eq i32 [[B:%.*]], 0
+; FNATTR-NEXT: br i1 [[TMP3]], label [[EX:%.*]], label [[HD:%.*]]
+; FNATTR: ex:
+; FNATTR-NEXT: [[TMP5:%.*]] = tail call i32 @g(i32* nonnull [[A:%.*]])
+; FNATTR-NEXT: ret i32 [[TMP5]]
+; FNATTR: hd:
+; FNATTR-NEXT: [[TMP7:%.*]] = phi i32 [ [[TMP8:%.*]], [[HD2:%.*]] ], [ 0, [[EN:%.*]] ]
+; FNATTR-NEXT: tail call void @h(i32* [[A]])
+; FNATTR-NEXT: br label [[HD2]]
+; FNATTR: hd2:
+; FNATTR-NEXT: [[TMP8]] = add nuw i32 [[TMP7]], 1
+; FNATTR-NEXT: [[TMP9:%.*]] = icmp eq i32 [[TMP8]], [[B]]
+; FNATTR-NEXT: br i1 [[TMP9]], label [[EX]], label [[HD]]
+;
+; ATTRIBUTOR-LABEL: define {{[^@]+}}@nonnull_exec_ctx_1b
+; ATTRIBUTOR-SAME: (i32* [[A:%.*]], i32 [[B:%.*]]) #5
+; ATTRIBUTOR-NEXT: en:
+; ATTRIBUTOR-NEXT: [[TMP3:%.*]] = icmp eq i32 [[B:%.*]], 0
+; ATTRIBUTOR-NEXT: br i1 [[TMP3]], label [[EX:%.*]], label [[HD:%.*]]
+; ATTRIBUTOR: ex:
+; ATTRIBUTOR-NEXT: [[TMP5:%.*]] = tail call i32 @g(i32* nonnull [[A:%.*]])
+; ATTRIBUTOR-NEXT: ret i32 [[TMP5]]
+; ATTRIBUTOR: hd:
+; ATTRIBUTOR-NEXT: [[TMP7:%.*]] = phi i32 [ [[TMP8:%.*]], [[HD2:%.*]] ], [ 0, [[EN:%.*]] ]
+; ATTRIBUTOR-NEXT: tail call void @h(i32* [[A]])
+; ATTRIBUTOR-NEXT: br label [[HD2]]
+; ATTRIBUTOR: hd2:
+; ATTRIBUTOR-NEXT: [[TMP8]] = add nuw i32 [[TMP7]], 1
+; ATTRIBUTOR-NEXT: [[TMP9:%.*]] = icmp eq i32 [[TMP8]], [[B]]
+; ATTRIBUTOR-NEXT: br i1 [[TMP9]], label [[EX]], label [[HD]]
+;
+en:
+ %tmp3 = icmp eq i32 %b, 0
+ br i1 %tmp3, label %ex, label %hd
+
+ex:
+ %tmp5 = tail call i32 @g(i32* nonnull %a)
+ ret i32 %tmp5
+
+hd:
+ %tmp7 = phi i32 [ %tmp8, %hd2 ], [ 0, %en ]
+ tail call void @h(i32* %a)
+ br label %hd2
+
+hd2:
+ %tmp8 = add nuw i32 %tmp7, 1
+ %tmp9 = icmp eq i32 %tmp8, %b
+ br i1 %tmp9, label %ex, label %hd
+}
+
+define i32 @nonnull_exec_ctx_2(i32* %a, i32 %b) willreturn nounwind {
+; FNATTR-LABEL: define {{[^@]+}}@nonnull_exec_ctx_2
+; FNATTR-SAME: (i32* [[A:%.*]], i32 [[B:%.*]]) #2
+; FNATTR-NEXT: en:
+; FNATTR-NEXT: [[TMP3:%.*]] = icmp eq i32 [[B:%.*]], 0
+; FNATTR-NEXT: br i1 [[TMP3]], label [[EX:%.*]], label [[HD:%.*]]
+; FNATTR: ex:
+; FNATTR-NEXT: [[TMP5:%.*]] = tail call i32 @g(i32* nonnull [[A:%.*]])
+; FNATTR-NEXT: ret i32 [[TMP5]]
+; FNATTR: hd:
+; FNATTR-NEXT: [[TMP7:%.*]] = phi i32 [ [[TMP8:%.*]], [[HD]] ], [ 0, [[EN:%.*]] ]
+; FNATTR-NEXT: tail call void @h(i32* [[A]])
+; FNATTR-NEXT: [[TMP8]] = add nuw i32 [[TMP7]], 1
+; FNATTR-NEXT: [[TMP9:%.*]] = icmp eq i32 [[TMP8]], [[B]]
+; FNATTR-NEXT: br i1 [[TMP9]], label [[EX]], label [[HD]]
+;
+; ATTRIBUTOR-LABEL: define {{[^@]+}}@nonnull_exec_ctx_2
+; ATTRIBUTOR-SAME: (i32* [[A:%.*]], i32 [[B:%.*]]) #3
+; ATTRIBUTOR-NEXT: en:
+; ATTRIBUTOR-NEXT: [[TMP3:%.*]] = icmp eq i32 [[B:%.*]], 0
+; ATTRIBUTOR-NEXT: br i1 [[TMP3]], label [[EX:%.*]], label [[HD:%.*]]
+; ATTRIBUTOR: ex:
+; ATTRIBUTOR-NEXT: [[TMP5:%.*]] = tail call i32 @g(i32* nonnull [[A:%.*]])
+; ATTRIBUTOR-NEXT: ret i32 [[TMP5]]
+; ATTRIBUTOR: hd:
+; ATTRIBUTOR-NEXT: [[TMP7:%.*]] = phi i32 [ [[TMP8:%.*]], [[HD]] ], [ 0, [[EN:%.*]] ]
+; ATTRIBUTOR-NEXT: tail call void @h(i32* nonnull [[A]])
+; ATTRIBUTOR-NEXT: [[TMP8]] = add nuw i32 [[TMP7]], 1
+; ATTRIBUTOR-NEXT: [[TMP9:%.*]] = icmp eq i32 [[TMP8]], [[B]]
+; ATTRIBUTOR-NEXT: br i1 [[TMP9]], label [[EX]], label [[HD]]
+;
+en:
+ %tmp3 = icmp eq i32 %b, 0
+ br i1 %tmp3, label %ex, label %hd
+
+ex:
+ %tmp5 = tail call i32 @g(i32* nonnull %a)
+ ret i32 %tmp5
+
+hd:
+ %tmp7 = phi i32 [ %tmp8, %hd ], [ 0, %en ]
+ tail call void @h(i32* %a)
+ %tmp8 = add nuw i32 %tmp7, 1
+ %tmp9 = icmp eq i32 %tmp8, %b
+ br i1 %tmp9, label %ex, label %hd
+}
+
+define i32 @nonnull_exec_ctx_2b(i32* %a, i32 %b) willreturn nounwind {
+; FNATTR-LABEL: define {{[^@]+}}@nonnull_exec_ctx_2b
+; FNATTR-SAME: (i32* [[A:%.*]], i32 [[B:%.*]]) #2
+; FNATTR-NEXT: en:
+; FNATTR-NEXT: [[TMP3:%.*]] = icmp eq i32 [[B:%.*]], 0
+; FNATTR-NEXT: br i1 [[TMP3]], label [[EX:%.*]], label [[HD:%.*]]
+; FNATTR: ex:
+; FNATTR-NEXT: [[TMP5:%.*]] = tail call i32 @g(i32* nonnull [[A:%.*]])
+; FNATTR-NEXT: ret i32 [[TMP5]]
+; FNATTR: hd:
+; FNATTR-NEXT: [[TMP7:%.*]] = phi i32 [ [[TMP8:%.*]], [[HD2:%.*]] ], [ 0, [[EN:%.*]] ]
+; FNATTR-NEXT: tail call void @h(i32* [[A]])
+; FNATTR-NEXT: br label [[HD2]]
+; FNATTR: hd2:
+; FNATTR-NEXT: [[TMP8]] = add nuw i32 [[TMP7]], 1
+; FNATTR-NEXT: [[TMP9:%.*]] = icmp eq i32 [[TMP8]], [[B]]
+; FNATTR-NEXT: br i1 [[TMP9]], label [[EX]], label [[HD]]
+;
+; ATTRIBUTOR-LABEL: define {{[^@]+}}@nonnull_exec_ctx_2b
+; ATTRIBUTOR-SAME: (i32* [[A:%.*]], i32 [[B:%.*]]) #3
+; ATTRIBUTOR-NEXT: en:
+; ATTRIBUTOR-NEXT: [[TMP3:%.*]] = icmp eq i32 [[B:%.*]], 0
+; ATTRIBUTOR-NEXT: br i1 [[TMP3]], label [[EX:%.*]], label [[HD:%.*]]
+; ATTRIBUTOR: ex:
+; ATTRIBUTOR-NEXT: [[TMP5:%.*]] = tail call i32 @g(i32* nonnull [[A:%.*]])
+; ATTRIBUTOR-NEXT: ret i32 [[TMP5]]
+; ATTRIBUTOR: hd:
+; ATTRIBUTOR-NEXT: [[TMP7:%.*]] = phi i32 [ [[TMP8:%.*]], [[HD2:%.*]] ], [ 0, [[EN:%.*]] ]
+; ATTRIBUTOR-NEXT: tail call void @h(i32* nonnull [[A]])
+; ATTRIBUTOR-NEXT: br label [[HD2]]
+; ATTRIBUTOR: hd2:
+; ATTRIBUTOR-NEXT: [[TMP8]] = add nuw i32 [[TMP7]], 1
+; ATTRIBUTOR-NEXT: [[TMP9:%.*]] = icmp eq i32 [[TMP8]], [[B]]
+; ATTRIBUTOR-NEXT: br i1 [[TMP9]], label [[EX]], label [[HD]]
+;
+en:
+ %tmp3 = icmp eq i32 %b, 0
+ br i1 %tmp3, label %ex, label %hd
+
+ex:
+ %tmp5 = tail call i32 @g(i32* nonnull %a)
+ ret i32 %tmp5
+
+hd:
+ %tmp7 = phi i32 [ %tmp8, %hd2 ], [ 0, %en ]
+ tail call void @h(i32* %a)
+ br label %hd2
+
+hd2:
+ %tmp8 = add nuw i32 %tmp7, 1
+ %tmp9 = icmp eq i32 %tmp8, %b
+ br i1 %tmp9, label %ex, label %hd
+}
+
attributes #0 = { "null-pointer-is-valid"="true" }
attributes #1 = { nounwind willreturn}
OpenPOWER on IntegriCloud