[llvm-branch-commits] [llvm] release/23.x: [InstCombine] Drop poison-generating annotations forming min/max for clamp (#231537) (PR #233355)
via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Mon Oct 19 00:08:15 PDT 2026
https://github.com/llvmbot created https://github.com/llvm/llvm-project/pull/233355
Backport 4662c4a4f6d119a2daaa0c60430f611f8251f5a3
Requested by: @nikic
>From c240fc8c14b0a6576467b0d2a416207b424915db Mon Sep 17 00:00:00 2001
From: Nikita Popov <npopov at redhat.com>
Date: Mon, 19 Oct 2026 08:59:59 +0200
Subject: [PATCH] [InstCombine] Drop poison-generating annotations forming
min/max for clamp (#231537)
When converting select to min/max intrinsics we have to be careful about
poison-propagation, because min/max propagate unconditionally, while
select does not. Normally, this is not a problem, because the select
arms are also used in the select condition, which propagates
unconditionally.
However, SPF also matches special patterns where this may not be the
case. In particular, for `(X <u C1) ? C1 : UMIN(X, C2) ==> UMAX(UMIN(X,
C2), C1)` style transforms, if the inner min/max has poison-generating
annotations, the result can be more-poisonous.
I'm fixing this in a somewhat hacky way, by dropping the poison
annotations as part of SPF canonicalization. This makes assumptions
about the kind of patterns SPF matches. Originally, I wanted to just
move this code out of SPF into a dedicated InstCombine fold, but this
did not work around because SPF also handles cases with casts in
between, and I did not want to replicate that logic.
For https://github.com/rust-lang/rust/issues/163728.
(cherry picked from commit 4662c4a4f6d119a2daaa0c60430f611f8251f5a3)
---
.../InstCombine/InstCombineSelect.cpp | 25 +++-
.../InstCombine/clamp-range-attr.ll | 126 ++++++++++++++++++
2 files changed, 149 insertions(+), 2 deletions(-)
create mode 100644 llvm/test/Transforms/InstCombine/clamp-range-attr.ll
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
index 98b2b7dd2c01f..cfdab8c3bad61 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
@@ -1602,6 +1602,24 @@ static Value *foldSelectCttzCtlz(ICmpInst *ICI, Value *TrueVal, Value *FalseVal,
return nullptr;
}
+/// SPF can match patterns like:
+/// (X <u C1) ? C1 : UMIN(X, C2) ==> UMAX(UMIN(X, C2), C1)
+/// However, if the UMIN has poison-generating annotations, the result may
+/// be more poisonous, because the poison is propagated unconditionally after
+/// the transform. Drop annotations in this case.
+/// FIXME: This is something of a hack that relies on details of patterns
+// supported by SPF.
+static void dropPoisonGeneratingAnnotationsForSPFCanonicalization(Value *Cond,
+ Value *LHS,
+ Value *RHS) {
+ if (!impliesPoison(LHS, Cond))
+ if (auto *I = dyn_cast<Instruction>(LHS))
+ I->dropPoisonGeneratingAnnotations();
+ if (!impliesPoison(RHS, Cond))
+ if (auto *I = dyn_cast<Instruction>(RHS))
+ I->dropPoisonGeneratingAnnotations();
+}
+
static Value *canonicalizeSPF(ICmpInst &Cmp, Value *TrueVal, Value *FalseVal,
InstCombinerImpl &IC) {
Value *LHS, *RHS;
@@ -1631,6 +1649,7 @@ static Value *canonicalizeSPF(ICmpInst &Cmp, Value *TrueVal, Value *FalseVal,
if (SelectPatternResult::isMinOrMax(SPF)) {
Intrinsic::ID IntrinsicID = getMinMaxIntrinsic(SPF);
+ dropPoisonGeneratingAnnotationsForSPFCanonicalization(&Cmp, LHS, RHS);
return IC.Builder.CreateBinaryIntrinsic(IntrinsicID, LHS, RHS);
}
@@ -4930,9 +4949,11 @@ Instruction *InstCombinerImpl::visitSelectInst(SelectInst &SI) {
CmpInst::Predicate MinMaxPred = getMinMaxPred(SPF, SPR.Ordered);
Value *Cmp;
- if (CmpInst::isIntPredicate(MinMaxPred))
+ if (CmpInst::isIntPredicate(MinMaxPred)) {
+ dropPoisonGeneratingAnnotationsForSPFCanonicalization(CondVal, LHS,
+ RHS);
Cmp = Builder.CreateICmp(MinMaxPred, LHS, RHS);
- else
+ } else
Cmp = Builder.CreateFCmpFMF(MinMaxPred, LHS, RHS,
cast<Instruction>(SI.getCondition()));
diff --git a/llvm/test/Transforms/InstCombine/clamp-range-attr.ll b/llvm/test/Transforms/InstCombine/clamp-range-attr.ll
new file mode 100644
index 0000000000000..863215b74ccfa
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/clamp-range-attr.ll
@@ -0,0 +1,126 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -S -passes=instcombine < %s | FileCheck %s
+
+declare void @use.i32(i32)
+
+define i16 @umax_of_umin_range1(i16 %x) {
+; CHECK-LABEL: define i16 @umax_of_umin_range1(
+; CHECK-SAME: i16 [[X:%.*]]) {
+; CHECK-NEXT: [[TMP1:%.*]] = call i16 @llvm.umax.i16(i16 [[X]], i16 42)
+; CHECK-NEXT: [[UMIN:%.*]] = call i16 @llvm.umin.i16(i16 [[TMP1]], i16 1000)
+; CHECK-NEXT: ret i16 [[UMIN]]
+;
+ %umin = call range(i16 43, 1001) i16 @llvm.umin.i16(i16 %x, i16 1000)
+ %cmp = icmp ult i16 %x, 43
+ %umax = select i1 %cmp, i16 42, i16 %umin
+ ret i16 %umax
+}
+
+define i16 @umax_of_umin_range2(i16 %x) {
+; CHECK-LABEL: define i16 @umax_of_umin_range2(
+; CHECK-SAME: i16 [[X:%.*]]) {
+; CHECK-NEXT: [[TMP1:%.*]] = call i16 @llvm.umax.i16(i16 [[X]], i16 42)
+; CHECK-NEXT: [[UMAX:%.*]] = call i16 @llvm.umin.i16(i16 [[TMP1]], i16 1000)
+; CHECK-NEXT: ret i16 [[UMAX]]
+;
+ %umin = call range(i16 30, 1001) i16 @llvm.umin.i16(i16 %x, i16 1000)
+ %cmp = icmp ult i16 %x, 43
+ %umax = select i1 %cmp, i16 42, i16 %umin
+ ret i16 %umax
+}
+
+define i16 @umin_of_umax_range1(i16 %x) {
+; CHECK-LABEL: define i16 @umin_of_umax_range1(
+; CHECK-SAME: i16 [[X:%.*]]) {
+; CHECK-NEXT: [[UMIN1:%.*]] = call i16 @llvm.umax.i16(i16 [[X]], i16 42)
+; CHECK-NEXT: [[UMIN:%.*]] = call i16 @llvm.umin.i16(i16 [[UMIN1]], i16 1000)
+; CHECK-NEXT: ret i16 [[UMIN]]
+;
+ %umin = call range(i16 43, 1001) i16 @llvm.umax.i16(i16 %x, i16 42)
+ %cmp = icmp ugt i16 %x, 999
+ %umax = select i1 %cmp, i16 1000, i16 %umin
+ ret i16 %umax
+}
+
+define i16 @umin_of_umax_range2(i16 %x) {
+; CHECK-LABEL: define i16 @umin_of_umax_range2(
+; CHECK-SAME: i16 [[X:%.*]]) {
+; CHECK-NEXT: [[UMIN:%.*]] = call i16 @llvm.umax.i16(i16 [[X]], i16 42)
+; CHECK-NEXT: [[UMAX:%.*]] = call i16 @llvm.umin.i16(i16 [[UMIN]], i16 1000)
+; CHECK-NEXT: ret i16 [[UMAX]]
+;
+ %umin = call range(i16 43, 1101) i16 @llvm.umax.i16(i16 %x, i16 42)
+ %cmp = icmp ugt i16 %x, 999
+ %umax = select i1 %cmp, i16 1000, i16 %umin
+ ret i16 %umax
+}
+
+define i16 @smax_of_smin_range1(i16 %x) {
+; CHECK-LABEL: define i16 @smax_of_smin_range1(
+; CHECK-SAME: i16 [[X:%.*]]) {
+; CHECK-NEXT: [[TMP1:%.*]] = call i16 @llvm.smax.i16(i16 [[X]], i16 -100)
+; CHECK-NEXT: [[SMIN:%.*]] = call i16 @llvm.smin.i16(i16 [[TMP1]], i16 100)
+; CHECK-NEXT: ret i16 [[SMIN]]
+;
+ %smin = call range(i16 -100, 101) i16 @llvm.smin.i16(i16 %x, i16 100)
+ %cmp = icmp slt i16 %x, -99
+ %smax = select i1 %cmp, i16 -100, i16 %smin
+ ret i16 %smax
+}
+
+define i16 @smax_of_smin_range2(i16 %x) {
+; CHECK-LABEL: define i16 @smax_of_smin_range2(
+; CHECK-SAME: i16 [[X:%.*]]) {
+; CHECK-NEXT: [[TMP1:%.*]] = call i16 @llvm.smax.i16(i16 [[X]], i16 -100)
+; CHECK-NEXT: [[SMAX:%.*]] = call i16 @llvm.smin.i16(i16 [[TMP1]], i16 100)
+; CHECK-NEXT: ret i16 [[SMAX]]
+;
+ %smin = call range(i16 -200, 101) i16 @llvm.smin.i16(i16 %x, i16 100)
+ %cmp = icmp slt i16 %x, -99
+ %smax = select i1 %cmp, i16 -100, i16 %smin
+ ret i16 %smax
+}
+
+define i16 @smin_of_smax_range1(i16 %x) {
+; CHECK-LABEL: define i16 @smin_of_smax_range1(
+; CHECK-SAME: i16 [[X:%.*]]) {
+; CHECK-NEXT: [[SMAX1:%.*]] = call i16 @llvm.smax.i16(i16 [[X]], i16 -100)
+; CHECK-NEXT: [[SMAX:%.*]] = call i16 @llvm.smin.i16(i16 [[SMAX1]], i16 100)
+; CHECK-NEXT: ret i16 [[SMAX]]
+;
+ %smax = call range(i16 -100, 101) i16 @llvm.smax.i16(i16 %x, i16 -100)
+ %cmp = icmp sgt i16 %x, 99
+ %smin = select i1 %cmp, i16 100, i16 %smax
+ ret i16 %smin
+}
+
+define i16 @smin_of_smax_range2(i16 %x) {
+; CHECK-LABEL: define i16 @smin_of_smax_range2(
+; CHECK-SAME: i16 [[X:%.*]]) {
+; CHECK-NEXT: [[SMAX:%.*]] = call i16 @llvm.smax.i16(i16 [[X]], i16 -100)
+; CHECK-NEXT: [[SMIN:%.*]] = call i16 @llvm.smin.i16(i16 [[SMAX]], i16 100)
+; CHECK-NEXT: ret i16 [[SMIN]]
+;
+ %smax = call range(i16 -100, 201) i16 @llvm.smax.i16(i16 %x, i16 -100)
+ %cmp = icmp sgt i16 %x, 99
+ %smin = select i1 %cmp, i16 100, i16 %smax
+ ret i16 %smin
+}
+
+define i32 @with_cast(i16 %x) {
+; CHECK-LABEL: define i32 @with_cast(
+; CHECK-SAME: i16 [[X:%.*]]) {
+; CHECK-NEXT: [[UMIN:%.*]] = call i16 @llvm.umin.i16(i16 [[X]], i16 1000)
+; CHECK-NEXT: [[EXT:%.*]] = zext nneg i16 [[UMIN]] to i32
+; CHECK-NEXT: call void @use.i32(i32 [[EXT]])
+; CHECK-NEXT: [[RV1:%.*]] = call i16 @llvm.umax.i16(i16 [[UMIN]], i16 1)
+; CHECK-NEXT: [[RV:%.*]] = zext nneg i16 [[RV1]] to i32
+; CHECK-NEXT: ret i32 [[RV]]
+;
+ %umin = call range(i16 50, 1000) i16 @llvm.umin.i16(i16 %x, i16 1000)
+ %ext = zext i16 %umin to i32
+ call void @use.i32(i32 %ext)
+ %cmp = icmp ugt i16 %x, 1
+ %rv = select i1 %cmp, i32 %ext, i32 1
+ ret i32 %rv
+}
More information about the llvm-branch-commits
mailing list