[llvm] [Instcombine] Fold away shift in or reduction chain. (PR #137875)

David Green via llvm-commits llvm-commits at lists.llvm.org
Tue Apr 29 13:48:03 PDT 2025


https://github.com/davemgreen created https://github.com/llvm/llvm-project/pull/137875

If we have `icmp eq or(a, shl(b)), 0` then the shift can be removed so long as it is nuw or nsw. It is still comparing that some bits are non-zero. 
https://alive2.llvm.org/ce/z/nhrBVX.

This is also true of ne, and true for longer or chains.

Thinking out loud, this is kind of like a "any-bit is demanded" combine. I'm not sure if that exists already.

>From 0254aa80dc61ea09bbc749a33a988351b2e013b2 Mon Sep 17 00:00:00 2001
From: David Green <david.green at arm.com>
Date: Tue, 29 Apr 2025 17:07:01 +0100
Subject: [PATCH 1/2] [InstCombine] Add test for shifts from or chains

---
 .../Transforms/InstCombine/icmp-of-or-x.ll    | 119 ++++++++++++++++++
 1 file changed, 119 insertions(+)

diff --git a/llvm/test/Transforms/InstCombine/icmp-of-or-x.ll b/llvm/test/Transforms/InstCombine/icmp-of-or-x.ll
index 993325f6ff0b0..d06a2f6526ac0 100644
--- a/llvm/test/Transforms/InstCombine/icmp-of-or-x.ll
+++ b/llvm/test/Transforms/InstCombine/icmp-of-or-x.ll
@@ -409,3 +409,122 @@ define i1 @PR38139(i8 %arg) {
   %r = icmp ne i8 %masked, %arg
   ret i1 %r
 }
+
+define i1 @remove_shift_nuw_ab(i8 %a, i8 %b, i8 %s) {
+; CHECK-LABEL: @remove_shift_nuw_ab(
+; CHECK-NEXT:    [[T:%.*]] = shl nuw i8 [[A:%.*]], [[S:%.*]]
+; CHECK-NEXT:    [[OR:%.*]] = or i8 [[T]], [[B:%.*]]
+; CHECK-NEXT:    [[IC:%.*]] = icmp eq i8 [[OR]], 0
+; CHECK-NEXT:    ret i1 [[IC]]
+;
+  %t = shl nuw i8 %a, %s
+  %or = or i8 %t, %b
+  %ic = icmp eq i8 %or, 0
+  ret i1 %ic
+}
+
+define i1 @remove_shift_nuw_ba(i8 %a, i8 %b, i8 %s) {
+; CHECK-LABEL: @remove_shift_nuw_ba(
+; CHECK-NEXT:    [[T:%.*]] = shl nuw i8 [[A:%.*]], [[S:%.*]]
+; CHECK-NEXT:    [[OR:%.*]] = or i8 [[B:%.*]], [[T]]
+; CHECK-NEXT:    [[IC:%.*]] = icmp eq i8 [[OR]], 0
+; CHECK-NEXT:    ret i1 [[IC]]
+;
+  %t = shl nuw i8 %a, %s
+  %or = or i8 %b, %t
+  %ic = icmp eq i8 %or, 0
+  ret i1 %ic
+}
+
+define i1 @remove_shift_nsw(i8 %a, i8 %b, i8 %s) {
+; CHECK-LABEL: @remove_shift_nsw(
+; CHECK-NEXT:    [[T:%.*]] = shl nsw i8 [[A:%.*]], [[S:%.*]]
+; CHECK-NEXT:    [[OR:%.*]] = or i8 [[T]], [[B:%.*]]
+; CHECK-NEXT:    [[IC:%.*]] = icmp eq i8 [[OR]], 0
+; CHECK-NEXT:    ret i1 [[IC]]
+;
+  %t = shl nsw i8 %a, %s
+  %or = or i8 %t, %b
+  %ic = icmp eq i8 %or, 0
+  ret i1 %ic
+}
+
+define i1 @remove_shift_nuw_ne(i8 %a, i8 %b, i8 %s) {
+; CHECK-LABEL: @remove_shift_nuw_ne(
+; CHECK-NEXT:    [[T:%.*]] = shl nuw i8 [[A:%.*]], [[S:%.*]]
+; CHECK-NEXT:    [[OR:%.*]] = or i8 [[T]], [[B:%.*]]
+; CHECK-NEXT:    [[IC:%.*]] = icmp eq i8 [[OR]], 0
+; CHECK-NEXT:    ret i1 [[IC]]
+;
+  %t = shl nuw i8 %a, %s
+  %or = or i8 %t, %b
+  %ic = icmp eq i8 %or, 0
+  ret i1 %ic
+}
+
+define i1 @remove_shift_nsw_ne(i8 %a, i8 %b, i8 %s) {
+; CHECK-LABEL: @remove_shift_nsw_ne(
+; CHECK-NEXT:    [[T:%.*]] = shl nsw i8 [[A:%.*]], [[S:%.*]]
+; CHECK-NEXT:    [[OR:%.*]] = or i8 [[T]], [[B:%.*]]
+; CHECK-NEXT:    [[IC:%.*]] = icmp eq i8 [[OR]], 0
+; CHECK-NEXT:    ret i1 [[IC]]
+;
+  %t = shl nsw i8 %a, %s
+  %or = or i8 %t, %b
+  %ic = icmp eq i8 %or, 0
+  ret i1 %ic
+}
+
+define i1 @remove_shift_wraps(i8 %a, i8 %b, i8 %s) {
+; CHECK-LABEL: @remove_shift_wraps(
+; CHECK-NEXT:    [[T:%.*]] = shl i8 [[A:%.*]], [[S:%.*]]
+; CHECK-NEXT:    [[OR:%.*]] = or i8 [[T]], [[B:%.*]]
+; CHECK-NEXT:    [[IC:%.*]] = icmp eq i8 [[OR]], 0
+; CHECK-NEXT:    ret i1 [[IC]]
+;
+  %t = shl i8 %a, %s
+  %or = or i8 %t, %b
+  %ic = icmp eq i8 %or, 0
+  ret i1 %ic
+}
+
+define i1 @remove_shift_chain_d(i8 %a, i8 %b, i8 %c, i8 %d, i8 %s) {
+; CHECK-LABEL: @remove_shift_chain_d(
+; CHECK-NEXT:    [[DT:%.*]] = shl nuw i8 [[D:%.*]], [[S:%.*]]
+; CHECK-NEXT:    [[OR1:%.*]] = or i8 [[A:%.*]], [[B:%.*]]
+; CHECK-NEXT:    [[OR2:%.*]] = or i8 [[C:%.*]], [[DT]]
+; CHECK-NEXT:    [[OR:%.*]] = or i8 [[OR1]], [[OR2]]
+; CHECK-NEXT:    [[IC:%.*]] = icmp eq i8 [[OR]], 0
+; CHECK-NEXT:    ret i1 [[IC]]
+;
+  %dt = shl nuw i8 %d, %s
+  %or1 = or i8 %a, %b
+  %or2 = or i8 %c, %dt
+  %or = or i8 %or1, %or2
+  %ic = icmp eq i8 %or, 0
+  ret i1 %ic
+}
+
+define i1 @remove_shift_chain_abcd(i8 %a, i8 %b, i8 %c, i8 %d, i8 %s) {
+; CHECK-LABEL: @remove_shift_chain_abcd(
+; CHECK-NEXT:    [[AT:%.*]] = shl nuw i8 [[A:%.*]], [[S:%.*]]
+; CHECK-NEXT:    [[BT:%.*]] = shl nuw i8 [[B:%.*]], 2
+; CHECK-NEXT:    [[CT:%.*]] = shl nuw i8 [[C:%.*]], 1
+; CHECK-NEXT:    [[DT:%.*]] = shl nuw i8 [[D:%.*]], [[S]]
+; CHECK-NEXT:    [[OR1:%.*]] = or i8 [[AT]], [[BT]]
+; CHECK-NEXT:    [[OR2:%.*]] = or i8 [[CT]], [[DT]]
+; CHECK-NEXT:    [[OR:%.*]] = or i8 [[OR1]], [[OR2]]
+; CHECK-NEXT:    [[IC:%.*]] = icmp eq i8 [[OR]], 0
+; CHECK-NEXT:    ret i1 [[IC]]
+;
+  %at = shl nuw i8 %a, %s
+  %bt = shl nuw i8 %b, 2
+  %ct = shl nuw i8 %c, 1
+  %dt = shl nuw i8 %d, %s
+  %or1 = or i8 %at, %bt
+  %or2 = or i8 %ct, %dt
+  %or = or i8 %or1, %or2
+  %ic = icmp eq i8 %or, 0
+  ret i1 %ic
+}
+

>From b2d83d95176d0dbf315b1f31e401702c96bc2966 Mon Sep 17 00:00:00 2001
From: David Green <david.green at arm.com>
Date: Tue, 29 Apr 2025 19:08:01 +0100
Subject: [PATCH 2/2] [Instcombine] Fold away shift in or reduction chain.

If we have `icmp eq or(a, shl(b)), 0` then the shift can be removed so long as
it is nuw or nsw. It is still comparing the same bits against 0.
https://alive2.llvm.org/ce/z/nhrBVX.

This is also true of ne, and true of longer or chains.

Thinking out loud, this is kind of like a "are any bits demanded" combine.
---
 .../InstCombine/InstCombineCompares.cpp       | 28 +++++++++++++++++++
 .../Transforms/InstCombine/icmp-of-or-x.ll    | 26 ++++++-----------
 2 files changed, 36 insertions(+), 18 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
index b7b0bb7361359..d2aa957b10b78 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
@@ -5046,6 +5046,29 @@ static Instruction *foldICmpOrXX(ICmpInst &I, const SimplifyQuery &Q,
   return nullptr;
 }
 
+static Value *foldShiftAwayFromOrChain(Instruction &I,
+                                       InstCombiner::BuilderTy &Builder) {
+  if (I.getOpcode() != Instruction::Or)
+    return nullptr;
+  Value *A, *B;
+  if (match(&I, m_c_Or(m_CombineOr(m_NSWShl(m_Value(A), m_Value()),
+                                   m_NUWShl(m_Value(A), m_Value())),
+                       m_Value(B))))
+    return Builder.CreateOr(A, B);
+
+  Value *Op0 = I.getOperand(0);
+  if (isa<Instruction>(Op0))
+    if (auto *X = foldShiftAwayFromOrChain(*cast<Instruction>(Op0), Builder))
+      Op0 = X;
+  Value *Op1 = I.getOperand(1);
+  if (isa<Instruction>(Op1))
+    if (auto *X = foldShiftAwayFromOrChain(*cast<Instruction>(Op1), Builder))
+      Op1 = X;
+  if (Op0 != I.getOperand(0) || Op1 != I.getOperand(1))
+    return Builder.CreateOr(Op0, Op1);
+  return nullptr;
+}
+
 static Instruction *foldICmpXorXX(ICmpInst &I, const SimplifyQuery &Q,
                                   InstCombinerImpl &IC) {
   Value *Op0 = I.getOperand(0), *Op1 = I.getOperand(1), *A;
@@ -7742,6 +7765,11 @@ Instruction *InstCombinerImpl::visitICmpInst(ICmpInst &I) {
     }
   }
 
+  // icmp eq/ne or(shl(a), b), 0 -> icmp eq/ne or(a, b)
+  if (I.isEquality() && match(Op1, m_Zero()) && isa<Instruction>(Op0))
+    if (auto *Res = foldShiftAwayFromOrChain(*cast<Instruction>(Op0), Builder))
+      return new ICmpInst(I.getPredicate(), Res, Op1);
+
   return Changed ? &I : nullptr;
 }
 
diff --git a/llvm/test/Transforms/InstCombine/icmp-of-or-x.ll b/llvm/test/Transforms/InstCombine/icmp-of-or-x.ll
index d06a2f6526ac0..baaa754e52894 100644
--- a/llvm/test/Transforms/InstCombine/icmp-of-or-x.ll
+++ b/llvm/test/Transforms/InstCombine/icmp-of-or-x.ll
@@ -412,8 +412,7 @@ define i1 @PR38139(i8 %arg) {
 
 define i1 @remove_shift_nuw_ab(i8 %a, i8 %b, i8 %s) {
 ; CHECK-LABEL: @remove_shift_nuw_ab(
-; CHECK-NEXT:    [[T:%.*]] = shl nuw i8 [[A:%.*]], [[S:%.*]]
-; CHECK-NEXT:    [[OR:%.*]] = or i8 [[T]], [[B:%.*]]
+; CHECK-NEXT:    [[OR:%.*]] = or i8 [[T:%.*]], [[B:%.*]]
 ; CHECK-NEXT:    [[IC:%.*]] = icmp eq i8 [[OR]], 0
 ; CHECK-NEXT:    ret i1 [[IC]]
 ;
@@ -425,8 +424,7 @@ define i1 @remove_shift_nuw_ab(i8 %a, i8 %b, i8 %s) {
 
 define i1 @remove_shift_nuw_ba(i8 %a, i8 %b, i8 %s) {
 ; CHECK-LABEL: @remove_shift_nuw_ba(
-; CHECK-NEXT:    [[T:%.*]] = shl nuw i8 [[A:%.*]], [[S:%.*]]
-; CHECK-NEXT:    [[OR:%.*]] = or i8 [[B:%.*]], [[T]]
+; CHECK-NEXT:    [[OR:%.*]] = or i8 [[B:%.*]], [[T:%.*]]
 ; CHECK-NEXT:    [[IC:%.*]] = icmp eq i8 [[OR]], 0
 ; CHECK-NEXT:    ret i1 [[IC]]
 ;
@@ -438,8 +436,7 @@ define i1 @remove_shift_nuw_ba(i8 %a, i8 %b, i8 %s) {
 
 define i1 @remove_shift_nsw(i8 %a, i8 %b, i8 %s) {
 ; CHECK-LABEL: @remove_shift_nsw(
-; CHECK-NEXT:    [[T:%.*]] = shl nsw i8 [[A:%.*]], [[S:%.*]]
-; CHECK-NEXT:    [[OR:%.*]] = or i8 [[T]], [[B:%.*]]
+; CHECK-NEXT:    [[OR:%.*]] = or i8 [[T:%.*]], [[B:%.*]]
 ; CHECK-NEXT:    [[IC:%.*]] = icmp eq i8 [[OR]], 0
 ; CHECK-NEXT:    ret i1 [[IC]]
 ;
@@ -451,8 +448,7 @@ define i1 @remove_shift_nsw(i8 %a, i8 %b, i8 %s) {
 
 define i1 @remove_shift_nuw_ne(i8 %a, i8 %b, i8 %s) {
 ; CHECK-LABEL: @remove_shift_nuw_ne(
-; CHECK-NEXT:    [[T:%.*]] = shl nuw i8 [[A:%.*]], [[S:%.*]]
-; CHECK-NEXT:    [[OR:%.*]] = or i8 [[T]], [[B:%.*]]
+; CHECK-NEXT:    [[OR:%.*]] = or i8 [[T:%.*]], [[B:%.*]]
 ; CHECK-NEXT:    [[IC:%.*]] = icmp eq i8 [[OR]], 0
 ; CHECK-NEXT:    ret i1 [[IC]]
 ;
@@ -464,8 +460,7 @@ define i1 @remove_shift_nuw_ne(i8 %a, i8 %b, i8 %s) {
 
 define i1 @remove_shift_nsw_ne(i8 %a, i8 %b, i8 %s) {
 ; CHECK-LABEL: @remove_shift_nsw_ne(
-; CHECK-NEXT:    [[T:%.*]] = shl nsw i8 [[A:%.*]], [[S:%.*]]
-; CHECK-NEXT:    [[OR:%.*]] = or i8 [[T]], [[B:%.*]]
+; CHECK-NEXT:    [[OR:%.*]] = or i8 [[T:%.*]], [[B:%.*]]
 ; CHECK-NEXT:    [[IC:%.*]] = icmp eq i8 [[OR]], 0
 ; CHECK-NEXT:    ret i1 [[IC]]
 ;
@@ -490,9 +485,8 @@ define i1 @remove_shift_wraps(i8 %a, i8 %b, i8 %s) {
 
 define i1 @remove_shift_chain_d(i8 %a, i8 %b, i8 %c, i8 %d, i8 %s) {
 ; CHECK-LABEL: @remove_shift_chain_d(
-; CHECK-NEXT:    [[DT:%.*]] = shl nuw i8 [[D:%.*]], [[S:%.*]]
 ; CHECK-NEXT:    [[OR1:%.*]] = or i8 [[A:%.*]], [[B:%.*]]
-; CHECK-NEXT:    [[OR2:%.*]] = or i8 [[C:%.*]], [[DT]]
+; CHECK-NEXT:    [[OR2:%.*]] = or i8 [[C:%.*]], [[DT:%.*]]
 ; CHECK-NEXT:    [[OR:%.*]] = or i8 [[OR1]], [[OR2]]
 ; CHECK-NEXT:    [[IC:%.*]] = icmp eq i8 [[OR]], 0
 ; CHECK-NEXT:    ret i1 [[IC]]
@@ -507,12 +501,8 @@ define i1 @remove_shift_chain_d(i8 %a, i8 %b, i8 %c, i8 %d, i8 %s) {
 
 define i1 @remove_shift_chain_abcd(i8 %a, i8 %b, i8 %c, i8 %d, i8 %s) {
 ; CHECK-LABEL: @remove_shift_chain_abcd(
-; CHECK-NEXT:    [[AT:%.*]] = shl nuw i8 [[A:%.*]], [[S:%.*]]
-; CHECK-NEXT:    [[BT:%.*]] = shl nuw i8 [[B:%.*]], 2
-; CHECK-NEXT:    [[CT:%.*]] = shl nuw i8 [[C:%.*]], 1
-; CHECK-NEXT:    [[DT:%.*]] = shl nuw i8 [[D:%.*]], [[S]]
-; CHECK-NEXT:    [[OR1:%.*]] = or i8 [[AT]], [[BT]]
-; CHECK-NEXT:    [[OR2:%.*]] = or i8 [[CT]], [[DT]]
+; CHECK-NEXT:    [[OR1:%.*]] = or i8 [[AT:%.*]], [[BT:%.*]]
+; CHECK-NEXT:    [[OR2:%.*]] = or i8 [[CT:%.*]], [[DT:%.*]]
 ; CHECK-NEXT:    [[OR:%.*]] = or i8 [[OR1]], [[OR2]]
 ; CHECK-NEXT:    [[IC:%.*]] = icmp eq i8 [[OR]], 0
 ; CHECK-NEXT:    ret i1 [[IC]]



More information about the llvm-commits mailing list