[llvm] fe799c9 - [MustExecute] Forward iterate over conditional branches
Mikael Holmén via llvm-commits
llvm-commits at lists.llvm.org
Thu Oct 31 01:51:10 PDT 2019
Hi Johannes,
I fixed a clang warning introduced with this patch in c95049540, please
take a look and check that I didn't mess up.
Thanks,
Mikael
On Wed, 2019-10-30 at 22:07 -0700, Johannes Doerfert via llvm-commits
wrote:
> Author: Johannes Doerfert
> Date: 2019-10-31T00:06:43-05:00
> New Revision: fe799c97fae0729e5952c6a8edf41e67bf60048f
>
> URL:
> https://protect2.fireeye.com/v1/url?k=183fbbd7-44adb6c8-183ffb4c-0cc47ad93db4-fbb129546cbfefd4&q=1&e=c8a13624-45f8-40b4-b6b1-85eaf8c5ce1d&u=https%3A%2F%2Fgithub.com%2Fllvm%2Fllvm-project%2Fcommit%2Ffe799c97fae0729e5952c6a8edf41e67bf60048f
> DIFF:
> https://protect2.fireeye.com/v1/url?k=a1183625-fd8a3b3a-a11876be-0cc47ad93db4-b131f81bce55dfa9&q=1&e=c8a13624-45f8-40b4-b6b1-85eaf8c5ce1d&u=https%3A%2F%2Fgithub.com%2Fllvm%2Fllvm-project%2Fcommit%2Ffe799c97fae0729e5952c6a8edf41e67bf60048f.diff
>
> LOG: [MustExecute] Forward iterate over conditional branches
>
> Summary:
> If a conditional branch is encountered we can try to find a join
> block
> where the execution is known to continue. This means finding a
> suitable
> block, e.g., the immediate post dominator of the conditional branch,
> and
> proofing control will always reach that block.
>
> This patch implements different techniques that work with and without
> provided analysis.
>
> Reviewers: uenoku, sstefan1, hfinkel
>
> Subscribers: hiraditya, bollu, llvm-commits
>
> Tags: #llvm
>
> Differential Revision:
> https://protect2.fireeye.com/v1/url?k=13c95c03-4f5b511c-13c91c98-0cc47ad93db4-673e381fd96836a0&q=1&e=c8a13624-45f8-40b4-b6b1-85eaf8c5ce1d&u=https%3A%2F%2Freviews.llvm.org%2FD68933
>
> Added:
>
>
> Modified:
> llvm/include/llvm/Analysis/MustExecute.h
> llvm/lib/Analysis/MustExecute.cpp
> llvm/test/Analysis/MustExecute/must_be_executed_context.ll
> llvm/test/Transforms/FunctionAttrs/nonnull.ll
>
> Removed:
>
>
>
> #####################################################################
> ###########
> diff --git a/llvm/include/llvm/Analysis/MustExecute.h
> b/llvm/include/llvm/Analysis/MustExecute.h
> index 045171b706f1..d88cdf40ed87 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 44527773115d..a796cc79ad89 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 da1aa7280b02..fd650872dd91 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 9a7eb114eaee..657ef7152eb7 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}
>
>
>
> _______________________________________________
> llvm-commits mailing list
> llvm-commits at lists.llvm.org
>
https://protect2.fireeye.com/v1/url?k=afe4c3ec-f376cef3-afe48377-0cc47ad93db4-60833c49f3d9d376&q=1&e=c8a13624-45f8-40b4-b6b1-85eaf8c5ce1d&u=https%3A%2F%2Flists.llvm.org%2Fcgi-bin%2Fmailman%2Flistinfo%2Fllvm-commits
More information about the llvm-commits
mailing list