[llvm] [InstCombine] select (trunc Y), 0, (X & 1) -> (X & 1) & ~Y (PR #222110)
via llvm-commits
llvm-commits at lists.llvm.org
Wed Sep 9 16:52:23 PDT 2026
https://github.com/AZero13 updated https://github.com/llvm/llvm-project/pull/222110
>From da2484a89f03665d27f2a5e074a505e3fea17bbc Mon Sep 17 00:00:00 2001
From: AZero13 <gfunni234 at gmail.com>
Date: Tue, 8 Sep 2026 14:29:03 -0400
Subject: [PATCH 1/2] Pre-commit test (NFC)
---
.../Transforms/InstCombine/and-not-select.ll | 76 +++++++++++++++++++
1 file changed, 76 insertions(+)
create mode 100644 llvm/test/Transforms/InstCombine/and-not-select.ll
diff --git a/llvm/test/Transforms/InstCombine/and-not-select.ll b/llvm/test/Transforms/InstCombine/and-not-select.ll
new file mode 100644
index 0000000000000..5c92a3e035133
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/and-not-select.ll
@@ -0,0 +1,76 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+
+; select (trunc b), 0, (a & 1) -> (a & 1) & ~b
+define i32 @test_select_and_not(i32 %a, i32 %b) {
+; CHECK-LABEL: @test_select_and_not(
+; CHECK-NEXT: [[TRUNC:%.*]] = trunc i32 [[B:%.*]] to i1
+; CHECK-NEXT: [[AND:%.*]] = and i32 [[A:%.*]], 1
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[TRUNC]], i32 0, i32 [[AND]]
+; CHECK-NEXT: ret i32 [[SEL]]
+;
+ %trunc = trunc i32 %b to i1
+ %and = and i32 %a, 1
+ %sel = select i1 %trunc, i32 0, i32 %and
+ ret i32 %sel
+}
+
+; Should not transform if mask is not 1 (for now)
+define i32 @test_select_and_not_mask2(i32 %a, i32 %b) {
+; CHECK-LABEL: @test_select_and_not_mask2(
+; CHECK-NEXT: [[TRUNC:%.*]] = trunc i32 [[B:%.*]] to i1
+; CHECK-NEXT: [[AND:%.*]] = and i32 [[A:%.*]], 2
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[TRUNC]], i32 0, i32 [[AND]]
+; CHECK-NEXT: ret i32 [[SEL]]
+;
+ %trunc = trunc i32 %b to i1
+ %and = and i32 %a, 2
+ %sel = select i1 %trunc, i32 0, i32 %and
+ ret i32 %sel
+}
+
+; Should not transform if trunc has multiple uses (would increase instruction count)
+define i32 @test_select_and_not_multiuse_trunc(i32 %a, i32 %b) {
+; CHECK-LABEL: @test_select_and_not_multiuse_trunc(
+; CHECK-NEXT: [[AND:%.*]] = and i32 [[A:%.*]], 1
+; CHECK-NEXT: [[TMP1:%.*]] = xor i32 [[B:%.*]], -1
+; CHECK-NEXT: [[SEL:%.*]] = and i32 [[AND]], [[TMP1]]
+; CHECK-NEXT: [[ZEXT:%.*]] = and i32 [[B]], 1
+; CHECK-NEXT: [[ADD:%.*]] = or i32 [[SEL]], [[ZEXT]]
+; CHECK-NEXT: ret i32 [[ADD]]
+;
+ %trunc = trunc i32 %b to i1
+ %and = and i32 %a, 1
+ %sel = select i1 %trunc, i32 0, i32 %and
+ %zext = zext i1 %trunc to i32
+ %add = add i32 %sel, %zext
+ ret i32 %add
+}
+
+; Inverse: select (trunc b), (a & 1), 0 -> (a & 1) & b
+define i32 @test_select_and(i32 %a, i32 %b) {
+; CHECK-LABEL: @test_select_and(
+; CHECK-NEXT: [[TRUNC:%.*]] = trunc i32 [[B:%.*]] to i1
+; CHECK-NEXT: [[AND:%.*]] = and i32 [[A:%.*]], 1
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[TRUNC]], i32 [[AND]], i32 0
+; CHECK-NEXT: ret i32 [[SEL]]
+;
+ %trunc = trunc i32 %b to i1
+ %and = and i32 %a, 1
+ %sel = select i1 %trunc, i32 %and, i32 0
+ ret i32 %sel
+}
+
+; Inverse: should not transform if mask is not 1
+define i32 @test_select_and_mask2(i32 %a, i32 %b) {
+; CHECK-LABEL: @test_select_and_mask2(
+; CHECK-NEXT: [[TRUNC:%.*]] = trunc i32 [[B:%.*]] to i1
+; CHECK-NEXT: [[AND:%.*]] = and i32 [[A:%.*]], 2
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[TRUNC]], i32 [[AND]], i32 0
+; CHECK-NEXT: ret i32 [[SEL]]
+;
+ %trunc = trunc i32 %b to i1
+ %and = and i32 %a, 2
+ %sel = select i1 %trunc, i32 %and, i32 0
+ ret i32 %sel
+}
>From 93f234d36e6dfd363eba6d2be230a940ebadd4e4 Mon Sep 17 00:00:00 2001
From: AZero13 <gfunni234 at gmail.com>
Date: Wed, 9 Sep 2026 19:40:21 -0400
Subject: [PATCH 2/2] [InstCombine] select (trunc Y), 0, (X & 1) -> (X & 1) &
~Y
Fixes https://github.com/llvm/llvm-project/issues/144282
---
.../Transforms/InstCombine/InstCombineSelect.cpp | 15 +++++++++++++++
.../test/Transforms/InstCombine/and-not-select.ll | 7 +++----
2 files changed, 18 insertions(+), 4 deletions(-)
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
index 4f0c21831cb4c..6e05de8823d77 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
@@ -4605,6 +4605,21 @@ Instruction *InstCombinerImpl::visitSelectInst(SelectInst &SI) {
if (Instruction *I = canonicalizeScalarSelectOfVecs(SI, *this))
return I;
+ Value *X, *Y;
+ // select (trunc Y), (X & 1), 0 -> (X & 1) & Y
+ if (match(CondVal, m_Trunc(m_Value(Y))) && match(FalseVal, m_Zero()) &&
+ match(TrueVal, m_And(m_Value(X), m_One())) && Y->getType() == SelType) {
+ return BinaryOperator::CreateAnd(TrueVal, Y);
+ }
+
+ // select (trunc Y), 0, (X & 1) -> (X & 1) & ~Y
+ if (match(CondVal, m_OneUse(m_Trunc(m_Value(Y)))) &&
+ match(TrueVal, m_Zero()) && match(FalseVal, m_And(m_Value(X), m_One())) &&
+ Y->getType() == SelType) {
+ Value *NotY = Builder.CreateNot(Y);
+ return BinaryOperator::CreateAnd(FalseVal, NotY);
+ }
+
// Fold: select (icmp ult X, 2), X, ctpop(X) --> ctpop(X)
// ctpop(0)==0 and ctpop(1)==1, so the guard is always redundant.
if (match(FalseVal, m_Ctpop(m_Specific(TrueVal))) &&
diff --git a/llvm/test/Transforms/InstCombine/and-not-select.ll b/llvm/test/Transforms/InstCombine/and-not-select.ll
index 5c92a3e035133..2330e23bf5c6d 100644
--- a/llvm/test/Transforms/InstCombine/and-not-select.ll
+++ b/llvm/test/Transforms/InstCombine/and-not-select.ll
@@ -4,9 +4,9 @@
; select (trunc b), 0, (a & 1) -> (a & 1) & ~b
define i32 @test_select_and_not(i32 %a, i32 %b) {
; CHECK-LABEL: @test_select_and_not(
-; CHECK-NEXT: [[TRUNC:%.*]] = trunc i32 [[B:%.*]] to i1
; CHECK-NEXT: [[AND:%.*]] = and i32 [[A:%.*]], 1
-; CHECK-NEXT: [[SEL:%.*]] = select i1 [[TRUNC]], i32 0, i32 [[AND]]
+; CHECK-NEXT: [[TMP1:%.*]] = xor i32 [[B:%.*]], -1
+; CHECK-NEXT: [[SEL:%.*]] = and i32 [[AND]], [[TMP1]]
; CHECK-NEXT: ret i32 [[SEL]]
;
%trunc = trunc i32 %b to i1
@@ -50,9 +50,8 @@ define i32 @test_select_and_not_multiuse_trunc(i32 %a, i32 %b) {
; Inverse: select (trunc b), (a & 1), 0 -> (a & 1) & b
define i32 @test_select_and(i32 %a, i32 %b) {
; CHECK-LABEL: @test_select_and(
-; CHECK-NEXT: [[TRUNC:%.*]] = trunc i32 [[B:%.*]] to i1
; CHECK-NEXT: [[AND:%.*]] = and i32 [[A:%.*]], 1
-; CHECK-NEXT: [[SEL:%.*]] = select i1 [[TRUNC]], i32 [[AND]], i32 0
+; CHECK-NEXT: [[SEL:%.*]] = and i32 [[AND]], [[B:%.*]]
; CHECK-NEXT: ret i32 [[SEL]]
;
%trunc = trunc i32 %b to i1
More information about the llvm-commits
mailing list