[llvm] [InstCombine] Div ceil optimizations (PR #190175)

Takashi Idobe via llvm-commits llvm-commits at lists.llvm.org
Sun Apr 5 12:57:52 PDT 2026


https://github.com/Takashiidobe updated https://github.com/llvm/llvm-project/pull/190175

>From e36d4247f61da8144884eafa76c51cc24289133f Mon Sep 17 00:00:00 2001
From: Takashiidobe <idobetakashi at gmail.com>
Date: Thu, 2 Apr 2026 20:27:03 -0400
Subject: [PATCH 1/3] Add tests for div ceil folds

---
 llvm/test/Transforms/InstCombine/divceil.ll | 203 ++++++++++++++++++++
 1 file changed, 203 insertions(+)
 create mode 100644 llvm/test/Transforms/InstCombine/divceil.ll

diff --git a/llvm/test/Transforms/InstCombine/divceil.ll b/llvm/test/Transforms/InstCombine/divceil.ll
new file mode 100644
index 0000000000000..efef59119bc7c
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/divceil.ll
@@ -0,0 +1,203 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+
+; Optimizes div_ceil idioms:
+;   same-type form: add(udiv(X, Y), zext(icmp ne(urem(X, Y), 0)))
+;     -> udiv(add nuw(X, Y - 1), Y)
+;   zext form: add(zext(udiv(X, Y)), zext(icmp ne(urem(X, Y), 0)))
+;     -> zext(udiv(add nuw(X, Y - 1), Y))
+; when X + (Y-1) is provably non-wrapping (via range info on both X and Y).
+
+declare void @use(i8)
+declare void @use16(i16)
+
+; Variable divisor: both X in [0,100] and Y in [1,10], so max X+(Y-1) = 109 <= 255.
+define i8 @divceil_i8_var_divisor(i8 range(i8 0, 101) %x, i8 range(i8 1, 11) %y) {
+; CHECK-LABEL: @divceil_i8_var_divisor(
+; CHECK-NEXT:    [[RESULT:%.*]] = udiv i8 [[TMP2:%.*]], [[Y:%.*]]
+; CHECK-NEXT:    [[R:%.*]] = urem i8 [[TMP2]], [[Y]]
+; CHECK-NEXT:    [[COND:%.*]] = icmp ne i8 [[R]], 0
+; CHECK-NEXT:    [[ROUND:%.*]] = zext i1 [[COND]] to i8
+; CHECK-NEXT:    [[RESULT1:%.*]] = add nuw i8 [[RESULT]], [[ROUND]]
+; CHECK-NEXT:    ret i8 [[RESULT1]]
+;
+  %q = udiv i8 %x, %y
+  %r = urem i8 %x, %y
+  %cond = icmp ne i8 %r, 0
+  %round = zext i1 %cond to i8
+  %result = add i8 %q, %round
+  ret i8 %result
+}
+
+; Variable divisor, commuted add.
+define i8 @divceil_i8_var_divisor_commuted(i8 range(i8 0, 101) %x, i8 range(i8 1, 11) %y) {
+; CHECK-LABEL: @divceil_i8_var_divisor_commuted(
+; CHECK-NEXT:    [[RESULT:%.*]] = udiv i8 [[TMP2:%.*]], [[Y:%.*]]
+; CHECK-NEXT:    [[R:%.*]] = urem i8 [[TMP2]], [[Y]]
+; CHECK-NEXT:    [[COND:%.*]] = icmp ne i8 [[R]], 0
+; CHECK-NEXT:    [[ROUND:%.*]] = zext i1 [[COND]] to i8
+; CHECK-NEXT:    [[RESULT1:%.*]] = add nuw i8 [[RESULT]], [[ROUND]]
+; CHECK-NEXT:    ret i8 [[RESULT1]]
+;
+  %q = udiv i8 %x, %y
+  %r = urem i8 %x, %y
+  %cond = icmp ne i8 %r, 0
+  %round = zext i1 %cond to i8
+  %result = add i8 %round, %q
+  ret i8 %result
+}
+
+; Variable divisor with i32: X in [0, 100], Y in [2, 8], max X+(Y-1) = 107 <= UINT32_MAX.
+define i32 @divceil_i32_var_divisor(i32 range(i32 0, 101) %x, i32 range(i32 2, 9) %y) {
+; CHECK-LABEL: @divceil_i32_var_divisor(
+; CHECK-NEXT:    [[RESULT:%.*]] = udiv i32 [[TMP2:%.*]], [[Y:%.*]]
+; CHECK-NEXT:    [[R:%.*]] = urem i32 [[TMP2]], [[Y]]
+; CHECK-NEXT:    [[COND:%.*]] = icmp ne i32 [[R]], 0
+; CHECK-NEXT:    [[ROUND:%.*]] = zext i1 [[COND]] to i32
+; CHECK-NEXT:    [[RESULT1:%.*]] = add nuw nsw i32 [[RESULT]], [[ROUND]]
+; CHECK-NEXT:    ret i32 [[RESULT1]]
+;
+  %q = udiv i32 %x, %y
+  %r = urem i32 %x, %y
+  %cond = icmp ne i32 %r, 0
+  %round = zext i1 %cond to i32
+  %result = add i32 %q, %round
+  ret i32 %result
+}
+
+; Negative: Y unbounded -> max Y = 255, max X+(Y-1) = 100+254 overflows i8.
+define i8 @divceil_i8_var_divisor_y_unbounded(i8 range(i8 0, 101) %x, i8 %y) {
+; CHECK-LABEL: @divceil_i8_var_divisor_y_unbounded(
+; CHECK-NEXT:    [[Q:%.*]] = udiv i8 [[X:%.*]], [[Y:%.*]]
+; CHECK-NEXT:    [[R:%.*]] = urem i8 [[X]], [[Y]]
+; CHECK-NEXT:    [[COND:%.*]] = icmp ne i8 [[R]], 0
+; CHECK-NEXT:    [[ROUND:%.*]] = zext i1 [[COND]] to i8
+; CHECK-NEXT:    [[RESULT:%.*]] = add nuw i8 [[Q]], [[ROUND]]
+; CHECK-NEXT:    ret i8 [[RESULT]]
+;
+  %q = udiv i8 %x, %y
+  %r = urem i8 %x, %y
+  %cond = icmp ne i8 %r, 0
+  %round = zext i1 %cond to i8
+  %result = add i8 %q, %round
+  ret i8 %result
+}
+
+; Negative: X unbounded -> max X+(Y-1) overflows even with bounded Y.
+define i8 @divceil_i8_var_divisor_x_unbounded(i8 %x, i8 range(i8 1, 11) %y) {
+; CHECK-LABEL: @divceil_i8_var_divisor_x_unbounded(
+; CHECK-NEXT:    [[Q:%.*]] = udiv i8 [[X:%.*]], [[Y:%.*]]
+; CHECK-NEXT:    [[R:%.*]] = urem i8 [[X]], [[Y]]
+; CHECK-NEXT:    [[COND:%.*]] = icmp ne i8 [[R]], 0
+; CHECK-NEXT:    [[ROUND:%.*]] = zext i1 [[COND]] to i8
+; CHECK-NEXT:    [[RESULT:%.*]] = add i8 [[Q]], [[ROUND]]
+; CHECK-NEXT:    ret i8 [[RESULT]]
+;
+  %q = udiv i8 %x, %y
+  %r = urem i8 %x, %y
+  %cond = icmp ne i8 %r, 0
+  %round = zext i1 %cond to i8
+  %result = add i8 %q, %round
+  ret i8 %result
+}
+
+; Zext form: udiv/urem in i8, add in i16.
+; X in [0,100], Y in [1,10]: max X+(Y-1) = 109 <= 255.
+define i16 @divceil_i8_var_divisor_zext(i8 range(i8 0, 101) %x, i8 range(i8 1, 11) %y) {
+; CHECK-LABEL: @divceil_i8_var_divisor_zext(
+; CHECK-NEXT:    [[TMP3:%.*]] = udiv i8 [[TMP2:%.*]], [[Y:%.*]]
+; CHECK-NEXT:    [[R:%.*]] = urem i8 [[TMP2]], [[Y]]
+; CHECK-NEXT:    [[COND:%.*]] = icmp ne i8 [[R]], 0
+; CHECK-NEXT:    [[Q_EXT:%.*]] = zext nneg i8 [[TMP3]] to i16
+; CHECK-NEXT:    [[ROUND:%.*]] = zext i1 [[COND]] to i16
+; CHECK-NEXT:    [[RESULT:%.*]] = add nuw nsw i16 [[Q_EXT]], [[ROUND]]
+; CHECK-NEXT:    ret i16 [[RESULT]]
+;
+  %q = udiv i8 %x, %y
+  %r = urem i8 %x, %y
+  %cond = icmp ne i8 %r, 0
+  %q_ext = zext i8 %q to i16
+  %round = zext i1 %cond to i16
+  %result = add i16 %q_ext, %round
+  ret i16 %result
+}
+
+; Zext form, commuted add.
+define i16 @divceil_i8_var_divisor_commuted_zext(i8 range(i8 0, 101) %x, i8 range(i8 1, 11) %y) {
+; CHECK-LABEL: @divceil_i8_var_divisor_commuted_zext(
+; CHECK-NEXT:    [[TMP3:%.*]] = udiv i8 [[TMP2:%.*]], [[Y:%.*]]
+; CHECK-NEXT:    [[R:%.*]] = urem i8 [[TMP2]], [[Y]]
+; CHECK-NEXT:    [[COND:%.*]] = icmp ne i8 [[R]], 0
+; CHECK-NEXT:    [[Q_EXT:%.*]] = zext nneg i8 [[TMP3]] to i16
+; CHECK-NEXT:    [[ROUND:%.*]] = zext i1 [[COND]] to i16
+; CHECK-NEXT:    [[RESULT:%.*]] = add nuw nsw i16 [[ROUND]], [[Q_EXT]]
+; CHECK-NEXT:    ret i16 [[RESULT]]
+;
+  %q = udiv i8 %x, %y
+  %r = urem i8 %x, %y
+  %cond = icmp ne i8 %r, 0
+  %q_ext = zext i8 %q to i16
+  %round = zext i1 %cond to i16
+  %result = add i16 %round, %q_ext
+  ret i16 %result
+}
+
+; Zext form with i16->i32: X in [0, 100], Y in [2, 8].
+define i32 @divceil_i16_var_divisor_zext(i16 range(i16 0, 101) %x, i16 range(i16 2, 9) %y) {
+; CHECK-LABEL: @divceil_i16_var_divisor_zext(
+; CHECK-NEXT:    [[TMP3:%.*]] = udiv i16 [[TMP2:%.*]], [[Y:%.*]]
+; CHECK-NEXT:    [[R:%.*]] = urem i16 [[TMP2]], [[Y]]
+; CHECK-NEXT:    [[COND:%.*]] = icmp ne i16 [[R]], 0
+; CHECK-NEXT:    [[Q_EXT:%.*]] = zext nneg i16 [[TMP3]] to i32
+; CHECK-NEXT:    [[ROUND:%.*]] = zext i1 [[COND]] to i32
+; CHECK-NEXT:    [[RESULT:%.*]] = add nuw nsw i32 [[Q_EXT]], [[ROUND]]
+; CHECK-NEXT:    ret i32 [[RESULT]]
+;
+  %q = udiv i16 %x, %y
+  %r = urem i16 %x, %y
+  %cond = icmp ne i16 %r, 0
+  %q_ext = zext i16 %q to i32
+  %round = zext i1 %cond to i32
+  %result = add i32 %q_ext, %round
+  ret i32 %result
+}
+
+; Zext form negative: Y unbounded -> overflows in i8 type.
+define i16 @divceil_i8_var_divisor_y_unbounded_zext(i8 range(i8 0, 101) %x, i8 %y) {
+; CHECK-LABEL: @divceil_i8_var_divisor_y_unbounded_zext(
+; CHECK-NEXT:    [[Q:%.*]] = udiv i8 [[X:%.*]], [[Y:%.*]]
+; CHECK-NEXT:    [[R:%.*]] = urem i8 [[X]], [[Y]]
+; CHECK-NEXT:    [[COND:%.*]] = icmp ne i8 [[R]], 0
+; CHECK-NEXT:    [[Q_EXT:%.*]] = zext nneg i8 [[Q]] to i16
+; CHECK-NEXT:    [[ROUND:%.*]] = zext i1 [[COND]] to i16
+; CHECK-NEXT:    [[RESULT:%.*]] = add nuw nsw i16 [[Q_EXT]], [[ROUND]]
+; CHECK-NEXT:    ret i16 [[RESULT]]
+;
+  %q = udiv i8 %x, %y
+  %r = urem i8 %x, %y
+  %cond = icmp ne i8 %r, 0
+  %q_ext = zext i8 %q to i16
+  %round = zext i1 %cond to i16
+  %result = add i16 %q_ext, %round
+  ret i16 %result
+}
+
+; Zext form negative: X unbounded -> overflows even with bounded Y.
+define i16 @divceil_i8_var_divisor_x_unbounded_zext(i8 %x, i8 range(i8 1, 11) %y) {
+; CHECK-LABEL: @divceil_i8_var_divisor_x_unbounded_zext(
+; CHECK-NEXT:    [[Q:%.*]] = udiv i8 [[X:%.*]], [[Y:%.*]]
+; CHECK-NEXT:    [[R:%.*]] = urem i8 [[X]], [[Y]]
+; CHECK-NEXT:    [[COND:%.*]] = icmp ne i8 [[R]], 0
+; CHECK-NEXT:    [[Q_EXT:%.*]] = zext i8 [[Q]] to i16
+; CHECK-NEXT:    [[ROUND:%.*]] = zext i1 [[COND]] to i16
+; CHECK-NEXT:    [[RESULT:%.*]] = add nuw nsw i16 [[Q_EXT]], [[ROUND]]
+; CHECK-NEXT:    ret i16 [[RESULT]]
+;
+  %q = udiv i8 %x, %y
+  %r = urem i8 %x, %y
+  %cond = icmp ne i8 %r, 0
+  %q_ext = zext i8 %q to i16
+  %round = zext i1 %cond to i16
+  %result = add i16 %q_ext, %round
+  ret i16 %result
+}

>From 187a4404307bd842b844fccf5d0f552d16b429e5 Mon Sep 17 00:00:00 2001
From: Takashiidobe <idobetakashi at gmail.com>
Date: Thu, 2 Apr 2026 20:27:26 -0400
Subject: [PATCH 2/3] Implement div ceil folds:

add(udiv(X, Y), zext(icmp ne(urem(X, Y), 0))) -> udiv(add nuw(X, Y - 1), Y)

add(zext(udiv(X, Y)), zext(icmp ne(urem(X, Y), 0))) -> zext(udiv(add nuw(X, Y - 1), Y))
---
 llvm/lib/Analysis/ValueTracking.cpp           |  4 ++
 .../InstCombine/InstCombineAddSub.cpp         | 68 +++++++++++++++++++
 .../InstCombine/InstCombineInternal.h         |  6 ++
 llvm/test/Transforms/InstCombine/divceil.ll   | 60 +++++++---------
 llvm/test/Transforms/InstCombine/fls.ll       |  2 +-
 5 files changed, 103 insertions(+), 37 deletions(-)

diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index 340a616f13e19..53e8ff15f3931 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -10361,6 +10361,10 @@ ConstantRange llvm::computeConstantRange(const Value *V, bool ForSigned,
         SI->getFalseValue(), ForSigned, UseInstrInfo, AC, CtxI, DT, Depth + 1);
     CR = CRTrue.unionWith(CRFalse);
     CR = CR.intersectWith(getRangeForSelectPattern(*SI, IIQ));
+  } else if (auto *TI = dyn_cast<TruncInst>(V)) {
+    ConstantRange SrcCR = computeConstantRange(
+        TI->getOperand(0), ForSigned, UseInstrInfo, AC, CtxI, DT, Depth + 1);
+    CR = SrcCR.truncate(BitWidth);
   } else if (isa<FPToUIInst>(V) || isa<FPToSIInst>(V)) {
     APInt Lower = APInt(BitWidth, 0);
     APInt Upper = APInt(BitWidth, 0);
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
index c781c6978b275..122bdcd8a6474 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
@@ -1523,6 +1523,68 @@ static Instruction *foldBoxMultiply(BinaryOperator &I) {
   return nullptr;
 }
 
+// Return true if X + (Y-1) is provably non-wrapping in X's type
+static bool checkDivCeilNUW(Value *X, Value *Y, BinaryOperator &I,
+                            AssumptionCache &AC, DominatorTree &DT) {
+  ConstantRange CRX = computeConstantRange(X, /*ForSigned=*/false,
+                                           /*UseInstrInfo=*/true, &AC, &I, &DT);
+  ConstantRange CRY = computeConstantRange(Y, /*ForSigned=*/false,
+                                           /*UseInstrInfo=*/true, &AC, &I, &DT);
+  APInt MaxX = CRX.getUnsignedMax();
+  APInt MaxY = CRY.getUnsignedMax();
+  unsigned BitWidth = MaxX.getBitWidth();
+  // MaxX + (MaxY - 1) <= UINT_MAX  <==>  MaxX <= UINT_MAX - (MaxY - 1)
+  return !MaxX.ugt(APInt::getMaxValue(BitWidth) - (MaxY - 1));
+}
+
+// Fold the same-type form of the div_ceil idiom:
+//   add(udiv(X, Y), zext(icmp ne(urem(X, Y), 0)))
+//     -> udiv(add nuw(X, Y - 1), Y)
+// where udiv and urem are the same type as the add.
+Instruction *InstCombinerImpl::foldSameTypeDivCeil(BinaryOperator &I) {
+  Value *X, *X2, *Y, *Y2;
+  CmpPredicate Pred;
+
+  auto UDivPat = m_OneUse(m_UDiv(m_Value(X), m_Value(Y)));
+  auto URemPat = m_OneUse(m_URem(m_Value(X2), m_Value(Y2)));
+  auto ICmpPat = m_OneUse(m_ICmp(Pred, URemPat, m_Zero()));
+  auto ZExtCmpPat = m_OneUse(m_ZExt(ICmpPat));
+
+  if (!match(&I, m_c_Add(UDivPat, ZExtCmpPat)) || Pred != ICmpInst::ICMP_NE ||
+      X != X2 || Y != Y2 || !checkDivCeilNUW(X, Y, I, AC, DT))
+    return nullptr;
+
+  Value *YMinusOne = Builder.CreateSub(Y, ConstantInt::get(Y->getType(), 1));
+  Value *NUWAdd = Builder.CreateAdd(X, YMinusOne, "", /*HasNUW=*/true);
+  return BinaryOperator::CreateUDiv(NUWAdd, Y);
+}
+
+// Fold the zext form of the div_ceil idiom:
+//   add(zext(udiv(X, Y)), zext(icmp ne(urem(X, Y), 0)))
+//     -> zext(udiv(add nuw(X, Y - 1), Y))
+// udiv and urem operate in a type narrower than the add; the result is
+// zexted back to the add's type.
+Instruction *InstCombinerImpl::foldZExtDivCeil(BinaryOperator &I) {
+  Value *X, *X2, *Y, *Y2;
+  CmpPredicate Pred;
+
+  auto UDivPat = m_OneUse(m_UDiv(m_Value(X), m_Value(Y)));
+  auto URemPat = m_OneUse(m_URem(m_Value(X2), m_Value(Y2)));
+  auto ICmpPat = m_OneUse(m_ICmp(Pred, URemPat, m_Zero()));
+  auto ZExtDivPat = m_OneUse(m_ZExt(UDivPat));
+  auto ZExtCmpPat = m_OneUse(m_ZExt(ICmpPat));
+
+  if (!match(&I, m_c_Add(ZExtDivPat, ZExtCmpPat)) ||
+      Pred != ICmpInst::ICMP_NE || X != X2 || Y != Y2 ||
+      !checkDivCeilNUW(X, Y, I, AC, DT))
+    return nullptr;
+
+  Value *YMinusOne = Builder.CreateSub(Y, ConstantInt::get(Y->getType(), 1));
+  Value *NUWAdd = Builder.CreateAdd(X, YMinusOne, "", /*HasNUW=*/true);
+  Value *Div = Builder.CreateUDiv(NUWAdd, Y);
+  return new ZExtInst(Div, I.getType());
+}
+
 Instruction *InstCombinerImpl::visitAdd(BinaryOperator &I) {
   if (Value *V = simplifyAddInst(I.getOperand(0), I.getOperand(1),
                                  I.hasNoSignedWrap(), I.hasNoUnsignedWrap(),
@@ -1915,6 +1977,12 @@ Instruction *InstCombinerImpl::visitAdd(BinaryOperator &I) {
   if (Instruction *Res = foldBinOpOfSelectAndCastOfSelectCondition(I))
     return Res;
 
+  if (Instruction *Res = foldSameTypeDivCeil(I))
+    return Res;
+
+  if (Instruction *Res = foldZExtDivCeil(I))
+    return Res;
+
   // Re-enqueue users of the induction variable of add recurrence if we infer
   // new nuw/nsw flags.
   if (Changed) {
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineInternal.h b/llvm/lib/Transforms/InstCombine/InstCombineInternal.h
index 160f766b60973..6404ab3f5b390 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineInternal.h
+++ b/llvm/lib/Transforms/InstCombine/InstCombineInternal.h
@@ -544,6 +544,12 @@ class LLVM_LIBRARY_VISIBILITY InstCombinerImpl final
   /// (Binop (cast C), (select C, T, F))
   ///    -> (select C, C0, C1)
   Instruction *foldBinOpOfSelectAndCastOfSelectCondition(BinaryOperator &I);
+  /// Same-type form: (add (udiv X, Y), (zext (icmp ne (urem X, Y), 0)))
+  ///    -> (udiv (add nuw X, Y-1), Y)
+  Instruction *foldSameTypeDivCeil(BinaryOperator &I);
+  /// Zext form: (add (zext (udiv X, Y)), (zext (icmp ne (urem X, Y), 0)))
+  ///    -> (zext (udiv (add nuw X, Y-1), Y))
+  Instruction *foldZExtDivCeil(BinaryOperator &I);
 
   /// This tries to simplify binary operations by factorizing out common terms
   /// (e. g. "(A*B)+(A*C)" -> "A*(B+C)").
diff --git a/llvm/test/Transforms/InstCombine/divceil.ll b/llvm/test/Transforms/InstCombine/divceil.ll
index efef59119bc7c..e6400f2b70212 100644
--- a/llvm/test/Transforms/InstCombine/divceil.ll
+++ b/llvm/test/Transforms/InstCombine/divceil.ll
@@ -14,12 +14,10 @@ declare void @use16(i16)
 ; Variable divisor: both X in [0,100] and Y in [1,10], so max X+(Y-1) = 109 <= 255.
 define i8 @divceil_i8_var_divisor(i8 range(i8 0, 101) %x, i8 range(i8 1, 11) %y) {
 ; CHECK-LABEL: @divceil_i8_var_divisor(
-; CHECK-NEXT:    [[RESULT:%.*]] = udiv i8 [[TMP2:%.*]], [[Y:%.*]]
-; CHECK-NEXT:    [[R:%.*]] = urem i8 [[TMP2]], [[Y]]
-; CHECK-NEXT:    [[COND:%.*]] = icmp ne i8 [[R]], 0
-; CHECK-NEXT:    [[ROUND:%.*]] = zext i1 [[COND]] to i8
-; CHECK-NEXT:    [[RESULT1:%.*]] = add nuw i8 [[RESULT]], [[ROUND]]
-; CHECK-NEXT:    ret i8 [[RESULT1]]
+; CHECK-NEXT:    [[ROUND:%.*]] = add nsw i8 [[Y:%.*]], -1
+; CHECK-NEXT:    [[RESULT1:%.*]] = add nuw i8 [[RESULT:%.*]], [[ROUND]]
+; CHECK-NEXT:    [[RESULT2:%.*]] = udiv i8 [[RESULT1]], [[Y]]
+; CHECK-NEXT:    ret i8 [[RESULT2]]
 ;
   %q = udiv i8 %x, %y
   %r = urem i8 %x, %y
@@ -32,12 +30,10 @@ define i8 @divceil_i8_var_divisor(i8 range(i8 0, 101) %x, i8 range(i8 1, 11) %y)
 ; Variable divisor, commuted add.
 define i8 @divceil_i8_var_divisor_commuted(i8 range(i8 0, 101) %x, i8 range(i8 1, 11) %y) {
 ; CHECK-LABEL: @divceil_i8_var_divisor_commuted(
-; CHECK-NEXT:    [[RESULT:%.*]] = udiv i8 [[TMP2:%.*]], [[Y:%.*]]
-; CHECK-NEXT:    [[R:%.*]] = urem i8 [[TMP2]], [[Y]]
-; CHECK-NEXT:    [[COND:%.*]] = icmp ne i8 [[R]], 0
-; CHECK-NEXT:    [[ROUND:%.*]] = zext i1 [[COND]] to i8
-; CHECK-NEXT:    [[RESULT1:%.*]] = add nuw i8 [[RESULT]], [[ROUND]]
-; CHECK-NEXT:    ret i8 [[RESULT1]]
+; CHECK-NEXT:    [[ROUND:%.*]] = add nsw i8 [[Y:%.*]], -1
+; CHECK-NEXT:    [[RESULT1:%.*]] = add nuw i8 [[RESULT:%.*]], [[ROUND]]
+; CHECK-NEXT:    [[RESULT2:%.*]] = udiv i8 [[RESULT1]], [[Y]]
+; CHECK-NEXT:    ret i8 [[RESULT2]]
 ;
   %q = udiv i8 %x, %y
   %r = urem i8 %x, %y
@@ -50,12 +46,10 @@ define i8 @divceil_i8_var_divisor_commuted(i8 range(i8 0, 101) %x, i8 range(i8 1
 ; Variable divisor with i32: X in [0, 100], Y in [2, 8], max X+(Y-1) = 107 <= UINT32_MAX.
 define i32 @divceil_i32_var_divisor(i32 range(i32 0, 101) %x, i32 range(i32 2, 9) %y) {
 ; CHECK-LABEL: @divceil_i32_var_divisor(
-; CHECK-NEXT:    [[RESULT:%.*]] = udiv i32 [[TMP2:%.*]], [[Y:%.*]]
-; CHECK-NEXT:    [[R:%.*]] = urem i32 [[TMP2]], [[Y]]
-; CHECK-NEXT:    [[COND:%.*]] = icmp ne i32 [[R]], 0
-; CHECK-NEXT:    [[ROUND:%.*]] = zext i1 [[COND]] to i32
-; CHECK-NEXT:    [[RESULT1:%.*]] = add nuw nsw i32 [[RESULT]], [[ROUND]]
-; CHECK-NEXT:    ret i32 [[RESULT1]]
+; CHECK-NEXT:    [[ROUND:%.*]] = add nsw i32 [[Y:%.*]], -1
+; CHECK-NEXT:    [[RESULT1:%.*]] = add nuw nsw i32 [[RESULT:%.*]], [[ROUND]]
+; CHECK-NEXT:    [[RESULT2:%.*]] = udiv i32 [[RESULT1]], [[Y]]
+; CHECK-NEXT:    ret i32 [[RESULT2]]
 ;
   %q = udiv i32 %x, %y
   %r = urem i32 %x, %y
@@ -105,12 +99,10 @@ define i8 @divceil_i8_var_divisor_x_unbounded(i8 %x, i8 range(i8 1, 11) %y) {
 ; X in [0,100], Y in [1,10]: max X+(Y-1) = 109 <= 255.
 define i16 @divceil_i8_var_divisor_zext(i8 range(i8 0, 101) %x, i8 range(i8 1, 11) %y) {
 ; CHECK-LABEL: @divceil_i8_var_divisor_zext(
-; CHECK-NEXT:    [[TMP3:%.*]] = udiv i8 [[TMP2:%.*]], [[Y:%.*]]
-; CHECK-NEXT:    [[R:%.*]] = urem i8 [[TMP2]], [[Y]]
-; CHECK-NEXT:    [[COND:%.*]] = icmp ne i8 [[R]], 0
-; CHECK-NEXT:    [[Q_EXT:%.*]] = zext nneg i8 [[TMP3]] to i16
-; CHECK-NEXT:    [[ROUND:%.*]] = zext i1 [[COND]] to i16
-; CHECK-NEXT:    [[RESULT:%.*]] = add nuw nsw i16 [[Q_EXT]], [[ROUND]]
+; CHECK-NEXT:    [[TMP1:%.*]] = add nsw i8 [[Y:%.*]], -1
+; CHECK-NEXT:    [[TMP2:%.*]] = add nuw i8 [[X:%.*]], [[TMP1]]
+; CHECK-NEXT:    [[TMP3:%.*]] = udiv i8 [[TMP2]], [[Y]]
+; CHECK-NEXT:    [[RESULT:%.*]] = zext i8 [[TMP3]] to i16
 ; CHECK-NEXT:    ret i16 [[RESULT]]
 ;
   %q = udiv i8 %x, %y
@@ -125,12 +117,10 @@ define i16 @divceil_i8_var_divisor_zext(i8 range(i8 0, 101) %x, i8 range(i8 1, 1
 ; Zext form, commuted add.
 define i16 @divceil_i8_var_divisor_commuted_zext(i8 range(i8 0, 101) %x, i8 range(i8 1, 11) %y) {
 ; CHECK-LABEL: @divceil_i8_var_divisor_commuted_zext(
-; CHECK-NEXT:    [[TMP3:%.*]] = udiv i8 [[TMP2:%.*]], [[Y:%.*]]
-; CHECK-NEXT:    [[R:%.*]] = urem i8 [[TMP2]], [[Y]]
-; CHECK-NEXT:    [[COND:%.*]] = icmp ne i8 [[R]], 0
-; CHECK-NEXT:    [[Q_EXT:%.*]] = zext nneg i8 [[TMP3]] to i16
-; CHECK-NEXT:    [[ROUND:%.*]] = zext i1 [[COND]] to i16
-; CHECK-NEXT:    [[RESULT:%.*]] = add nuw nsw i16 [[ROUND]], [[Q_EXT]]
+; CHECK-NEXT:    [[TMP1:%.*]] = add nsw i8 [[Y:%.*]], -1
+; CHECK-NEXT:    [[TMP2:%.*]] = add nuw i8 [[X:%.*]], [[TMP1]]
+; CHECK-NEXT:    [[TMP3:%.*]] = udiv i8 [[TMP2]], [[Y]]
+; CHECK-NEXT:    [[RESULT:%.*]] = zext i8 [[TMP3]] to i16
 ; CHECK-NEXT:    ret i16 [[RESULT]]
 ;
   %q = udiv i8 %x, %y
@@ -145,12 +135,10 @@ define i16 @divceil_i8_var_divisor_commuted_zext(i8 range(i8 0, 101) %x, i8 rang
 ; Zext form with i16->i32: X in [0, 100], Y in [2, 8].
 define i32 @divceil_i16_var_divisor_zext(i16 range(i16 0, 101) %x, i16 range(i16 2, 9) %y) {
 ; CHECK-LABEL: @divceil_i16_var_divisor_zext(
-; CHECK-NEXT:    [[TMP3:%.*]] = udiv i16 [[TMP2:%.*]], [[Y:%.*]]
-; CHECK-NEXT:    [[R:%.*]] = urem i16 [[TMP2]], [[Y]]
-; CHECK-NEXT:    [[COND:%.*]] = icmp ne i16 [[R]], 0
-; CHECK-NEXT:    [[Q_EXT:%.*]] = zext nneg i16 [[TMP3]] to i32
-; CHECK-NEXT:    [[ROUND:%.*]] = zext i1 [[COND]] to i32
-; CHECK-NEXT:    [[RESULT:%.*]] = add nuw nsw i32 [[Q_EXT]], [[ROUND]]
+; CHECK-NEXT:    [[TMP1:%.*]] = add nsw i16 [[Y:%.*]], -1
+; CHECK-NEXT:    [[TMP2:%.*]] = add nuw nsw i16 [[X:%.*]], [[TMP1]]
+; CHECK-NEXT:    [[TMP3:%.*]] = udiv i16 [[TMP2]], [[Y]]
+; CHECK-NEXT:    [[RESULT:%.*]] = zext i16 [[TMP3]] to i32
 ; CHECK-NEXT:    ret i32 [[RESULT]]
 ;
   %q = udiv i16 %x, %y
diff --git a/llvm/test/Transforms/InstCombine/fls.ll b/llvm/test/Transforms/InstCombine/fls.ll
index 68bc0a2fc8a1d..ea757268259f5 100644
--- a/llvm/test/Transforms/InstCombine/fls.ll
+++ b/llvm/test/Transforms/InstCombine/fls.ll
@@ -33,7 +33,7 @@ define i32 @flsnotconst(i64 %z) {
 ; CHECK-LABEL: @flsnotconst(
 ; CHECK-NEXT:    [[CTLZ:%.*]] = call range(i64 0, 65) i64 @llvm.ctlz.i64(i64 [[Z:%.*]], i1 false)
 ; CHECK-NEXT:    [[TMP1:%.*]] = trunc nuw nsw i64 [[CTLZ]] to i32
-; CHECK-NEXT:    [[GOO:%.*]] = sub nsw i32 64, [[TMP1]]
+; CHECK-NEXT:    [[GOO:%.*]] = sub nuw nsw i32 64, [[TMP1]]
 ; CHECK-NEXT:    ret i32 [[GOO]]
 ;
   %goo = call i32 @flsl(i64 %z)

>From 6fe7875080c2dee84f4570dbe95b13f5dc194181 Mon Sep 17 00:00:00 2001
From: Takashiidobe <idobetakashi at gmail.com>
Date: Sun, 5 Apr 2026 15:57:40 -0400
Subject: [PATCH 3/3] code review feedback

---
 llvm/lib/Analysis/ValueTracking.cpp           |  2 +-
 .../InstCombine/InstCombineAddSub.cpp         | 39 +++++++++----------
 2 files changed, 19 insertions(+), 22 deletions(-)

diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index 53e8ff15f3931..4d7c11e10514e 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -10364,7 +10364,7 @@ ConstantRange llvm::computeConstantRange(const Value *V, bool ForSigned,
   } else if (auto *TI = dyn_cast<TruncInst>(V)) {
     ConstantRange SrcCR = computeConstantRange(
         TI->getOperand(0), ForSigned, UseInstrInfo, AC, CtxI, DT, Depth + 1);
-    CR = SrcCR.truncate(BitWidth);
+    CR = SrcCR.truncate(BitWidth, TI->getNoWrapKind());
   } else if (isa<FPToUIInst>(V) || isa<FPToSIInst>(V)) {
     APInt Lower = APInt(BitWidth, 0);
     APInt Upper = APInt(BitWidth, 0);
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
index 122bdcd8a6474..75353004106a4 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
@@ -1523,7 +1523,7 @@ static Instruction *foldBoxMultiply(BinaryOperator &I) {
   return nullptr;
 }
 
-// Return true if X + (Y-1) is provably non-wrapping in X's type
+/// Return true if X + (Y-1) is provably non-wrapping in X's type
 static bool checkDivCeilNUW(Value *X, Value *Y, BinaryOperator &I,
                             AssumptionCache &AC, DominatorTree &DT) {
   ConstantRange CRX = computeConstantRange(X, /*ForSigned=*/false,
@@ -1537,21 +1537,20 @@ static bool checkDivCeilNUW(Value *X, Value *Y, BinaryOperator &I,
   return !MaxX.ugt(APInt::getMaxValue(BitWidth) - (MaxY - 1));
 }
 
-// Fold the same-type form of the div_ceil idiom:
-//   add(udiv(X, Y), zext(icmp ne(urem(X, Y), 0)))
-//     -> udiv(add nuw(X, Y - 1), Y)
-// where udiv and urem are the same type as the add.
+/// Fold the same-type form of the div_ceil idiom:
+///   add(udiv(X, Y), zext(icmp ne(urem(X, Y), 0)))
+///     -> udiv(add nuw(X, Y - 1), Y)
+/// where udiv and urem are the same type as the add.
 Instruction *InstCombinerImpl::foldSameTypeDivCeil(BinaryOperator &I) {
-  Value *X, *X2, *Y, *Y2;
-  CmpPredicate Pred;
+  Value *X, *Y;
 
   auto UDivPat = m_OneUse(m_UDiv(m_Value(X), m_Value(Y)));
-  auto URemPat = m_OneUse(m_URem(m_Value(X2), m_Value(Y2)));
-  auto ICmpPat = m_OneUse(m_ICmp(Pred, URemPat, m_Zero()));
+  auto URemPat = m_OneUse(m_URem(m_Deferred(X), m_Deferred(Y)));
+  auto ICmpPat = m_OneUse(m_SpecificICmp(ICmpInst::ICMP_NE, URemPat, m_Zero()));
   auto ZExtCmpPat = m_OneUse(m_ZExt(ICmpPat));
 
-  if (!match(&I, m_c_Add(UDivPat, ZExtCmpPat)) || Pred != ICmpInst::ICMP_NE ||
-      X != X2 || Y != Y2 || !checkDivCeilNUW(X, Y, I, AC, DT))
+  if (!match(&I, m_c_Add(UDivPat, ZExtCmpPat)) ||
+      !checkDivCeilNUW(X, Y, I, AC, DT))
     return nullptr;
 
   Value *YMinusOne = Builder.CreateSub(Y, ConstantInt::get(Y->getType(), 1));
@@ -1559,23 +1558,21 @@ Instruction *InstCombinerImpl::foldSameTypeDivCeil(BinaryOperator &I) {
   return BinaryOperator::CreateUDiv(NUWAdd, Y);
 }
 
-// Fold the zext form of the div_ceil idiom:
-//   add(zext(udiv(X, Y)), zext(icmp ne(urem(X, Y), 0)))
-//     -> zext(udiv(add nuw(X, Y - 1), Y))
-// udiv and urem operate in a type narrower than the add; the result is
-// zexted back to the add's type.
+/// Fold the zext form of the div_ceil idiom:
+///   add(zext(udiv(X, Y)), zext(icmp ne(urem(X, Y), 0)))
+///     -> zext(udiv(add nuw(X, Y - 1), Y))
+/// udiv and urem operate in a type narrower than the add; the result is
+/// zexted back to the add's type.
 Instruction *InstCombinerImpl::foldZExtDivCeil(BinaryOperator &I) {
-  Value *X, *X2, *Y, *Y2;
-  CmpPredicate Pred;
+  Value *X, *Y;
 
   auto UDivPat = m_OneUse(m_UDiv(m_Value(X), m_Value(Y)));
-  auto URemPat = m_OneUse(m_URem(m_Value(X2), m_Value(Y2)));
-  auto ICmpPat = m_OneUse(m_ICmp(Pred, URemPat, m_Zero()));
+  auto URemPat = m_OneUse(m_URem(m_Deferred(X), m_Deferred(Y)));
+  auto ICmpPat = m_OneUse(m_SpecificICmp(ICmpInst::ICMP_NE, URemPat, m_Zero()));
   auto ZExtDivPat = m_OneUse(m_ZExt(UDivPat));
   auto ZExtCmpPat = m_OneUse(m_ZExt(ICmpPat));
 
   if (!match(&I, m_c_Add(ZExtDivPat, ZExtCmpPat)) ||
-      Pred != ICmpInst::ICMP_NE || X != X2 || Y != Y2 ||
       !checkDivCeilNUW(X, Y, I, AC, DT))
     return nullptr;
 



More information about the llvm-commits mailing list