[llvm] [InstCombine] Fold `(trunc X)` into `X & Mask` inside `decomposeBitTestICmp` (PR #171195)

Tirthankar Mazumder via llvm-commits llvm-commits at lists.llvm.org
Sat Jan 10 06:44:44 PST 2026


https://github.com/wermos updated https://github.com/llvm/llvm-project/pull/171195

>From 098c0694c9cc970a801f6a2a9f74f59776d1683d Mon Sep 17 00:00:00 2001
From: Tirthankar Mazumder <tmazumder.github at gmail.com>
Date: Fri, 12 Dec 2025 00:21:49 +0530
Subject: [PATCH 1/3] Pre-commit tests

---
 llvm/test/Transforms/InstCombine/and-or-icmps.ll | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/llvm/test/Transforms/InstCombine/and-or-icmps.ll b/llvm/test/Transforms/InstCombine/and-or-icmps.ll
index 290e344acb980..bd62e7726b629 100644
--- a/llvm/test/Transforms/InstCombine/and-or-icmps.ll
+++ b/llvm/test/Transforms/InstCombine/and-or-icmps.ll
@@ -3721,3 +3721,19 @@ define i1 @merge_range_check_or(i8 %a) {
   %and = or i1 %cmp1, %cmp2
   ret i1 %and
 }
+
+; Just a very complicated way of checking if v1 == 0.
+define i1 @complicated_zero_equality_test(i64 %v1) {
+; CHECK-LABEL: @complicated_zero_equality_test(
+; CHECK-NEXT:    [[V2:%.*]] = trunc i64 [[V1:%.*]] to i32
+; CHECK-NEXT:    [[V3:%.*]] = icmp eq i32 [[V2]], 0
+; CHECK-NEXT:    [[V4:%.*]] = icmp ult i64 [[V1]], 4294967296
+; CHECK-NEXT:    [[V5:%.*]] = and i1 [[V4]], [[V3]]
+; CHECK-NEXT:    ret i1 [[V5]]
+;
+  %v2 = trunc i64 %v1 to i32
+  %v3 = icmp eq i32 %v2, 0
+  %v4 = icmp ult i64 %v1, 4294967296 ; 2 ^ 32
+  %v5 = and i1 %v4, %v3
+  ret i1 %v5
+}

>From 749bbd125c2a865bb4b1e1c83f03eb4394f6a339 Mon Sep 17 00:00:00 2001
From: Tirthankar Mazumder <tmazumder.github at gmail.com>
Date: Fri, 12 Dec 2025 00:22:47 +0530
Subject: [PATCH 2/3] Implement missing optimization in `decompostBitTestICmp`.

---
 llvm/lib/Analysis/CmpInstAnalysis.cpp                 |  8 ++++++++
 .../Transforms/InstCombine/InstCombineAndOrXor.cpp    |  3 ++-
 .../Transforms/InstCombine/InstCombineCompares.cpp    | 11 ++++++-----
 llvm/test/Transforms/InstCombine/getelementptr.ll     |  2 +-
 4 files changed, 17 insertions(+), 7 deletions(-)

diff --git a/llvm/lib/Analysis/CmpInstAnalysis.cpp b/llvm/lib/Analysis/CmpInstAnalysis.cpp
index a6d0d3ff4fcd4..880006c0fcfac 100644
--- a/llvm/lib/Analysis/CmpInstAnalysis.cpp
+++ b/llvm/lib/Analysis/CmpInstAnalysis.cpp
@@ -162,6 +162,14 @@ llvm::decomposeBitTestICmp(Value *LHS, Value *RHS, CmpInst::Predicate Pred,
       break;
     }
 
+    // Try to convert (trunc X) eq/ne C into (X & Mask) eq/ne C
+    if (LookThroughTrunc && isa<TruncInst>(LHS)) {
+      Result.Pred = Pred;
+      Result.Mask = APInt::getAllOnes(C.getBitWidth());
+      Result.C = C;
+      break;
+    }
+
     return std::nullopt;
   }
   }
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
index 0d22bc1599bdf..9e035fabe50f4 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
@@ -190,7 +190,7 @@ static unsigned conjugateICmpMask(unsigned Mask) {
 static bool decomposeBitTest(Value *Cond, CmpInst::Predicate &Pred, Value *&X,
                              Value *&Y, Value *&Z) {
   auto Res = llvm::decomposeBitTest(Cond, /*LookThroughTrunc=*/true,
-                                    /*AllowNonZeroC=*/true);
+                                    /*AllowNonZeroC=*/true, /*DecomposeAnd=*/true);
   if (!Res)
     return false;
 
@@ -198,6 +198,7 @@ static bool decomposeBitTest(Value *Cond, CmpInst::Predicate &Pred, Value *&X,
   X = Res->X;
   Y = ConstantInt::get(X->getType(), Res->Mask);
   Z = ConstantInt::get(X->getType(), Res->C);
+  dbgs() << "Cond = " << *Cond <<  "\nX = " << *X << "\nY = " << *Y << "\nZ = " << *Z << '\n';
   return true;
 }
 
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
index efbd20f556471..ba12eb765977a 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
@@ -6290,11 +6290,12 @@ Instruction *InstCombinerImpl::foldICmpWithTrunc(ICmpInst &ICmp) {
 
   // This matches patterns corresponding to tests of the signbit as well as:
   // (trunc X) pred C2 --> (X & Mask) == C
-  if (auto Res = decomposeBitTestICmp(Op0, Op1, Pred, /*LookThroughTrunc=*/true,
-                                      /*AllowNonZeroC=*/true)) {
-    Value *And = Builder.CreateAnd(Res->X, Res->Mask);
-    Constant *C = ConstantInt::get(Res->X->getType(), Res->C);
-    return new ICmpInst(Res->Pred, And, C);
+  if (auto Res =
+          decomposeBitTestICmp(Op0, Op1, Pred, /*LookThroughTrunc=*/true,
+                               /*AllowNonZeroC=*/true)) {
+      Value *And = Builder.CreateAnd(Res->X, Res->Mask);
+      Constant *C = ConstantInt::get(Res->X->getType(), Res->C);
+      return new ICmpInst(Res->Pred, And, C);
   }
 
   unsigned SrcBits = X->getType()->getScalarSizeInBits();
diff --git a/llvm/test/Transforms/InstCombine/getelementptr.ll b/llvm/test/Transforms/InstCombine/getelementptr.ll
index 92b76c5d1b46a..d01e4a9290def 100644
--- a/llvm/test/Transforms/InstCombine/getelementptr.ll
+++ b/llvm/test/Transforms/InstCombine/getelementptr.ll
@@ -680,7 +680,7 @@ entry:
 define i32 @test28() nounwind  {
 ; CHECK-LABEL: @test28(
 ; CHECK-NEXT:  entry:
-; CHECK-NEXT:    [[ORIENTATIONS:%.*]] = alloca [1 x [1 x %struct.x]], align 8
+; CHECK-NEXT:    [[ORIENTATIONS:%.*]] = alloca [1 x [1 x [[STRUCT_X:%.*]]]], align 8
 ; CHECK-NEXT:    [[T3:%.*]] = call i32 @puts(ptr noundef nonnull dereferenceable(1) @.str) #[[ATTR0]]
 ; CHECK-NEXT:    br label [[BB10:%.*]]
 ; CHECK:       bb10:

>From b22fc12606549a2d4035ac2c2c133365dece434f Mon Sep 17 00:00:00 2001
From: Tirthankar Mazumder <tmazumder.github at gmail.com>
Date: Mon, 5 Jan 2026 19:59:57 +0530
Subject: [PATCH 3/3] Updated tests

---
 .../InstCombine/InstCombineAndOrXor.cpp        |  6 +++---
 .../InstCombine/InstCombineCompares.cpp        | 11 +++++------
 .../Transforms/InstCombine/and-or-icmps.ll     |  5 +----
 .../Transforms/InstCombine/getelementptr.ll    |  2 +-
 .../Transforms/InstCombine/icmp-logical.ll     |  4 +---
 llvm/test/Transforms/InstCombine/merge-icmp.ll | 18 ++++++------------
 6 files changed, 17 insertions(+), 29 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
index 9e035fabe50f4..f1fd3a2439435 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
@@ -189,8 +189,9 @@ static unsigned conjugateICmpMask(unsigned Mask) {
 // Adapts the external decomposeBitTest for local use.
 static bool decomposeBitTest(Value *Cond, CmpInst::Predicate &Pred, Value *&X,
                              Value *&Y, Value *&Z) {
-  auto Res = llvm::decomposeBitTest(Cond, /*LookThroughTrunc=*/true,
-                                    /*AllowNonZeroC=*/true, /*DecomposeAnd=*/true);
+  auto Res =
+      llvm::decomposeBitTest(Cond, /*LookThroughTrunc=*/true,
+                             /*AllowNonZeroC=*/true, /*DecomposeAnd=*/true);
   if (!Res)
     return false;
 
@@ -198,7 +199,6 @@ static bool decomposeBitTest(Value *Cond, CmpInst::Predicate &Pred, Value *&X,
   X = Res->X;
   Y = ConstantInt::get(X->getType(), Res->Mask);
   Z = ConstantInt::get(X->getType(), Res->C);
-  dbgs() << "Cond = " << *Cond <<  "\nX = " << *X << "\nY = " << *Y << "\nZ = " << *Z << '\n';
   return true;
 }
 
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
index ba12eb765977a..efbd20f556471 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
@@ -6290,12 +6290,11 @@ Instruction *InstCombinerImpl::foldICmpWithTrunc(ICmpInst &ICmp) {
 
   // This matches patterns corresponding to tests of the signbit as well as:
   // (trunc X) pred C2 --> (X & Mask) == C
-  if (auto Res =
-          decomposeBitTestICmp(Op0, Op1, Pred, /*LookThroughTrunc=*/true,
-                               /*AllowNonZeroC=*/true)) {
-      Value *And = Builder.CreateAnd(Res->X, Res->Mask);
-      Constant *C = ConstantInt::get(Res->X->getType(), Res->C);
-      return new ICmpInst(Res->Pred, And, C);
+  if (auto Res = decomposeBitTestICmp(Op0, Op1, Pred, /*LookThroughTrunc=*/true,
+                                      /*AllowNonZeroC=*/true)) {
+    Value *And = Builder.CreateAnd(Res->X, Res->Mask);
+    Constant *C = ConstantInt::get(Res->X->getType(), Res->C);
+    return new ICmpInst(Res->Pred, And, C);
   }
 
   unsigned SrcBits = X->getType()->getScalarSizeInBits();
diff --git a/llvm/test/Transforms/InstCombine/and-or-icmps.ll b/llvm/test/Transforms/InstCombine/and-or-icmps.ll
index bd62e7726b629..975d3a072bcd3 100644
--- a/llvm/test/Transforms/InstCombine/and-or-icmps.ll
+++ b/llvm/test/Transforms/InstCombine/and-or-icmps.ll
@@ -3725,10 +3725,7 @@ define i1 @merge_range_check_or(i8 %a) {
 ; Just a very complicated way of checking if v1 == 0.
 define i1 @complicated_zero_equality_test(i64 %v1) {
 ; CHECK-LABEL: @complicated_zero_equality_test(
-; CHECK-NEXT:    [[V2:%.*]] = trunc i64 [[V1:%.*]] to i32
-; CHECK-NEXT:    [[V3:%.*]] = icmp eq i32 [[V2]], 0
-; CHECK-NEXT:    [[V4:%.*]] = icmp ult i64 [[V1]], 4294967296
-; CHECK-NEXT:    [[V5:%.*]] = and i1 [[V4]], [[V3]]
+; CHECK-NEXT:    [[V5:%.*]] = icmp eq i64 [[V1:%.*]], 0
 ; CHECK-NEXT:    ret i1 [[V5]]
 ;
   %v2 = trunc i64 %v1 to i32
diff --git a/llvm/test/Transforms/InstCombine/getelementptr.ll b/llvm/test/Transforms/InstCombine/getelementptr.ll
index d01e4a9290def..92b76c5d1b46a 100644
--- a/llvm/test/Transforms/InstCombine/getelementptr.ll
+++ b/llvm/test/Transforms/InstCombine/getelementptr.ll
@@ -680,7 +680,7 @@ entry:
 define i32 @test28() nounwind  {
 ; CHECK-LABEL: @test28(
 ; CHECK-NEXT:  entry:
-; CHECK-NEXT:    [[ORIENTATIONS:%.*]] = alloca [1 x [1 x [[STRUCT_X:%.*]]]], align 8
+; CHECK-NEXT:    [[ORIENTATIONS:%.*]] = alloca [1 x [1 x %struct.x]], align 8
 ; CHECK-NEXT:    [[T3:%.*]] = call i32 @puts(ptr noundef nonnull dereferenceable(1) @.str) #[[ATTR0]]
 ; CHECK-NEXT:    br label [[BB10:%.*]]
 ; CHECK:       bb10:
diff --git a/llvm/test/Transforms/InstCombine/icmp-logical.ll b/llvm/test/Transforms/InstCombine/icmp-logical.ll
index df8442e069b78..71706af914933 100644
--- a/llvm/test/Transforms/InstCombine/icmp-logical.ll
+++ b/llvm/test/Transforms/InstCombine/icmp-logical.ll
@@ -1793,9 +1793,7 @@ define <2 x i1> @masked_icmps_bmask_notmixed_or_vec(<2 x i8> %A) {
 define <2 x i1> @masked_icmps_bmask_notmixed_or_vec_poison1(<2 x i8> %A) {
 ; CHECK-LABEL: @masked_icmps_bmask_notmixed_or_vec_poison1(
 ; CHECK-NEXT:    [[MASK1:%.*]] = and <2 x i8> [[A:%.*]], splat (i8 15)
-; CHECK-NEXT:    [[TST1:%.*]] = icmp eq <2 x i8> [[MASK1]], <i8 3, i8 poison>
-; CHECK-NEXT:    [[TST2:%.*]] = icmp eq <2 x i8> [[A]], splat (i8 -13)
-; CHECK-NEXT:    [[RES:%.*]] = or <2 x i1> [[TST1]], [[TST2]]
+; CHECK-NEXT:    [[RES:%.*]] = icmp eq <2 x i8> [[MASK1]], splat (i8 3)
 ; CHECK-NEXT:    ret <2 x i1> [[RES]]
 ;
   %mask1 = and <2 x i8> %A, <i8 15, i8 15> ; 0x0f
diff --git a/llvm/test/Transforms/InstCombine/merge-icmp.ll b/llvm/test/Transforms/InstCombine/merge-icmp.ll
index 4f658273ce098..7f35933470fc5 100644
--- a/llvm/test/Transforms/InstCombine/merge-icmp.ll
+++ b/llvm/test/Transforms/InstCombine/merge-icmp.ll
@@ -167,9 +167,8 @@ define i1 @or_extra_use1(i16 %load) {
 ; CHECK-NEXT:    [[TRUNC:%.*]] = trunc i16 [[LOAD:%.*]] to i8
 ; CHECK-NEXT:    [[CMP1:%.*]] = icmp ne i8 [[TRUNC]], 127
 ; CHECK-NEXT:    call void @use.i1(i1 [[CMP1]])
-; CHECK-NEXT:    [[AND:%.*]] = and i16 [[LOAD]], -4096
-; CHECK-NEXT:    [[CMP2:%.*]] = icmp ne i16 [[AND]], 20480
-; CHECK-NEXT:    [[OR:%.*]] = or i1 [[CMP1]], [[CMP2]]
+; CHECK-NEXT:    [[TMP1:%.*]] = and i16 [[LOAD]], -3841
+; CHECK-NEXT:    [[OR:%.*]] = icmp ne i16 [[TMP1]], 20607
 ; CHECK-NEXT:    ret i1 [[OR]]
 ;
   %trunc = trunc i16 %load to i8
@@ -183,12 +182,11 @@ define i1 @or_extra_use1(i16 %load) {
 
 define i1 @or_extra_use2(i16 %load) {
 ; CHECK-LABEL: @or_extra_use2(
-; CHECK-NEXT:    [[TRUNC:%.*]] = trunc i16 [[LOAD:%.*]] to i8
-; CHECK-NEXT:    [[CMP1:%.*]] = icmp ne i8 [[TRUNC]], 127
-; CHECK-NEXT:    [[AND:%.*]] = and i16 [[LOAD]], -4096
+; CHECK-NEXT:    [[AND:%.*]] = and i16 [[LOAD:%.*]], -4096
 ; CHECK-NEXT:    [[CMP2:%.*]] = icmp ne i16 [[AND]], 20480
 ; CHECK-NEXT:    call void @use.i1(i1 [[CMP2]])
-; CHECK-NEXT:    [[OR:%.*]] = or i1 [[CMP1]], [[CMP2]]
+; CHECK-NEXT:    [[TMP1:%.*]] = and i16 [[LOAD]], -3841
+; CHECK-NEXT:    [[OR:%.*]] = icmp ne i16 [[TMP1]], 20607
 ; CHECK-NEXT:    ret i1 [[OR]]
 ;
   %trunc = trunc i16 %load to i8
@@ -316,11 +314,7 @@ define i1 @or_wrong_const1(i16 %load) {
 
 define i1 @or_wrong_const2(i16 %load) {
 ; CHECK-LABEL: @or_wrong_const2(
-; CHECK-NEXT:    [[TRUNC:%.*]] = trunc i16 [[LOAD:%.*]] to i8
-; CHECK-NEXT:    [[CMP1:%.*]] = icmp ne i8 [[TRUNC]], 127
-; CHECK-NEXT:    [[AND:%.*]] = and i16 [[LOAD]], -255
-; CHECK-NEXT:    [[CMP2:%.*]] = icmp ne i16 [[AND]], 17665
-; CHECK-NEXT:    [[OR:%.*]] = or i1 [[CMP1]], [[CMP2]]
+; CHECK-NEXT:    [[OR:%.*]] = icmp ne i16 [[LOAD:%.*]], 17791
 ; CHECK-NEXT:    ret i1 [[OR]]
 ;
   %trunc = trunc i16 %load to i8



More information about the llvm-commits mailing list