[llvm] [InstCombine] Fold select XOR with a masked false arm (PR #218094)

via llvm-commits llvm-commits at lists.llvm.org
Fri Aug 21 21:02:40 PDT 2026


https://github.com/SNSuperNova created https://github.com/llvm/llvm-project/pull/218094

### Summary

Fold the following pattern:

```text
(select C, B, A) ^ (A & ~C) -> B & C
```

The select and the masked `and` are both required to have one use, so the
transform reduces the instruction count.

### Correctness

When `C` is true, the select produces `B` and the masked value is zero, so the
result is `B`. When `C` is false, both XOR operands are `A`, so the result is
zero. Therefore, the expression simplifies to `B & C`.

Under LLVM poison semantics, the target may be more defined than the source.
Alive2 was used to validate the required source-to-target refinement for the
affected regression cases.

### Profitability

The existing regression cases are reduced from four non-terminator SSA
instructions to a single `and`.

### Testing

- Built `opt` from LLVM main in Release mode with assertions enabled.
- Passed both directly affected InstCombine tests.
- Passed a 12-test related InstCombine lit subset covering adjacent
  AND/OR/XOR/select transformations.

### Tool assistance

Assisted-by: OpenAI Codex


>From 47683ce9e43abc4ee9e98540eced2eabf512c138 Mon Sep 17 00:00:00 2001
From: Hayate <sse62840 at gmail.com>
Date: Sat, 22 Aug 2026 11:55:57 +0800
Subject: [PATCH] [InstCombine] Fold select XOR with a masked false arm

Fold (select C, B, A) ^ (A & ~C) to B & C when both the select and the masked AND are one-use. This reduces the instruction count and simplifies two existing logical-select regression cases to a single AND.

Alive2 verifies source-to-target refinement for the affected cases. The reverse direction does not hold because the target may be more defined for poison inputs.

Assisted-by: OpenAI Codex
---
 .../Transforms/InstCombine/InstCombineAndOrXor.cpp    | 11 ++++++++++-
 .../InstCombine/logical-select-inseltpoison.ll        |  7 ++-----
 llvm/test/Transforms/InstCombine/logical-select.ll    |  7 ++-----
 3 files changed, 14 insertions(+), 11 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
index 7bc0d7d2f11b8..29bd3784c83b3 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
@@ -5548,6 +5548,16 @@ Instruction *InstCombinerImpl::visitXor(BinaryOperator &I) {
   if (Instruction *FoldedLogic = foldBinOpSelectBinOp(I))
     return FoldedLogic;
 
+  // (select C, B, A) ^ (A & ~C) --> B & C
+  // Both the select and the AND must be one-use, so this reduces instruction
+  // count.
+  Value *A, *B, *C;
+  if (match(&I,
+            m_c_Xor(m_OneUse(m_Select(m_Value(C), m_Value(B), m_Value(A))),
+                    m_OneUse(m_c_And(m_Deferred(A),
+                                    m_Not(m_Deferred(C)))))))
+    return BinaryOperator::CreateAnd(B, C);
+
   // Y ^ (X | Y) --> X & ~Y
   // Y ^ (Y | X) --> X & ~Y
   if (match(Op1, m_OneUse(m_c_Or(m_Value(X), m_Specific(Op0)))))
@@ -5570,7 +5580,6 @@ Instruction *InstCombinerImpl::visitXor(BinaryOperator &I) {
       match(Op0, m_OneUse(m_c_And(m_Value(X), m_Specific(Op1)))))
     return BinaryOperator::CreateAnd(Op1, Builder.CreateNot(X));
 
-  Value *A, *B, *C;
   // (A ^ B) ^ (A | C) --> (~A & C) ^ B -- There are 4 commuted variants.
   if (match(&I, m_c_Xor(m_OneUse(m_Xor(m_Value(A), m_Value(B))),
                         m_OneUse(m_c_Or(m_Deferred(A), m_Value(C))))))
diff --git a/llvm/test/Transforms/InstCombine/logical-select-inseltpoison.ll b/llvm/test/Transforms/InstCombine/logical-select-inseltpoison.ll
index 834d48f925305..1f699d1370433 100644
--- a/llvm/test/Transforms/InstCombine/logical-select-inseltpoison.ll
+++ b/llvm/test/Transforms/InstCombine/logical-select-inseltpoison.ll
@@ -392,11 +392,8 @@ define i1 @bools_logical(i1 %a, i1 %b, i1 %c) {
 
 define i1 @bools_multi_uses1(i1 %a, i1 %b, i1 %c) {
 ; CHECK-LABEL: @bools_multi_uses1(
-; CHECK-NEXT:    [[NOT:%.*]] = xor i1 [[C:%.*]], true
-; CHECK-NEXT:    [[AND1:%.*]] = and i1 [[A:%.*]], [[NOT]]
-; CHECK-NEXT:    [[OR:%.*]] = select i1 [[C]], i1 [[B:%.*]], i1 [[A]]
-; CHECK-NEXT:    [[XOR:%.*]] = xor i1 [[OR]], [[AND1]]
-; CHECK-NEXT:    ret i1 [[XOR]]
+; CHECK-NEXT:    [[R:%.*]] = and i1 [[B:%.*]], [[C:%.*]]
+; CHECK-NEXT:    ret i1 [[R]]
 ;
   %not = xor i1 %c, -1
   %and1 = and i1 %not, %a
diff --git a/llvm/test/Transforms/InstCombine/logical-select.ll b/llvm/test/Transforms/InstCombine/logical-select.ll
index 85e8c98455c91..28cd7bad413cb 100644
--- a/llvm/test/Transforms/InstCombine/logical-select.ll
+++ b/llvm/test/Transforms/InstCombine/logical-select.ll
@@ -397,11 +397,8 @@ define i1 @bools_logical(i1 %a, i1 %b, i1 %c) {
 
 define i1 @bools_multi_uses1(i1 %a, i1 %b, i1 %c) {
 ; CHECK-LABEL: @bools_multi_uses1(
-; CHECK-NEXT:    [[NOT:%.*]] = xor i1 [[C:%.*]], true
-; CHECK-NEXT:    [[AND1:%.*]] = and i1 [[A:%.*]], [[NOT]]
-; CHECK-NEXT:    [[OR:%.*]] = select i1 [[C]], i1 [[B:%.*]], i1 [[A]]
-; CHECK-NEXT:    [[XOR:%.*]] = xor i1 [[OR]], [[AND1]]
-; CHECK-NEXT:    ret i1 [[XOR]]
+; CHECK-NEXT:    [[R:%.*]] = and i1 [[B:%.*]], [[C:%.*]]
+; CHECK-NEXT:    ret i1 [[R]]
 ;
   %not = xor i1 %c, -1
   %and1 = and i1 %not, %a



More information about the llvm-commits mailing list