[llvm] [InstCombine] Fold umin(mul/add(zext, zext), max) into overflow intrinsics (PR #221809)

via llvm-commits llvm-commits at lists.llvm.org
Mon Sep 7 13:46:10 PDT 2026


https://github.com/AZero13 updated https://github.com/llvm/llvm-project/pull/221809

>From 3d54b00e885184fd024cf19e819802e5d2711600 Mon Sep 17 00:00:00 2001
From: AZero13 <gfunni234 at gmail.com>
Date: Mon, 7 Sep 2026 15:13:12 -0400
Subject: [PATCH 1/4] [InstCombine] Pre-commit tests for saturating overflow
 folds

---
 .../InstCombine/umin-mul-overflow.ll          | 169 ++++++++++++++++++
 1 file changed, 169 insertions(+)
 create mode 100644 llvm/test/Transforms/InstCombine/umin-mul-overflow.ll

diff --git a/llvm/test/Transforms/InstCombine/umin-mul-overflow.ll b/llvm/test/Transforms/InstCombine/umin-mul-overflow.ll
new file mode 100644
index 0000000000000..938c04c63bbee
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/umin-mul-overflow.ll
@@ -0,0 +1,169 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+
+define i32 @test_mul_sat_32(i32 %a, i32 %b) {
+; CHECK-LABEL: @test_mul_sat_32(
+; CHECK-NEXT:    [[ZA:%.*]] = zext i32 [[A:%.*]] to i64
+; CHECK-NEXT:    [[ZB:%.*]] = zext i32 [[B:%.*]] to i64
+; CHECK-NEXT:    [[MUL:%.*]] = mul nuw i64 [[ZA]], [[ZB]]
+; CHECK-NEXT:    [[UMIN:%.*]] = call i64 @llvm.umin.i64(i64 [[MUL]], i64 4294967295)
+; CHECK-NEXT:    [[TRUNC:%.*]] = trunc nuw i64 [[UMIN]] to i32
+; CHECK-NEXT:    ret i32 [[TRUNC]]
+;
+  %za = zext i32 %a to i64
+  %zb = zext i32 %b to i64
+  %mul = mul nuw i64 %za, %zb
+  %umin = call i64 @llvm.umin.i64(i64 %mul, i64 4294967295)
+  %trunc = trunc i64 %umin to i32
+  ret i32 %trunc
+}
+
+define i64 @test_mul_sat_64(i64 %a, i64 %b) {
+; CHECK-LABEL: @test_mul_sat_64(
+; CHECK-NEXT:    [[ZA:%.*]] = zext i64 [[A:%.*]] to i128
+; CHECK-NEXT:    [[ZB:%.*]] = zext i64 [[B:%.*]] to i128
+; CHECK-NEXT:    [[MUL:%.*]] = mul nuw i128 [[ZA]], [[ZB]]
+; CHECK-NEXT:    [[UMIN:%.*]] = call i128 @llvm.umin.i128(i128 [[MUL]], i128 18446744073709551615)
+; CHECK-NEXT:    [[TRUNC:%.*]] = trunc nuw i128 [[UMIN]] to i64
+; CHECK-NEXT:    ret i64 [[TRUNC]]
+;
+  %za = zext i64 %a to i128
+  %zb = zext i64 %b to i128
+  %mul = mul nuw i128 %za, %zb
+  %umin = call i128 @llvm.umin.i128(i128 %mul, i128 18446744073709551615)
+  %trunc = trunc i128 %umin to i64
+  ret i64 %trunc
+}
+
+declare i64 @llvm.umin.i64(i64, i64)
+declare i128 @llvm.umin.i128(i128, i128)
+
+define i32 @test_add_sat_32(i32 %a, i32 %b) {
+; CHECK-LABEL: @test_add_sat_32(
+; CHECK-NEXT:    [[TRUNC:%.*]] = call i32 @llvm.uadd.sat.i32(i32 [[A:%.*]], i32 [[B:%.*]])
+; CHECK-NEXT:    ret i32 [[TRUNC]]
+;
+  %za = zext i32 %a to i64
+  %zb = zext i32 %b to i64
+  %add = add nuw i64 %za, %zb
+  %umin = call i64 @llvm.umin.i64(i64 %add, i64 4294967295)
+  %trunc = trunc i64 %umin to i32
+  ret i32 %trunc
+}
+
+define i64 @test_add_sat_64(i64 %a, i64 %b) {
+; CHECK-LABEL: @test_add_sat_64(
+; CHECK-NEXT:    [[TRUNC:%.*]] = call i64 @llvm.uadd.sat.i64(i64 [[A:%.*]], i64 [[B:%.*]])
+; CHECK-NEXT:    ret i64 [[TRUNC]]
+;
+  %za = zext i64 %a to i128
+  %zb = zext i64 %b to i128
+  %add = add nuw i128 %za, %zb
+  %umin = call i128 @llvm.umin.i128(i128 %add, i128 18446744073709551615)
+  %trunc = trunc i128 %umin to i64
+  ret i64 %trunc
+}
+
+define i9 @test_add_nsw_poison(i8 %a, i8 %b) {
+; CHECK-LABEL: @test_add_nsw_poison(
+; CHECK-NEXT:    [[Y1:%.*]] = zext i8 [[B1:%.*]] to i9
+; CHECK-NEXT:    [[Y:%.*]] = zext i8 [[B:%.*]] to i9
+; CHECK-NEXT:    [[P:%.*]] = add nuw nsw i9 [[Y1]], [[Y]]
+; CHECK-NEXT:    ret i9 [[P]]
+;
+  %x = zext i8 %a to i9
+  %y = zext i8 %b to i9
+  %p = add nsw i9 %x, %y
+  %r = call i9 @llvm.umin.i9(i9 %p, i9 255)
+  ret i9 %r
+}
+
+; Negative test: mul has multi-use, fold should NOT fire
+define i64 @test_mul_multiuse(i32 %a, i32 %b) {
+; CHECK-LABEL: @test_mul_multiuse(
+; CHECK-NEXT:    [[ZA:%.*]] = zext i32 [[A:%.*]] to i64
+; CHECK-NEXT:    [[ZB:%.*]] = zext i32 [[B:%.*]] to i64
+; CHECK-NEXT:    [[MUL:%.*]] = mul nuw i64 [[ZA]], [[ZB]]
+; CHECK-NEXT:    [[UMIN:%.*]] = call i64 @llvm.umin.i64(i64 [[MUL]], i64 4294967295)
+; CHECK-NEXT:    [[OTHER:%.*]] = add i64 [[MUL]], 1
+; CHECK-NEXT:    [[RES:%.*]] = add i64 [[OTHER]], [[UMIN]]
+; CHECK-NEXT:    ret i64 [[RES]]
+;
+  %za = zext i32 %a to i64
+  %zb = zext i32 %b to i64
+  %mul = mul nuw i64 %za, %zb
+  %umin = call i64 @llvm.umin.i64(i64 %mul, i64 4294967295)
+  %trunc = trunc i64 %umin to i32
+  %other = add i64 %mul, 1
+  %res = add i64 %other, %umin
+  ret i64 %res
+}
+
+; Negative test: add has multi-use, fold should NOT fire
+define i64 @test_add_multiuse(i32 %a, i32 %b) {
+; CHECK-LABEL: @test_add_multiuse(
+; CHECK-NEXT:    [[ZA:%.*]] = zext i32 [[A:%.*]] to i64
+; CHECK-NEXT:    [[ZB:%.*]] = zext i32 [[B:%.*]] to i64
+; CHECK-NEXT:    [[ADD:%.*]] = add nuw nsw i64 [[ZA]], [[ZB]]
+; CHECK-NEXT:    [[UMIN:%.*]] = call i64 @llvm.umin.i64(i64 [[ADD]], i64 4294967295)
+; CHECK-NEXT:    [[OTHER:%.*]] = add nuw nsw i64 [[ADD]], 1
+; CHECK-NEXT:    [[RES:%.*]] = add nuw nsw i64 [[OTHER]], [[UMIN]]
+; CHECK-NEXT:    ret i64 [[RES]]
+;
+  %za = zext i32 %a to i64
+  %zb = zext i32 %b to i64
+  %add = add nuw i64 %za, %zb
+  %umin = call i64 @llvm.umin.i64(i64 %add, i64 4294967295)
+  %trunc = trunc i64 %umin to i32
+  %other = add i64 %add, 1
+  %res = add i64 %other, %umin
+  ret i64 %res
+}
+
+; Positive test: mul has multi-use, but they are all truncs <= SrcBitWidth
+define i32 @test_mul_multiuse_trunc(i32 %a, i32 %b) {
+; CHECK-LABEL: @test_mul_multiuse_trunc(
+; CHECK-NEXT:    [[ZA:%.*]] = zext i32 [[A:%.*]] to i64
+; CHECK-NEXT:    [[ZB:%.*]] = zext i32 [[B:%.*]] to i64
+; CHECK-NEXT:    [[MUL:%.*]] = mul nuw i64 [[ZA]], [[ZB]]
+; CHECK-NEXT:    [[UMIN:%.*]] = call i64 @llvm.umin.i64(i64 [[MUL]], i64 4294967295)
+; CHECK-NEXT:    [[TRUNC1:%.*]] = trunc nuw i64 [[UMIN]] to i32
+; CHECK-NEXT:    [[UMUL_VALUE:%.*]] = trunc i64 [[MUL]] to i32
+; CHECK-NEXT:    [[ZEXT:%.*]] = and i32 [[UMUL_VALUE]], 65535
+; CHECK-NEXT:    [[RES:%.*]] = add i32 [[ZEXT]], [[TRUNC1]]
+; CHECK-NEXT:    ret i32 [[RES]]
+;
+  %za = zext i32 %a to i64
+  %zb = zext i32 %b to i64
+  %mul = mul nuw i64 %za, %zb
+  %umin = call i64 @llvm.umin.i64(i64 %mul, i64 4294967295)
+  %trunc1 = trunc i64 %umin to i32
+  %trunc2 = trunc i64 %mul to i16
+  %zext = zext i16 %trunc2 to i32
+  %res = add i32 %trunc1, %zext
+  ret i32 %res
+}
+
+; Positive test: add has multi-use, but they are all truncs <= SrcBitWidth
+define i32 @test_add_multiuse_trunc(i32 %a, i32 %b) {
+; CHECK-LABEL: @test_add_multiuse_trunc(
+; CHECK-NEXT:    [[ZA:%.*]] = zext i32 [[A:%.*]] to i64
+; CHECK-NEXT:    [[ZB:%.*]] = zext i32 [[B:%.*]] to i64
+; CHECK-NEXT:    [[ADD:%.*]] = add nuw nsw i64 [[ZA]], [[ZB]]
+; CHECK-NEXT:    [[UMIN:%.*]] = call i64 @llvm.umin.i64(i64 [[ADD]], i64 4294967295)
+; CHECK-NEXT:    [[TRUNC1:%.*]] = trunc nuw i64 [[UMIN]] to i32
+; CHECK-NEXT:    [[UADD:%.*]] = trunc i64 [[ADD]] to i32
+; CHECK-NEXT:    [[ZEXT:%.*]] = and i32 [[UADD]], 65535
+; CHECK-NEXT:    [[RES:%.*]] = add i32 [[ZEXT]], [[TRUNC1]]
+; CHECK-NEXT:    ret i32 [[RES]]
+;
+  %za = zext i32 %a to i64
+  %zb = zext i32 %b to i64
+  %add = add nuw i64 %za, %zb
+  %umin = call i64 @llvm.umin.i64(i64 %add, i64 4294967295)
+  %trunc1 = trunc i64 %umin to i32
+  %trunc2 = trunc i64 %add to i16
+  %zext = zext i16 %trunc2 to i32
+  %res = add i32 %trunc1, %zext
+  ret i32 %res
+}

>From bfa10b7bf0eca3ec1423e9aec4a0e9a6f2a82a54 Mon Sep 17 00:00:00 2001
From: AZero13 <gfunni234 at gmail.com>
Date: Mon, 7 Sep 2026 16:37:47 -0400
Subject: [PATCH 2/4] [InstCombine] Pre-commit tests for saturating overflow
 folds

---
 .../InstCombine/InstCombineCalls.cpp          | 80 +++++++++++++++++++
 .../InstCombine/umin-mul-overflow.ll          | 47 +++++------
 2 files changed, 99 insertions(+), 28 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
index e888281baeae2..667248b941b0d 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
@@ -2037,6 +2037,82 @@ static Value *foldCmpIntrinsicOfExtended(IntrinsicInst *II,
   return Builder.CreateIntrinsic(II->getType(), NewIID, {X, Y});
 }
 
+static Instruction *foldUMinSatOverflow(IntrinsicInst &II, Value *I0, Value *I1,
+                                        InstCombiner::BuilderTy &Builder,
+                                        InstCombinerImpl &IC) {
+  // umin(mul/add(zext(A), zext(B)), MaxVal) ->
+  // select(umul/uadd.with.overflow(A, B), MaxVal, zext(mul/add))
+  const APInt *C;
+  Value *X, *Y;
+  if (match(I1, m_APInt(C)) &&
+      match(I0, m_BinOp(m_ZExt(m_Value(X)), m_ZExt(m_Value(Y))))) {
+    unsigned Opc = cast<BinaryOperator>(I0)->getOpcode();
+    if (Opc == Instruction::Mul || Opc == Instruction::Add) {
+      Type *Ty = X->getType();
+      if (Ty == Y->getType() && Ty->isIntOrIntVectorTy()) {
+        unsigned SrcBitWidth = Ty->getScalarSizeInBits();
+        unsigned DstBitWidth = I0->getType()->getScalarSizeInBits();
+        bool IsMul = Opc == Instruction::Mul;
+
+        if (C->isMask(SrcBitWidth) &&
+            DstBitWidth >= (IsMul ? 2 * SrcBitWidth : SrcBitWidth + 1)) {
+          // Check that all other users of the wide binop only need the
+          // low bits (trunc to <= SrcBitWidth, or and with low mask).
+          // This mirrors processUZExtIdiom's multi-use handling.
+          bool CanReplace = true;
+          if (!I0->hasOneUse()) {
+            for (User *U : I0->users()) {
+              if (U == &II)
+                continue;
+              if (auto *TI = dyn_cast<TruncInst>(U)) {
+                if (TI->getType()->getScalarSizeInBits() > SrcBitWidth) {
+                  CanReplace = false;
+                  break;
+                }
+              } else {
+                CanReplace = false;
+                break;
+              }
+            }
+          }
+          if (!CanReplace)
+            return nullptr;
+
+          Value *Val, *Ov;
+          if (IsMul) {
+            Value *Ovf = Builder.CreateIntrinsic(Intrinsic::umul_with_overflow,
+                                                 Ty, {X, Y},
+                                                 /*FMFSource=*/nullptr, "umul");
+            Val = Builder.CreateExtractValue(Ovf, 0, "umul.value");
+            Ov = Builder.CreateExtractValue(Ovf, 1, "umul.overflow");
+          } else {
+            Val = Builder.CreateAdd(X, Y, "uadd");
+            Ov = Builder.CreateICmpULT(Val, X, "uadd.overflow");
+          }
+          Value *ZExtVal = Builder.CreateZExt(
+              Val, I0->getType(), IsMul ? "umul.zext" : "uadd.zext");
+
+          // Replace other users of the wide binop with the narrow result.
+          if (!I0->hasOneUse()) {
+            for (User *U : llvm::make_early_inc_range(I0->users())) {
+              if (U == &II)
+                continue;
+              auto *TI = cast<TruncInst>(U);
+              if (TI->getType()->getScalarSizeInBits() == SrcBitWidth)
+                IC.replaceInstUsesWith(*TI, Val);
+              else
+                TI->setOperand(0, ZExtVal);
+              IC.addToWorklist(TI);
+            }
+          }
+          return SelectInst::Create(Ov, I1, ZExtVal);
+        }
+      }
+    }
+  }
+  return nullptr;
+}
+
 /// CallInst simplification. This mostly only handles folding of intrinsic
 /// instructions. For normal calls, it allows visitCallBase to do the heavy
 /// lifting.
@@ -2265,6 +2341,10 @@ Instruction *InstCombinerImpl::visitCallInst(CallInst &CI) {
             foldMinimumOverTrailingOrLeadingZeroCount<Intrinsic::ctlz>(
                 I0, I1, DL, Builder))
       return replaceInstUsesWith(*II, FoldedCtlz);
+
+    if (Instruction *I = foldUMinSatOverflow(*II, I0, I1, Builder, *this))
+      return I;
+
     [[fallthrough]];
   }
   case Intrinsic::umax: {
diff --git a/llvm/test/Transforms/InstCombine/umin-mul-overflow.ll b/llvm/test/Transforms/InstCombine/umin-mul-overflow.ll
index 938c04c63bbee..019ad5001964a 100644
--- a/llvm/test/Transforms/InstCombine/umin-mul-overflow.ll
+++ b/llvm/test/Transforms/InstCombine/umin-mul-overflow.ll
@@ -3,11 +3,10 @@
 
 define i32 @test_mul_sat_32(i32 %a, i32 %b) {
 ; CHECK-LABEL: @test_mul_sat_32(
-; CHECK-NEXT:    [[ZA:%.*]] = zext i32 [[A:%.*]] to i64
-; CHECK-NEXT:    [[ZB:%.*]] = zext i32 [[B:%.*]] to i64
-; CHECK-NEXT:    [[MUL:%.*]] = mul nuw i64 [[ZA]], [[ZB]]
-; CHECK-NEXT:    [[UMIN:%.*]] = call i64 @llvm.umin.i64(i64 [[MUL]], i64 4294967295)
-; CHECK-NEXT:    [[TRUNC:%.*]] = trunc nuw i64 [[UMIN]] to i32
+; CHECK-NEXT:    [[UMUL:%.*]] = call { i32, i1 } @llvm.umul.with.overflow.i32(i32 [[A:%.*]], i32 [[B:%.*]])
+; CHECK-NEXT:    [[UMUL_VALUE:%.*]] = extractvalue { i32, i1 } [[UMUL]], 0
+; CHECK-NEXT:    [[UMUL_OVERFLOW:%.*]] = extractvalue { i32, i1 } [[UMUL]], 1
+; CHECK-NEXT:    [[TRUNC:%.*]] = select i1 [[UMUL_OVERFLOW]], i32 -1, i32 [[UMUL_VALUE]]
 ; CHECK-NEXT:    ret i32 [[TRUNC]]
 ;
   %za = zext i32 %a to i64
@@ -20,11 +19,10 @@ define i32 @test_mul_sat_32(i32 %a, i32 %b) {
 
 define i64 @test_mul_sat_64(i64 %a, i64 %b) {
 ; CHECK-LABEL: @test_mul_sat_64(
-; CHECK-NEXT:    [[ZA:%.*]] = zext i64 [[A:%.*]] to i128
-; CHECK-NEXT:    [[ZB:%.*]] = zext i64 [[B:%.*]] to i128
-; CHECK-NEXT:    [[MUL:%.*]] = mul nuw i128 [[ZA]], [[ZB]]
-; CHECK-NEXT:    [[UMIN:%.*]] = call i128 @llvm.umin.i128(i128 [[MUL]], i128 18446744073709551615)
-; CHECK-NEXT:    [[TRUNC:%.*]] = trunc nuw i128 [[UMIN]] to i64
+; CHECK-NEXT:    [[UMUL:%.*]] = call { i64, i1 } @llvm.umul.with.overflow.i64(i64 [[A:%.*]], i64 [[B:%.*]])
+; CHECK-NEXT:    [[UMUL_VALUE:%.*]] = extractvalue { i64, i1 } [[UMUL]], 0
+; CHECK-NEXT:    [[UMUL_OVERFLOW:%.*]] = extractvalue { i64, i1 } [[UMUL]], 1
+; CHECK-NEXT:    [[TRUNC:%.*]] = select i1 [[UMUL_OVERFLOW]], i64 -1, i64 [[UMUL_VALUE]]
 ; CHECK-NEXT:    ret i64 [[TRUNC]]
 ;
   %za = zext i64 %a to i128
@@ -66,10 +64,9 @@ define i64 @test_add_sat_64(i64 %a, i64 %b) {
 
 define i9 @test_add_nsw_poison(i8 %a, i8 %b) {
 ; CHECK-LABEL: @test_add_nsw_poison(
-; CHECK-NEXT:    [[Y1:%.*]] = zext i8 [[B1:%.*]] to i9
-; CHECK-NEXT:    [[Y:%.*]] = zext i8 [[B:%.*]] to i9
-; CHECK-NEXT:    [[P:%.*]] = add nuw nsw i9 [[Y1]], [[Y]]
-; CHECK-NEXT:    ret i9 [[P]]
+; CHECK-NEXT:    [[B:%.*]] = call i8 @llvm.uadd.sat.i8(i8 [[A:%.*]], i8 [[B1:%.*]])
+; CHECK-NEXT:    [[Y:%.*]] = zext i8 [[B]] to i9
+; CHECK-NEXT:    ret i9 [[Y]]
 ;
   %x = zext i8 %a to i9
   %y = zext i8 %b to i9
@@ -123,14 +120,12 @@ define i64 @test_add_multiuse(i32 %a, i32 %b) {
 ; Positive test: mul has multi-use, but they are all truncs <= SrcBitWidth
 define i32 @test_mul_multiuse_trunc(i32 %a, i32 %b) {
 ; CHECK-LABEL: @test_mul_multiuse_trunc(
-; CHECK-NEXT:    [[ZA:%.*]] = zext i32 [[A:%.*]] to i64
-; CHECK-NEXT:    [[ZB:%.*]] = zext i32 [[B:%.*]] to i64
-; CHECK-NEXT:    [[MUL:%.*]] = mul nuw i64 [[ZA]], [[ZB]]
-; CHECK-NEXT:    [[UMIN:%.*]] = call i64 @llvm.umin.i64(i64 [[MUL]], i64 4294967295)
-; CHECK-NEXT:    [[TRUNC1:%.*]] = trunc nuw i64 [[UMIN]] to i32
-; CHECK-NEXT:    [[UMUL_VALUE:%.*]] = trunc i64 [[MUL]] to i32
+; CHECK-NEXT:    [[UMUL:%.*]] = call { i32, i1 } @llvm.umul.with.overflow.i32(i32 [[A:%.*]], i32 [[B:%.*]])
+; CHECK-NEXT:    [[UMUL_VALUE:%.*]] = extractvalue { i32, i1 } [[UMUL]], 0
+; CHECK-NEXT:    [[UMUL_OVERFLOW:%.*]] = extractvalue { i32, i1 } [[UMUL]], 1
+; CHECK-NEXT:    [[TRUNC1:%.*]] = select i1 [[UMUL_OVERFLOW]], i32 -1, i32 [[UMUL_VALUE]]
 ; CHECK-NEXT:    [[ZEXT:%.*]] = and i32 [[UMUL_VALUE]], 65535
-; CHECK-NEXT:    [[RES:%.*]] = add i32 [[ZEXT]], [[TRUNC1]]
+; CHECK-NEXT:    [[RES:%.*]] = add i32 [[TRUNC1]], [[ZEXT]]
 ; CHECK-NEXT:    ret i32 [[RES]]
 ;
   %za = zext i32 %a to i64
@@ -147,14 +142,10 @@ define i32 @test_mul_multiuse_trunc(i32 %a, i32 %b) {
 ; Positive test: add has multi-use, but they are all truncs <= SrcBitWidth
 define i32 @test_add_multiuse_trunc(i32 %a, i32 %b) {
 ; CHECK-LABEL: @test_add_multiuse_trunc(
-; CHECK-NEXT:    [[ZA:%.*]] = zext i32 [[A:%.*]] to i64
-; CHECK-NEXT:    [[ZB:%.*]] = zext i32 [[B:%.*]] to i64
-; CHECK-NEXT:    [[ADD:%.*]] = add nuw nsw i64 [[ZA]], [[ZB]]
-; CHECK-NEXT:    [[UMIN:%.*]] = call i64 @llvm.umin.i64(i64 [[ADD]], i64 4294967295)
-; CHECK-NEXT:    [[TRUNC1:%.*]] = trunc nuw i64 [[UMIN]] to i32
-; CHECK-NEXT:    [[UADD:%.*]] = trunc i64 [[ADD]] to i32
+; CHECK-NEXT:    [[UADD:%.*]] = add i32 [[A:%.*]], [[B:%.*]]
+; CHECK-NEXT:    [[TRUNC1:%.*]] = call i32 @llvm.uadd.sat.i32(i32 [[A]], i32 [[B]])
 ; CHECK-NEXT:    [[ZEXT:%.*]] = and i32 [[UADD]], 65535
-; CHECK-NEXT:    [[RES:%.*]] = add i32 [[ZEXT]], [[TRUNC1]]
+; CHECK-NEXT:    [[RES:%.*]] = add i32 [[TRUNC1]], [[ZEXT]]
 ; CHECK-NEXT:    ret i32 [[RES]]
 ;
   %za = zext i32 %a to i64

>From 9b1c4fd54e56c9386fa93c4a27bc90950c4b1241 Mon Sep 17 00:00:00 2001
From: AZero13 <gfunni234 at gmail.com>
Date: Mon, 7 Sep 2026 16:40:30 -0400
Subject: [PATCH 3/4] [InstCombine] Fold umin(mul/add(zext, zext), max) into
 overflow intrinsics

---
 .../InstCombine/InstCombineCalls.cpp          | 37 ++++++++++++++++---
 1 file changed, 31 insertions(+), 6 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
index 667248b941b0d..11e6083721315 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
@@ -2069,6 +2069,21 @@ static Instruction *foldUMinSatOverflow(IntrinsicInst &II, Value *I0, Value *I1,
                   CanReplace = false;
                   break;
                 }
+              } else if (auto *BO = dyn_cast<BinaryOperator>(U)) {
+                if (BO->getOpcode() != Instruction::And) {
+                  CanReplace = false;
+                  break;
+                }
+                if (auto *CI = dyn_cast<ConstantInt>(BO->getOperand(1))) {
+                  const APInt &CVal = CI->getValue();
+                  if (CVal.getBitWidth() - CVal.countl_zero() > SrcBitWidth) {
+                    CanReplace = false;
+                    break;
+                  }
+                } else {
+                  CanReplace = false;
+                  break;
+                }
               } else {
                 CanReplace = false;
                 break;
@@ -2097,12 +2112,22 @@ static Instruction *foldUMinSatOverflow(IntrinsicInst &II, Value *I0, Value *I1,
             for (User *U : llvm::make_early_inc_range(I0->users())) {
               if (U == &II)
                 continue;
-              auto *TI = cast<TruncInst>(U);
-              if (TI->getType()->getScalarSizeInBits() == SrcBitWidth)
-                IC.replaceInstUsesWith(*TI, Val);
-              else
-                TI->setOperand(0, ZExtVal);
-              IC.addToWorklist(TI);
+              if (auto *TI = dyn_cast<TruncInst>(U)) {
+                if (TI->getType()->getScalarSizeInBits() == SrcBitWidth)
+                  IC.replaceInstUsesWith(*TI, Val);
+                else
+                  TI->setOperand(0, ZExtVal);
+                IC.addToWorklist(TI);
+              } else if (auto *BO = dyn_cast<BinaryOperator>(U)) {
+                assert(BO->getOpcode() == Instruction::And);
+                // Replace (WideOp & mask) --> zext (Val & short_mask)
+                ConstantInt *CI = cast<ConstantInt>(BO->getOperand(1));
+                APInt ShortMask = CI->getValue().trunc(SrcBitWidth);
+                Value *ShortAnd = Builder.CreateAnd(Val, ShortMask);
+                Value *Zext = Builder.CreateZExt(ShortAnd, BO->getType());
+                IC.replaceInstUsesWith(*BO, Zext);
+                IC.addToWorklist(BO);
+              }
             }
           }
           return SelectInst::Create(Ov, I1, ZExtVal);

>From a87489167c7e0f5c4af600fb0e3d34ce08a5aaaf Mon Sep 17 00:00:00 2001
From: AZero13 <gfunni234 at gmail.com>
Date: Mon, 7 Sep 2026 16:45:59 -0400
Subject: [PATCH 4/4] [InstCombine] NFC: Extract shared multi-use overflow
 logic

---
 .../InstCombine/InstCombineCalls.cpp          | 56 +------------------
 .../InstCombine/InstCombineCompares.cpp       | 54 +-----------------
 .../InstCombine/InstCombineInternal.h         |  8 +++
 .../InstCombine/InstructionCombining.cpp      | 56 +++++++++++++++++++
 4 files changed, 69 insertions(+), 105 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
index 11e6083721315..0e2ca175162d2 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
@@ -2059,38 +2059,7 @@ static Instruction *foldUMinSatOverflow(IntrinsicInst &II, Value *I0, Value *I1,
           // Check that all other users of the wide binop only need the
           // low bits (trunc to <= SrcBitWidth, or and with low mask).
           // This mirrors processUZExtIdiom's multi-use handling.
-          bool CanReplace = true;
-          if (!I0->hasOneUse()) {
-            for (User *U : I0->users()) {
-              if (U == &II)
-                continue;
-              if (auto *TI = dyn_cast<TruncInst>(U)) {
-                if (TI->getType()->getScalarSizeInBits() > SrcBitWidth) {
-                  CanReplace = false;
-                  break;
-                }
-              } else if (auto *BO = dyn_cast<BinaryOperator>(U)) {
-                if (BO->getOpcode() != Instruction::And) {
-                  CanReplace = false;
-                  break;
-                }
-                if (auto *CI = dyn_cast<ConstantInt>(BO->getOperand(1))) {
-                  const APInt &CVal = CI->getValue();
-                  if (CVal.getBitWidth() - CVal.countl_zero() > SrcBitWidth) {
-                    CanReplace = false;
-                    break;
-                  }
-                } else {
-                  CanReplace = false;
-                  break;
-                }
-              } else {
-                CanReplace = false;
-                break;
-              }
-            }
-          }
-          if (!CanReplace)
+          if (!IC.canReplaceWideOverflowIdiomUsers(I0, SrcBitWidth, &II))
             return nullptr;
 
           Value *Val, *Ov;
@@ -2108,28 +2077,7 @@ static Instruction *foldUMinSatOverflow(IntrinsicInst &II, Value *I0, Value *I1,
               Val, I0->getType(), IsMul ? "umul.zext" : "uadd.zext");
 
           // Replace other users of the wide binop with the narrow result.
-          if (!I0->hasOneUse()) {
-            for (User *U : llvm::make_early_inc_range(I0->users())) {
-              if (U == &II)
-                continue;
-              if (auto *TI = dyn_cast<TruncInst>(U)) {
-                if (TI->getType()->getScalarSizeInBits() == SrcBitWidth)
-                  IC.replaceInstUsesWith(*TI, Val);
-                else
-                  TI->setOperand(0, ZExtVal);
-                IC.addToWorklist(TI);
-              } else if (auto *BO = dyn_cast<BinaryOperator>(U)) {
-                assert(BO->getOpcode() == Instruction::And);
-                // Replace (WideOp & mask) --> zext (Val & short_mask)
-                ConstantInt *CI = cast<ConstantInt>(BO->getOperand(1));
-                APInt ShortMask = CI->getValue().trunc(SrcBitWidth);
-                Value *ShortAnd = Builder.CreateAnd(Val, ShortMask);
-                Value *Zext = Builder.CreateZExt(ShortAnd, BO->getType());
-                IC.replaceInstUsesWith(*BO, Zext);
-                IC.addToWorklist(BO);
-              }
-            }
-          }
+          IC.replaceWideOverflowIdiomUsers(I0, SrcBitWidth, Val, &II);
           return SelectInst::Create(Ov, I1, ZExtVal);
         }
       }
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
index 270d3596ad679..2bdabdcc9eda6 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
@@ -6826,34 +6826,8 @@ static Instruction *processUZExtIdiom(ICmpInst &I, Value *Val,
   // In order to replace the original result with a narrower one, all uses must
   // ignore upper bits of the result. The number of used low bits must be not
   // greater than the width of add or mul.with.overflow.
-  if (Val->hasNUsesOrMore(2))
-    for (User *U : Val->users()) {
-      if (U == &I)
-        continue;
-      if (TruncInst *TI = dyn_cast<TruncInst>(U)) {
-        // Check if truncation ignores bits above ResultWidth.
-        unsigned TruncWidth = TI->getType()->getPrimitiveSizeInBits();
-        if (TruncWidth > ResultWidth)
-          return nullptr;
-      } else if (BinaryOperator *BO = dyn_cast<BinaryOperator>(U)) {
-        // Check if AND ignores bits above ResultWidth.
-        if (BO->getOpcode() != Instruction::And)
-          return nullptr;
-        if (ConstantInt *CI = dyn_cast<ConstantInt>(BO->getOperand(1))) {
-          const APInt &CVal = CI->getValue();
-          if (CVal.getBitWidth() - CVal.countl_zero() > ResultWidth)
-            return nullptr;
-        } else {
-          // In this case we could have the operand of the binary operation
-          // being defined in another block, and performing the replacement
-          // could break the dominance relation.
-          return nullptr;
-        }
-      } else {
-        // Other uses prohibit this transformation.
-        return nullptr;
-      }
-    }
+  if (!IC.canReplaceWideOverflowIdiomUsers(Val, ResultWidth, &I))
+    return nullptr;
 
   // Recognize patterns
   switch (I.getPredicate()) {
@@ -6919,29 +6893,7 @@ static Instruction *processUZExtIdiom(ICmpInst &I, Value *Val,
   IC.addToWorklist(Instr);
 
   // Replace uses of the original add/mul result with the new arithmetic result
-  if (Val->hasNUsesOrMore(2)) {
-    for (User *U : make_early_inc_range(Val->users())) {
-      if (U == &I)
-        continue;
-      if (TruncInst *TI = dyn_cast<TruncInst>(U)) {
-        if (TI->getType()->getPrimitiveSizeInBits() == ResultWidth)
-          IC.replaceInstUsesWith(*TI, ArithResult);
-        else
-          TI->setOperand(0, ArithResult);
-      } else if (BinaryOperator *BO = dyn_cast<BinaryOperator>(U)) {
-        assert(BO->getOpcode() == Instruction::And);
-        // Replace (ArithResult & mask) --> zext (ArithResult & short_mask)
-        ConstantInt *CI = cast<ConstantInt>(BO->getOperand(1));
-        APInt ShortMask = CI->getValue().trunc(ResultWidth);
-        Value *ShortAnd = Builder.CreateAnd(ArithResult, ShortMask);
-        Value *Zext = Builder.CreateZExt(ShortAnd, BO->getType());
-        IC.replaceInstUsesWith(*BO, Zext);
-      } else {
-        llvm_unreachable("Unexpected Binary operation");
-      }
-      IC.addToWorklist(cast<Instruction>(U));
-    }
-  }
+  IC.replaceWideOverflowIdiomUsers(Val, ResultWidth, ArithResult, &I);
 
   return IC.replaceInstUsesWith(I, OverflowCheck);
 }
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineInternal.h b/llvm/lib/Transforms/InstCombine/InstCombineInternal.h
index 8b759e701da60..eaa2da1255db5 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineInternal.h
+++ b/llvm/lib/Transforms/InstCombine/InstCombineInternal.h
@@ -598,6 +598,14 @@ class LLVM_LIBRARY_VISIBILITY InstCombinerImpl final
                                          const SimplifyQuery &Q,
                                          unsigned Depth = 0);
 
+  /// Check if a wide add/mul has only users that demand the lower bits.
+  bool canReplaceWideOverflowIdiomUsers(Value *WideOp, unsigned NarrowWidth,
+                                        User *Exclude = nullptr);
+
+  /// Replace the valid users of WideOp with NarrowVal.
+  void replaceWideOverflowIdiomUsers(Value *WideOp, unsigned NarrowWidth,
+                                     Value *NarrowVal, User *Exclude = nullptr);
+
   /// Helper routine of SimplifyDemandedUseBits. It tries to simplify demanded
   /// bit for "r1 = shr x, c1; r2 = shl r1, c2" instruction sequence.
   Value *simplifyShrShlDemandedBits(
diff --git a/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp b/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
index b877f79a000cf..c30688feef317 100644
--- a/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
@@ -6330,6 +6330,62 @@ bool InstructionCombiningPass::runOnFunction(Function &F) {
                                          BFI, BPI, PSI, InstCombineOptions());
 }
 
+bool InstCombinerImpl::canReplaceWideOverflowIdiomUsers(Value *WideOp,
+                                                        unsigned NarrowWidth,
+                                                        User *Exclude) {
+  if (WideOp->hasNUsesOrMore(2)) {
+    for (User *U : WideOp->users()) {
+      if (U == Exclude)
+        continue;
+      if (auto *TI = dyn_cast<TruncInst>(U)) {
+        if (TI->getType()->getScalarSizeInBits() > NarrowWidth)
+          return false;
+      } else if (auto *BO = dyn_cast<BinaryOperator>(U)) {
+        if (BO->getOpcode() != Instruction::And)
+          return false;
+        if (auto *CI = dyn_cast<ConstantInt>(BO->getOperand(1))) {
+          const APInt &CVal = CI->getValue();
+          if (CVal.getBitWidth() - CVal.countl_zero() > NarrowWidth)
+            return false;
+        } else {
+          return false;
+        }
+      } else {
+        return false;
+      }
+    }
+  }
+  return true;
+}
+
+void InstCombinerImpl::replaceWideOverflowIdiomUsers(Value *WideOp,
+                                                     unsigned NarrowWidth,
+                                                     Value *NarrowVal,
+                                                     User *Exclude) {
+  if (WideOp->hasNUsesOrMore(2)) {
+    for (User *U : llvm::make_early_inc_range(WideOp->users())) {
+      if (U == Exclude)
+        continue;
+      if (auto *TI = dyn_cast<TruncInst>(U)) {
+        if (TI->getType()->getScalarSizeInBits() == NarrowWidth)
+          replaceInstUsesWith(*TI, NarrowVal);
+        else
+          TI->setOperand(0, NarrowVal);
+        addToWorklist(TI);
+      } else if (auto *BO = dyn_cast<BinaryOperator>(U)) {
+        assert(BO->getOpcode() == Instruction::And);
+        // Replace (WideOp & mask) --> zext (NarrowVal & short_mask)
+        ConstantInt *CI = cast<ConstantInt>(BO->getOperand(1));
+        APInt ShortMask = CI->getValue().trunc(NarrowWidth);
+        Value *ShortAnd = Builder.CreateAnd(NarrowVal, ShortMask);
+        Value *Zext = Builder.CreateZExt(ShortAnd, BO->getType());
+        replaceInstUsesWith(*BO, Zext);
+        addToWorklist(BO);
+      }
+    }
+  }
+}
+
 char InstructionCombiningPass::ID = 0;
 
 InstructionCombiningPass::InstructionCombiningPass() : FunctionPass(ID) {}



More information about the llvm-commits mailing list