[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:38:17 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 ae3fdf6d2c6e177b85c3090ccfa0cf80ab5576b2 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 | 96 +++++++++++--------
.../Transforms/ConstraintElimination/abs.ll | 3 +-
.../ConstraintElimination/minmax.ll | 3 +-
.../umin-result-may-be-poison.ll | 3 +-
6 files changed, 143 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..60e94bee8e63e 100644
--- a/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
@@ -1083,6 +1083,46 @@ 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)) {
+ if (I->getOpcode() == Instruction::PHI)
+ return;
+ for (auto &Op : I->operands())
+ if (impliesPoison(Op, V))
+ addNonPoisonValueFactRecursive(Op.get(), AddFact);
+ }
+}
+
void State::addInfoFor(BasicBlock &BB) {
addInfoForInductions(BB);
@@ -1134,6 +1174,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 +1426,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 +1454,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 +1626,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 +1961,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