[llvm] [InstCombine] Fold icmp (zext X), (and (trunc Y), Mask) -> icmp X, trunc Y (PR #221189)

armandeep singh via llvm-commits llvm-commits at lists.llvm.org
Tue Sep 15 11:59:53 PDT 2026


https://github.com/armandeep23947 updated https://github.com/llvm/llvm-project/pull/221189

>From 7546dcabf6ad7129915915c7c8060876e431bc9c Mon Sep 17 00:00:00 2001
From: armandeep <armandeepsinghbains123 at gmail.com>
Date: Fri, 4 Sep 2026 12:52:59 +0530
Subject: [PATCH 1/4] [InstCombine] Precommit tests for icmp zext/trunc-and
 fold (NFC)

---
 .../InstCombine/icmp-zext-trunc-and.ll        | 106 ++++++++++++++++++
 1 file changed, 106 insertions(+)
 create mode 100644 llvm/test/Transforms/InstCombine/icmp-zext-trunc-and.ll

diff --git a/llvm/test/Transforms/InstCombine/icmp-zext-trunc-and.ll b/llvm/test/Transforms/InstCombine/icmp-zext-trunc-and.ll
new file mode 100644
index 0000000000000..331ef4e465932
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/icmp-zext-trunc-and.ll
@@ -0,0 +1,106 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+
+; Exact shape from issue #115466 — ugt, not eq.
+define i1 @test_zext_trunc_and_ugt(i16 %x, i64 %y) {
+; CHECK-LABEL: define i1 @test_zext_trunc_and_ugt(
+; CHECK-SAME: i16 [[X:%.*]], i64 [[Y:%.*]]) {
+; CHECK-NEXT:    [[EXT:%.*]] = zext i16 [[X]] to i32
+; CHECK-NEXT:    [[TRUNC:%.*]] = trunc i64 [[Y]] to i32
+; CHECK-NEXT:    [[MASK:%.*]] = and i32 [[TRUNC]], 65535
+; CHECK-NEXT:    [[CMP:%.*]] = icmp samesign ult i32 [[MASK]], [[EXT]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %ext = zext i16 %x to i32
+  %trunc = trunc i64 %y to i32
+  %mask = and i32 %trunc, 65535
+  %cmp = icmp ugt i32 %ext, %mask
+  ret i1 %cmp
+}
+
+; AND operands reversed (mask first) — needs m_c_And to catch this.
+define i1 @test_zext_trunc_and_ugt_commuted(i16 %x, i64 %y) {
+; CHECK-LABEL: define i1 @test_zext_trunc_and_ugt_commuted(
+; CHECK-SAME: i16 [[X:%.*]], i64 [[Y:%.*]]) {
+; CHECK-NEXT:    [[EXT:%.*]] = zext i16 [[X]] to i32
+; CHECK-NEXT:    [[TRUNC:%.*]] = trunc i64 [[Y]] to i32
+; CHECK-NEXT:    [[MASK:%.*]] = and i32 [[TRUNC]], 65535
+; CHECK-NEXT:    [[CMP:%.*]] = icmp samesign ult i32 [[MASK]], [[EXT]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %ext = zext i16 %x to i32
+  %trunc = trunc i64 %y to i32
+  %mask = and i32 65535, %trunc
+  %cmp = icmp ugt i32 %ext, %mask
+  ret i1 %cmp
+}
+
+; Signed predicate — must NOT fold.
+define i1 @test_signed_cmp_not_folded(i16 %x, i64 %y) {
+; CHECK-LABEL: define i1 @test_signed_cmp_not_folded(
+; CHECK-SAME: i16 [[X:%.*]], i64 [[Y:%.*]]) {
+; CHECK-NEXT:    [[EXT:%.*]] = zext i16 [[X]] to i32
+; CHECK-NEXT:    [[TRUNC:%.*]] = trunc i64 [[Y]] to i32
+; CHECK-NEXT:    [[MASK:%.*]] = and i32 [[TRUNC]], 65535
+; CHECK-NEXT:    [[CMP:%.*]] = icmp samesign ult i32 [[MASK]], [[EXT]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %ext = zext i16 %x to i32
+  %trunc = trunc i64 %y to i32
+  %mask = and i32 %trunc, 65535
+  %cmp = icmp sgt i32 %ext, %mask
+  ret i1 %cmp
+}
+
+; AND has another use — must NOT fold.
+define i1 @test_and_multiuse_not_folded(i16 %x, i64 %y, ptr %p) {
+; CHECK-LABEL: define i1 @test_and_multiuse_not_folded(
+; CHECK-SAME: i16 [[X:%.*]], i64 [[Y:%.*]], ptr [[P:%.*]]) {
+; CHECK-NEXT:    [[EXT:%.*]] = zext i16 [[X]] to i32
+; CHECK-NEXT:    [[TRUNC:%.*]] = trunc i64 [[Y]] to i32
+; CHECK-NEXT:    [[MASK:%.*]] = and i32 [[TRUNC]], 65535
+; CHECK-NEXT:    store i32 [[MASK]], ptr [[P]], align 4
+; CHECK-NEXT:    [[CMP:%.*]] = icmp samesign ult i32 [[MASK]], [[EXT]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %ext = zext i16 %x to i32
+  %trunc = trunc i64 %y to i32
+  %mask = and i32 %trunc, 65535
+  store i32 %mask, ptr %p
+  %cmp = icmp ugt i32 %ext, %mask
+  ret i1 %cmp
+}
+
+; Mask too large for X's width — must NOT fold.
+define i1 @negative_mask_too_big(i16 %x, i64 %y) {
+; CHECK-LABEL: define i1 @negative_mask_too_big(
+; CHECK-SAME: i16 [[X:%.*]], i64 [[Y:%.*]]) {
+; CHECK-NEXT:    [[EXT:%.*]] = zext i16 [[X]] to i32
+; CHECK-NEXT:    [[TRUNC:%.*]] = trunc i64 [[Y]] to i32
+; CHECK-NEXT:    [[MASK:%.*]] = and i32 [[TRUNC]], 131071
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[MASK]], [[EXT]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %ext = zext i16 %x to i32
+  %trunc = trunc i64 %y to i32
+  %mask = and i32 %trunc, 131071
+  %cmp = icmp eq i32 %ext, %mask
+  ret i1 %cmp
+}
+; Direct ult form — zext stays at operand(0), no ugt-swap canonicalization needed.
+; Exercises the original (non-mirrored) fold path in foldICmpWithZextOrSext.
+define i1 @test_zext_trunc_and_ult_direct(i16 %x, i64 %y) {
+; CHECK-LABEL: define i1 @test_zext_trunc_and_ult_direct(
+; CHECK-SAME: i16 [[X:%.*]], i64 [[Y:%.*]]) {
+; CHECK-NEXT:    [[EXT:%.*]] = zext i16 [[X]] to i32
+; CHECK-NEXT:    [[TRUNC:%.*]] = trunc i64 [[Y]] to i32
+; CHECK-NEXT:    [[MASK:%.*]] = and i32 [[TRUNC]], 65535
+; CHECK-NEXT:    [[CMP:%.*]] = icmp samesign ult i32 [[MASK]], [[EXT]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %ext = zext i16 %x to i32
+  %trunc = trunc i64 %y to i32
+  %mask = and i32 %trunc, 65535
+  %cmp = icmp ult i32 %mask, %ext
+  ret i1 %cmp
+}

>From d15837a34fd137adde34ca3cf7bc26584bda664e Mon Sep 17 00:00:00 2001
From: armandeep <armandeepsinghbains123 at gmail.com>
Date: Fri, 4 Sep 2026 12:55:00 +0530
Subject: [PATCH 2/4] [InstCombine] Fold icmp (zext X), (and (trunc Y), Mask)
 -> icmp X, trunc Y

Fixes #115466
---
 .../InstCombine/InstCombineCompares.cpp       | 54 ++++++++++++++++++-
 .../InstCombine/icmp-zext-trunc-and.ll        | 28 ++++------
 2 files changed, 63 insertions(+), 19 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
index 72f08b398e45d..1ce67ca10fc23 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
@@ -12,6 +12,7 @@
 
 #include "InstCombineInternal.h"
 #include "llvm/ADT/APFloat.h"
+#include "llvm/ADT/APInt.h"
 #include "llvm/ADT/APSInt.h"
 #include "llvm/ADT/SetVector.h"
 #include "llvm/ADT/Statistic.h"
@@ -6575,6 +6576,27 @@ Instruction *InstCombinerImpl::foldICmpWithZextOrSext(ICmpInst &ICmp) {
     return new ICmpInst(ICmp.getUnsignedPredicate(), X, Y);
   }
 
+  // Operand(0) is already known to be a zero extension of X
+  // check if operand(1) is an AND between a truncated Y and a constant mask
+  const APInt *Mask;
+  if (!IsSignedExt && !IsSignedCmp &&
+      match(ICmp.getOperand(1),
+            m_OneUse(m_c_And(m_Trunc(m_Value(Y)), m_APInt(Mask))))) {
+    // get the bit width of X.
+    Type *SmallType = X->getType();
+    unsigned SmallWidth = SmallType->getScalarSizeInBits();
+
+    // if the mask only uses bits that fit in X, the higher bits are zero
+    //  this means we can safely truncate Y to X's type
+    if (*Mask == APInt::getLowBitsSet(Mask->getBitWidth(), SmallWidth)) {
+      // truncate Y directly to type of X
+      Value *NewTrunc = Builder.CreateTrunc(Y, SmallType);
+      // use the unsigned predicate since both values now have the same width.
+
+      return new ICmpInst(ICmp.getUnsignedPredicate(), X, NewTrunc);
+    }
+  }
+
   // Below here, we are only folding a compare with constant.
   auto *C = dyn_cast<Constant>(ICmp.getOperand(1));
   if (!C)
@@ -6626,12 +6648,42 @@ Instruction *InstCombinerImpl::foldICmpWithCastOp(ICmpInst &ICmp) {
     return new ICmpInst(ICmp.getPredicate(),
                         SimplifiedOp0 ? SimplifiedOp0 : ICmp.getOperand(0),
                         SimplifiedOp1 ? SimplifiedOp1 : ICmp.getOperand(1));
+  // Catch the mirrored operand order: icmp Pred (and (trunc Y), Mask), (zext
+  // X). Canonicalization (like ugt -> ult) often swaps operands, pushing the
+  // zext to Operand 1. We need to handle this to avoid missing optimizations.
+  {
+    Value *X, *Y;
+    const APInt *Mask;
 
+    // Ensure we have an unsigned compare with a zext on the right side.
+    // Then look for our one use AND with a mask on the left.
+    if (match(ICmp.getOperand(1), m_ZExt(m_Value(X))) && !ICmp.isSigned() &&
+        match(ICmp.getOperand(0),
+              m_OneUse(m_c_And(m_Trunc(m_Value(Y)), m_APInt(Mask))))) {
+
+      Type *SmallType = X->getType();
+      unsigned SmallWidth = SmallType->getScalarSizeInBits();
+
+      // If the mask exactly covers the bits of the narrower type, the higher
+      // bits are already guaranteed to be zero. We can bypass the extension and
+      // compare the truncated values directly.
+      if (*Mask == APInt::getLowBitsSet(Mask->getBitWidth(), SmallWidth)) {
+        Value *NewTrunc = Builder.CreateTrunc(Y, SmallType);
+
+        // Preserve the original comparison logic by swapping the predicate back
+        // and comparing our newly truncated Y against X.
+
+        return new ICmpInst(ICmp.getUnsignedPredicate(), NewTrunc, X);
+      }
+    }
+  }
   auto *CastOp0 = dyn_cast<CastInst>(ICmp.getOperand(0));
   Value *Op1 = ICmp.getOperand(1);
   if (!CastOp0)
     return nullptr;
-  if (!isa<Constant>(ICmp.getOperand(1)) && !isa<CastInst>(ICmp.getOperand(1)))
+  if (!isa<Constant>(ICmp.getOperand(1)) &&
+      !isa<CastInst>(ICmp.getOperand(1)) &&
+      !isa<BinaryOperator>(ICmp.getOperand(1)))
     return nullptr;
 
   Value *Op0Src = CastOp0->getOperand(0);
diff --git a/llvm/test/Transforms/InstCombine/icmp-zext-trunc-and.ll b/llvm/test/Transforms/InstCombine/icmp-zext-trunc-and.ll
index 331ef4e465932..b948b47513bdf 100644
--- a/llvm/test/Transforms/InstCombine/icmp-zext-trunc-and.ll
+++ b/llvm/test/Transforms/InstCombine/icmp-zext-trunc-and.ll
@@ -5,10 +5,8 @@
 define i1 @test_zext_trunc_and_ugt(i16 %x, i64 %y) {
 ; CHECK-LABEL: define i1 @test_zext_trunc_and_ugt(
 ; CHECK-SAME: i16 [[X:%.*]], i64 [[Y:%.*]]) {
-; CHECK-NEXT:    [[EXT:%.*]] = zext i16 [[X]] to i32
-; CHECK-NEXT:    [[TRUNC:%.*]] = trunc i64 [[Y]] to i32
-; CHECK-NEXT:    [[MASK:%.*]] = and i32 [[TRUNC]], 65535
-; CHECK-NEXT:    [[CMP:%.*]] = icmp samesign ult i32 [[MASK]], [[EXT]]
+; CHECK-NEXT:    [[TMP1:%.*]] = trunc i64 [[Y]] to i16
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ugt i16 [[X]], [[TMP1]]
 ; CHECK-NEXT:    ret i1 [[CMP]]
 ;
   %ext = zext i16 %x to i32
@@ -22,10 +20,8 @@ define i1 @test_zext_trunc_and_ugt(i16 %x, i64 %y) {
 define i1 @test_zext_trunc_and_ugt_commuted(i16 %x, i64 %y) {
 ; CHECK-LABEL: define i1 @test_zext_trunc_and_ugt_commuted(
 ; CHECK-SAME: i16 [[X:%.*]], i64 [[Y:%.*]]) {
-; CHECK-NEXT:    [[EXT:%.*]] = zext i16 [[X]] to i32
-; CHECK-NEXT:    [[TRUNC:%.*]] = trunc i64 [[Y]] to i32
-; CHECK-NEXT:    [[MASK:%.*]] = and i32 [[TRUNC]], 65535
-; CHECK-NEXT:    [[CMP:%.*]] = icmp samesign ult i32 [[MASK]], [[EXT]]
+; CHECK-NEXT:    [[TMP1:%.*]] = trunc i64 [[Y]] to i16
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ugt i16 [[X]], [[TMP1]]
 ; CHECK-NEXT:    ret i1 [[CMP]]
 ;
   %ext = zext i16 %x to i32
@@ -35,17 +31,15 @@ define i1 @test_zext_trunc_and_ugt_commuted(i16 %x, i64 %y) {
   ret i1 %cmp
 }
 
-; Signed predicate — must NOT fold.
-define i1 @test_signed_cmp_not_folded(i16 %x, i64 %y) {
+; Signed predicate on a value that cannot be proven non-negative — must NOT fold.
+define i1 @test_signed_cmp_not_folded(i32 %ext, i64 %y) {
 ; CHECK-LABEL: define i1 @test_signed_cmp_not_folded(
-; CHECK-SAME: i16 [[X:%.*]], i64 [[Y:%.*]]) {
-; CHECK-NEXT:    [[EXT:%.*]] = zext i16 [[X]] to i32
+; CHECK-SAME: i32 [[EXT:%.*]], i64 [[Y:%.*]]) {
 ; CHECK-NEXT:    [[TRUNC:%.*]] = trunc i64 [[Y]] to i32
 ; CHECK-NEXT:    [[MASK:%.*]] = and i32 [[TRUNC]], 65535
-; CHECK-NEXT:    [[CMP:%.*]] = icmp samesign ult i32 [[MASK]], [[EXT]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp sgt i32 [[EXT]], [[MASK]]
 ; CHECK-NEXT:    ret i1 [[CMP]]
 ;
-  %ext = zext i16 %x to i32
   %trunc = trunc i64 %y to i32
   %mask = and i32 %trunc, 65535
   %cmp = icmp sgt i32 %ext, %mask
@@ -92,10 +86,8 @@ define i1 @negative_mask_too_big(i16 %x, i64 %y) {
 define i1 @test_zext_trunc_and_ult_direct(i16 %x, i64 %y) {
 ; CHECK-LABEL: define i1 @test_zext_trunc_and_ult_direct(
 ; CHECK-SAME: i16 [[X:%.*]], i64 [[Y:%.*]]) {
-; CHECK-NEXT:    [[EXT:%.*]] = zext i16 [[X]] to i32
-; CHECK-NEXT:    [[TRUNC:%.*]] = trunc i64 [[Y]] to i32
-; CHECK-NEXT:    [[MASK:%.*]] = and i32 [[TRUNC]], 65535
-; CHECK-NEXT:    [[CMP:%.*]] = icmp samesign ult i32 [[MASK]], [[EXT]]
+; CHECK-NEXT:    [[TMP1:%.*]] = trunc i64 [[Y]] to i16
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ugt i16 [[X]], [[TMP1]]
 ; CHECK-NEXT:    ret i1 [[CMP]]
 ;
   %ext = zext i16 %x to i32

>From c598bb13ea6cf90c9800b18b0aba5b184957b192 Mon Sep 17 00:00:00 2001
From: armandeep <armandeepsinghbains123 at gmail.com>
Date: Tue, 15 Sep 2026 09:43:33 +0530
Subject: [PATCH 3/4] Address review: remove dead code, move fold to
 foldICmpBinOp, simplify matchers with m_And/isMask

---
 .../InstCombine/InstCombineCompares.cpp       | 83 +++++++------------
 1 file changed, 31 insertions(+), 52 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
index 6b863a8076253..aaccbe9428756 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
@@ -5315,6 +5315,36 @@ Instruction *InstCombinerImpl::foldICmpBinOp(ICmpInst &I,
   const CmpInst::Predicate Pred = I.getPredicate();
   Value *X;
 
+  // Catch the mirrored operand order: icmp Pred (and (trunc Y), Mask), (zext
+  // X). Canonicalization (like ugt -> ult) often swaps operands, pushing the
+  // zext to Operand 1. We need to handle this to avoid missing optimizations.
+  {
+    Value *X, *Y;
+    const APInt *Mask;
+
+    // Ensure we have an unsigned compare with a zext on the right side.
+    // Then look for our one use AND with a mask on the left.
+    if (match(I.getOperand(1), m_ZExt(m_Value(X))) && !I.isSigned() &&
+        match(I.getOperand(0),
+              m_OneUse(m_And(m_Trunc(m_Value(Y)), m_APInt(Mask))))) {
+
+      Type *SmallType = X->getType();
+      unsigned SmallWidth = SmallType->getScalarSizeInBits();
+
+      // If the mask exactly covers the bits of the narrower type, the higher
+      // bits are already guaranteed to be zero. We can bypass the extension and
+      // compare the truncated values directly.
+      if (Mask->isMask(SmallWidth)) {
+        Value *NewTrunc = Builder.CreateTrunc(Y, SmallType);
+
+        // Preserve the original comparison logic by swapping the predicate back
+        // and comparing our newly truncated Y against X.
+
+        return new ICmpInst(I.getUnsignedPredicate(), NewTrunc, X);
+      }
+    }
+  }
+
   // Convert add-with-unsigned-overflow comparisons into a 'not' with compare.
   // (Op1 + X) u</u>= Op1 --> ~Op1 u</u>= X
   if (match(Op0, m_OneUse(m_c_Add(m_Specific(Op1), m_Value(X)))) &&
@@ -6576,27 +6606,6 @@ Instruction *InstCombinerImpl::foldICmpWithZextOrSext(ICmpInst &ICmp) {
     return new ICmpInst(ICmp.getUnsignedPredicate(), X, Y);
   }
 
-  // Operand(0) is already known to be a zero extension of X
-  // check if operand(1) is an AND between a truncated Y and a constant mask
-  const APInt *Mask;
-  if (!IsSignedExt && !IsSignedCmp &&
-      match(ICmp.getOperand(1),
-            m_OneUse(m_c_And(m_Trunc(m_Value(Y)), m_APInt(Mask))))) {
-    // get the bit width of X.
-    Type *SmallType = X->getType();
-    unsigned SmallWidth = SmallType->getScalarSizeInBits();
-
-    // if the mask only uses bits that fit in X, the higher bits are zero
-    //  this means we can safely truncate Y to X's type
-    if (*Mask == APInt::getLowBitsSet(Mask->getBitWidth(), SmallWidth)) {
-      // truncate Y directly to type of X
-      Value *NewTrunc = Builder.CreateTrunc(Y, SmallType);
-      // use the unsigned predicate since both values now have the same width.
-
-      return new ICmpInst(ICmp.getUnsignedPredicate(), X, NewTrunc);
-    }
-  }
-
   // Below here, we are only folding a compare with constant.
   auto *C = dyn_cast<Constant>(ICmp.getOperand(1));
   if (!C)
@@ -6648,42 +6657,12 @@ Instruction *InstCombinerImpl::foldICmpWithCastOp(ICmpInst &ICmp) {
     return new ICmpInst(ICmp.getPredicate(),
                         SimplifiedOp0 ? SimplifiedOp0 : ICmp.getOperand(0),
                         SimplifiedOp1 ? SimplifiedOp1 : ICmp.getOperand(1));
-  // Catch the mirrored operand order: icmp Pred (and (trunc Y), Mask), (zext
-  // X). Canonicalization (like ugt -> ult) often swaps operands, pushing the
-  // zext to Operand 1. We need to handle this to avoid missing optimizations.
-  {
-    Value *X, *Y;
-    const APInt *Mask;
-
-    // Ensure we have an unsigned compare with a zext on the right side.
-    // Then look for our one use AND with a mask on the left.
-    if (match(ICmp.getOperand(1), m_ZExt(m_Value(X))) && !ICmp.isSigned() &&
-        match(ICmp.getOperand(0),
-              m_OneUse(m_c_And(m_Trunc(m_Value(Y)), m_APInt(Mask))))) {
-
-      Type *SmallType = X->getType();
-      unsigned SmallWidth = SmallType->getScalarSizeInBits();
-
-      // If the mask exactly covers the bits of the narrower type, the higher
-      // bits are already guaranteed to be zero. We can bypass the extension and
-      // compare the truncated values directly.
-      if (*Mask == APInt::getLowBitsSet(Mask->getBitWidth(), SmallWidth)) {
-        Value *NewTrunc = Builder.CreateTrunc(Y, SmallType);
 
-        // Preserve the original comparison logic by swapping the predicate back
-        // and comparing our newly truncated Y against X.
-
-        return new ICmpInst(ICmp.getUnsignedPredicate(), NewTrunc, X);
-      }
-    }
-  }
   auto *CastOp0 = dyn_cast<CastInst>(ICmp.getOperand(0));
   Value *Op1 = ICmp.getOperand(1);
   if (!CastOp0)
     return nullptr;
-  if (!isa<Constant>(ICmp.getOperand(1)) &&
-      !isa<CastInst>(ICmp.getOperand(1)) &&
-      !isa<BinaryOperator>(ICmp.getOperand(1)))
+  if (!isa<Constant>(ICmp.getOperand(1)) && !isa<CastInst>(ICmp.getOperand(1)))
     return nullptr;
 
   Value *Op0Src = CastOp0->getOperand(0);

>From 209ab50e89ba6e3a66890055ebe192b0e128b49d Mon Sep 17 00:00:00 2001
From: armandeep <armandeepsinghbains123 at gmail.com>
Date: Wed, 16 Sep 2026 00:26:40 +0530
Subject: [PATCH 4/4] Address review: shorten comments, move fold to end of
 foldICmpBinOp, reorder cheap check first, use Pred, remove redundant tests

---
 .../InstCombine/InstCombineCompares.cpp       | 47 +++++++------------
 .../InstCombine/icmp-zext-trunc-and.ll        | 31 +-----------
 2 files changed, 19 insertions(+), 59 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
index 0a28c0131871e..d4a83ca81073a 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
@@ -5328,36 +5328,6 @@ Instruction *InstCombinerImpl::foldICmpBinOp(ICmpInst &I,
 
   Value *X;
 
-  // Catch the mirrored operand order: icmp Pred (and (trunc Y), Mask), (zext
-  // X). Canonicalization (like ugt -> ult) often swaps operands, pushing the
-  // zext to Operand 1. We need to handle this to avoid missing optimizations.
-  {
-    Value *X, *Y;
-    const APInt *Mask;
-
-    // Ensure we have an unsigned compare with a zext on the right side.
-    // Then look for our one use AND with a mask on the left.
-    if (match(I.getOperand(1), m_ZExt(m_Value(X))) && !I.isSigned() &&
-        match(I.getOperand(0),
-              m_OneUse(m_And(m_Trunc(m_Value(Y)), m_APInt(Mask))))) {
-
-      Type *SmallType = X->getType();
-      unsigned SmallWidth = SmallType->getScalarSizeInBits();
-
-      // If the mask exactly covers the bits of the narrower type, the higher
-      // bits are already guaranteed to be zero. We can bypass the extension and
-      // compare the truncated values directly.
-      if (Mask->isMask(SmallWidth)) {
-        Value *NewTrunc = Builder.CreateTrunc(Y, SmallType);
-
-        // Preserve the original comparison logic by swapping the predicate back
-        // and comparing our newly truncated Y against X.
-
-        return new ICmpInst(I.getUnsignedPredicate(), NewTrunc, X);
-      }
-    }
-  }
-
   // Convert add-with-unsigned-overflow comparisons into a 'not' with compare.
   // (Op1 + X) u</u>= Op1 --> ~Op1 u</u>= X
   if (match(Op0, m_OneUse(m_c_Add(m_Specific(Op1), m_Value(X)))) &&
@@ -5856,6 +5826,23 @@ Instruction *InstCombinerImpl::foldICmpBinOp(ICmpInst &I,
   if (Value *V = foldShiftIntoShiftInAnotherHandOfAndInICmp(I, SQ, Builder))
     return replaceInstUsesWith(I, V);
 
+  // icmp (zext X), (and (trunc Y), Mask) -> icmp X, trunc Y IFF Mask exactly
+  // covers the bits of X
+  {
+    Value *Y;
+    const APInt *Mask;
+    if (!I.isSigned() && match(I.getOperand(1), m_ZExt(m_Value(X))) &&
+        match(I.getOperand(0),
+              m_OneUse(m_And(m_Trunc(m_Value(Y)), m_APInt(Mask))))) {
+      Type *SmallType = X->getType();
+      unsigned SmallWidth = SmallType->getScalarSizeInBits();
+      if (Mask->isMask(SmallWidth)) {
+        Value *NewTrunc = Builder.CreateTrunc(Y, SmallType);
+        return new ICmpInst(Pred, NewTrunc, X);
+      }
+    }
+  }
+
   return nullptr;
 }
 
diff --git a/llvm/test/Transforms/InstCombine/icmp-zext-trunc-and.ll b/llvm/test/Transforms/InstCombine/icmp-zext-trunc-and.ll
index b948b47513bdf..9cb27b07fba0a 100644
--- a/llvm/test/Transforms/InstCombine/icmp-zext-trunc-and.ll
+++ b/llvm/test/Transforms/InstCombine/icmp-zext-trunc-and.ll
@@ -16,20 +16,7 @@ define i1 @test_zext_trunc_and_ugt(i16 %x, i64 %y) {
   ret i1 %cmp
 }
 
-; AND operands reversed (mask first) — needs m_c_And to catch this.
-define i1 @test_zext_trunc_and_ugt_commuted(i16 %x, i64 %y) {
-; CHECK-LABEL: define i1 @test_zext_trunc_and_ugt_commuted(
-; CHECK-SAME: i16 [[X:%.*]], i64 [[Y:%.*]]) {
-; CHECK-NEXT:    [[TMP1:%.*]] = trunc i64 [[Y]] to i16
-; CHECK-NEXT:    [[CMP:%.*]] = icmp ugt i16 [[X]], [[TMP1]]
-; CHECK-NEXT:    ret i1 [[CMP]]
-;
-  %ext = zext i16 %x to i32
-  %trunc = trunc i64 %y to i32
-  %mask = and i32 65535, %trunc
-  %cmp = icmp ugt i32 %ext, %mask
-  ret i1 %cmp
-}
+
 
 ; Signed predicate on a value that cannot be proven non-negative — must NOT fold.
 define i1 @test_signed_cmp_not_folded(i32 %ext, i64 %y) {
@@ -81,18 +68,4 @@ define i1 @negative_mask_too_big(i16 %x, i64 %y) {
   %cmp = icmp eq i32 %ext, %mask
   ret i1 %cmp
 }
-; Direct ult form — zext stays at operand(0), no ugt-swap canonicalization needed.
-; Exercises the original (non-mirrored) fold path in foldICmpWithZextOrSext.
-define i1 @test_zext_trunc_and_ult_direct(i16 %x, i64 %y) {
-; CHECK-LABEL: define i1 @test_zext_trunc_and_ult_direct(
-; CHECK-SAME: i16 [[X:%.*]], i64 [[Y:%.*]]) {
-; CHECK-NEXT:    [[TMP1:%.*]] = trunc i64 [[Y]] to i16
-; CHECK-NEXT:    [[CMP:%.*]] = icmp ugt i16 [[X]], [[TMP1]]
-; CHECK-NEXT:    ret i1 [[CMP]]
-;
-  %ext = zext i16 %x to i32
-  %trunc = trunc i64 %y to i32
-  %mask = and i32 %trunc, 65535
-  %cmp = icmp ult i32 %mask, %ext
-  ret i1 %cmp
-}
+



More information about the llvm-commits mailing list