[llvm] f044217 - [ConstraintElim] Add reproducer remarks.

Florian Hahn via llvm-commits llvm-commits at lists.llvm.org
Tue Feb 14 07:16:19 PST 2023


Author: Florian Hahn
Date: 2023-02-14T15:15:57Z
New Revision: f04421739232b19d70fd00fbb66b136a3f1b22ed

URL: https://github.com/llvm/llvm-project/commit/f04421739232b19d70fd00fbb66b136a3f1b22ed
DIFF: https://github.com/llvm/llvm-project/commit/f04421739232b19d70fd00fbb66b136a3f1b22ed.diff

LOG: [ConstraintElim] Add reproducer remarks.

This patch adds an optimization remark for each performed optimization
containing a module that can be used to reproduce the transformation.

The reproducer function contains a series of @llvm.assume calls, one for
each condition currently in scope. For each condition, the operand
instruction are cloned until we reach operands that have an entry in the
constraint system. Those will then be added as function arguments.

The reproducer functions are minimal, that is, they only contain the
conditions required for a given simplification. The resulting IR is very
compact and can be used to verify each transformation individually.

It also provides a python script to extract the IR from the remarks and
create LLVM IR files from it.

Reviewed By: paquette

Differential Revision: https://reviews.llvm.org/D143323

Added: 
    llvm/test/Transforms/ConstraintElimination/reproducer-remarks-debug.ll
    llvm/test/Transforms/ConstraintElimination/reproducer-remarks.ll
    llvm/tools/opt-viewer/extract-reproducers.py

Modified: 
    llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
    llvm/test/Transforms/ConstraintElimination/analysis-invalidation.ll

Removed: 
    


################################################################################
diff  --git a/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp b/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
index f1bb73a63dee0..4ec3a58f52584 100644
--- a/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
@@ -18,6 +18,7 @@
 #include "llvm/ADT/Statistic.h"
 #include "llvm/Analysis/ConstraintSystem.h"
 #include "llvm/Analysis/GlobalsModRef.h"
+#include "llvm/Analysis/OptimizationRemarkEmitter.h"
 #include "llvm/Analysis/ValueTracking.h"
 #include "llvm/IR/DataLayout.h"
 #include "llvm/IR/Dominators.h"
@@ -26,11 +27,14 @@
 #include "llvm/IR/IRBuilder.h"
 #include "llvm/IR/Instructions.h"
 #include "llvm/IR/PatternMatch.h"
+#include "llvm/IR/Verifier.h"
 #include "llvm/Pass.h"
 #include "llvm/Support/CommandLine.h"
 #include "llvm/Support/Debug.h"
 #include "llvm/Support/DebugCounter.h"
 #include "llvm/Support/MathExtras.h"
+#include "llvm/Transforms/Utils/Cloning.h"
+#include "llvm/Transforms/Utils/ValueMapper.h"
 
 #include <cmath>
 #include <string>
@@ -48,6 +52,10 @@ static cl::opt<unsigned>
     MaxRows("constraint-elimination-max-rows", cl::init(500), cl::Hidden,
             cl::desc("Maximum number of rows to keep in constraint system"));
 
+static cl::opt<bool> DumpReproducers(
+    "constraint-elimination-dump-reproducers", cl::init(false), cl::Hidden,
+    cl::desc("Dump IR to reproduce successful transformations."));
+
 static int64_t MaxConstraintValue = std::numeric_limits<int64_t>::max();
 static int64_t MinSignedConstraintValue = std::numeric_limits<int64_t>::min();
 
@@ -746,7 +754,162 @@ void State::addInfoFor(BasicBlock &BB) {
         FactOrCheck::getFact(DT.getNode(Br->getSuccessor(1)), CmpI, true));
 }
 
-static bool checkAndReplaceCondition(CmpInst *Cmp, ConstraintInfo &Info) {
+namespace {
+/// Helper to keep track of a condition and if it should be treated as negated
+/// for reproducer construction.
+struct ReproducerEntry {
+  CmpInst *Cond;
+  bool IsNot;
+
+  ReproducerEntry(CmpInst *Cond, bool IsNot) : Cond(Cond), IsNot(IsNot) {}
+};
+} // namespace
+
+/// Helper function to generate a reproducer function for simplifying \p Cond.
+/// The reproducer function contains a series of @llvm.assume calls, one for
+/// each condition in \p Stack. For each condition, the operand instruction are
+/// cloned until we reach operands that have an entry in \p Value2Index. Those
+/// will then be added as function arguments. \p DT is used to order cloned
+/// instructions. The reproducer function will get added to \p M, if it is
+/// non-null. Otherwise no reproducer function is generated.
+static void generateReproducer(CmpInst *Cond, Module *M,
+                               ArrayRef<ReproducerEntry> Stack,
+                               ConstraintInfo &Info, DominatorTree &DT) {
+  if (!M)
+    return;
+
+  LLVMContext &Ctx = Cond->getContext();
+
+  LLVM_DEBUG(dbgs() << "Creating reproducer for " << *Cond << "\n");
+
+  ValueToValueMapTy Old2New;
+  SmallVector<Value *> Args;
+  SmallPtrSet<Value *, 8> Seen;
+  // Traverse Cond and its operands recursively until we reach a value that's in
+  // Value2Index or not an instruction, or not a operation that
+  // ConstraintElimination can decompose. Such values will be considered as
+  // external inputs to the reproducer, they are collected and added as function
+  // arguments later.
+  auto CollectArguments = [&](CmpInst *Cond) {
+    if (!Cond)
+      return;
+    auto &Value2Index =
+        Info.getValue2Index(CmpInst::isSigned(Cond->getPredicate()));
+    SmallVector<Value *, 4> WorkList;
+    WorkList.push_back(Cond);
+    while (!WorkList.empty()) {
+      Value *V = WorkList.pop_back_val();
+      if (!Seen.insert(V).second)
+        continue;
+      if (Old2New.find(V) != Old2New.end())
+        continue;
+      if (isa<Constant>(V))
+        continue;
+
+      auto *I = dyn_cast<Instruction>(V);
+      if (Value2Index.find(V) != Value2Index.end() || !I ||
+          !isa<CmpInst, BinaryOperator, GetElementPtrInst, CastInst>(V)) {
+        Old2New[V] = V;
+        Args.push_back(V);
+        LLVM_DEBUG(dbgs() << "  found external input " << *V << "\n");
+      } else {
+        append_range(WorkList, I->operands());
+      }
+    }
+  };
+
+  for (auto &Entry : Stack)
+    CollectArguments(Entry.Cond);
+  CollectArguments(Cond);
+
+  SmallVector<Type *> ParamTys;
+  for (auto *P : Args)
+    ParamTys.push_back(P->getType());
+
+  FunctionType *FTy = FunctionType::get(Cond->getType(), ParamTys,
+                                        /*isVarArg=*/false);
+  Function *F = Function::Create(FTy, Function::ExternalLinkage,
+                                 Cond->getModule()->getName() +
+                                     Cond->getFunction()->getName() + "repro",
+                                 M);
+  // Add arguments to the reproducer function for each external value collected.
+  for (unsigned I = 0; I < Args.size(); ++I) {
+    F->getArg(I)->setName(Args[I]->getName());
+    Old2New[Args[I]] = F->getArg(I);
+  }
+
+  BasicBlock *Entry = BasicBlock::Create(Ctx, "entry", F);
+  IRBuilder<> Builder(Entry);
+  Builder.CreateRet(Builder.getTrue());
+  Builder.SetInsertPoint(Entry->getTerminator());
+
+  // Clone instructions in \p Ops and their operands recursively until reaching
+  // an value in Value2Index (external input to the reproducer). Update Old2New
+  // mapping for the original and cloned instructions. Sort instructions to
+  // clone by dominance, then insert the cloned instructions in the function.
+  auto CloneInstructions = [&](ArrayRef<Value *> Ops, bool IsSigned) {
+    SmallVector<Value *, 4> WorkList(Ops);
+    SmallVector<Instruction *> ToClone;
+    auto &Value2Index = Info.getValue2Index(IsSigned);
+    while (!WorkList.empty()) {
+      Value *V = WorkList.pop_back_val();
+      if (Old2New.find(V) != Old2New.end())
+        continue;
+
+      auto *I = dyn_cast<Instruction>(V);
+      if (Value2Index.find(V) == Value2Index.end() && I) {
+        Old2New[V] = nullptr;
+        ToClone.push_back(I);
+        append_range(WorkList, I->operands());
+      }
+    }
+
+    sort(ToClone,
+         [&DT](Instruction *A, Instruction *B) { return DT.dominates(A, B); });
+    for (Instruction *I : ToClone) {
+      Instruction *Cloned = I->clone();
+      Old2New[I] = Cloned;
+      Old2New[I]->setName(I->getName());
+      Cloned->insertBefore(&*Builder.GetInsertPoint());
+      Cloned->dropUnknownNonDebugMetadata();
+      Cloned->setDebugLoc({});
+    }
+  };
+
+  // Materialize the assumptions for the reproducer using the entries in Stack.
+  // That is, first clone the operands of the condition recursively until we
+  // reach an external input to the reproducer and add them to the reproducer
+  // function. Then add an ICmp for the condition (with the inverse predicate if
+  // the entry is negated) and an assert using the ICmp.
+  for (auto &Entry : Stack) {
+    if (!Entry.Cond)
+      continue;
+
+    LLVM_DEBUG(dbgs() << "  Materializing assumption " << *Entry.Cond << "\n");
+    CmpInst::Predicate Pred = Entry.Cond->getPredicate();
+    if (Entry.IsNot)
+      Pred = CmpInst::getInversePredicate(Pred);
+
+    CloneInstructions({Entry.Cond->getOperand(0), Entry.Cond->getOperand(1)},
+                      CmpInst::isSigned(Entry.Cond->getPredicate()));
+
+    auto *Cmp = Builder.CreateICmp(Pred, Entry.Cond->getOperand(0),
+                                   Entry.Cond->getOperand(1));
+    Builder.CreateAssumption(Cmp);
+  }
+
+  // Finally, clone the condition to reproduce and remap instruction operands in
+  // the reproducer using Old2New.
+  CloneInstructions(Cond, CmpInst::isSigned(Cond->getPredicate()));
+  Entry->getTerminator()->setOperand(0, Cond);
+  remapInstructionsInBlocks({Entry}, Old2New);
+
+  assert(!verifyFunction(*F, &dbgs()));
+}
+
+static bool checkAndReplaceCondition(
+    CmpInst *Cmp, ConstraintInfo &Info, Module *ReproducerModule,
+    ArrayRef<ReproducerEntry> ReproducerCondStack, DominatorTree &DT) {
   LLVM_DEBUG(dbgs() << "Checking " << *Cmp << "\n");
 
   CmpInst::Predicate Pred = Cmp->getPredicate();
@@ -780,6 +943,7 @@ static bool checkAndReplaceCondition(CmpInst *Cmp, ConstraintInfo &Info) {
       dbgs() << "Condition " << *Cmp << " implied by dominating constraints\n";
       dumpWithNames(CSToUse, Info.getValue2Index(R.IsSigned));
     });
+    generateReproducer(Cmp, ReproducerModule, ReproducerCondStack, Info, DT);
     Constant *TrueC =
         ConstantInt::getTrue(CmpInst::makeCmpResultType(Cmp->getType()));
     Cmp->replaceUsesWithIf(TrueC, [](Use &U) {
@@ -799,6 +963,7 @@ static bool checkAndReplaceCondition(CmpInst *Cmp, ConstraintInfo &Info) {
       dbgs() << "Condition !" << *Cmp << " implied by dominating constraints\n";
       dumpWithNames(CSToUse, Info.getValue2Index(R.IsSigned));
     });
+    generateReproducer(Cmp, ReproducerModule, ReproducerCondStack, Info, DT);
     Constant *FalseC =
         ConstantInt::getFalse(CmpInst::makeCmpResultType(Cmp->getType()));
     Cmp->replaceAllUsesWith(FalseC);
@@ -919,12 +1084,15 @@ tryToSimplifyOverflowMath(IntrinsicInst *II, ConstraintInfo &Info,
   return Changed;
 }
 
-static bool eliminateConstraints(Function &F, DominatorTree &DT) {
+static bool eliminateConstraints(Function &F, DominatorTree &DT,
+                                 OptimizationRemarkEmitter &ORE) {
   bool Changed = false;
   DT.updateDFSNumbers();
 
   ConstraintInfo Info(F.getParent()->getDataLayout());
   State S(DT);
+  std::unique_ptr<Module> ReproducerModule(
+      DumpReproducers ? new Module(F.getName(), F.getContext()) : nullptr);
 
   // First, collect conditions implied by branches and blocks with their
   // Dominator DFS in and out numbers.
@@ -968,6 +1136,7 @@ static bool eliminateConstraints(Function &F, DominatorTree &DT) {
 
   // Finally, process ordered worklist and eliminate implied conditions.
   SmallVector<StackEntry, 16> DFSInStack;
+  SmallVector<ReproducerEntry> ReproducerCondStack;
   for (FactOrCheck &CB : S.WorkList) {
     // First, pop entries from the stack that are out-of-scope for CB. Remove
     // the corresponding entry from the constraint system.
@@ -993,6 +1162,8 @@ static bool eliminateConstraints(Function &F, DominatorTree &DT) {
         Mapping.erase(V);
       Info.popLastNVariables(E.IsSigned, E.ValuesToRelease.size());
       DFSInStack.pop_back();
+      if (ReproducerModule)
+        ReproducerCondStack.pop_back();
     }
 
     LLVM_DEBUG({
@@ -1010,7 +1181,8 @@ static bool eliminateConstraints(Function &F, DominatorTree &DT) {
       if (auto *II = dyn_cast<WithOverflowInst>(CB.Inst)) {
         Changed |= tryToSimplifyOverflowMath(II, Info, ToRemove);
       } else if (auto *Cmp = dyn_cast<ICmpInst>(CB.Inst)) {
-        Changed |= checkAndReplaceCondition(Cmp, Info);
+        Changed |= checkAndReplaceCondition(Cmp, Info, ReproducerModule.get(),
+                                            ReproducerCondStack, S.DT);
       }
       continue;
     }
@@ -1032,10 +1204,32 @@ static bool eliminateConstraints(Function &F, DominatorTree &DT) {
         Pred = CmpInst::getInversePredicate(Pred);
 
       Info.addFact(Pred, A, B, CB.NumIn, CB.NumOut, DFSInStack);
+      if (ReproducerModule && DFSInStack.size() > ReproducerCondStack.size())
+        ReproducerCondStack.emplace_back(cast<CmpInst>(Cmp), CB.Not);
+
       Info.transferToOtherSystem(Pred, A, B, CB.NumIn, CB.NumOut, DFSInStack);
+      if (ReproducerModule && DFSInStack.size() > ReproducerCondStack.size()) {
+        // Add dummy entries to ReproducerCondStack to keep it in sync with
+        // DFSInStack.
+        for (unsigned I = 0,
+                      E = (DFSInStack.size() - ReproducerCondStack.size());
+             I < E; ++I) {
+          ReproducerCondStack.emplace_back(nullptr, false);
+        }
+      }
     }
   }
 
+  if (ReproducerModule && !ReproducerModule->functions().empty()) {
+    std::string S;
+    raw_string_ostream StringS(S);
+    ReproducerModule->print(StringS, nullptr);
+    StringS.flush();
+    OptimizationRemark Rem(DEBUG_TYPE, "Reproducer", &F);
+    Rem << ore::NV("module") << S;
+    ORE.emit(Rem);
+  }
+
 #ifndef NDEBUG
   unsigned SignedEntries =
       count_if(DFSInStack, [](const StackEntry &E) { return E.IsSigned; });
@@ -1053,7 +1247,8 @@ static bool eliminateConstraints(Function &F, DominatorTree &DT) {
 PreservedAnalyses ConstraintEliminationPass::run(Function &F,
                                                  FunctionAnalysisManager &AM) {
   auto &DT = AM.getResult<DominatorTreeAnalysis>(F);
-  if (!eliminateConstraints(F, DT))
+  auto &ORE = AM.getResult<OptimizationRemarkEmitterAnalysis>(F);
+  if (!eliminateConstraints(F, DT, ORE))
     return PreservedAnalyses::all();
 
   PreservedAnalyses PA;

diff  --git a/llvm/test/Transforms/ConstraintElimination/analysis-invalidation.ll b/llvm/test/Transforms/ConstraintElimination/analysis-invalidation.ll
index 66f3e19b036a1..7eed403f5b08a 100644
--- a/llvm/test/Transforms/ConstraintElimination/analysis-invalidation.ll
+++ b/llvm/test/Transforms/ConstraintElimination/analysis-invalidation.ll
@@ -11,6 +11,7 @@
 ; CHECK-NEXT: Running analysis: TargetIRAnalysis on ssub_no_overflow_due_to_or_conds
 ; CHECK-NEXT: Running analysis: DominatorTreeAnalysis on ssub_no_overflow_due_to_or_conds
 ; CHECK-NEXT: Running pass: ConstraintEliminationPass on ssub_no_overflow_due_to_or_conds
+; CHECK-NEXT: Running analysis: OptimizationRemarkEmitterAnalysis on ssub_no_overflow_due_to_or_conds
 ; CHECK-NEXT: Invalidating analysis: DemandedBitsAnalysis on ssub_no_overflow_due_to_or_conds
 ; CHECK-NEXT: Running pass: RequireAnalysisPass
 ; CHECK-NEXT: Running analysis: DemandedBitsAnalysis on ssub_no_overflow_due_to_or_conds
@@ -21,6 +22,7 @@
 ; CHECK-NEXT: Running analysis: TargetIRAnalysis on uge_zext
 ; CHECK-NEXT: Running analysis: DominatorTreeAnalysis on uge_zext
 ; CHECK-NEXT: Running pass: ConstraintEliminationPass on uge_zext
+; CHECK-NEXT: Running analysis: OptimizationRemarkEmitterAnalysis on uge_zext
 ; CHECK-NEXT: Invalidating analysis: DemandedBitsAnalysis on uge_zext
 ; CHECK-NEXT: Running pass: RequireAnalysisPass
 ; CHECK-NEXT: Running analysis: DemandedBitsAnalysis on uge_zext

diff  --git a/llvm/test/Transforms/ConstraintElimination/reproducer-remarks-debug.ll b/llvm/test/Transforms/ConstraintElimination/reproducer-remarks-debug.ll
new file mode 100644
index 0000000000000..a4cf0dcca335a
--- /dev/null
+++ b/llvm/test/Transforms/ConstraintElimination/reproducer-remarks-debug.ll
@@ -0,0 +1,33 @@
+; RUN: opt -passes=constraint-elimination -constraint-elimination-dump-reproducers -pass-remarks=constraint-elimination -debug %s 2>&1 | FileCheck %s
+
+; REQUIRES: asserts
+
+target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
+
+; CHECK:      Condition   %c.2 = icmp eq ptr %a, null implied by dominating constraints
+; CHECK-NEXT: %a + -1 * % <= 0
+; CHECK-NEXT: Creating reproducer for   %c.2 = icmp eq ptr %a, null
+; CHECK-NEXT:   found external input ptr %a
+; CHECK-NEXT:   Materializing assumption   %c.1 = icmp eq ptr %a, null
+; CHECK-NEXT: ---
+
+define i1 @test_ptr_null_constant(ptr %a) {
+; CHECK-LABEL: define i1 @"{{.+}}test_ptr_null_constantrepro"(ptr %a) {
+; CHECK-NEXT: entry:
+; CHECK-NEXT:   %0 = icmp eq ptr %a, null
+; CHECK-NEXT:   call void @llvm.assume(i1 %0)
+; CHECK-NEXT:   %c.2 = icmp eq ptr %a, null
+; CHECK-NEXT:   ret i1 %c.2
+; CHECK-NEXT: }
+;
+entry:
+  %c.1 = icmp eq ptr %a, null
+  br i1 %c.1, label %then, label %else
+
+then:
+  %c.2 = icmp eq ptr %a, null
+  ret i1 %c.2
+
+else:
+  ret i1 false
+}

diff  --git a/llvm/test/Transforms/ConstraintElimination/reproducer-remarks.ll b/llvm/test/Transforms/ConstraintElimination/reproducer-remarks.ll
new file mode 100644
index 0000000000000..adcbeb6e1b1eb
--- /dev/null
+++ b/llvm/test/Transforms/ConstraintElimination/reproducer-remarks.ll
@@ -0,0 +1,322 @@
+; RUN: opt -passes=constraint-elimination -constraint-elimination-dump-reproducers -pass-remarks=constraint-elimination %s 2>&1 | FileCheck %s
+
+target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
+
+declare void @use(i1)
+
+declare void @llvm.assume(i1)
+
+define i1 @test_no_known_facts(ptr %dst) {
+; CHECK: remark: <unknown>:0:0: module; ModuleID = 'test_no_known_facts'
+; CHECK-LABEL: define i1 @"{{.+}}test_no_known_factsrepro"(ptr %dst)
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    %dst.0 = getelementptr inbounds ptr, ptr %dst, i64 0
+; CHECK-NEXT:    %upper = getelementptr inbounds ptr, ptr %dst, i64 2
+; CHECK-NEXT:    %c = icmp ult ptr %dst.0, %upper
+; CHECK-NEXT:    ret i1 %c
+; CHECK-NEXT:  }
+;
+entry:
+  %dst.0 = getelementptr inbounds ptr, ptr %dst, i64 0
+  %upper = getelementptr inbounds ptr, ptr %dst, i64 2
+  %c = icmp ult i32* %dst.0, %upper
+  ret i1 %c
+}
+
+define void @test_one_known_fact_true_branch(i8 %start, i8 %high) {
+; CHECK: remark: <unknown>:0:0: module; ModuleID = 'test_one_known_fact_true_branch'
+
+; CHECK-LABEL: define i1 @"{{.*}}test_one_known_fact_true_branchrepro"(i8 %high, i8 %start) {
+; CHECK-NEXT: entry:
+; CHECK-NEXT:   %add.ptr.i = add nuw i8 %start, 3
+; CHECK-NEXT:   %0 = icmp ult i8 %add.ptr.i, %high
+; CHECK-NEXT:   call void @llvm.assume(i1 %0)
+; CHECK-NEXT:   %t.0 = icmp ult i8 %start, %high
+; CHECK-NEXT:   ret i1 %t.0
+; CHECK-NEXT: }
+;
+entry:
+  %add.ptr.i = add nuw i8 %start, 3
+  %c.1 = icmp ult i8 %add.ptr.i, %high
+  br i1 %c.1, label %if.then, label %if.end
+
+if.then:
+  %t.0 = icmp ult i8 %start, %high
+  call void @use(i1 %t.0)
+  ret void
+
+if.end:
+  ret void
+}
+
+define void @test_one_known_fact_false_branch(i8 %start, i8 %high) {
+; CHECK: remark: <unknown>:0:0: module; ModuleID = 'test_one_known_fact_false_branch'
+;
+; CHECK-LABEL:define i1 @"{{.*}}test_one_known_fact_false_branchrepro"(i8 %high, i8 %start) {
+; CHECK-NEXT: entry:
+; CHECK-NEXT:   %add.ptr.i = add nuw i8 %start, 3
+; CHECK-NEXT:   %0 = icmp ult i8 %add.ptr.i, %high
+; CHECK-NEXT:   call void @llvm.assume(i1 %0)
+; CHECK-NEXT:   %t.0 = icmp ult i8 %start, %high
+; CHECK-NEXT:   ret i1 %t.0
+; CHECK-NEXT: }
+;
+entry:
+  %add.ptr.i = add nuw i8 %start, 3
+  %c.1 = icmp uge i8 %add.ptr.i, %high
+  br i1 %c.1, label %if.then, label %if.end
+
+if.then:
+  ret void
+
+if.end:
+  %t.0 = icmp ult i8 %start, %high
+  call void @use(i1 %t.0)
+  ret void
+}
+
+define void @test_multiple_known_facts_branches_1(i8 %a, i8 %b) {
+; CHECK: remark: <unknown>:0:0: module; ModuleID = 'test_multiple_known_facts_branches_1'
+
+; CHECK-LABEL: define i1 @"{{.*}}test_multiple_known_facts_branches_1repro"(i8 %a, i8 %b) {
+; CHECK-NEXT: entry:
+; CHECK-NEXT:   %0 = icmp ugt i8 %a, 10
+; CHECK-NEXT:   call void @llvm.assume(i1 %0)
+; CHECK-NEXT:   %1 = icmp ugt i8 %b, 10
+; CHECK-NEXT:   call void @llvm.assume(i1 %1)
+; CHECK-NEXT:   %add = add nuw i8 %a, %b
+; CHECK-NEXT:   %t.0 = icmp ugt i8 %add, 20
+; CHECK-NEXT:   ret i1 %t.0
+; CHECK-NEXT: }
+;
+entry:
+  %c.1 = icmp ugt i8 %a, 10
+  br i1 %c.1, label %then.1, label %else.1
+
+then.1:
+  %c.2 = icmp ugt i8 %b, 10
+  br i1 %c.2, label %then.2, label %else.1
+
+then.2:
+  %add = add nuw i8 %a, %b
+  %t.0 = icmp ugt i8 %add, 20
+  call void @use(i1 %t.0)
+  ret void
+
+else.1:
+  ret void
+}
+
+define void @test_multiple_known_facts_branches_2(i8 %a, i8 %b) {
+; CHECK: remark: <unknown>:0:0: module; ModuleID = 'test_multiple_known_facts_branches_2'
+;
+; CHECK-LABEL: define i1 @"{{.*}}test_multiple_known_facts_branches_2repro"(i8 %a, i8 %b) {
+; CHECK-NEXT: entry:
+; CHECK-NEXT:   %0 = icmp ugt i8 %a, 10
+; CHECK-NEXT:   call void @llvm.assume(i1 %0)
+; CHECK-NEXT:   %1 = icmp ugt i8 %b, 10
+; CHECK-NEXT:   call void @llvm.assume(i1 %1)
+; CHECK-NEXT:   %add = add nuw i8 %a, %b
+; CHECK-NEXT:   %t.0 = icmp ugt i8 %add, 20
+; CHECK-NEXT:   ret i1 %t.0
+; CHECK-NEXT: }
+;
+entry:
+  %c.1 = icmp ugt i8 %a, 10
+  br i1 %c.1, label %then.1, label %exit
+
+then.1:
+  %c.2 = icmp ule i8 %b, 10
+  br i1 %c.2, label %exit, label %else.2
+
+else.2:
+  %add = add nuw i8 %a, %b
+  %t.0 = icmp ugt i8 %add, 20
+  call void @use(i1 %t.0)
+  ret void
+
+exit:
+  ret void
+}
+
+define void @test_assumes(i8 %a, i8 %b) {
+; CHECK-LABEL: define i1 @"{{.*}}test_assumesrepro.2"(i8 %a, i8 %b) {
+; CHECK-NEXT: entry:
+; CHECK-NEXT:   %0 = icmp ugt i8 %a, 10
+; CHECK-NEXT:   call void @llvm.assume(i1 %0)
+; CHECK-NEXT:   %1 = icmp ugt i8 %b, 10
+; CHECK-NEXT:   call void @llvm.assume(i1 %1)
+; CHECK-NEXT:   %add = add nuw i8 %a, %b
+; CHECK-NEXT:   %t.0 = icmp ult i8 %add, 20
+; CHECK-NEXT:   ret i1 %t.0
+; CHECK-NEXT: }
+;
+entry:
+  %c.1 = icmp ugt i8 %a, 10
+  call void @llvm.assume(i1 %c.1)
+  %c.2 = icmp ugt i8 %b, 10
+  call void @llvm.assume(i1 %c.2)
+  %add = add nuw i8 %a, %b
+  %t.0 = icmp ult i8 %add, 20
+  call void @use(i1 %t.0)
+  ret void
+}
+
+declare void @noundef(ptr noundef)
+
+; Currently this fails decomposition. No reproducer should be generated.
+define i1 @test_inbounds_precondition(ptr %src, i32 %n, i32 %idx) {
+; CHECK-NOT: test_inbounds_precondition
+entry:
+  %upper = getelementptr inbounds i32, ptr %src, i64 5
+  %src.idx.4 = getelementptr i32, ptr %src, i64 4
+  %cmp.upper.4 = icmp ule ptr %src.idx.4, %upper
+  br i1 %cmp.upper.4, label %then, label %else
+
+then:
+  ret i1 true
+
+else:
+  ret i1 false
+}
+
+define i32 @test_branch(i32 %a) {
+; CHECK-LABEL: define i1 @"{{.+}}test_branchrepro"(i32 %a) {
+; CHECK-NEXT: entry:
+; CHECK-NEXT:   %0 = icmp ult i32 %a, 0
+; CHECK-NEXT:   call void @llvm.assume(i1 %0)
+; CHECK-NEXT:   %c.2 = icmp ugt i32 0, 0
+; CHECK-NEXT:   ret i1 %c.2
+; CHECK-NEXT: }
+;
+entry:
+  %c.1 = icmp ult i32 %a, 0
+  br i1 %c.1, label %then, label %exit
+
+then:
+  %c.2 = icmp ugt i32 0, 0
+  br label %exit
+
+exit:
+  ret i32 0
+}
+
+define i32 @test_invoke(i32 %a) personality ptr null {
+; CHECK-LABEL: define i1 @"{{.+}}test_invokerepro"(i32 %l, i32 %a) {
+; CHECK-NEXT: entry:
+; CHECK-NEXT:  %0 = icmp slt i32 %a, %l
+; CHECK-NEXT:  call void @llvm.assume(i1 %0)
+; CHECK-NEXT:  %c.2 = icmp eq i32 0, 0
+; CHECK-NEXT:  ret i1 %c.2
+; CHECK-NEXT:}
+;
+entry:
+  %call = invoke ptr null(i64 0)
+          to label %cont unwind label %lpad
+
+cont:
+  %l = load i32, ptr %call, align 4
+  %c.1 = icmp slt i32 %a, %l
+  br i1 %c.1, label %then, label %exit
+
+lpad:
+  %lp = landingpad { ptr, i32 }
+          catch ptr null
+          catch ptr null
+  ret i32 0
+
+then:
+  %c.2 = icmp eq i32 0, 0
+  br label %exit
+
+exit:
+  ret i32 0
+}
+
+define <2 x i1> @vector_cmp(<2 x ptr> %vec) {
+; CHECK-LABEL: define <2 x i1> @"{{.+}}vector_cmprepro"(<2 x ptr> %vec) {
+; CHECK-NEXT: entry:
+; CHECK-NEXT:   %gep.1 = getelementptr inbounds i32, <2 x ptr> %vec, i64 1
+; CHECK-NEXT:   %t.1 = icmp ult <2 x ptr> %vec, %gep.1
+; CHECK-NEXT:   ret <2 x i1> %t.1
+; CHECK-NEXT: }
+;
+  %gep.1 = getelementptr inbounds i32, <2 x ptr> %vec, i64 1
+  %t.1 = icmp ult <2 x ptr> %vec, %gep.1
+  ret <2 x i1> %t.1
+}
+
+define i1 @shared_operand() {
+; CHECK-LABEL: define i1 @"{{.+}}shared_operandrepro"() {
+; CHECK-NEXT: entry:
+; CHECK-NEXT:   %sub = sub i8 0, 0
+; CHECK-NEXT:   %sub.2 = sub nuw i8 %sub, 0
+; CHECK-NEXT:   %c.5 = icmp ult i8 %sub.2, %sub
+; CHECK-NEXT:   ret i1 %c.5
+; CHECK-NEXT: }
+;
+entry:
+  %sub = sub i8 0, 0
+  %sub.2 = sub nuw i8 %sub, 0
+  %c.5 = icmp ult i8 %sub.2, %sub
+  ret i1 %c.5
+}
+
+ at glob = external global i32
+
+define i1 @load_global() {
+; CHECK-LABEL: define i1 @"{{.*}}load_globalrepro"(i32 %l) {
+; CHECK-NEXT: entry:
+; CHECK-NEXT:  %c = icmp ugt i32 %l, %l
+; CHECK-NEXT:  ret i1 %c
+; CHECK-NEXT:}
+;
+entry:
+  %l = load i32, ptr @glob, align 8
+  %c = icmp ugt i32 %l, %l
+  ret i1 %c
+}
+
+define i1 @test_ptr_null_constant(ptr %a) {
+; CHECK-LABEL: define i1 @"{{.+}}test_ptr_null_constantrepro"(ptr %a) {
+; CHECK-NEXT: entry:
+; CHECK-NEXT:   %0 = icmp eq ptr %a, null
+; CHECK-NEXT:   call void @llvm.assume(i1 %0)
+; CHECK-NEXT:   %c.2 = icmp eq ptr %a, null
+; CHECK-NEXT:   ret i1 %c.2
+; CHECK-NEXT: }
+;
+entry:
+  %c.1 = icmp eq ptr %a, null
+  br i1 %c.1, label %then, label %else
+
+then:
+  %c.2 = icmp eq ptr %a, null
+  ret i1 %c.2
+
+else:
+  ret i1 false
+}
+
+define i1 @test_both_signed_and_unsigned_conds_needed_in_reproducer(ptr %src, ptr %lower, ptr %upper, i16 %N) {
+; CHECK-LABEL: define i1 @"{{.+}}test_both_signed_and_unsigned_conds_needed_in_reproducerrepro"(i16 %N, ptr %src) {
+; CHECK-NEXT: entry:
+; CHECK-NEXT:   %0 = icmp sge i16 %N, 0
+; CHECK-NEXT:   call void @llvm.assume(i1 %0)
+; CHECK-NEXT:   %src.end = getelementptr inbounds i8, ptr %src, i16 %N
+; CHECK-NEXT:   %cmp.src.start = icmp ule ptr %src, %src.end
+; CHECK-NEXT:   ret i1 %cmp.src.start
+; CHECK-NEXT: }
+;
+entry:
+  %N.pos = icmp sge i16 %N, 0
+  br i1 %N.pos, label %then, label %else
+
+then:
+  %src.end = getelementptr inbounds i8, ptr %src, i16 %N
+  %cmp.src.start = icmp ule ptr %src, %src.end
+  ret i1 %cmp.src.start
+
+else:
+  ret i1 false
+}

diff  --git a/llvm/tools/opt-viewer/extract-reproducers.py b/llvm/tools/opt-viewer/extract-reproducers.py
new file mode 100644
index 0000000000000..1fa3fb9360e15
--- /dev/null
+++ b/llvm/tools/opt-viewer/extract-reproducers.py
@@ -0,0 +1,39 @@
+#!/usr/bin/env python
+
+desc = '''
+A script to extract ConstraintElimination's reproducer remarks. The extracted
+modules are written as textual LLVM IR to files named reproducerXXXX.ll in the
+current directory.
+'''
+
+import optrecord
+import argparse
+
+if __name__ == '__main__':
+    parser = argparse.ArgumentParser(description=desc)
+    parser.add_argument(
+        'yaml_dirs_or_files',
+        nargs='+',
+        help='List of optimization record files or directories searched '
+             'for optimization record files.')
+
+    args = parser.parse_args()
+
+    print_progress = False
+    jobs = 1
+
+    files = optrecord.find_opt_files(*args.yaml_dirs_or_files)
+    if not files:
+        parser.error("No *.opt.yaml files found")
+        sys.exit(1)
+
+    all_remarks, file_remarks, _ = optrecord.gather_results(
+        files, jobs, True)
+
+    i = 0
+    for r in all_remarks:
+        if r[1] != 'constraint-elimination' or r[2] != 'Reproducer':
+            continue
+        with open('reproducer{}.ll'.format(i), 'wt') as f:
+            f.write(r[7][1][0][1])
+        i += 1


        


More information about the llvm-commits mailing list