[llvm] [InstCombine] Extend canonicalization of addition to positive numbers (PR #179343)
via llvm-commits
llvm-commits at lists.llvm.org
Tue Feb 3 06:59:21 PST 2026
https://github.com/SiliconA-Z updated https://github.com/llvm/llvm-project/pull/179343
>From 4a4d8590383f7d60cc1a37b82316d97e28c7f348 Mon Sep 17 00:00:00 2001
From: AZero13 <gfunni234 at gmail.com>
Date: Mon, 2 Feb 2026 16:10:42 -0500
Subject: [PATCH 1/2] Pre-commit tests (NFC)
---
.../InstCombine/saturating-add-sub.ll | 144 ++++++++++++++++++
1 file changed, 144 insertions(+)
diff --git a/llvm/test/Transforms/InstCombine/saturating-add-sub.ll b/llvm/test/Transforms/InstCombine/saturating-add-sub.ll
index 80676de47f193..61c7499883d2b 100644
--- a/llvm/test/Transforms/InstCombine/saturating-add-sub.ll
+++ b/llvm/test/Transforms/InstCombine/saturating-add-sub.ll
@@ -2708,6 +2708,150 @@ define i8 @neg_neg_constant(i8 %x, i8 %y) {
ret i8 %s
}
+;
+; Negative constant signed saturated add - saturates to INT_MIN
+;
+
+; Basic pattern: (x <= threshold) ? INT_MIN : (x + C) where C < 0
+; threshold should be INT_MIN - C or INT_MIN - C + 1
+
+; (x <= -119) ? -128 : (x + -10) --> sadd.sat(x, -10)
+define i8 @sadd_sat_neg_constant(i8 %x) {
+; CHECK-LABEL: @sadd_sat_neg_constant(
+; CHECK-NEXT: [[TMP1:%.*]] = call i8 @llvm.smax.i8(i8 [[X:%.*]], i8 -118)
+; CHECK-NEXT: [[R:%.*]] = add nsw i8 [[TMP1]], -10
+; CHECK-NEXT: ret i8 [[R]]
+;
+ %cmp = icmp sle i8 %x, -119
+ %add = add i8 %x, -10
+ %r = select i1 %cmp, i8 -128, i8 %add
+ ret i8 %r
+}
+
+; (x < -118) ? -128 : (x + -10) --> sadd.sat(x, -10)
+define i8 @sadd_sat_neg_constant_slt(i8 %x) {
+; CHECK-LABEL: @sadd_sat_neg_constant_slt(
+; CHECK-NEXT: [[TMP1:%.*]] = call i8 @llvm.smax.i8(i8 [[X:%.*]], i8 -118)
+; CHECK-NEXT: [[R:%.*]] = add nsw i8 [[TMP1]], -10
+; CHECK-NEXT: ret i8 [[R]]
+;
+ %cmp = icmp slt i8 %x, -118
+ %add = add i8 %x, -10
+ %r = select i1 %cmp, i8 -128, i8 %add
+ ret i8 %r
+}
+
+; Commuted select: (x >= threshold) ? (x + C) : INT_MIN --> sadd.sat(x, C)
+define i8 @sadd_sat_neg_constant_commuted_select(i8 %x) {
+; CHECK-LABEL: @sadd_sat_neg_constant_commuted_select(
+; CHECK-NEXT: [[TMP1:%.*]] = call i8 @llvm.smax.i8(i8 [[X:%.*]], i8 -118)
+; CHECK-NEXT: [[R:%.*]] = add nsw i8 [[TMP1]], -10
+; CHECK-NEXT: ret i8 [[R]]
+;
+ %cmp = icmp sge i8 %x, -118
+ %add = add i8 %x, -10
+ %r = select i1 %cmp, i8 %add, i8 -128
+ ret i8 %r
+}
+
+; sle minimum signed value is canonicalized to eq and requires special handling
+; (x == INT_MIN) ? INT_MIN : x + -1 --> sadd.sat(x, -1)
+define i8 @sadd_sat_eq_int_min(i8 %x) {
+; CHECK-LABEL: @sadd_sat_eq_int_min(
+; CHECK-NEXT: [[CMP:%.*]] = icmp eq i8 [[X:%.*]], -128
+; CHECK-NEXT: [[ADD:%.*]] = add i8 [[X]], -1
+; CHECK-NEXT: [[R:%.*]] = select i1 [[CMP]], i8 -128, i8 [[ADD]]
+; CHECK-NEXT: ret i8 [[R]]
+;
+ %cmp = icmp eq i8 %x, -128
+ %add = add i8 %x, -1
+ %r = select i1 %cmp, i8 -128, i8 %add
+ ret i8 %r
+}
+
+; Vector version of negative constant pattern
+define <2 x i8> @sadd_sat_neg_constant_vector(<2 x i8> %x) {
+; CHECK-LABEL: @sadd_sat_neg_constant_vector(
+; CHECK-NEXT: [[TMP1:%.*]] = call <2 x i8> @llvm.smax.v2i8(<2 x i8> [[X:%.*]], <2 x i8> splat (i8 -118))
+; CHECK-NEXT: [[R:%.*]] = add nsw <2 x i8> [[TMP1]], splat (i8 -10)
+; CHECK-NEXT: ret <2 x i8> [[R]]
+;
+ %cmp = icmp sle <2 x i8> %x, <i8 -119, i8 -119>
+ %add = add <2 x i8> %x, <i8 -10, i8 -10>
+ %r = select <2 x i1> %cmp, <2 x i8> <i8 -128, i8 -128>, <2 x i8> %add
+ ret <2 x i8> %r
+}
+
+; Pattern with variable: (INT_MIN - X > Y) ? INT_MIN : (X + Y) with nsw sub
+define i8 @sadd_sat_int_min_minus_x_nsw(i8 %x, i8 %y) {
+; CHECK-LABEL: @sadd_sat_int_min_minus_x_nsw(
+; CHECK-NEXT: [[SUB:%.*]] = sub nsw i8 -128, [[X:%.*]]
+; CHECK-NEXT: [[CMP:%.*]] = icmp sgt i8 [[SUB]], [[Y:%.*]]
+; CHECK-NEXT: [[ADD:%.*]] = add i8 [[X]], [[Y]]
+; CHECK-NEXT: [[R:%.*]] = select i1 [[CMP]], i8 -128, i8 [[ADD]]
+; CHECK-NEXT: ret i8 [[R]]
+;
+ %sub = sub nsw i8 -128, %x
+ %cmp = icmp sgt i8 %sub, %y
+ %add = add i8 %x, %y
+ %r = select i1 %cmp, i8 -128, i8 %add
+ ret i8 %r
+}
+
+; Negative test: wrong saturation value
+define i8 @sadd_sat_neg_constant_wrong_sat_value(i8 %x) {
+; CHECK-LABEL: @sadd_sat_neg_constant_wrong_sat_value(
+; CHECK-NEXT: [[CMP:%.*]] = icmp slt i8 [[X:%.*]], -118
+; CHECK-NEXT: [[ADD:%.*]] = add i8 [[X]], -10
+; CHECK-NEXT: [[R:%.*]] = select i1 [[CMP]], i8 -127, i8 [[ADD]]
+; CHECK-NEXT: ret i8 [[R]]
+;
+ %cmp = icmp sle i8 %x, -119
+ %add = add i8 %x, -10
+ %r = select i1 %cmp, i8 -127, i8 %add
+ ret i8 %r
+}
+
+; Boundary case: threshold allows exact match at the saturation limit
+define i8 @sadd_sat_neg_constant_boundary(i8 %x) {
+; CHECK-LABEL: @sadd_sat_neg_constant_boundary(
+; CHECK-NEXT: [[R:%.*]] = call i8 @llvm.sadd.sat.i8(i8 [[X:%.*]], i8 -10)
+; CHECK-NEXT: ret i8 [[R]]
+;
+ %cmp = icmp sle i8 %x, -118
+ %add = add i8 %x, -10
+ %r = select i1 %cmp, i8 -128, i8 %add
+ ret i8 %r
+}
+
+; Negative test: threshold too high
+define i8 @sadd_sat_neg_constant_wrong_threshold(i8 %x) {
+; CHECK-LABEL: @sadd_sat_neg_constant_wrong_threshold(
+; CHECK-NEXT: [[CMP:%.*]] = icmp slt i8 [[X:%.*]], -116
+; CHECK-NEXT: [[ADD:%.*]] = add i8 [[X]], -10
+; CHECK-NEXT: [[R:%.*]] = select i1 [[CMP]], i8 -128, i8 [[ADD]]
+; CHECK-NEXT: ret i8 [[R]]
+;
+ %cmp = icmp sle i8 %x, -117
+ %add = add i8 %x, -10
+ %r = select i1 %cmp, i8 -128, i8 %add
+ ret i8 %r
+}
+
+; Negative test: positive constant (should use INT_MAX saturation, not INT_MIN)
+define i8 @sadd_sat_neg_pattern_positive_constant(i8 %x) {
+; CHECK-LABEL: @sadd_sat_neg_pattern_positive_constant(
+; CHECK-NEXT: [[CMP:%.*]] = icmp slt i8 [[X:%.*]], -118
+; CHECK-NEXT: [[ADD:%.*]] = add i8 [[X]], 10
+; CHECK-NEXT: [[R:%.*]] = select i1 [[CMP]], i8 -128, i8 [[ADD]]
+; CHECK-NEXT: ret i8 [[R]]
+;
+ %cmp = icmp sle i8 %x, -119
+ %add = add i8 %x, 10
+ %r = select i1 %cmp, i8 -128, i8 %add
+ ret i8 %r
+}
+
; Make sure we don't crash in this case.
define i32 @pr153053_strict_pred_with_nonconstant_rhs(i32 %x, i32 %y) {
; CHECK-LABEL: @pr153053_strict_pred_with_nonconstant_rhs(
>From fdeea0a6c9f1013601ace93b0f1c99a595c346ce Mon Sep 17 00:00:00 2001
From: AZero13 <gfunni234 at gmail.com>
Date: Mon, 2 Feb 2026 16:29:24 -0500
Subject: [PATCH 2/2] [InstCombine] Extend canonicalization of addition to
positive numbers
https://alive2.llvm.org/ce/z/6PQdaP
Extend this to positive numbers too.
---
.../InstCombine/InstCombineSelect.cpp | 88 ++++++++++++++-----
.../InstCombine/saturating-add-sub.ll | 21 ++---
2 files changed, 72 insertions(+), 37 deletions(-)
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
index fb30a4545cffe..62ace0474ed7c 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
@@ -1147,39 +1147,48 @@ static Value *canonicalizeSaturatedAddSigned(ICmpInst *Cmp, Value *TVal,
Value *Cmp0 = Cmp->getOperand(0);
Value *Cmp1 = Cmp->getOperand(1);
ICmpInst::Predicate Pred = Cmp->getPredicate();
- Value *X;
- const APInt *C;
- // Canonicalize INT_MAX to true value of the select.
- if (match(FVal, m_MaxSignedValue())) {
+ // Canonicalize TVal to be the saturation constant.
+ if (match(FVal, m_MaxSignedValue()) || match(FVal, m_SignMask())) {
std::swap(TVal, FVal);
Pred = CmpInst::getInversePredicate(Pred);
}
- if (!match(TVal, m_MaxSignedValue()))
+ const APInt *SatC;
+ if (!match(TVal, m_APInt(SatC)) ||
+ !(SatC->isMaxSignedValue() || SatC->isSignMask()))
return nullptr;
+ bool IsMax = SatC->isMaxSignedValue();
+
// sge maximum signed value is canonicalized to eq maximum signed value and
- // requires special handling (a == INT_MAX) ? INT_MAX : a + 1 -> sadd.sat(a,
- // 1)
- if (Pred == ICmpInst::ICMP_EQ) {
- if (match(FVal, m_Add(m_Specific(Cmp0), m_One())) && Cmp1 == TVal) {
+ // requires special handling. sle minimum signed value is similarly
+ // canonicalized to eq minimum signed value.
+ if (Pred == ICmpInst::ICMP_EQ && Cmp1 == TVal) {
+ // (a == INT_MAX) ? INT_MAX : a + 1 -> sadd.sat(a, 1)
+ if (IsMax && match(FVal, m_Add(m_Specific(Cmp0), m_One()))) {
return Builder.CreateBinaryIntrinsic(
Intrinsic::sadd_sat, Cmp0, ConstantInt::get(Cmp0->getType(), 1));
}
+
+ // (a == INT_MIN) ? INT_MIN : a + -1 -> sadd.sat(a, -1)
+ if (!IsMax && match(FVal, m_Add(m_Specific(Cmp0), m_AllOnes()))) {
+ return Builder.CreateBinaryIntrinsic(
+ Intrinsic::sadd_sat, Cmp0,
+ ConstantInt::getAllOnesValue(Cmp0->getType()));
+ }
return nullptr;
}
+ const APInt *C;
+
// (X > Y) ? INT_MAX : (X + C) --> sadd.sat(X, C)
// (X >= Y) ? INT_MAX : (X + C) --> sadd.sat(X, C)
- // where Y is INT_MAX - C or INT_MAX - C - 1, and C > 0
- if ((Pred == ICmpInst::ICMP_SGT || Pred == ICmpInst::ICMP_SGE) &&
+ // where C > 0 and Y is INT_MAX - C or INT_MAX - C - 1
+ if (IsMax && (Pred == ICmpInst::ICMP_SGT || Pred == ICmpInst::ICMP_SGE) &&
isa<Constant>(Cmp1) &&
match(FVal, m_Add(m_Specific(Cmp0), m_StrictlyPositive(C)))) {
- APInt IntMax =
- APInt::getSignedMaxValue(Cmp1->getType()->getScalarSizeInBits());
-
- // For SGE, try to flip to SGT to normalize the comparison constant.
+ // Normalize SGE to SGT for threshold comparison.
if (Pred == ICmpInst::ICMP_SGE) {
if (auto Flipped = getFlippedStrictnessPredicateAndConstant(
Pred, cast<Constant>(Cmp1))) {
@@ -1187,11 +1196,35 @@ static Value *canonicalizeSaturatedAddSigned(ICmpInst *Cmp, Value *TVal,
Cmp1 = Flipped->second;
}
}
-
- // Check the pattern: X > INT_MAX - C or X > INT_MAX - C - 1
+ // Check: X > INT_MAX - C or X > INT_MAX - C - 1
+ APInt Threshold = *SatC - *C;
if (Pred == ICmpInst::ICMP_SGT &&
- (match(Cmp1, m_SpecificIntAllowPoison(IntMax - *C)) ||
- match(Cmp1, m_SpecificIntAllowPoison(IntMax - *C - 1))))
+ (match(Cmp1, m_SpecificIntAllowPoison(Threshold)) ||
+ match(Cmp1, m_SpecificIntAllowPoison(Threshold - 1))))
+ return Builder.CreateBinaryIntrinsic(
+ Intrinsic::sadd_sat, Cmp0, ConstantInt::get(Cmp0->getType(), *C));
+ }
+
+ // (X < Y) ? INT_MIN : (X + C) --> sadd.sat(X, C)
+ // (X <= Y) ? INT_MIN : (X + C) --> sadd.sat(X, C)
+ // where C < 0 and Y is INT_MIN - C or INT_MIN - C + 1
+ if (!IsMax && (Pred == ICmpInst::ICMP_SLT || Pred == ICmpInst::ICMP_SLE) &&
+ isa<Constant>(Cmp1) &&
+ match(FVal, m_Add(m_Specific(Cmp0), m_Negative(C)))) {
+ // Normalize SLE to SLT for threshold comparison.
+ if (Pred == ICmpInst::ICMP_SLE) {
+ if (auto Flipped = getFlippedStrictnessPredicateAndConstant(
+ Pred, cast<Constant>(Cmp1))) {
+ Pred = Flipped->first;
+ Cmp1 = Flipped->second;
+ }
+ }
+ // Check: X < INT_MIN - C or X < INT_MIN - C + 1
+ // INT_MIN - C for negative C is like INT_MIN + |C|
+ APInt Threshold = *SatC - *C;
+ if (Pred == ICmpInst::ICMP_SLT &&
+ (match(Cmp1, m_SpecificIntAllowPoison(Threshold)) ||
+ match(Cmp1, m_SpecificIntAllowPoison(Threshold + 1))))
return Builder.CreateBinaryIntrinsic(
Intrinsic::sadd_sat, Cmp0, ConstantInt::get(Cmp0->getType(), *C));
}
@@ -1205,13 +1238,24 @@ static Value *canonicalizeSaturatedAddSigned(ICmpInst *Cmp, Value *TVal,
if (Pred != ICmpInst::ICMP_SLT && Pred != ICmpInst::ICMP_SLE)
return nullptr;
- if (match(Cmp0, m_NSWSub(m_MaxSignedValue(), m_Value(X))) &&
+ Value *X;
+
+ // (INT_MAX - X s< Y) ? INT_MAX : (X + Y) --> sadd.sat(X, Y)
+ // (INT_MAX - X s< Y) ? INT_MAX : (Y + X) --> sadd.sat(X, Y)
+ if (IsMax && match(Cmp0, m_NSWSub(m_SpecificInt(*SatC), m_Value(X))) &&
match(FVal, m_c_Add(m_Specific(X), m_Specific(Cmp1)))) {
- // (INT_MAX - X s< Y) ? INT_MAX : (X + Y) --> sadd.sat(X, Y)
- // (INT_MAX - X s< Y) ? INT_MAX : (Y + X) --> sadd.sat(X, Y)
return Builder.CreateBinaryIntrinsic(Intrinsic::sadd_sat, X, Cmp1);
}
+ // (INT_MIN - X s> Y) ? INT_MIN : (X + Y) --> sadd.sat(X, Y)
+ // (INT_MIN - X s> Y) ? INT_MIN : (Y + X) --> sadd.sat(X, Y)
+ // After swapping operands from the SGT/SGE canonicalization above,
+ // this becomes (Y s< INT_MIN - X).
+ if (!IsMax && match(Cmp1, m_NSWSub(m_SpecificInt(*SatC), m_Value(X))) &&
+ match(FVal, m_c_Add(m_Specific(X), m_Specific(Cmp0)))) {
+ return Builder.CreateBinaryIntrinsic(Intrinsic::sadd_sat, X, Cmp0);
+ }
+
return nullptr;
}
diff --git a/llvm/test/Transforms/InstCombine/saturating-add-sub.ll b/llvm/test/Transforms/InstCombine/saturating-add-sub.ll
index 61c7499883d2b..efa89db4af61a 100644
--- a/llvm/test/Transforms/InstCombine/saturating-add-sub.ll
+++ b/llvm/test/Transforms/InstCombine/saturating-add-sub.ll
@@ -2718,8 +2718,7 @@ define i8 @neg_neg_constant(i8 %x, i8 %y) {
; (x <= -119) ? -128 : (x + -10) --> sadd.sat(x, -10)
define i8 @sadd_sat_neg_constant(i8 %x) {
; CHECK-LABEL: @sadd_sat_neg_constant(
-; CHECK-NEXT: [[TMP1:%.*]] = call i8 @llvm.smax.i8(i8 [[X:%.*]], i8 -118)
-; CHECK-NEXT: [[R:%.*]] = add nsw i8 [[TMP1]], -10
+; CHECK-NEXT: [[R:%.*]] = call i8 @llvm.sadd.sat.i8(i8 [[X:%.*]], i8 -10)
; CHECK-NEXT: ret i8 [[R]]
;
%cmp = icmp sle i8 %x, -119
@@ -2731,8 +2730,7 @@ define i8 @sadd_sat_neg_constant(i8 %x) {
; (x < -118) ? -128 : (x + -10) --> sadd.sat(x, -10)
define i8 @sadd_sat_neg_constant_slt(i8 %x) {
; CHECK-LABEL: @sadd_sat_neg_constant_slt(
-; CHECK-NEXT: [[TMP1:%.*]] = call i8 @llvm.smax.i8(i8 [[X:%.*]], i8 -118)
-; CHECK-NEXT: [[R:%.*]] = add nsw i8 [[TMP1]], -10
+; CHECK-NEXT: [[R:%.*]] = call i8 @llvm.sadd.sat.i8(i8 [[X:%.*]], i8 -10)
; CHECK-NEXT: ret i8 [[R]]
;
%cmp = icmp slt i8 %x, -118
@@ -2744,8 +2742,7 @@ define i8 @sadd_sat_neg_constant_slt(i8 %x) {
; Commuted select: (x >= threshold) ? (x + C) : INT_MIN --> sadd.sat(x, C)
define i8 @sadd_sat_neg_constant_commuted_select(i8 %x) {
; CHECK-LABEL: @sadd_sat_neg_constant_commuted_select(
-; CHECK-NEXT: [[TMP1:%.*]] = call i8 @llvm.smax.i8(i8 [[X:%.*]], i8 -118)
-; CHECK-NEXT: [[R:%.*]] = add nsw i8 [[TMP1]], -10
+; CHECK-NEXT: [[R:%.*]] = call i8 @llvm.sadd.sat.i8(i8 [[X:%.*]], i8 -10)
; CHECK-NEXT: ret i8 [[R]]
;
%cmp = icmp sge i8 %x, -118
@@ -2758,9 +2755,7 @@ define i8 @sadd_sat_neg_constant_commuted_select(i8 %x) {
; (x == INT_MIN) ? INT_MIN : x + -1 --> sadd.sat(x, -1)
define i8 @sadd_sat_eq_int_min(i8 %x) {
; CHECK-LABEL: @sadd_sat_eq_int_min(
-; CHECK-NEXT: [[CMP:%.*]] = icmp eq i8 [[X:%.*]], -128
-; CHECK-NEXT: [[ADD:%.*]] = add i8 [[X]], -1
-; CHECK-NEXT: [[R:%.*]] = select i1 [[CMP]], i8 -128, i8 [[ADD]]
+; CHECK-NEXT: [[R:%.*]] = call i8 @llvm.sadd.sat.i8(i8 [[X:%.*]], i8 -1)
; CHECK-NEXT: ret i8 [[R]]
;
%cmp = icmp eq i8 %x, -128
@@ -2772,8 +2767,7 @@ define i8 @sadd_sat_eq_int_min(i8 %x) {
; Vector version of negative constant pattern
define <2 x i8> @sadd_sat_neg_constant_vector(<2 x i8> %x) {
; CHECK-LABEL: @sadd_sat_neg_constant_vector(
-; CHECK-NEXT: [[TMP1:%.*]] = call <2 x i8> @llvm.smax.v2i8(<2 x i8> [[X:%.*]], <2 x i8> splat (i8 -118))
-; CHECK-NEXT: [[R:%.*]] = add nsw <2 x i8> [[TMP1]], splat (i8 -10)
+; CHECK-NEXT: [[R:%.*]] = call <2 x i8> @llvm.sadd.sat.v2i8(<2 x i8> [[X:%.*]], <2 x i8> splat (i8 -10))
; CHECK-NEXT: ret <2 x i8> [[R]]
;
%cmp = icmp sle <2 x i8> %x, <i8 -119, i8 -119>
@@ -2785,10 +2779,7 @@ define <2 x i8> @sadd_sat_neg_constant_vector(<2 x i8> %x) {
; Pattern with variable: (INT_MIN - X > Y) ? INT_MIN : (X + Y) with nsw sub
define i8 @sadd_sat_int_min_minus_x_nsw(i8 %x, i8 %y) {
; CHECK-LABEL: @sadd_sat_int_min_minus_x_nsw(
-; CHECK-NEXT: [[SUB:%.*]] = sub nsw i8 -128, [[X:%.*]]
-; CHECK-NEXT: [[CMP:%.*]] = icmp sgt i8 [[SUB]], [[Y:%.*]]
-; CHECK-NEXT: [[ADD:%.*]] = add i8 [[X]], [[Y]]
-; CHECK-NEXT: [[R:%.*]] = select i1 [[CMP]], i8 -128, i8 [[ADD]]
+; CHECK-NEXT: [[R:%.*]] = call i8 @llvm.sadd.sat.i8(i8 [[X:%.*]], i8 [[Y:%.*]])
; CHECK-NEXT: ret i8 [[R]]
;
%sub = sub nsw i8 -128, %x
More information about the llvm-commits
mailing list