[llvm] [InstCombine] Fold correlated poison-blocking logical selects (PR #221130)
via llvm-commits
llvm-commits at lists.llvm.org
Thu Sep 3 21:09:07 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-llvm-transforms
Author: Jeong Jihyeon (JihyeonJeong129)
<details>
<summary>Changes</summary>
This patch folds correlated poison-blocking logical select conditions of the following form:
```
C = select A, B, false
G = select !A, true, B
select G, (select C, T, F), X
-> select A, (select B, T, X), F
```
The transformation is implemented as part of `foldNestedSelects()` and uses its existing profitability rule to avoid increasing the instruction count.
Tests cover:
- The canonical scalar poison-blocking pattern
- A non-canonical guard normalized by `foldSelectOfBools()`
- Fixed-width vector conditions
- An inner mux with multiple uses
- The negative case where both the guard and inner mux have multiple uses
- Bitwise `and` and `or` conditions excluded from the fold
- Swapped poison-blocking AND and OR operands
- A vector NOT mask containing a poison lane
- Preservation of outer fast-math flags without propagating inner flags
Testing:
```
llvm/test/Transforms/InstCombine/select-correlated-poison-blocking.ll
ninja -C build check-llvm
```
Fixes #<!-- -->198820
---
Full diff: https://github.com/llvm/llvm-project/pull/221130.diff
3 Files Affected:
- (modified) llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp (+32)
- (modified) llvm/test/Transforms/InstCombine/nested-select.ll (+208)
- (modified) llvm/test/Transforms/InstCombine/preserve-profile.ll (+15)
``````````diff
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
index 81d949fd1fb28..8732f3c5c01eb 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
@@ -3626,6 +3626,11 @@ foldSelectOfSymmetricSelect(SelectInst &OuterSelVal,
/// and rewrite it as
/// %inner.sel = select i1 %cond.alternative, i8 %sel.outer.t, i8 %sel.inner.t
/// %sel.outer = select i1 %cond.inner, i8 %inner.sel, i8 %sel.inner.f
+///
+/// Also fold correlated poison-blocking conditions
+/// select (!A || B), (select (A && B), T, F), X
+/// into
+/// select A, (select B, T, X), F
static Instruction *foldNestedSelects(SelectInst &OuterSelVal,
InstCombiner::BuilderTy &Builder) {
// We must start with a `select`.
@@ -3662,6 +3667,33 @@ static Instruction *foldNestedSelects(SelectInst &OuterSelVal,
if (match(InnerSel.Cond, m_Not(m_Value(InnerSel.Cond))))
std::swap(InnerSel.TrueVal, InnerSel.FalseVal);
+ // Fold correlated poison-blocking logical selects:
+ // C = A && B
+ // G = !A || B
+ // select G, (select C, T, F), X
+ // --> select A, (select B, T, X), F
+ // Only the canonical poison-blocking select forms with this exact operand
+ // order are handled. Bitwise and/or, commuted operands and a 'not' with
+ // poison lanes may also be valid, but they have not been verified yet.
+ if (!IsAndVariant && isa<SelectInst>(OuterSel.Cond) &&
+ isa<SelectInst>(InnerSel.Cond)) {
+ Value *A, *B;
+ if (match(OuterSel.Cond,
+ m_LogicalOr(m_NotForbidPoison(m_Value(A)), m_Value(B))) &&
+ match(InnerSel.Cond, m_LogicalAnd(m_Specific(A), m_Specific(B)))) {
+ Value *NewInner = Builder.CreateSelectWithUnknownProfile(
+ B, InnerSel.TrueVal, OuterSel.FalseVal, DEBUG_TYPE);
+ NewInner->takeName(InnerSelVal);
+
+ auto *NewOuter = SelectInst::Create(A, NewInner, InnerSel.FalseVal);
+ setExplicitlyUnknownBranchWeightsIfProfiled(*NewOuter, DEBUG_TYPE,
+ OuterSelVal.getFunction());
+ if (auto *FPOp = dyn_cast<FPMathOperator>(&OuterSelVal))
+ NewOuter->setFastMathFlags(FPOp->getFastMathFlags());
+ return NewOuter;
+ }
+ }
+
Value *AltCond = nullptr;
auto matchOuterCond = [OuterSel, IsAndVariant, &AltCond](auto m_InnerCond) {
// An unsimplified select condition can match both LogicalAnd and LogicalOr
diff --git a/llvm/test/Transforms/InstCombine/nested-select.ll b/llvm/test/Transforms/InstCombine/nested-select.ll
index b8a7d4e8ab789..db4aefda6c753 100644
--- a/llvm/test/Transforms/InstCombine/nested-select.ll
+++ b/llvm/test/Transforms/InstCombine/nested-select.ll
@@ -29,6 +29,214 @@ define i8 @orcond(i1 %inner.cond, i1 %alt.cond, i8 %inner.sel.trueval, i8 %inner
ret i8 %outer.sel
}
+; Correlated poison-blocking logical conditions
+
+define i8 @correlated_poison_blocking_conditions(i1 %a, i1 %b, i8 %t, i8 %f, i8 %x) {
+; CHECK-LABEL: @correlated_poison_blocking_conditions(
+; CHECK-NEXT: [[MUX:%.*]] = select i1 [[B:%.*]], i8 [[T:%.*]], i8 [[X:%.*]]
+; CHECK-NEXT: [[RET:%.*]] = select i1 [[A:%.*]], i8 [[MUX]], i8 [[F:%.*]]
+; CHECK-NEXT: ret i8 [[RET]]
+;
+ %and = select i1 %a, i1 %b, i1 false
+ %not.a = xor i1 %a, true
+ %guard = select i1 %not.a, i1 true, i1 %b
+ %mux = select i1 %and, i8 %t, i8 %f
+ %ret = select i1 %guard, i8 %mux, i8 %x
+ ret i8 %ret
+}
+
+; Verify that foldSelectOfBools canonicalizes the guard before this fold.
+
+define i8 @correlated_poison_blocking_conditions_noncanonical_guard(i1 %a, i1 %b, i8 %t, i8 %f, i8 %x) {
+; CHECK-LABEL: @correlated_poison_blocking_conditions_noncanonical_guard(
+; CHECK-NEXT: [[MUX:%.*]] = select i1 [[B:%.*]], i8 [[T:%.*]], i8 [[X:%.*]]
+; CHECK-NEXT: [[RET:%.*]] = select i1 [[A:%.*]], i8 [[MUX]], i8 [[F:%.*]]
+; CHECK-NEXT: ret i8 [[RET]]
+;
+ %and = select i1 %a, i1 %b, i1 false
+ %guard = select i1 %a, i1 %b, i1 true
+ %mux = select i1 %and, i8 %t, i8 %f
+ %ret = select i1 %guard, i8 %mux, i8 %x
+ ret i8 %ret
+}
+
+define <2 x i8> @correlated_poison_blocking_conditions_vec(<2 x i1> %a, <2 x i1> %b, <2 x i8> %t, <2 x i8> %f, <2 x i8> %x) {
+; CHECK-LABEL: @correlated_poison_blocking_conditions_vec(
+; CHECK-NEXT: [[MUX:%.*]] = select <2 x i1> [[B:%.*]], <2 x i8> [[T:%.*]], <2 x i8> [[X:%.*]]
+; CHECK-NEXT: [[RET:%.*]] = select <2 x i1> [[A:%.*]], <2 x i8> [[MUX]], <2 x i8> [[F:%.*]]
+; CHECK-NEXT: ret <2 x i8> [[RET]]
+;
+ %and = select <2 x i1> %a, <2 x i1> %b, <2 x i1> zeroinitializer
+ %not.a = xor <2 x i1> %a, <i1 true, i1 true>
+ %guard = select <2 x i1> %not.a, <2 x i1> <i1 true, i1 true>, <2 x i1> %b
+ %mux = select <2 x i1> %and, <2 x i8> %t, <2 x i8> %f
+ %ret = select <2 x i1> %guard, <2 x i8> %mux, <2 x i8> %x
+ ret <2 x i8> %ret
+}
+
+; The existing profitability rule permits reassociation when the guard is
+; one-use, even if the inner mux has another use.
+
+define i8 @correlated_poison_blocking_conditions_mux_multiuse(i1 %a, i1 %b, i8 %t, i8 %f, i8 %x) {
+; CHECK-LABEL: @correlated_poison_blocking_conditions_mux_multiuse(
+; CHECK-NEXT: [[AND:%.*]] = select i1 [[A:%.*]], i1 [[B:%.*]], i1 false
+; CHECK-NEXT: [[OLD_MUX:%.*]] = select i1 [[AND]], i8 [[T:%.*]], i8 [[F:%.*]]
+; CHECK-NEXT: call void @use.i8(i8 [[OLD_MUX]])
+; CHECK-NEXT: [[MUX:%.*]] = select i1 [[B]], i8 [[T]], i8 [[X:%.*]]
+; CHECK-NEXT: [[RET:%.*]] = select i1 [[A]], i8 [[MUX]], i8 [[F]]
+; CHECK-NEXT: ret i8 [[RET]]
+;
+ %and = select i1 %a, i1 %b, i1 false
+ %not.a = xor i1 %a, true
+ %guard = select i1 %not.a, i1 true, i1 %b
+ %mux = select i1 %and, i8 %t, i8 %f
+ call void @use.i8(i8 %mux)
+ %ret = select i1 %guard, i8 %mux, i8 %x
+ ret i8 %ret
+}
+
+; Do not increase instruction count when neither the guard nor the mux is
+; one-use.
+
+define i8 @correlated_poison_blocking_conditions_both_multiuse(i1 %a, i1 %b, i8 %t, i8 %f, i8 %x) {
+; CHECK-LABEL: @correlated_poison_blocking_conditions_both_multiuse(
+; CHECK-NEXT: [[AND:%.*]] = select i1 [[A:%.*]], i1 [[B:%.*]], i1 false
+; CHECK-NEXT: [[NOT_A:%.*]] = xor i1 [[A]], true
+; CHECK-NEXT: [[GUARD:%.*]] = select i1 [[NOT_A]], i1 true, i1 [[B]]
+; CHECK-NEXT: call void @use.i1(i1 [[GUARD]])
+; CHECK-NEXT: [[MUX:%.*]] = select i1 [[AND]], i8 [[T:%.*]], i8 [[F:%.*]]
+; CHECK-NEXT: call void @use.i8(i8 [[MUX]])
+; CHECK-NEXT: [[RET:%.*]] = select i1 [[GUARD]], i8 [[MUX]], i8 [[X:%.*]]
+; CHECK-NEXT: ret i8 [[RET]]
+;
+ %and = select i1 %a, i1 %b, i1 false
+ %not.a = xor i1 %a, true
+ %guard = select i1 %not.a, i1 true, i1 %b
+ call void @use.i1(i1 %guard)
+ %mux = select i1 %and, i8 %t, i8 %f
+ call void @use.i8(i8 %mux)
+ %ret = select i1 %guard, i8 %mux, i8 %x
+ ret i8 %ret
+}
+
+; Bitwise or is not handled yet. It may also be valid, but it has not been
+; verified.
+
+define i8 @correlated_conditions_bitwise_or(i1 %a, i8 %b8, i8 %t, i8 %f, i8 %x) {
+; CHECK-LABEL: @correlated_conditions_bitwise_or(
+; CHECK-NEXT: [[B:%.*]] = trunc i8 [[B8:%.*]] to i1
+; CHECK-NEXT: [[AND:%.*]] = select i1 [[A:%.*]], i1 [[B]], i1 false
+; CHECK-NEXT: [[NOT_A:%.*]] = xor i1 [[A]], true
+; CHECK-NEXT: [[GUARD:%.*]] = or i1 [[NOT_A]], [[B]]
+; CHECK-NEXT: [[MUX:%.*]] = select i1 [[AND]], i8 [[T:%.*]], i8 [[F:%.*]]
+; CHECK-NEXT: [[RET:%.*]] = select i1 [[GUARD]], i8 [[MUX]], i8 [[X:%.*]]
+; CHECK-NEXT: ret i8 [[RET]]
+;
+ %b = trunc i8 %b8 to i1
+ %and = select i1 %a, i1 %b, i1 false
+ %not.a = xor i1 %a, true
+ %guard = or i1 %not.a, %b
+ %mux = select i1 %and, i8 %t, i8 %f
+ %ret = select i1 %guard, i8 %mux, i8 %x
+ ret i8 %ret
+}
+
+; Bitwise and is not handled yet. It may also be valid, but it has not been
+; verified.
+
+define i8 @correlated_conditions_bitwise_and(i1 %a, i8 %b8, i8 %t, i8 %f, i8 %x) {
+; CHECK-LABEL: @correlated_conditions_bitwise_and(
+; CHECK-NEXT: [[B:%.*]] = trunc i8 [[B8:%.*]] to i1
+; CHECK-NEXT: [[AND:%.*]] = and i1 [[A:%.*]], [[B]]
+; CHECK-NEXT: [[NOT_A:%.*]] = xor i1 [[A]], true
+; CHECK-NEXT: [[GUARD:%.*]] = select i1 [[NOT_A]], i1 true, i1 [[B]]
+; CHECK-NEXT: [[MUX:%.*]] = select i1 [[AND]], i8 [[T:%.*]], i8 [[F:%.*]]
+; CHECK-NEXT: [[RET:%.*]] = select i1 [[GUARD]], i8 [[MUX]], i8 [[X:%.*]]
+; CHECK-NEXT: ret i8 [[RET]]
+;
+ %b = trunc i8 %b8 to i1
+ %and = and i1 %a, %b
+ %not.a = xor i1 %a, true
+ %guard = select i1 %not.a, i1 true, i1 %b
+ %mux = select i1 %and, i8 %t, i8 %f
+ %ret = select i1 %guard, i8 %mux, i8 %x
+ ret i8 %ret
+}
+
+; Commuted AND operands are not handled yet. They may also be valid, but this
+; has not been verified.
+
+define i8 @correlated_conditions_swapped_and(i1 %a, i1 %b, i8 %t, i8 %f, i8 %x) {
+; CHECK-LABEL: @correlated_conditions_swapped_and(
+; CHECK-NEXT: [[AND:%.*]] = select i1 [[B:%.*]], i1 [[A:%.*]], i1 false
+; CHECK-NEXT: [[NOT_A:%.*]] = xor i1 [[A]], true
+; CHECK-NEXT: [[GUARD:%.*]] = select i1 [[NOT_A]], i1 true, i1 [[B]]
+; CHECK-NEXT: [[MUX:%.*]] = select i1 [[AND]], i8 [[T:%.*]], i8 [[F:%.*]]
+; CHECK-NEXT: [[RET:%.*]] = select i1 [[GUARD]], i8 [[MUX]], i8 [[X:%.*]]
+; CHECK-NEXT: ret i8 [[RET]]
+;
+ %and = select i1 %b, i1 %a, i1 false
+ %not.a = xor i1 %a, true
+ %guard = select i1 %not.a, i1 true, i1 %b
+ %mux = select i1 %and, i8 %t, i8 %f
+ %ret = select i1 %guard, i8 %mux, i8 %x
+ ret i8 %ret
+}
+
+; Commuted OR operands are not handled yet. They may also be valid, but this
+; has not been verified.
+
+define i8 @correlated_conditions_swapped_or(i1 %a, i1 %b, i8 %t, i8 %f, i8 %x) {
+; CHECK-LABEL: @correlated_conditions_swapped_or(
+; CHECK-NEXT: [[AND:%.*]] = select i1 [[A:%.*]], i1 [[B:%.*]], i1 false
+; CHECK-NEXT: [[NOT_A:%.*]] = xor i1 [[A]], true
+; CHECK-NEXT: [[GUARD:%.*]] = select i1 [[B]], i1 true, i1 [[NOT_A]]
+; CHECK-NEXT: [[MUX:%.*]] = select i1 [[AND]], i8 [[T:%.*]], i8 [[F:%.*]]
+; CHECK-NEXT: [[RET:%.*]] = select i1 [[GUARD]], i8 [[MUX]], i8 [[X:%.*]]
+; CHECK-NEXT: ret i8 [[RET]]
+;
+ %and = select i1 %a, i1 %b, i1 false
+ %not.a = xor i1 %a, true
+ %guard = select i1 %b, i1 true, i1 %not.a
+ %mux = select i1 %and, i8 %t, i8 %f
+ %ret = select i1 %guard, i8 %mux, i8 %x
+ ret i8 %ret
+}
+
+; A 'not' with a poison lane is not handled yet (m_NotForbidPoison). It may
+; also be valid, but it has not been verified.
+
+define <2 x i8> @correlated_conditions_poison_in_not(<2 x i1> %a, <2 x i1> %b, <2 x i8> %t, <2 x i8> %f, <2 x i8> %x) {
+; CHECK-LABEL: @correlated_conditions_poison_in_not(
+; CHECK-NEXT: [[AND:%.*]] = select <2 x i1> [[A:%.*]], <2 x i1> [[B:%.*]], <2 x i1> zeroinitializer
+; CHECK-NEXT: [[NOT_A:%.*]] = xor <2 x i1> [[A]], <i1 true, i1 poison>
+; CHECK-NEXT: [[GUARD:%.*]] = select <2 x i1> [[NOT_A]], <2 x i1> splat (i1 true), <2 x i1> [[B]]
+; CHECK-NEXT: [[MUX:%.*]] = select <2 x i1> [[AND]], <2 x i8> [[T:%.*]], <2 x i8> [[F:%.*]]
+; CHECK-NEXT: [[RET:%.*]] = select <2 x i1> [[GUARD]], <2 x i8> [[MUX]], <2 x i8> [[X:%.*]]
+; CHECK-NEXT: ret <2 x i8> [[RET]]
+;
+ %and = select <2 x i1> %a, <2 x i1> %b, <2 x i1> zeroinitializer
+ %not.a = xor <2 x i1> %a, <i1 true, i1 poison>
+ %guard = select <2 x i1> %not.a, <2 x i1> <i1 true, i1 true>, <2 x i1> %b
+ %mux = select <2 x i1> %and, <2 x i8> %t, <2 x i8> %f
+ %ret = select <2 x i1> %guard, <2 x i8> %mux, <2 x i8> %x
+ ret <2 x i8> %ret
+}
+
+define float @correlated_poison_blocking_conditions_fmf(i1 %a, i1 %b, float %t, float %f, float %x) {
+; CHECK-LABEL: @correlated_poison_blocking_conditions_fmf(
+; CHECK-NEXT: [[MUX:%.*]] = select i1 [[B:%.*]], float [[T:%.*]], float [[X:%.*]]
+; CHECK-NEXT: [[RET:%.*]] = select nnan i1 [[A:%.*]], float [[MUX]], float [[F:%.*]]
+; CHECK-NEXT: ret float [[RET]]
+;
+ %and = select i1 %a, i1 %b, i1 false
+ %not.a = xor i1 %a, true
+ %guard = select i1 %not.a, i1 true, i1 %b
+ %mux = select ninf i1 %and, float %t, float %f
+ %ret = select nnan i1 %guard, float %mux, float %x
+ ret float %ret
+}
+
; Extra use tests (basic test, no inversions)
define i8 @andcond.extrause0(i1 %inner.cond, i1 %alt.cond, i8 %inner.sel.trueval, i8 %inner.sel.falseval, i8 %outer.sel.trueval) {
diff --git a/llvm/test/Transforms/InstCombine/preserve-profile.ll b/llvm/test/Transforms/InstCombine/preserve-profile.ll
index 8cb3e685ae302..3cb8f9669f8cc 100644
--- a/llvm/test/Transforms/InstCombine/preserve-profile.ll
+++ b/llvm/test/Transforms/InstCombine/preserve-profile.ll
@@ -83,6 +83,21 @@ define i32 @add_zext_zext_i1(i1 %a) !prof !0 {
ret i32 %add
}
+define i32 @correlated_poison_blocking_selects(i1 %a, i1 %b, i32 %t, i32 %f, i32 %x) !prof !0 {
+; CHECK-LABEL: define i32 @correlated_poison_blocking_selects(
+; CHECK-SAME: i1 [[A:%.*]], i1 [[B:%.*]], i32 [[T:%.*]], i32 [[F:%.*]], i32 [[X:%.*]]) !prof [[PROF0]] {
+; CHECK-NEXT: [[MUX:%.*]] = select i1 [[B]], i32 [[T]], i32 [[X]], !prof [[PROF2]]
+; CHECK-NEXT: [[RET:%.*]] = select i1 [[A]], i32 [[MUX]], i32 [[F]], !prof [[PROF2]]
+; CHECK-NEXT: ret i32 [[RET]]
+;
+ %and = select i1 %a, i1 %b, i1 false
+ %not.a = xor i1 %a, true
+ %guard = select i1 %not.a, i1 true, i1 %b
+ %mux = select i1 %and, i32 %t, i32 %f, !prof !1
+ %ret = select i1 %guard, i32 %mux, i32 %x, !prof !1
+ ret i32 %ret
+}
+
define i32 @no_count_no_branch_weights(i1 %a) {
; CHECK-LABEL: define i32 @no_count_no_branch_weights(
; CHECK-SAME: i1 [[A:%.*]]) {
``````````
</details>
https://github.com/llvm/llvm-project/pull/221130
More information about the llvm-commits
mailing list