[llvm] [ConstraintElim] Add facts about non-poison intrinsics on demand (PR #136558)

Yingwei Zheng via llvm-commits llvm-commits at lists.llvm.org
Mon Apr 21 05:29:36 PDT 2025


https://github.com/dtcxzyw updated https://github.com/llvm/llvm-project/pull/136558

>From 0e92ae9f25c6d0c251f617fea722269052b420b1 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Mon, 21 Apr 2025 18:15:19 +0800
Subject: [PATCH 1/2] [ConstraintElim] Add facts about non-poison intrinsics on
 demand

---
 .../Scalar/ConstraintElimination.cpp          | 123 ++++++++++--------
 .../Transforms/ConstraintElimination/abs.ll   |  15 ++-
 .../ConstraintElimination/minmax.ll           |   7 +-
 .../umin-result-may-be-poison.ll              |   3 +-
 4 files changed, 87 insertions(+), 61 deletions(-)

diff --git a/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp b/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
index 58d705eb6aa96..3c6b528a0fd37 100644
--- a/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
@@ -1120,14 +1120,10 @@ void State::addInfoFor(BasicBlock &BB) {
       }
       break;
     }
-    // Enqueue ssub_with_overflow for simplification.
+    // Enqueue intrinsic for simplification.
     case Intrinsic::ssub_with_overflow:
     case Intrinsic::ucmp:
     case Intrinsic::scmp:
-      WorkList.push_back(
-          FactOrCheck::getCheck(DT.getNode(&BB), cast<CallInst>(&I)));
-      break;
-    // Enqueue the intrinsics to add extra info.
     case Intrinsic::umin:
     case Intrinsic::umax:
     case Intrinsic::smin:
@@ -1135,13 +1131,6 @@ void State::addInfoFor(BasicBlock &BB) {
       // TODO: handle llvm.abs as well
       WorkList.push_back(
           FactOrCheck::getCheck(DT.getNode(&BB), cast<CallInst>(&I)));
-      // TODO: Check if it is possible to instead only added the min/max facts
-      // when simplifying uses of the min/max intrinsics.
-      if (!isGuaranteedNotToBePoison(&I))
-        break;
-      [[fallthrough]];
-    case Intrinsic::abs:
-      WorkList.push_back(FactOrCheck::getInstFact(DT.getNode(&BB), &I));
       break;
     }
 
@@ -1385,10 +1374,64 @@ static void generateReproducer(CmpInst *Cond, Module *M,
   assert(!verifyFunction(*F, &dbgs()));
 }
 
+static void addNonPoisonIntrinsicInstFact(
+    IntrinsicInst *II,
+    function_ref<void(CmpPredicate, Value *, Value *)> AddFact) {
+  Intrinsic::ID IID = II->getIntrinsicID();
+  switch (IID) {
+  case Intrinsic::umin:
+  case Intrinsic::umax:
+  case Intrinsic::smin:
+  case Intrinsic::smax: {
+    ICmpInst::Predicate Pred =
+        ICmpInst::getNonStrictPredicate(MinMaxIntrinsic::getPredicate(IID));
+    AddFact(Pred, II, II->getArgOperand(0));
+    AddFact(Pred, II, II->getArgOperand(1));
+    break;
+  }
+  case Intrinsic::abs: {
+    if (cast<ConstantInt>(II->getArgOperand(1))->isOne())
+      AddFact(CmpInst::ICMP_SGE, II, ConstantInt::get(II->getType(), 0));
+    AddFact(CmpInst::ICMP_SGE, II, II->getArgOperand(0));
+    break;
+  }
+  default:
+    break;
+  }
+}
+
+static void
+removeEntryFromStack(const StackEntry &E, ConstraintInfo &Info,
+                     SmallVectorImpl<StackEntry> &DFSInStack,
+                     SmallVectorImpl<ReproducerEntry> *ReproducerCondStack) {
+  Info.popLastConstraint(E.IsSigned);
+  // Remove variables in the system that went out of scope.
+  auto &Mapping = Info.getValue2Index(E.IsSigned);
+  for (Value *V : E.ValuesToRelease)
+    Mapping.erase(V);
+  Info.popLastNVariables(E.IsSigned, E.ValuesToRelease.size());
+  DFSInStack.pop_back();
+  if (ReproducerCondStack)
+    ReproducerCondStack->pop_back();
+}
+
 static std::optional<bool> checkCondition(CmpInst::Predicate Pred, Value *A,
                                           Value *B, Instruction *CheckInst,
                                           ConstraintInfo &Info) {
   LLVM_DEBUG(dbgs() << "Checking " << *CheckInst << "\n");
+  SmallVector<StackEntry, 8> DFSInStack;
+  auto StackRestorer = make_scope_exit([&]() {
+    while (!DFSInStack.empty())
+      removeEntryFromStack(DFSInStack.back(), Info, DFSInStack, nullptr);
+  });
+  auto AddFact = [&](CmpPredicate Pred, Value *A, Value *B) {
+    Info.addFact(Pred, A, B, 0, 0, DFSInStack);
+  };
+
+  if (auto *II = dyn_cast<IntrinsicInst>(A))
+    addNonPoisonIntrinsicInstFact(II, AddFact);
+  if (auto *II = dyn_cast<IntrinsicInst>(B))
+    addNonPoisonIntrinsicInstFact(II, AddFact);
 
   auto R = Info.getConstraintForSolving(Pred, A, B);
   if (R.empty() || !R.isValid(Info)){
@@ -1517,22 +1560,6 @@ static bool checkAndReplaceCmp(CmpIntrinsic *I, ConstraintInfo &Info,
   return false;
 }
 
-static void
-removeEntryFromStack(const StackEntry &E, ConstraintInfo &Info,
-                     Module *ReproducerModule,
-                     SmallVectorImpl<ReproducerEntry> &ReproducerCondStack,
-                     SmallVectorImpl<StackEntry> &DFSInStack) {
-  Info.popLastConstraint(E.IsSigned);
-  // Remove variables in the system that went out of scope.
-  auto &Mapping = Info.getValue2Index(E.IsSigned);
-  for (Value *V : E.ValuesToRelease)
-    Mapping.erase(V);
-  Info.popLastNVariables(E.IsSigned, E.ValuesToRelease.size());
-  DFSInStack.pop_back();
-  if (ReproducerModule)
-    ReproducerCondStack.pop_back();
-}
-
 /// Check if either the first condition of an AND or OR is implied by the
 /// (negated in case of OR) second condition or vice versa.
 static bool checkOrAndOpImpliedByOther(
@@ -1554,8 +1581,8 @@ static bool checkOrAndOpImpliedByOther(
     // Remove entries again.
     while (OldSize < DFSInStack.size()) {
       StackEntry E = DFSInStack.back();
-      removeEntryFromStack(E, Info, ReproducerModule, ReproducerCondStack,
-                           DFSInStack);
+      removeEntryFromStack(E, Info, DFSInStack,
+                           ReproducerModule ? &ReproducerCondStack : nullptr);
     }
   });
   bool IsOr = match(JoinOp, m_LogicalOr());
@@ -1571,6 +1598,14 @@ static bool checkOrAndOpImpliedByOther(
         Pred = CmpInst::getInversePredicate(Pred);
       // Optimistically add fact from the other compares in the AND/OR.
       Info.addFact(Pred, LHS, RHS, CB.NumIn, CB.NumOut, DFSInStack);
+      auto AddFact = [&](CmpPredicate Pred, Value *A, Value *B) {
+        Info.addFact(Pred, A, B, CB.NumIn, CB.NumOut, DFSInStack);
+      };
+
+      if (auto *II = dyn_cast<IntrinsicInst>(LHS))
+        addNonPoisonIntrinsicInstFact(II, AddFact);
+      if (auto *II = dyn_cast<IntrinsicInst>(RHS))
+        addNonPoisonIntrinsicInstFact(II, AddFact);
       continue;
     }
     if (IsOr ? match(Val, m_LogicalOr(m_Value(LHS), m_Value(RHS)))
@@ -1807,8 +1842,8 @@ static bool eliminateConstraints(Function &F, DominatorTree &DT, LoopInfo &LI,
                        Info.getValue2Index(E.IsSigned));
         dbgs() << "\n";
       });
-      removeEntryFromStack(E, Info, ReproducerModule.get(), ReproducerCondStack,
-                           DFSInStack);
+      removeEntryFromStack(E, Info, DFSInStack,
+                           ReproducerModule ? &ReproducerCondStack : nullptr);
     }
 
     // For a block, check if any CmpInsts become known based on the current set
@@ -1879,25 +1914,6 @@ static bool eliminateConstraints(Function &F, DominatorTree &DT, LoopInfo &LI,
     };
 
     CmpPredicate Pred;
-    if (!CB.isConditionFact()) {
-      Value *X;
-      if (match(CB.Inst, m_Intrinsic<Intrinsic::abs>(m_Value(X)))) {
-        // If is_int_min_poison is true then we may assume llvm.abs >= 0.
-        if (cast<ConstantInt>(CB.Inst->getOperand(1))->isOne())
-          AddFact(CmpInst::ICMP_SGE, CB.Inst,
-                  ConstantInt::get(CB.Inst->getType(), 0));
-        AddFact(CmpInst::ICMP_SGE, CB.Inst, X);
-        continue;
-      }
-
-      if (auto *MinMax = dyn_cast<MinMaxIntrinsic>(CB.Inst)) {
-        Pred = ICmpInst::getNonStrictPredicate(MinMax->getPredicate());
-        AddFact(Pred, MinMax, MinMax->getLHS());
-        AddFact(Pred, MinMax, MinMax->getRHS());
-        continue;
-      }
-    }
-
     Value *A = nullptr, *B = nullptr;
     if (CB.isConditionFact()) {
       Pred = CB.Cond.Pred;
@@ -1922,6 +1938,11 @@ static bool eliminateConstraints(Function &F, DominatorTree &DT, LoopInfo &LI,
       assert(Matched && "Must have an assume intrinsic with a icmp operand");
     }
     AddFact(Pred, A, B);
+    // Now both A and B is guaranteed not to be poison.
+    if (auto *II = dyn_cast<IntrinsicInst>(A))
+      addNonPoisonIntrinsicInstFact(II, AddFact);
+    if (auto *II = dyn_cast<IntrinsicInst>(B))
+      addNonPoisonIntrinsicInstFact(II, AddFact);
   }
 
   if (ReproducerModule && !ReproducerModule->functions().empty()) {
diff --git a/llvm/test/Transforms/ConstraintElimination/abs.ll b/llvm/test/Transforms/ConstraintElimination/abs.ll
index 9fc68b0e72663..d6c159f270360 100644
--- a/llvm/test/Transforms/ConstraintElimination/abs.ll
+++ b/llvm/test/Transforms/ConstraintElimination/abs.ll
@@ -28,7 +28,8 @@ define i1 @abs_plus_one(i32 %arg) {
 ; CHECK-SAME: i32 [[ARG:%.*]]) {
 ; CHECK-NEXT:    [[ABS:%.*]] = tail call i32 @llvm.abs.i32(i32 [[ARG]], i1 true)
 ; CHECK-NEXT:    [[ABS_PLUS_ONE:%.*]] = add nsw i32 [[ABS]], 1
-; CHECK-NEXT:    ret i1 true
+; CHECK-NEXT:    [[CMP:%.*]] = icmp sge i32 [[ABS_PLUS_ONE]], [[ARG]]
+; CHECK-NEXT:    ret i1 [[CMP]]
 ;
   %abs = tail call i32 @llvm.abs.i32(i32 %arg, i1 true)
   %abs_plus_one = add nsw i32 %abs, 1
@@ -69,7 +70,8 @@ define i1 @abs_plus_one_unsigned_greater_or_equal_nonnegative_arg(i32 %arg) {
 ; CHECK-NEXT:    call void @llvm.assume(i1 [[CMP_ARG_NONNEGATIVE]])
 ; CHECK-NEXT:    [[ABS:%.*]] = tail call i32 @llvm.abs.i32(i32 [[ARG]], i1 true)
 ; CHECK-NEXT:    [[ABS_PLUS_ONE:%.*]] = add nuw i32 [[ABS]], 1
-; CHECK-NEXT:    ret i1 true
+; CHECK-NEXT:    [[CMP:%.*]] = icmp uge i32 [[ABS_PLUS_ONE]], [[ARG]]
+; CHECK-NEXT:    ret i1 [[CMP]]
 ;
   %cmp_arg_nonnegative = icmp sge i32 %arg, 0
   call void @llvm.assume(i1 %cmp_arg_nonnegative)
@@ -107,7 +109,8 @@ define i1 @abs_constant_negative_arg() {
 define i1 @abs_constant_positive_arg() {
 ; CHECK-LABEL: define i1 @abs_constant_positive_arg() {
 ; CHECK-NEXT:    [[ABS:%.*]] = tail call i32 @llvm.abs.i32(i32 3, i1 false)
-; CHECK-NEXT:    ret i1 true
+; CHECK-NEXT:    [[CMP:%.*]] = icmp sge i32 [[ABS]], 3
+; CHECK-NEXT:    ret i1 [[CMP]]
 ;
   %abs = tail call i32 @llvm.abs.i32(i32 3, i1 false)
   %cmp = icmp sge i32 %abs, 3
@@ -142,7 +145,8 @@ define i1 @abs_is_nonnegative_int_min_is_poison(i32 %arg) {
 ; CHECK-LABEL: define i1 @abs_is_nonnegative_int_min_is_poison(
 ; CHECK-SAME: i32 [[ARG:%.*]]) {
 ; CHECK-NEXT:    [[ABS:%.*]] = tail call i32 @llvm.abs.i32(i32 [[ARG]], i1 true)
-; CHECK-NEXT:    ret i1 true
+; CHECK-NEXT:    [[CMP:%.*]] = icmp sge i32 [[ABS]], 0
+; CHECK-NEXT:    ret i1 [[CMP]]
 ;
   %abs = tail call i32 @llvm.abs.i32(i32 %arg, i1 true)
   %cmp = icmp sge i32 %abs, 0
@@ -152,7 +156,8 @@ define i1 @abs_is_nonnegative_int_min_is_poison(i32 %arg) {
 define i1 @abs_is_nonnegative_constant_arg() {
 ; CHECK-LABEL: define i1 @abs_is_nonnegative_constant_arg() {
 ; CHECK-NEXT:    [[ABS:%.*]] = tail call i32 @llvm.abs.i32(i32 -3, i1 true)
-; CHECK-NEXT:    ret i1 true
+; CHECK-NEXT:    [[CMP:%.*]] = icmp sge i32 [[ABS]], 0
+; CHECK-NEXT:    ret i1 [[CMP]]
 ;
   %abs = tail call i32 @llvm.abs.i32(i32 -3, i1 true)
   %cmp = icmp sge i32 %abs, 0
diff --git a/llvm/test/Transforms/ConstraintElimination/minmax.ll b/llvm/test/Transforms/ConstraintElimination/minmax.ll
index 029b6508a2106..e0ac453c10ab3 100644
--- a/llvm/test/Transforms/ConstraintElimination/minmax.ll
+++ b/llvm/test/Transforms/ConstraintElimination/minmax.ll
@@ -222,7 +222,8 @@ define i1 @umax_uge_ugt_with_add_nuw(i32 %x, i32 %y) {
 ; CHECK-NEXT:    [[CMP:%.*]] = icmp uge i32 [[Y]], [[SUM]]
 ; CHECK-NEXT:    br i1 [[CMP]], label [[IF:%.*]], label [[END:%.*]]
 ; CHECK:       if:
-; CHECK-NEXT:    ret i1 true
+; CHECK-NEXT:    [[CMP2:%.*]] = icmp ugt i32 [[Y]], [[X]]
+; CHECK-NEXT:    ret i1 [[CMP2]]
 ; CHECK:       end:
 ; CHECK-NEXT:    ret i1 false
 ;
@@ -306,9 +307,7 @@ define i1 @smin_branchless(i32 %x, i32 %y) {
 ; CHECK-SAME: (i32 [[X:%.*]], i32 [[Y:%.*]]) {
 ; CHECK-NEXT:  entry:
 ; CHECK-NEXT:    [[MIN:%.*]] = call i32 @llvm.smin.i32(i32 [[X]], i32 [[Y]])
-; CHECK-NEXT:    [[CMP1:%.*]] = icmp sle i32 [[MIN]], [[X]]
-; CHECK-NEXT:    [[CMP2:%.*]] = icmp sgt i32 [[MIN]], [[X]]
-; CHECK-NEXT:    [[RET:%.*]] = xor i1 [[CMP1]], [[CMP2]]
+; CHECK-NEXT:    [[RET:%.*]] = xor i1 true, false
 ; CHECK-NEXT:    ret i1 [[RET]]
 ;
 entry:
diff --git a/llvm/test/Transforms/ConstraintElimination/umin-result-may-be-poison.ll b/llvm/test/Transforms/ConstraintElimination/umin-result-may-be-poison.ll
index 6d1d95ec4fdba..82f2c84b600ac 100644
--- a/llvm/test/Transforms/ConstraintElimination/umin-result-may-be-poison.ll
+++ b/llvm/test/Transforms/ConstraintElimination/umin-result-may-be-poison.ll
@@ -22,11 +22,12 @@ define i1 @umin_not_used(i32 %arg) {
 define i1 @umin_poison_is_UB_via_call(i32 %arg) {
 ; CHECK-LABEL: define i1 @umin_poison_is_UB_via_call(
 ; CHECK-SAME: i32 [[ARG:%.*]]) {
+; CHECK-NEXT:    [[ICMP:%.*]] = icmp slt i32 [[ARG]], 0
 ; CHECK-NEXT:    [[SHL:%.*]] = shl nuw nsw i32 [[ARG]], 3
 ; CHECK-NEXT:    [[MIN:%.*]] = call i32 @llvm.umin.i32(i32 [[SHL]], i32 80)
 ; CHECK-NEXT:    call void @noundef(i32 noundef [[MIN]])
 ; CHECK-NEXT:    [[CMP2:%.*]] = shl nuw nsw i32 [[ARG]], 3
-; CHECK-NEXT:    ret i1 false
+; CHECK-NEXT:    ret i1 [[ICMP]]
 ;
   %icmp = icmp slt i32 %arg, 0
   %shl = shl nuw nsw i32 %arg, 3

>From 86fb1b966cad62455a8cc8808934753b035c07a9 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Mon, 21 Apr 2025 19:11:44 +0800
Subject: [PATCH 2/2] [ConstriantElim] Add non poison values recursively

---
 llvm/include/llvm/Analysis/ValueTracking.h    | 82 ++++++++++++++++
 llvm/lib/Analysis/ValueTracking.cpp           | 82 ----------------
 .../Scalar/ConstraintElimination.cpp          | 95 +++++++++++--------
 .../Transforms/ConstraintElimination/abs.ll   |  3 +-
 .../ConstraintElimination/minmax.ll           |  3 +-
 .../umin-result-may-be-poison.ll              |  3 +-
 6 files changed, 142 insertions(+), 126 deletions(-)

diff --git a/llvm/include/llvm/Analysis/ValueTracking.h b/llvm/include/llvm/Analysis/ValueTracking.h
index f927838c843ac..faaa6d319f437 100644
--- a/llvm/include/llvm/Analysis/ValueTracking.h
+++ b/llvm/include/llvm/Analysis/ValueTracking.h
@@ -719,6 +719,88 @@ bool isGuaranteedToExecuteForEveryIteration(const Instruction *I,
 /// getGuaranteedNonPoisonOp.
 bool propagatesPoison(const Use &PoisonOp);
 
+/// Enumerates all operands of \p I that are guaranteed to not be undef or
+/// poison. If the callback \p Handle returns true, stop processing and return
+/// true. Otherwise, return false.
+template <typename CallableT>
+bool handleGuaranteedWellDefinedOps(const Instruction *I,
+                                    const CallableT &Handle) {
+  switch (I->getOpcode()) {
+  case Instruction::Store:
+    if (Handle(cast<StoreInst>(I)->getPointerOperand()))
+      return true;
+    break;
+
+  case Instruction::Load:
+    if (Handle(cast<LoadInst>(I)->getPointerOperand()))
+      return true;
+    break;
+
+  // Since dereferenceable attribute imply noundef, atomic operations
+  // also implicitly have noundef pointers too
+  case Instruction::AtomicCmpXchg:
+    if (Handle(cast<AtomicCmpXchgInst>(I)->getPointerOperand()))
+      return true;
+    break;
+
+  case Instruction::AtomicRMW:
+    if (Handle(cast<AtomicRMWInst>(I)->getPointerOperand()))
+      return true;
+    break;
+
+  case Instruction::Call:
+  case Instruction::Invoke: {
+    const CallBase *CB = cast<CallBase>(I);
+    if (CB->isIndirectCall() && Handle(CB->getCalledOperand()))
+      return true;
+    for (unsigned i = 0; i < CB->arg_size(); ++i)
+      if ((CB->paramHasAttr(i, Attribute::NoUndef) ||
+           CB->paramHasAttr(i, Attribute::Dereferenceable) ||
+           CB->paramHasAttr(i, Attribute::DereferenceableOrNull)) &&
+          Handle(CB->getArgOperand(i)))
+        return true;
+    break;
+  }
+  case Instruction::Ret:
+    if (I->getFunction()->hasRetAttribute(Attribute::NoUndef) &&
+        Handle(I->getOperand(0)))
+      return true;
+    break;
+  case Instruction::Switch:
+    if (Handle(cast<SwitchInst>(I)->getCondition()))
+      return true;
+    break;
+  case Instruction::Br: {
+    auto *BR = cast<BranchInst>(I);
+    if (BR->isConditional() && Handle(BR->getCondition()))
+      return true;
+    break;
+  }
+  default:
+    break;
+  }
+
+  return false;
+}
+
+/// Enumerates all operands of \p I that are guaranteed to not be poison.
+template <typename CallableT>
+bool handleGuaranteedNonPoisonOps(const Instruction *I,
+                                  const CallableT &Handle) {
+  if (handleGuaranteedWellDefinedOps(I, Handle))
+    return true;
+  switch (I->getOpcode()) {
+  // Divisors of these operations are allowed to be partially undef.
+  case Instruction::UDiv:
+  case Instruction::SDiv:
+  case Instruction::URem:
+  case Instruction::SRem:
+    return Handle(I->getOperand(1));
+  default:
+    return false;
+  }
+}
+
 /// Return true if the given instruction must trigger undefined behavior
 /// when I is executed with any operands which appear in KnownPoison holding
 /// a poison value at the point of execution.
diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index cdf7f052943c8..b3d4164984b0d 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -8213,88 +8213,6 @@ bool llvm::propagatesPoison(const Use &PoisonOp) {
   }
 }
 
-/// Enumerates all operands of \p I that are guaranteed to not be undef or
-/// poison. If the callback \p Handle returns true, stop processing and return
-/// true. Otherwise, return false.
-template <typename CallableT>
-static bool handleGuaranteedWellDefinedOps(const Instruction *I,
-                                           const CallableT &Handle) {
-  switch (I->getOpcode()) {
-    case Instruction::Store:
-      if (Handle(cast<StoreInst>(I)->getPointerOperand()))
-        return true;
-      break;
-
-    case Instruction::Load:
-      if (Handle(cast<LoadInst>(I)->getPointerOperand()))
-        return true;
-      break;
-
-    // Since dereferenceable attribute imply noundef, atomic operations
-    // also implicitly have noundef pointers too
-    case Instruction::AtomicCmpXchg:
-      if (Handle(cast<AtomicCmpXchgInst>(I)->getPointerOperand()))
-        return true;
-      break;
-
-    case Instruction::AtomicRMW:
-      if (Handle(cast<AtomicRMWInst>(I)->getPointerOperand()))
-        return true;
-      break;
-
-    case Instruction::Call:
-    case Instruction::Invoke: {
-      const CallBase *CB = cast<CallBase>(I);
-      if (CB->isIndirectCall() && Handle(CB->getCalledOperand()))
-        return true;
-      for (unsigned i = 0; i < CB->arg_size(); ++i)
-        if ((CB->paramHasAttr(i, Attribute::NoUndef) ||
-             CB->paramHasAttr(i, Attribute::Dereferenceable) ||
-             CB->paramHasAttr(i, Attribute::DereferenceableOrNull)) &&
-            Handle(CB->getArgOperand(i)))
-          return true;
-      break;
-    }
-    case Instruction::Ret:
-      if (I->getFunction()->hasRetAttribute(Attribute::NoUndef) &&
-          Handle(I->getOperand(0)))
-        return true;
-      break;
-    case Instruction::Switch:
-      if (Handle(cast<SwitchInst>(I)->getCondition()))
-        return true;
-      break;
-    case Instruction::Br: {
-      auto *BR = cast<BranchInst>(I);
-      if (BR->isConditional() && Handle(BR->getCondition()))
-        return true;
-      break;
-    }
-    default:
-      break;
-  }
-
-  return false;
-}
-
-/// Enumerates all operands of \p I that are guaranteed to not be poison.
-template <typename CallableT>
-static bool handleGuaranteedNonPoisonOps(const Instruction *I,
-                                         const CallableT &Handle) {
-  if (handleGuaranteedWellDefinedOps(I, Handle))
-    return true;
-  switch (I->getOpcode()) {
-  // Divisors of these operations are allowed to be partially undef.
-  case Instruction::UDiv:
-  case Instruction::SDiv:
-  case Instruction::URem:
-  case Instruction::SRem:
-    return Handle(I->getOperand(1));
-  default:
-    return false;
-  }
-}
-
 bool llvm::mustTriggerUB(const Instruction *I,
                          const SmallPtrSetImpl<const Value *> &KnownPoison) {
   return handleGuaranteedNonPoisonOps(
diff --git a/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp b/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
index 3c6b528a0fd37..c3c07485a2e91 100644
--- a/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
@@ -23,6 +23,7 @@
 #include "llvm/Analysis/ScalarEvolution.h"
 #include "llvm/Analysis/ScalarEvolutionExpressions.h"
 #include "llvm/Analysis/ValueTracking.h"
+#include "llvm/IR/CmpPredicate.h"
 #include "llvm/IR/DataLayout.h"
 #include "llvm/IR/Dominators.h"
 #include "llvm/IR/Function.h"
@@ -1083,6 +1084,44 @@ void State::addInfoForInductions(BasicBlock &BB) {
   }
 }
 
+static void addNonPoisonIntrinsicInstFact(
+    IntrinsicInst *II,
+    function_ref<void(CmpPredicate, Value *, Value *)> AddFact) {
+  Intrinsic::ID IID = II->getIntrinsicID();
+  switch (IID) {
+  case Intrinsic::umin:
+  case Intrinsic::umax:
+  case Intrinsic::smin:
+  case Intrinsic::smax: {
+    ICmpInst::Predicate Pred =
+        ICmpInst::getNonStrictPredicate(MinMaxIntrinsic::getPredicate(IID));
+    AddFact(Pred, II, II->getArgOperand(0));
+    AddFact(Pred, II, II->getArgOperand(1));
+    break;
+  }
+  case Intrinsic::abs: {
+    if (cast<ConstantInt>(II->getArgOperand(1))->isOne())
+      AddFact(CmpInst::ICMP_SGE, II, ConstantInt::get(II->getType(), 0));
+    AddFact(CmpInst::ICMP_SGE, II, II->getArgOperand(0));
+    break;
+  }
+  default:
+    break;
+  }
+}
+
+static void addNonPoisonValueFactRecursive(
+    Value *V, function_ref<void(CmpPredicate, Value *, Value *)> AddFact) {
+  if (auto *II = dyn_cast<IntrinsicInst>(V))
+    addNonPoisonIntrinsicInstFact(II, AddFact);
+
+  if (auto *I = dyn_cast<Instruction>(V)) {
+    for (auto &Op : I->operands())
+      if (impliesPoison(Op, V))
+        addNonPoisonValueFactRecursive(Op.get(), AddFact);
+  }
+}
+
 void State::addInfoFor(BasicBlock &BB) {
   addInfoForInductions(BB);
 
@@ -1134,6 +1173,18 @@ void State::addInfoFor(BasicBlock &BB) {
       break;
     }
 
+    if (GuaranteedToExecute) {
+      auto AddFact = [&](CmpPredicate Pred, Value *A, Value *B) {
+        WorkList.emplace_back(
+            FactOrCheck::getConditionFact(DT.getNode(&BB), Pred, A, B));
+      };
+
+      handleGuaranteedWellDefinedOps(&I, [&](const Value *Op) {
+        addNonPoisonValueFactRecursive(const_cast<Value *>(Op), AddFact);
+        return false;
+      });
+    }
+
     GuaranteedToExecute &= isGuaranteedToTransferExecutionToSuccessor(&I);
   }
 
@@ -1374,32 +1425,6 @@ static void generateReproducer(CmpInst *Cond, Module *M,
   assert(!verifyFunction(*F, &dbgs()));
 }
 
-static void addNonPoisonIntrinsicInstFact(
-    IntrinsicInst *II,
-    function_ref<void(CmpPredicate, Value *, Value *)> AddFact) {
-  Intrinsic::ID IID = II->getIntrinsicID();
-  switch (IID) {
-  case Intrinsic::umin:
-  case Intrinsic::umax:
-  case Intrinsic::smin:
-  case Intrinsic::smax: {
-    ICmpInst::Predicate Pred =
-        ICmpInst::getNonStrictPredicate(MinMaxIntrinsic::getPredicate(IID));
-    AddFact(Pred, II, II->getArgOperand(0));
-    AddFact(Pred, II, II->getArgOperand(1));
-    break;
-  }
-  case Intrinsic::abs: {
-    if (cast<ConstantInt>(II->getArgOperand(1))->isOne())
-      AddFact(CmpInst::ICMP_SGE, II, ConstantInt::get(II->getType(), 0));
-    AddFact(CmpInst::ICMP_SGE, II, II->getArgOperand(0));
-    break;
-  }
-  default:
-    break;
-  }
-}
-
 static void
 removeEntryFromStack(const StackEntry &E, ConstraintInfo &Info,
                      SmallVectorImpl<StackEntry> &DFSInStack,
@@ -1428,10 +1453,8 @@ static std::optional<bool> checkCondition(CmpInst::Predicate Pred, Value *A,
     Info.addFact(Pred, A, B, 0, 0, DFSInStack);
   };
 
-  if (auto *II = dyn_cast<IntrinsicInst>(A))
-    addNonPoisonIntrinsicInstFact(II, AddFact);
-  if (auto *II = dyn_cast<IntrinsicInst>(B))
-    addNonPoisonIntrinsicInstFact(II, AddFact);
+  addNonPoisonValueFactRecursive(A, AddFact);
+  addNonPoisonValueFactRecursive(B, AddFact);
 
   auto R = Info.getConstraintForSolving(Pred, A, B);
   if (R.empty() || !R.isValid(Info)){
@@ -1602,10 +1625,8 @@ static bool checkOrAndOpImpliedByOther(
         Info.addFact(Pred, A, B, CB.NumIn, CB.NumOut, DFSInStack);
       };
 
-      if (auto *II = dyn_cast<IntrinsicInst>(LHS))
-        addNonPoisonIntrinsicInstFact(II, AddFact);
-      if (auto *II = dyn_cast<IntrinsicInst>(RHS))
-        addNonPoisonIntrinsicInstFact(II, AddFact);
+      addNonPoisonValueFactRecursive(LHS, AddFact);
+      addNonPoisonValueFactRecursive(RHS, AddFact);
       continue;
     }
     if (IsOr ? match(Val, m_LogicalOr(m_Value(LHS), m_Value(RHS)))
@@ -1939,10 +1960,8 @@ static bool eliminateConstraints(Function &F, DominatorTree &DT, LoopInfo &LI,
     }
     AddFact(Pred, A, B);
     // Now both A and B is guaranteed not to be poison.
-    if (auto *II = dyn_cast<IntrinsicInst>(A))
-      addNonPoisonIntrinsicInstFact(II, AddFact);
-    if (auto *II = dyn_cast<IntrinsicInst>(B))
-      addNonPoisonIntrinsicInstFact(II, AddFact);
+    addNonPoisonValueFactRecursive(A, AddFact);
+    addNonPoisonValueFactRecursive(B, AddFact);
   }
 
   if (ReproducerModule && !ReproducerModule->functions().empty()) {
diff --git a/llvm/test/Transforms/ConstraintElimination/abs.ll b/llvm/test/Transforms/ConstraintElimination/abs.ll
index d6c159f270360..13e0f32c15024 100644
--- a/llvm/test/Transforms/ConstraintElimination/abs.ll
+++ b/llvm/test/Transforms/ConstraintElimination/abs.ll
@@ -28,8 +28,7 @@ define i1 @abs_plus_one(i32 %arg) {
 ; CHECK-SAME: i32 [[ARG:%.*]]) {
 ; CHECK-NEXT:    [[ABS:%.*]] = tail call i32 @llvm.abs.i32(i32 [[ARG]], i1 true)
 ; CHECK-NEXT:    [[ABS_PLUS_ONE:%.*]] = add nsw i32 [[ABS]], 1
-; CHECK-NEXT:    [[CMP:%.*]] = icmp sge i32 [[ABS_PLUS_ONE]], [[ARG]]
-; CHECK-NEXT:    ret i1 [[CMP]]
+; CHECK-NEXT:    ret i1 true
 ;
   %abs = tail call i32 @llvm.abs.i32(i32 %arg, i1 true)
   %abs_plus_one = add nsw i32 %abs, 1
diff --git a/llvm/test/Transforms/ConstraintElimination/minmax.ll b/llvm/test/Transforms/ConstraintElimination/minmax.ll
index e0ac453c10ab3..a079e092cb72a 100644
--- a/llvm/test/Transforms/ConstraintElimination/minmax.ll
+++ b/llvm/test/Transforms/ConstraintElimination/minmax.ll
@@ -222,8 +222,7 @@ define i1 @umax_uge_ugt_with_add_nuw(i32 %x, i32 %y) {
 ; CHECK-NEXT:    [[CMP:%.*]] = icmp uge i32 [[Y]], [[SUM]]
 ; CHECK-NEXT:    br i1 [[CMP]], label [[IF:%.*]], label [[END:%.*]]
 ; CHECK:       if:
-; CHECK-NEXT:    [[CMP2:%.*]] = icmp ugt i32 [[Y]], [[X]]
-; CHECK-NEXT:    ret i1 [[CMP2]]
+; CHECK-NEXT:    ret i1 true
 ; CHECK:       end:
 ; CHECK-NEXT:    ret i1 false
 ;
diff --git a/llvm/test/Transforms/ConstraintElimination/umin-result-may-be-poison.ll b/llvm/test/Transforms/ConstraintElimination/umin-result-may-be-poison.ll
index 82f2c84b600ac..6d1d95ec4fdba 100644
--- a/llvm/test/Transforms/ConstraintElimination/umin-result-may-be-poison.ll
+++ b/llvm/test/Transforms/ConstraintElimination/umin-result-may-be-poison.ll
@@ -22,12 +22,11 @@ define i1 @umin_not_used(i32 %arg) {
 define i1 @umin_poison_is_UB_via_call(i32 %arg) {
 ; CHECK-LABEL: define i1 @umin_poison_is_UB_via_call(
 ; CHECK-SAME: i32 [[ARG:%.*]]) {
-; CHECK-NEXT:    [[ICMP:%.*]] = icmp slt i32 [[ARG]], 0
 ; CHECK-NEXT:    [[SHL:%.*]] = shl nuw nsw i32 [[ARG]], 3
 ; CHECK-NEXT:    [[MIN:%.*]] = call i32 @llvm.umin.i32(i32 [[SHL]], i32 80)
 ; CHECK-NEXT:    call void @noundef(i32 noundef [[MIN]])
 ; CHECK-NEXT:    [[CMP2:%.*]] = shl nuw nsw i32 [[ARG]], 3
-; CHECK-NEXT:    ret i1 [[ICMP]]
+; CHECK-NEXT:    ret i1 false
 ;
   %icmp = icmp slt i32 %arg, 0
   %shl = shl nuw nsw i32 %arg, 3



More information about the llvm-commits mailing list