[llvm] [InstCombine] Fold ((X | C1) >> N) & C2 to ((X >> N) & C2) | ((C1 >> N) & C2) for disjoint ors (PR #194470)

Josh Hutton via llvm-commits llvm-commits at lists.llvm.org
Mon Apr 27 14:54:37 PDT 2026


https://github.com/JoshHuttonCode created https://github.com/llvm/llvm-project/pull/194470

This reassociation separates the constant portion `(C1 >> N) & C2` from the variable portion of the expression, helping SeparateConstOffsetFromGEP to split GEPs into a variadic base and constant offset.

Proof: https://alive2.llvm.org/ce/z/zgnm4L

>From 2cb6e48f857082af47f4ebb24e811abff9015f2e Mon Sep 17 00:00:00 2001
From: Josh Hutton <joshhuttonemail at gmail.com>
Date: Mon, 27 Apr 2026 13:20:48 -0700
Subject: [PATCH] [InstCombine] Fold ((X | C1) >> N) & C2 to ((X >> N) & C2) |
 ((C1 >> N) & C2) for disjoint ors

Co-authored-by: Jeffrey Byrnes <Jeffrey.Byrnes at amd.com>
---
 .../InstCombine/InstCombineAndOrXor.cpp       |  20 +++
 .../InstCombine/and-lshr-disjoint-or.ll       | 143 ++++++++++++++++++
 2 files changed, 163 insertions(+)
 create mode 100644 llvm/test/Transforms/InstCombine/and-lshr-disjoint-or.ll

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
index d81ed56290476..0813eebe70f64 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
@@ -2506,6 +2506,26 @@ Instruction *InstCombinerImpl::visitAnd(BinaryOperator &I) {
       return BinaryOperator::CreateOr(And, ConstantInt::get(Ty, Together));
     }
 
+    // ((X | C1) >> N) & C2 --> ((X >> N) & C2) | ((C1 >> N) & C2)
+    // This reassociates the and-mask through the shift to expose a constant
+    // that can be folded into GEP offsets.
+    // Only profitable when the or is disjoint, enabling the transformation.
+    const APInt *DisjointOrC, *ShAmtN;
+    if (match(Op0,
+              m_OneUse(m_LShr(m_DisjointOr(m_Value(X), m_APInt(DisjointOrC)),
+                              m_APInt(ShAmtN))))) {
+      unsigned ShAmt = ShAmtN->getZExtValue();
+      // Compute the constant result: (C1 >> N) & C2
+      APInt ConstResult = DisjointOrC->lshr(ShAmt) & *C;
+
+      // Create: (X >> N) & C2
+      Value *NewLShr = Builder.CreateLShr(X, ConstantInt::get(Ty, *ShAmtN));
+      Value *NewAnd = Builder.CreateAnd(NewLShr, ConstantInt::get(Ty, *C));
+      // Create: ((X & (C2 << N)) >> N) | ((C1 >> N) & C2)
+      return BinaryOperator::CreateDisjointOr(
+          NewAnd, ConstantInt::get(Ty, ConstResult));
+    }
+
     unsigned Width = Ty->getScalarSizeInBits();
     const APInt *ShiftC;
     if (match(Op0, m_OneUse(m_SExt(m_AShr(m_Value(X), m_APInt(ShiftC))))) &&
diff --git a/llvm/test/Transforms/InstCombine/and-lshr-disjoint-or.ll b/llvm/test/Transforms/InstCombine/and-lshr-disjoint-or.ll
new file mode 100644
index 0000000000000..a77288a9cb760
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/and-lshr-disjoint-or.ll
@@ -0,0 +1,143 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+
+; Test reassociation of: ((X | C1) >> N) & C2 --> ((X >> N) & C2) | ((C1 >> N) & C2)
+; This is valid when the or is disjoint, exposing a constant for GEP offset folding.
+
+define i32 @and_lshr_disjoint_or(i32 %val) {
+; CHECK-LABEL: @and_lshr_disjoint_or(
+; CHECK-NEXT:    [[TMP1:%.*]] = lshr i32 [[VAL:%.*]], 4
+; CHECK-NEXT:    [[TMP2:%.*]] = and i32 [[TMP1]], 752
+; CHECK-NEXT:    [[AND:%.*]] = or disjoint i32 [[TMP2]], 2048
+; CHECK-NEXT:    ret i32 [[AND]]
+;
+  %or = or disjoint i32 %val, 32768
+  %lshr = lshr i32 %or, 4
+  %and = and i32 %lshr, 2800
+  ret i32 %and
+}
+
+define i32 @and_lshr_disjoint_or_exact(i32 %val) {
+; CHECK-LABEL: @and_lshr_disjoint_or_exact(
+; CHECK-NEXT:    [[TMP1:%.*]] = lshr i32 [[VAL:%.*]], 4
+; CHECK-NEXT:    [[TMP2:%.*]] = and i32 [[TMP1]], 752
+; CHECK-NEXT:    [[AND:%.*]] = or disjoint i32 [[TMP2]], 2048
+; CHECK-NEXT:    ret i32 [[AND]]
+;
+  %or = or disjoint i32 %val, 32768
+  %lshr = lshr exact i32 %or, 4
+  %and = and i32 %lshr, 2800
+  ret i32 %and
+}
+
+define i32 @and_lshr_disjoint_or_different_constant(i32 %val) {
+; CHECK-LABEL: @and_lshr_disjoint_or_different_constant(
+; CHECK-NEXT:    [[TMP1:%.*]] = lshr i32 [[VAL:%.*]], 4
+; CHECK-NEXT:    [[TMP2:%.*]] = and i32 [[TMP1]], 752
+; CHECK-NEXT:    [[AND:%.*]] = or disjoint i32 [[TMP2]], 2048
+; CHECK-NEXT:    ret i32 [[AND]]
+;
+  %or = or disjoint i32 %val, 32800
+  %lshr = lshr exact i32 %or, 4
+  %and = and i32 %lshr, 2800
+  ret i32 %and
+}
+
+; Negative test: non-disjoint or should not be transformed
+define i32 @and_lshr_or_non_disjoint(i32 %val) {
+; CHECK-LABEL: @and_lshr_or_non_disjoint(
+; CHECK-NEXT:    [[LSHR:%.*]] = lshr i32 [[OR:%.*]], 4
+; CHECK-NEXT:    [[LSHR1:%.*]] = and i32 [[LSHR]], 752
+; CHECK-NEXT:    [[AND:%.*]] = or disjoint i32 [[LSHR1]], 2048
+; CHECK-NEXT:    ret i32 [[AND]]
+;
+  %or = or i32 %val, 32768
+  %lshr = lshr i32 %or, 4
+  %and = and i32 %lshr, 2800
+  ret i32 %and
+}
+
+; Negative test: multi-use lshr should not be transformed
+define i32 @and_lshr_disjoint_or_multi_use(i32 %val, ptr %p) {
+; CHECK-LABEL: @and_lshr_disjoint_or_multi_use(
+; CHECK-NEXT:    [[OR:%.*]] = lshr i32 [[VAL:%.*]], 4
+; CHECK-NEXT:    [[LSHR:%.*]] = or i32 [[OR]], 2048
+; CHECK-NEXT:    store i32 [[LSHR]], ptr [[P:%.*]], align 4
+; CHECK-NEXT:    [[AND:%.*]] = and i32 [[LSHR]], 2800
+; CHECK-NEXT:    ret i32 [[AND]]
+;
+  %or = or disjoint i32 %val, 32768
+  %lshr = lshr i32 %or, 4
+  store i32 %lshr, ptr %p
+  %and = and i32 %lshr, 2800
+  ret i32 %and
+}
+
+; Test with larger shift amount
+define i32 @and_lshr_disjoint_or_larger_shift(i32 %val) {
+; CHECK-LABEL: @and_lshr_disjoint_or_larger_shift(
+; CHECK-NEXT:    [[TMP1:%.*]] = lshr i32 [[VAL:%.*]], 8
+; CHECK-NEXT:    [[TMP2:%.*]] = and i32 [[TMP1]], 240
+; CHECK-NEXT:    [[AND:%.*]] = or disjoint i32 [[TMP2]], 15
+; CHECK-NEXT:    ret i32 [[AND]]
+;
+  %or = or disjoint i32 %val, 4080
+  %lshr = lshr i32 %or, 8
+  %and = and i32 %lshr, 255
+  ret i32 %and
+}
+
+; Test with i64
+define i64 @and_lshr_disjoint_or_i64(i64 %val) {
+; CHECK-LABEL: @and_lshr_disjoint_or_i64(
+; CHECK-NEXT:    [[TMP1:%.*]] = lshr i64 [[VAL:%.*]], 4
+; CHECK-NEXT:    [[TMP2:%.*]] = and i64 [[TMP1]], 752
+; CHECK-NEXT:    [[AND:%.*]] = or disjoint i64 [[TMP2]], 2048
+; CHECK-NEXT:    ret i64 [[AND]]
+;
+  %or = or disjoint i64 %val, 32768
+  %lshr = lshr i64 %or, 4
+  %and = and i64 %lshr, 2800
+  ret i64 %and
+}
+
+; Test with vector types
+define <4 x i32> @and_lshr_disjoint_or_vec(<4 x i32> %val) {
+; CHECK-LABEL: @and_lshr_disjoint_or_vec(
+; CHECK-NEXT:    [[TMP1:%.*]] = lshr <4 x i32> [[VAL:%.*]], splat (i32 4)
+; CHECK-NEXT:    [[TMP2:%.*]] = and <4 x i32> [[TMP1]], splat (i32 752)
+; CHECK-NEXT:    [[AND:%.*]] = or disjoint <4 x i32> [[TMP2]], splat (i32 2048)
+; CHECK-NEXT:    ret <4 x i32> [[AND]]
+;
+  %or = or disjoint <4 x i32> %val, <i32 32768, i32 32768, i32 32768, i32 32768>
+  %lshr = lshr <4 x i32> %or, <i32 4, i32 4, i32 4, i32 4>
+  %and = and <4 x i32> %lshr, <i32 2800, i32 2800, i32 2800, i32 2800>
+  ret <4 x i32> %and
+}
+
+; Negative test: ashr should not be transformed (this pattern is for lshr only)
+define i32 @and_ashr_disjoint_or(i32 %val) {
+; CHECK-LABEL: @and_ashr_disjoint_or(
+; CHECK-NEXT:    [[OR:%.*]] = lshr i32 [[VAL:%.*]], 4
+; CHECK-NEXT:    [[ASHR:%.*]] = and i32 [[OR]], 752
+; CHECK-NEXT:    [[AND:%.*]] = or disjoint i32 [[ASHR]], 2048
+; CHECK-NEXT:    ret i32 [[AND]]
+;
+  %or = or disjoint i32 %val, 32768
+  %ashr = ashr i32 %or, 4
+  %and = and i32 %ashr, 2800
+  ret i32 %and
+}
+
+; Test when result constant is zero (optimization opportunity)
+define i32 @and_lshr_disjoint_or_const_result_zero(i32 %val) {
+; CHECK-LABEL: @and_lshr_disjoint_or_const_result_zero(
+; CHECK-NEXT:    [[TMP1:%.*]] = lshr i32 [[VAL:%.*]], 8
+; CHECK-NEXT:    [[AND:%.*]] = and i32 [[TMP1]], 255
+; CHECK-NEXT:    ret i32 [[AND]]
+;
+  %or = or disjoint i32 %val, 128
+  %lshr = lshr i32 %or, 8
+  %and = and i32 %lshr, 255
+  ret i32 %and
+}



More information about the llvm-commits mailing list