[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