[llvm] [InstCombine] Fold comparisons of urem with dividend (PR #214933)

Hadong Lee via llvm-commits llvm-commits at lists.llvm.org
Sat Aug 22 17:09:11 PDT 2026


https://github.com/ChrisLee02 updated https://github.com/llvm/llvm-project/pull/214933

>From df00c4872e8d5ad7d4185fe3ea38ee864e48b9fd Mon Sep 17 00:00:00 2001
From: Chrislee02 <hdchris765 at snu.ac.kr>
Date: Sun, 23 Aug 2026 09:05:03 +0900
Subject: [PATCH 1/2] [InstCombine] Add tests for urem dividend comparisons

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

diff --git a/llvm/test/Transforms/InstCombine/icmp-urem.ll b/llvm/test/Transforms/InstCombine/icmp-urem.ll
new file mode 100644
index 0000000000000..6334d9a91b1ee
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/icmp-urem.ll
@@ -0,0 +1,153 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+
+; (urem X, Y) == X --> X ult Y
+; (urem X, Y) != X --> X uge Y
+
+; Scalar tests.
+
+define i1 @icmp_eq_urem_dividend(i32 %x, i32 %y) {
+; CHECK-LABEL: define i1 @icmp_eq_urem_dividend(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]]) {
+; CHECK-NEXT:    [[REM:%.*]] = urem i32 [[X]], [[Y]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[REM]], [[X]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %rem = urem i32 %x, %y
+  %cmp = icmp eq i32 %rem, %x
+  ret i1 %cmp
+}
+
+; The compare operands may be commuted.
+
+define i1 @icmp_ne_urem_dividend_commuted(i32 %x, i32 %y) {
+; CHECK-LABEL: define i1 @icmp_ne_urem_dividend_commuted(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]]) {
+; CHECK-NEXT:    [[REM:%.*]] = urem i32 [[X]], [[Y]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ne i32 [[X]], [[REM]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %rem = urem i32 %x, %y
+  %cmp = icmp ne i32 %x, %rem
+  ret i1 %cmp
+}
+
+; Vector tests.
+
+define <2 x i1> @icmp_eq_urem_dividend_v2i32(<2 x i32> %x, <2 x i32> %y) {
+; CHECK-LABEL: define <2 x i1> @icmp_eq_urem_dividend_v2i32(
+; CHECK-SAME: <2 x i32> [[X:%.*]], <2 x i32> [[Y:%.*]]) {
+; CHECK-NEXT:    [[REM:%.*]] = urem <2 x i32> [[X]], [[Y]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq <2 x i32> [[REM]], [[X]]
+; CHECK-NEXT:    ret <2 x i1> [[CMP]]
+;
+  %rem = urem <2 x i32> %x, %y
+  %cmp = icmp eq <2 x i32> %rem, %x
+  ret <2 x i1> %cmp
+}
+
+define <vscale x 2 x i1> @icmp_ne_urem_dividend_nxv2i32(
+; CHECK-LABEL: define <vscale x 2 x i1> @icmp_ne_urem_dividend_nxv2i32(
+; CHECK-SAME: <vscale x 2 x i32> [[X:%.*]], <vscale x 2 x i32> [[Y:%.*]]) {
+; CHECK-NEXT:    [[REM:%.*]] = urem <vscale x 2 x i32> [[X]], [[Y]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ne <vscale x 2 x i32> [[REM]], [[X]]
+; CHECK-NEXT:    ret <vscale x 2 x i1> [[CMP]]
+;
+  <vscale x 2 x i32> %x, <vscale x 2 x i32> %y) {
+  %rem = urem <vscale x 2 x i32> %x, %y
+  %cmp = icmp ne <vscale x 2 x i32> %rem, %x
+  ret <vscale x 2 x i1> %cmp
+}
+
+; The fold does not require the urem to have one use.
+
+declare void @use_i32(i32)
+
+define i1 @icmp_eq_urem_dividend_extra_use(i32 %x, i32 %y) {
+; CHECK-LABEL: define i1 @icmp_eq_urem_dividend_extra_use(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]]) {
+; CHECK-NEXT:    [[REM:%.*]] = urem i32 [[X]], [[Y]]
+; CHECK-NEXT:    call void @use_i32(i32 [[REM]])
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[REM]], [[X]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %rem = urem i32 %x, %y
+  call void @use_i32(i32 %rem)
+  %cmp = icmp eq i32 %rem, %x
+  ret i1 %cmp
+}
+
+; Preserve the umax simplification after replacing the comparison.
+
+declare i64 @llvm.umax.i64(i64, i64)
+
+define i64 @icmp_eq_urem_dividend_umax(i64 %x, i64 %y) {
+; CHECK-LABEL: define i64 @icmp_eq_urem_dividend_umax(
+; CHECK-SAME: i64 [[X:%.*]], i64 [[Y:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[REM:%.*]] = urem i64 [[X]], [[Y]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i64 [[X]], [[REM]]
+; CHECK-NEXT:    br i1 [[CMP]], label %[[EXIT:.*]], label %[[BODY:.*]]
+; CHECK:       [[BODY]]:
+; CHECK-NEXT:    [[SUB:%.*]] = sub nuw i64 [[X]], [[REM]]
+; CHECK-NEXT:    ret i64 [[SUB]]
+; CHECK:       [[EXIT]]:
+; CHECK-NEXT:    ret i64 0
+;
+entry:
+  %rem = urem i64 %x, %y
+  %sub = sub nuw i64 %x, %rem
+  %cmp = icmp eq i64 %x, %rem
+  br i1 %cmp, label %exit, label %body
+
+body:
+  %umax = call i64 @llvm.umax.i64(i64 %sub, i64 1)
+  ret i64 %umax
+
+exit:
+  ret i64 0
+}
+
+; Tests that must not fold.
+
+; The value compared with the remainder must be its dividend.
+
+define i1 @not_icmp_eq_urem_different_dividend(i32 %x, i32 %y, i32 %z) {
+; CHECK-LABEL: define i1 @not_icmp_eq_urem_different_dividend(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]], i32 [[Z:%.*]]) {
+; CHECK-NEXT:    [[REM:%.*]] = urem i32 [[X]], [[Y]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[REM]], [[Z]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %rem = urem i32 %x, %y
+  %cmp = icmp eq i32 %rem, %z
+  ret i1 %cmp
+}
+
+; The analogous signed-remainder fold requires comparing magnitudes.
+
+define i1 @not_icmp_eq_srem_dividend(i32 %x, i32 %y) {
+; CHECK-LABEL: define i1 @not_icmp_eq_srem_dividend(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]]) {
+; CHECK-NEXT:    [[REM:%.*]] = srem i32 [[X]], [[Y]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[REM]], [[X]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %rem = srem i32 %x, %y
+  %cmp = icmp eq i32 %rem, %x
+  ret i1 %cmp
+}
+
+; This fold is only valid for equality predicates.
+
+define i1 @not_icmp_slt_urem_dividend(i32 %x, i32 %y) {
+; CHECK-LABEL: define i1 @not_icmp_slt_urem_dividend(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]]) {
+; CHECK-NEXT:    [[REM:%.*]] = urem i32 [[X]], [[Y]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp slt i32 [[REM]], [[X]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %rem = urem i32 %x, %y
+  %cmp = icmp slt i32 %rem, %x
+  ret i1 %cmp
+}

>From e151c15681ccaa966dd56540b4bfc34349989439 Mon Sep 17 00:00:00 2001
From: Chrislee02 <hdchris765 at snu.ac.kr>
Date: Sun, 23 Aug 2026 09:08:51 +0900
Subject: [PATCH 2/2] [InstCombine] Fold comparisons of urem with dividend

---
 .../InstCombine/InstCombineCompares.cpp        | 12 ++++++++++++
 llvm/test/Transforms/InstCombine/icmp-urem.ll  | 18 +++++++-----------
 2 files changed, 19 insertions(+), 11 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
index 5f0b3fb888ecf..e903b4470f8be 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
@@ -5310,6 +5310,18 @@ Instruction *InstCombinerImpl::foldICmpBinOp(ICmpInst &I,
     return NewICmp;
 
   const CmpInst::Predicate Pred = I.getPredicate();
+
+  // (X urem Y) == X --> X u< Y
+  // (X urem Y) != X --> X u>= Y
+  Value *Dividend, *Divisor;
+  if (I.isEquality() &&
+      match(&I, m_c_ICmp(m_URem(m_Value(Dividend), m_Value(Divisor)),
+                         m_Deferred(Dividend)))) {
+    CmpInst::Predicate NewPred =
+        Pred == ICmpInst::ICMP_EQ ? ICmpInst::ICMP_ULT : ICmpInst::ICMP_UGE;
+    return new ICmpInst(NewPred, Dividend, Divisor);
+  }
+
   Value *X;
 
   // Convert add-with-unsigned-overflow comparisons into a 'not' with compare.
diff --git a/llvm/test/Transforms/InstCombine/icmp-urem.ll b/llvm/test/Transforms/InstCombine/icmp-urem.ll
index 6334d9a91b1ee..8d9803dede045 100644
--- a/llvm/test/Transforms/InstCombine/icmp-urem.ll
+++ b/llvm/test/Transforms/InstCombine/icmp-urem.ll
@@ -9,8 +9,7 @@
 define i1 @icmp_eq_urem_dividend(i32 %x, i32 %y) {
 ; CHECK-LABEL: define i1 @icmp_eq_urem_dividend(
 ; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]]) {
-; CHECK-NEXT:    [[REM:%.*]] = urem i32 [[X]], [[Y]]
-; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[REM]], [[X]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i32 [[X]], [[Y]]
 ; CHECK-NEXT:    ret i1 [[CMP]]
 ;
   %rem = urem i32 %x, %y
@@ -23,8 +22,7 @@ define i1 @icmp_eq_urem_dividend(i32 %x, i32 %y) {
 define i1 @icmp_ne_urem_dividend_commuted(i32 %x, i32 %y) {
 ; CHECK-LABEL: define i1 @icmp_ne_urem_dividend_commuted(
 ; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]]) {
-; CHECK-NEXT:    [[REM:%.*]] = urem i32 [[X]], [[Y]]
-; CHECK-NEXT:    [[CMP:%.*]] = icmp ne i32 [[X]], [[REM]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp uge i32 [[X]], [[Y]]
 ; CHECK-NEXT:    ret i1 [[CMP]]
 ;
   %rem = urem i32 %x, %y
@@ -37,8 +35,7 @@ define i1 @icmp_ne_urem_dividend_commuted(i32 %x, i32 %y) {
 define <2 x i1> @icmp_eq_urem_dividend_v2i32(<2 x i32> %x, <2 x i32> %y) {
 ; CHECK-LABEL: define <2 x i1> @icmp_eq_urem_dividend_v2i32(
 ; CHECK-SAME: <2 x i32> [[X:%.*]], <2 x i32> [[Y:%.*]]) {
-; CHECK-NEXT:    [[REM:%.*]] = urem <2 x i32> [[X]], [[Y]]
-; CHECK-NEXT:    [[CMP:%.*]] = icmp eq <2 x i32> [[REM]], [[X]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ult <2 x i32> [[X]], [[Y]]
 ; CHECK-NEXT:    ret <2 x i1> [[CMP]]
 ;
   %rem = urem <2 x i32> %x, %y
@@ -49,8 +46,7 @@ define <2 x i1> @icmp_eq_urem_dividend_v2i32(<2 x i32> %x, <2 x i32> %y) {
 define <vscale x 2 x i1> @icmp_ne_urem_dividend_nxv2i32(
 ; CHECK-LABEL: define <vscale x 2 x i1> @icmp_ne_urem_dividend_nxv2i32(
 ; CHECK-SAME: <vscale x 2 x i32> [[X:%.*]], <vscale x 2 x i32> [[Y:%.*]]) {
-; CHECK-NEXT:    [[REM:%.*]] = urem <vscale x 2 x i32> [[X]], [[Y]]
-; CHECK-NEXT:    [[CMP:%.*]] = icmp ne <vscale x 2 x i32> [[REM]], [[X]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp uge <vscale x 2 x i32> [[X]], [[Y]]
 ; CHECK-NEXT:    ret <vscale x 2 x i1> [[CMP]]
 ;
   <vscale x 2 x i32> %x, <vscale x 2 x i32> %y) {
@@ -68,7 +64,7 @@ define i1 @icmp_eq_urem_dividend_extra_use(i32 %x, i32 %y) {
 ; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]]) {
 ; CHECK-NEXT:    [[REM:%.*]] = urem i32 [[X]], [[Y]]
 ; CHECK-NEXT:    call void @use_i32(i32 [[REM]])
-; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[REM]], [[X]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i32 [[X]], [[Y]]
 ; CHECK-NEXT:    ret i1 [[CMP]]
 ;
   %rem = urem i32 %x, %y
@@ -85,10 +81,10 @@ define i64 @icmp_eq_urem_dividend_umax(i64 %x, i64 %y) {
 ; CHECK-LABEL: define i64 @icmp_eq_urem_dividend_umax(
 ; CHECK-SAME: i64 [[X:%.*]], i64 [[Y:%.*]]) {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
-; CHECK-NEXT:    [[REM:%.*]] = urem i64 [[X]], [[Y]]
-; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i64 [[X]], [[REM]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i64 [[X]], [[Y]]
 ; CHECK-NEXT:    br i1 [[CMP]], label %[[EXIT:.*]], label %[[BODY:.*]]
 ; CHECK:       [[BODY]]:
+; CHECK-NEXT:    [[REM:%.*]] = urem i64 [[X]], [[Y]]
 ; CHECK-NEXT:    [[SUB:%.*]] = sub nuw i64 [[X]], [[REM]]
 ; CHECK-NEXT:    ret i64 [[SUB]]
 ; CHECK:       [[EXIT]]:



More information about the llvm-commits mailing list