[llvm] [InstCombine] Fold select of and/or subset identity to `or` (PR #209072)
Joel Walker via llvm-commits
llvm-commits at lists.llvm.org
Sun Sep 13 17:03:13 PDT 2026
https://github.com/Joel-Wwalker updated https://github.com/llvm/llvm-project/pull/209072
>From 8e8790806726d7ced6dc2d118501ec87c6a97747 Mon Sep 17 00:00:00 2001
From: Joel-Wwalker <theagingboy05 at gmail.com>
Date: Sun, 12 Jul 2026 21:38:30 -0400
Subject: [PATCH 1/2] [InstCombine] Fold select of and/or subset identity to
`or`
Fold selects whose condition proves that one `or` operand is redundant:
select (icmp ne (and A, B), B), (or A, B), A --> or A, B
select (icmp eq (and A, B), B), A, (or A, B) --> or A, B
When `(A & B) == B`, every set bit of B is already set in A, so `A | B == A`
and the select yields A; otherwise the select already yields `A | B`. Either
way the result is `A | B`.
The result is materialized as a fresh flag-free `or` rather than reusing the
matched `or`, which may be `disjoint`: on the path where the `A` arm is
selected, `or disjoint A, B` can be poison while A is not, so reusing it would
introduce poison.
Fixes #207909.
---
.../InstCombine/InstCombineSelect.cpp | 59 +++++++
llvm/test/Transforms/InstCombine/select.ll | 162 ++++++++++++++++++
2 files changed, 221 insertions(+)
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
index a4061bbd60770..6a447fe491a89 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
@@ -141,6 +141,57 @@ static Instruction *foldSelectBinOpIdentity(SelectInst &Sel,
return IC.replaceOperand(Sel, IsEq ? 1 : 2, FoldedVal);
}
+/// Fold a select whose condition proves that one 'or' operand is redundant:
+/// select (icmp ne (and A, B), B), (or A, B), A --> or A, B
+/// select (icmp eq (and A, B), B), A, (or A, B) --> or A, B
+/// When (A & B) == B, every set bit of B is already set in A, so A | B == A and
+/// the select yields A; otherwise the select already yields A | B. Either way
+/// the result is A | B.
+///
+/// The matched 'or' may be 'disjoint', a flag the select did not carry, so a
+/// fresh flag-free 'or' is built to avoid introducing poison on the path where
+/// the 'A' arm was selected.
+static Value *foldSelectAndOrSubset(SelectInst &SI,
+ InstCombiner::BuilderTy &Builder) {
+ auto *Cmp = dyn_cast<ICmpInst>(SI.getCondition());
+ if (!Cmp)
+ return nullptr;
+ ICmpInst::Predicate Pred = Cmp->getPredicate();
+ if (!ICmpInst::isEquality(Pred))
+ return nullptr;
+
+ // Match '(and A, B)' compared against 'B' (a test that B's bits are a subset
+ // of A's). The 'and' may be on either side of the compare, and its operands
+ // may be commuted.
+ Value *A = nullptr, *B = nullptr;
+ auto MatchSubsetTest = [&](Value *MaybeAnd, Value *Other) {
+ Value *X, *Y;
+ if (!match(MaybeAnd, m_And(m_Value(X), m_Value(Y))))
+ return false;
+ if (X == Other)
+ A = Y;
+ else if (Y == Other)
+ A = X;
+ else
+ return false;
+ B = Other;
+ return true;
+ };
+ if (!MatchSubsetTest(Cmp->getOperand(0), Cmp->getOperand(1)) &&
+ !MatchSubsetTest(Cmp->getOperand(1), Cmp->getOperand(0)))
+ return nullptr;
+
+ // The arm selected when (A & B) == B must be 'A' (which equals 'A | B' under
+ // that condition); the other arm must be 'or A, B'.
+ bool IsNE = Pred == ICmpInst::ICMP_NE;
+ Value *OrArm = IsNE ? SI.getTrueValue() : SI.getFalseValue();
+ Value *SubsetArm = IsNE ? SI.getFalseValue() : SI.getTrueValue();
+ if (SubsetArm != A || !match(OrArm, m_c_Or(m_Specific(A), m_Specific(B))))
+ return nullptr;
+
+ return Builder.CreateOr(A, B);
+}
+
/// This folds:
/// select (icmp eq (and X, C1)), TC, FC
/// iff C1 is a power 2 and the difference between TC and FC is a power-of-2.
@@ -4982,6 +5033,14 @@ Instruction *InstCombinerImpl::visitSelectInst(SelectInst &SI) {
if (auto *NewGep = SelectGepWithBase(FalseGep, TrueVal, true))
return NewGep;
+ // Fold a select that is equivalent to a bitwise 'or' identity, e.g.
+ // select (icmp ne (and A, B), B), (or A, B), A --> or A, B
+ // Run this before folding the select into an operand below, which would
+ // otherwise obscure the pattern.
+ if (SelType->isIntOrIntVectorTy())
+ if (Value *V = foldSelectAndOrSubset(SI, Builder))
+ return replaceInstUsesWith(SI, V);
+
// See if we can fold the select into one of our operands.
if (SelType->isIntOrIntVectorTy() || SelType->isFPOrFPVectorTy()) {
if (Instruction *FoldI = foldSelectIntoOp(SI, TrueVal, FalseVal))
diff --git a/llvm/test/Transforms/InstCombine/select.ll b/llvm/test/Transforms/InstCombine/select.ll
index 203f15cd6f2fc..8ca21b28ab2ee 100644
--- a/llvm/test/Transforms/InstCombine/select.ll
+++ b/llvm/test/Transforms/InstCombine/select.ll
@@ -5800,3 +5800,165 @@ entry:
call void @use_v2i64(<2 x i64> %neg)
ret <2 x i64> %r
}
+
+; Fold `select (icmp ne (and A, B), B), (or A, B), A` (and the equivalent `eq`
+; spelling) to `or A, B`: when (A & B) == B, A already equals A | B.
+
+define i32 @and_or_subset_ne(i32 %a, i32 %b) {
+; CHECK-LABEL: define i32 @and_or_subset_ne(
+; CHECK-SAME: i32 [[A:%.*]], i32 [[B:%.*]]) {
+; CHECK-NEXT: [[SEL:%.*]] = or i32 [[A]], [[B]]
+; CHECK-NEXT: ret i32 [[SEL]]
+;
+ %and = and i32 %a, %b
+ %cmp = icmp ne i32 %and, %b
+ %or = or i32 %a, %b
+ %sel = select i1 %cmp, i32 %or, i32 %a
+ ret i32 %sel
+}
+
+define i32 @and_or_subset_eq(i32 %a, i32 %b) {
+; CHECK-LABEL: define i32 @and_or_subset_eq(
+; CHECK-SAME: i32 [[A:%.*]], i32 [[B:%.*]]) {
+; CHECK-NEXT: [[SEL:%.*]] = or i32 [[A]], [[B]]
+; CHECK-NEXT: ret i32 [[SEL]]
+;
+ %and = and i32 %a, %b
+ %cmp = icmp eq i32 %and, %b
+ %or = or i32 %a, %b
+ %sel = select i1 %cmp, i32 %a, i32 %or
+ ret i32 %sel
+}
+
+define i32 @and_or_subset_commuted(i32 %a, i32 %b) {
+; CHECK-LABEL: define i32 @and_or_subset_commuted(
+; CHECK-SAME: i32 [[A:%.*]], i32 [[B:%.*]]) {
+; CHECK-NEXT: [[SEL:%.*]] = or i32 [[A]], [[B]]
+; CHECK-NEXT: ret i32 [[SEL]]
+;
+ %and = and i32 %b, %a
+ %cmp = icmp ne i32 %and, %b
+ %or = or i32 %b, %a
+ %sel = select i1 %cmp, i32 %or, i32 %a
+ ret i32 %sel
+}
+
+define i32 @and_or_subset_swapped_cmp(i32 %a, i32 %b) {
+; CHECK-LABEL: define i32 @and_or_subset_swapped_cmp(
+; CHECK-SAME: i32 [[A:%.*]], i32 [[B:%.*]]) {
+; CHECK-NEXT: [[SEL:%.*]] = or i32 [[A]], [[B]]
+; CHECK-NEXT: ret i32 [[SEL]]
+;
+ %and = and i32 %a, %b
+ %cmp = icmp ne i32 %b, %and
+ %or = or i32 %a, %b
+ %sel = select i1 %cmp, i32 %or, i32 %a
+ ret i32 %sel
+}
+
+; Compare the 'and' against its other operand: subset arm is %b.
+define i32 @and_or_subset_other_operand(i32 %a, i32 %b) {
+; CHECK-LABEL: define i32 @and_or_subset_other_operand(
+; CHECK-SAME: i32 [[A:%.*]], i32 [[B:%.*]]) {
+; CHECK-NEXT: [[SEL:%.*]] = or i32 [[B]], [[A]]
+; CHECK-NEXT: ret i32 [[SEL]]
+;
+ %and = and i32 %a, %b
+ %cmp = icmp ne i32 %and, %a
+ %or = or i32 %a, %b
+ %sel = select i1 %cmp, i32 %or, i32 %b
+ ret i32 %sel
+}
+
+define <2 x i32> @and_or_subset_vec(<2 x i32> %a, <2 x i32> %b) {
+; CHECK-LABEL: define <2 x i32> @and_or_subset_vec(
+; CHECK-SAME: <2 x i32> [[A:%.*]], <2 x i32> [[B:%.*]]) {
+; CHECK-NEXT: [[SEL:%.*]] = or <2 x i32> [[A]], [[B]]
+; CHECK-NEXT: ret <2 x i32> [[SEL]]
+;
+ %and = and <2 x i32> %a, %b
+ %cmp = icmp ne <2 x i32> %and, %b
+ %or = or <2 x i32> %a, %b
+ %sel = select <2 x i1> %cmp, <2 x i32> %or, <2 x i32> %a
+ ret <2 x i32> %sel
+}
+
+; The matched 'or' is 'disjoint'. The result must be a flag-free 'or': on the
+; path where the false arm (%a) is chosen, `or disjoint %a, %b` may be poison
+; while %a is not, so reusing it would be a miscompile.
+define i32 @and_or_subset_disjoint(i32 %a, i32 %b) {
+; CHECK-LABEL: define i32 @and_or_subset_disjoint(
+; CHECK-SAME: i32 [[A:%.*]], i32 [[B:%.*]]) {
+; CHECK-NEXT: [[SEL:%.*]] = or i32 [[A]], [[B]]
+; CHECK-NEXT: ret i32 [[SEL]]
+;
+ %and = and i32 %a, %b
+ %cmp = icmp ne i32 %and, %b
+ %or = or disjoint i32 %a, %b
+ %sel = select i1 %cmp, i32 %or, i32 %a
+ ret i32 %sel
+}
+
+define i32 @and_or_subset_disjoint_eq(i32 %a, i32 %b) {
+; CHECK-LABEL: define i32 @and_or_subset_disjoint_eq(
+; CHECK-SAME: i32 [[A:%.*]], i32 [[B:%.*]]) {
+; CHECK-NEXT: [[SEL:%.*]] = or i32 [[A]], [[B]]
+; CHECK-NEXT: ret i32 [[SEL]]
+;
+ %and = and i32 %a, %b
+ %cmp = icmp eq i32 %and, %b
+ %or = or disjoint i32 %a, %b
+ %sel = select i1 %cmp, i32 %a, i32 %or
+ ret i32 %sel
+}
+
+; The 'or' has an extra use: still fold the select to a fresh 'or'.
+define i32 @and_or_subset_multiuse(i32 %a, i32 %b) {
+; CHECK-LABEL: define i32 @and_or_subset_multiuse(
+; CHECK-SAME: i32 [[A:%.*]], i32 [[B:%.*]]) {
+; CHECK-NEXT: [[OR:%.*]] = or i32 [[A]], [[B]]
+; CHECK-NEXT: call void @use_i32(i32 [[OR]])
+; CHECK-NEXT: [[SEL:%.*]] = or i32 [[A]], [[B]]
+; CHECK-NEXT: ret i32 [[SEL]]
+;
+ %and = and i32 %a, %b
+ %cmp = icmp ne i32 %and, %b
+ %or = or i32 %a, %b
+ call void @use_i32(i32 %or)
+ %sel = select i1 %cmp, i32 %or, i32 %a
+ ret i32 %sel
+}
+
+; Negative: the compare is against an unrelated value, so no subset is proven.
+define i32 @and_or_subset_neg_wrong_cmp(i32 %a, i32 %b, i32 %c) {
+; CHECK-LABEL: define i32 @and_or_subset_neg_wrong_cmp(
+; CHECK-SAME: i32 [[A:%.*]], i32 [[B:%.*]], i32 [[C:%.*]]) {
+; CHECK-NEXT: [[AND:%.*]] = and i32 [[A]], [[B]]
+; CHECK-NEXT: [[CMP_NOT:%.*]] = icmp eq i32 [[AND]], [[C]]
+; CHECK-NEXT: [[OR:%.*]] = select i1 [[CMP_NOT]], i32 0, i32 [[B]]
+; CHECK-NEXT: [[SEL:%.*]] = or i32 [[A]], [[OR]]
+; CHECK-NEXT: ret i32 [[SEL]]
+;
+ %and = and i32 %a, %b
+ %cmp = icmp ne i32 %and, %c
+ %or = or i32 %a, %b
+ %sel = select i1 %cmp, i32 %or, i32 %a
+ ret i32 %sel
+}
+
+; Negative: the non-'or' arm is not the superset operand.
+define i32 @and_or_subset_neg_wrong_arm(i32 %a, i32 %b, i32 %c) {
+; CHECK-LABEL: define i32 @and_or_subset_neg_wrong_arm(
+; CHECK-SAME: i32 [[A:%.*]], i32 [[B:%.*]], i32 [[C:%.*]]) {
+; CHECK-NEXT: [[AND:%.*]] = and i32 [[A]], [[B]]
+; CHECK-NEXT: [[CMP_NOT:%.*]] = icmp eq i32 [[AND]], [[B]]
+; CHECK-NEXT: [[OR:%.*]] = or i32 [[A]], [[B]]
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[CMP_NOT]], i32 [[C]], i32 [[OR]]
+; CHECK-NEXT: ret i32 [[SEL]]
+;
+ %and = and i32 %a, %b
+ %cmp = icmp ne i32 %and, %b
+ %or = or i32 %a, %b
+ %sel = select i1 %cmp, i32 %or, i32 %c
+ ret i32 %sel
+}
>From 1aa2bbd067ae9310dc9053f5d8b13058fb96e29a Mon Sep 17 00:00:00 2001
From: Joel-Wwalker <theagingboy05 at gmail.com>
Date: Sat, 5 Sep 2026 14:42:03 -0400
Subject: [PATCH 2/2] [InstCombine] Match the subset test with m_c_ICmp/m_c_And
WIP commit message: Joel rewrites this before pushing.
---
.../InstCombine/InstCombineSelect.cpp | 34 +++++--------------
1 file changed, 8 insertions(+), 26 deletions(-)
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
index 6a447fe491a89..46f74c5a51708 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
@@ -153,32 +153,14 @@ static Instruction *foldSelectBinOpIdentity(SelectInst &Sel,
/// the 'A' arm was selected.
static Value *foldSelectAndOrSubset(SelectInst &SI,
InstCombiner::BuilderTy &Builder) {
- auto *Cmp = dyn_cast<ICmpInst>(SI.getCondition());
- if (!Cmp)
- return nullptr;
- ICmpInst::Predicate Pred = Cmp->getPredicate();
- if (!ICmpInst::isEquality(Pred))
- return nullptr;
-
- // Match '(and A, B)' compared against 'B' (a test that B's bits are a subset
- // of A's). The 'and' may be on either side of the compare, and its operands
- // may be commuted.
- Value *A = nullptr, *B = nullptr;
- auto MatchSubsetTest = [&](Value *MaybeAnd, Value *Other) {
- Value *X, *Y;
- if (!match(MaybeAnd, m_And(m_Value(X), m_Value(Y))))
- return false;
- if (X == Other)
- A = Y;
- else if (Y == Other)
- A = X;
- else
- return false;
- B = Other;
- return true;
- };
- if (!MatchSubsetTest(Cmp->getOperand(0), Cmp->getOperand(1)) &&
- !MatchSubsetTest(Cmp->getOperand(1), Cmp->getOperand(0)))
+ // Match '(and A, B)' compared for equality against 'B' (a test that B's bits
+ // are a subset of A's). B is bound from the non-'and' side first so that the
+ // commutative 'and' matcher can find it in either operand position.
+ CmpPredicate Pred;
+ Value *A, *B;
+ if (!match(SI.getCondition(),
+ m_c_ICmp(Pred, m_Value(B), m_c_And(m_Value(A), m_Deferred(B)))) ||
+ !ICmpInst::isEquality(Pred))
return nullptr;
// The arm selected when (A & B) == B must be 'A' (which equals 'A | B' under
More information about the llvm-commits
mailing list