[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