Skip to content

[InstCombine] Fold uadd.sat(X, C) - C to umin(X, ~C) - #215130

Merged
dtcxzyw merged 1 commit into
llvm:mainfrom
Bagodiya:instcombine-uaddsat-sub
Aug 10, 2026
Merged

[InstCombine] Fold uadd.sat(X, C) - C to umin(X, ~C)#215130
dtcxzyw merged 1 commit into
llvm:mainfrom
Bagodiya:instcombine-uaddsat-sub

Conversation

@Bagodiya

@Bagodiya Bagodiya commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

uadd.sat(X, C) - C --> umin(X, ~C) for nonzero C.

The saturating add gives X + C or UMAX, so subtracting C leaves X or
UMAX - C, which is the unsigned minimum. UMAX - C == ~C, so the constant
is just the inverted C.

There was already a test documenting this miss in saturating-add-sub.ll
(test_scalar_uadd_sub_const) - it folds now.

https://alive2.llvm.org/ce/z/kLFWy7

Fixes #215103

@Bagodiya
Bagodiya requested a review from nikic as a code owner August 9, 2026 19:14
@llvmorg-github-actions llvmorg-github-actions Bot added llvm:instcombine Covers the InstCombine, InstSimplify and AggressiveInstCombine passes llvm:transforms labels Aug 9, 2026
@llvmorg-github-actions

Copy link
Copy Markdown

@llvm/pr-subscribers-llvm-transforms

Author: Bagodiya (Bagodiya)

Changes

uadd.sat(X, C) - C --> umin(X, ~C) for nonzero C.

The saturating add gives X + C or UMAX, so subtracting C leaves X or
UMAX - C, which is the unsigned minimum. UMAX - C == ~C, so the constant
is just the inverted C.

There was already a test documenting this miss in saturating-add-sub.ll
(test_scalar_uadd_sub_const) - it folds now.

Alive2: https://alive2.llvm.org/ce/z/3MdJCo

Fixes #215103


Full diff: https://github.com/llvm/llvm-project/pull/215130.diff

2 Files Affected:

  • (modified) llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp (+11-1)
  • (modified) llvm/test/Transforms/InstCombine/saturating-add-sub.ll (+50)
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
index 52a57e153bb4b..1d74547b5c6d0 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
@@ -1003,7 +1003,17 @@ Instruction *InstCombinerImpl::foldAddWithConstant(BinaryOperator &Add) {
     return replaceInstUsesWith(
         Add, Builder.CreateBinaryIntrinsic(
                  Intrinsic::usub_sat, X, ConstantInt::get(Add.getType(), -*C)));
-
+  // uadd.sat(X, C) + -C --> umin(X, ~C)
+  // The saturating add gives X + C or UMAX, so subtracting C leaves X or
+  // UMAX - C. Note UMAX - C == ~C.
+  {
+    APInt SatC = -*C;
+    if (!SatC.isZero() && match(Op0, m_OneUse(m_Intrinsic<Intrinsic::uadd_sat>(
+                                         m_Value(X), m_SpecificInt(SatC)))))
+      return replaceInstUsesWith(
+          Add, Builder.CreateBinaryIntrinsic(Intrinsic::umin, X,
+                                             ConstantInt::get(Ty, ~SatC)));
+  }
   // Fold (add (zext (add X, -C)), C) -> (zext X) if X u>= C.
   // Truncate C to the narrow type to avoid mismatched width comparisons.
   {
diff --git a/llvm/test/Transforms/InstCombine/saturating-add-sub.ll b/llvm/test/Transforms/InstCombine/saturating-add-sub.ll
index f1cc997a41ba1..341459ed94c9d 100644
--- a/llvm/test/Transforms/InstCombine/saturating-add-sub.ll
+++ b/llvm/test/Transforms/InstCombine/saturating-add-sub.ll
@@ -1367,15 +1367,65 @@ define i8 @test_scalar_uadd_sub_commuted_wrong(i8 %a, i8 %b) {
 
 define i8 @test_scalar_uadd_sub_const(i8 %a) {
 ; CHECK-LABEL: @test_scalar_uadd_sub_const(
+; CHECK-NEXT:    [[RES:%.*]] = call i8 @llvm.umin.i8(i8 [[A:%.*]], i8 -43)
+; CHECK-NEXT:    ret i8 [[RES]]
+;
+  %sat = call i8 @llvm.uadd.sat.i8(i8 %a, i8 42)
+  %res = sub i8 %sat, 42
+  ret i8 %res
+}
+
+define <2 x i8> @test_vector_uadd_sub_const(<2 x i8> %a) {
+; CHECK-LABEL: @test_vector_uadd_sub_const(
+; CHECK-NEXT:    [[RES:%.*]] = call <2 x i8> @llvm.umin.v2i8(<2 x i8> [[A:%.*]], <2 x i8> splat (i8 -43))
+; CHECK-NEXT:    ret <2 x i8> [[RES]]
+;
+  %sat = call <2 x i8> @llvm.uadd.sat.v2i8(<2 x i8> %a, <2 x i8> splat (i8 42))
+  %res = sub <2 x i8> %sat, splat (i8 42)
+  ret <2 x i8> %res
+}
+
+; negative test - the constants do not cancel
+
+define i8 @test_scalar_uadd_sub_const_mismatch(i8 %a) {
+; CHECK-LABEL: @test_scalar_uadd_sub_const_mismatch(
+; CHECK-NEXT:    [[SAT:%.*]] = call i8 @llvm.uadd.sat.i8(i8 [[A:%.*]], i8 42)
+; CHECK-NEXT:    [[RES:%.*]] = add i8 [[SAT]], -43
+; CHECK-NEXT:    ret i8 [[RES]]
+;
+  %sat = call i8 @llvm.uadd.sat.i8(i8 %a, i8 42)
+  %res = sub i8 %sat, 43
+  ret i8 %res
+}
+
+; negative test - extra use of the saturating add
+
+define i8 @test_scalar_uadd_sub_const_multiuse(i8 %a) {
+; CHECK-LABEL: @test_scalar_uadd_sub_const_multiuse(
 ; CHECK-NEXT:    [[SAT:%.*]] = call i8 @llvm.uadd.sat.i8(i8 [[A:%.*]], i8 42)
 ; CHECK-NEXT:    [[RES:%.*]] = add i8 [[SAT]], -42
+; CHECK-NEXT:    call void @usei8(i8 [[SAT]])
 ; CHECK-NEXT:    ret i8 [[RES]]
 ;
   %sat = call i8 @llvm.uadd.sat.i8(i8 %a, i8 42)
   %res = sub i8 %sat, 42
+  call void @usei8(i8 %sat)
   ret i8 %res
 }
 
+; negative test - non-splat vector
+
+define <2 x i8> @test_vector_uadd_sub_const_nonsplat(<2 x i8> %a) {
+; CHECK-LABEL: @test_vector_uadd_sub_const_nonsplat(
+; CHECK-NEXT:    [[SAT:%.*]] = call <2 x i8> @llvm.uadd.sat.v2i8(<2 x i8> [[A:%.*]], <2 x i8> <i8 42, i8 3>)
+; CHECK-NEXT:    [[RES:%.*]] = add <2 x i8> [[SAT]], <i8 -42, i8 -3>
+; CHECK-NEXT:    ret <2 x i8> [[RES]]
+;
+  %sat = call <2 x i8> @llvm.uadd.sat.v2i8(<2 x i8> %a, <2 x i8> <i8 42, i8 3>)
+  %res = sub <2 x i8> %sat, <i8 42, i8 3>
+  ret <2 x i8> %res
+}
+
 define i1 @scalar_uadd_eq_zero(i8 %a, i8 %b) {
 ; CHECK-LABEL: @scalar_uadd_eq_zero(
 ; CHECK-NEXT:    [[TMP1:%.*]] = or i8 [[A:%.*]], [[B:%.*]]

@dtcxzyw dtcxzyw left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.
Generalized proof: https://alive2.llvm.org/ce/z/kLFWy7

// UMAX - C. Note UMAX - C == ~C.
{
APInt SatC = -*C;
if (!SatC.isZero() && match(Op0, m_OneUse(m_Intrinsic<Intrinsic::uadd_sat>(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (!SatC.isZero() && match(Op0, m_OneUse(m_Intrinsic<Intrinsic::uadd_sat>(
if (match(Op0, m_OneUse(m_Intrinsic<Intrinsic::uadd_sat>(

Redundant check.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, thanks.

@dtcxzyw

dtcxzyw commented Aug 10, 2026

Copy link
Copy Markdown
Member

@Bagodiya Please follow https://llvm.org/docs/InstCombineContributorGuide.html#proofs to provide a generalized proof next time you create a new PR.

@Bagodiya
Bagodiya force-pushed the instcombine-uaddsat-sub branch from bc47d06 to bb6dc05 Compare August 10, 2026 12:21
@Bagodiya

Copy link
Copy Markdown
Contributor Author

Thanks for the pointer, I'll provide generalized proofs going forward

@dtcxzyw
dtcxzyw enabled auto-merge (squash) August 10, 2026 12:51
@dtcxzyw
dtcxzyw merged commit 45d4f99 into llvm:main Aug 10, 2026
10 of 12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

llvm:instcombine Covers the InstCombine, InstSimplify and AggressiveInstCombine passes llvm:transforms

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[InstCombine] Missed fold uadd.sat(x, C) - C to umin(x, umax - C)

2 participants