[llvm] [InstSimplify] Fold (X & Y) + ((X & Y) ^ Y) to Y (PR #221920)

via llvm-commits llvm-commits at lists.llvm.org
Tue Sep 8 01:44:03 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-transforms

Author: Phong Do (phongndo)

<details>
<summary>Changes</summary>

Issue #<!-- -->221351.

 Fold (X & Y) + ((X & Y) ^ Y) to Y: the addends partition the set bits of Y, so their
 sum is Y.

 Implemented in InstSimplify because the fold returns an existing value without
 creating instructions. The shift/ctz variant is out of scope.

 ## Validation

 - Tests cover commuted operands, vectors, flags, and negative cases.
 - InstSimplify and InstCombine suites: 2,018 passed, 144 unsupported in my build.
 - Alive2 proofs passed for i8, flagged i8, <2 x i4>, and i1, with undef checking
   enabled.
 - The <2 x i8> proof timed out; reducing the vector element width to i4 succeeded.

 ## Motivation

 The fold partially improves the supplied example, but my bounded real-code scan found
 no benefit. This draft is intended to gather broader benchmark evidence before
 requesting final review.

---
Full diff: https://github.com/llvm/llvm-project/pull/221920.diff


2 Files Affected:

- (modified) llvm/lib/Analysis/InstructionSimplify.cpp (+7) 
- (added) llvm/test/Transforms/InstSimplify/add-xor-and.ll (+121) 


``````````diff
diff --git a/llvm/lib/Analysis/InstructionSimplify.cpp b/llvm/lib/Analysis/InstructionSimplify.cpp
index b8cdd16266948..f68dfd237de99 100644
--- a/llvm/lib/Analysis/InstructionSimplify.cpp
+++ b/llvm/lib/Analysis/InstructionSimplify.cpp
@@ -620,6 +620,13 @@ static Value *simplifyAddInst(Value *Op0, Value *Op1, bool IsNSW, bool IsNUW,
       match(Op0, m_Sub(m_Value(Y), m_Specific(Op1))))
     return Y;
 
+  // (X & Y) + ((X & Y) ^ Y) -> Y: the addends partition the set bits of Y.
+  if ((match(Op1, m_c_Xor(m_Specific(Op0), m_Value(Y))) &&
+       match(Op0, m_c_And(m_Value(), m_Specific(Y)))) ||
+      (match(Op0, m_c_Xor(m_Specific(Op1), m_Value(Y))) &&
+       match(Op1, m_c_And(m_Value(), m_Specific(Y)))))
+    return Y;
+
   // X + ~X -> -1   since   ~X = -X-1
   Type *Ty = Op0->getType();
   if (match(Op0, m_Not(m_Specific(Op1))) || match(Op1, m_Not(m_Specific(Op0))))
diff --git a/llvm/test/Transforms/InstSimplify/add-xor-and.ll b/llvm/test/Transforms/InstSimplify/add-xor-and.ll
new file mode 100644
index 0000000000000..c474453d39065
--- /dev/null
+++ b/llvm/test/Transforms/InstSimplify/add-xor-and.ll
@@ -0,0 +1,121 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function-signature
+; RUN: opt -S -passes=instsimplify < %s | FileCheck %s
+
+define i8 @add_xor_and(i8 %x, i8 %mask) {
+; CHECK-LABEL: define {{[^@]+}}@add_xor_and
+; CHECK-SAME: (i8 [[X:%.*]], i8 [[MASK:%.*]]) {
+; CHECK-NEXT:    ret i8 [[MASK]]
+;
+  %a = and i8 %x, %mask
+  %b = xor i8 %a, %mask
+  %sum = add i8 %a, %b
+  ret i8 %sum
+}
+
+define i8 @commute_add(i8 %x, i8 %mask) {
+; CHECK-LABEL: define {{[^@]+}}@commute_add
+; CHECK-SAME: (i8 [[X:%.*]], i8 [[MASK:%.*]]) {
+; CHECK-NEXT:    ret i8 [[MASK]]
+;
+  %a = and i8 %x, %mask
+  %b = xor i8 %a, %mask
+  %sum = add i8 %b, %a
+  ret i8 %sum
+}
+
+define i8 @commute_xor(i8 %x, i8 %mask) {
+; CHECK-LABEL: define {{[^@]+}}@commute_xor
+; CHECK-SAME: (i8 [[X:%.*]], i8 [[MASK:%.*]]) {
+; CHECK-NEXT:    ret i8 [[MASK]]
+;
+  %a = and i8 %x, %mask
+  %b = xor i8 %mask, %a
+  %sum = add i8 %a, %b
+  ret i8 %sum
+}
+
+define i8 @commute_and(i8 %x, i8 %mask) {
+; CHECK-LABEL: define {{[^@]+}}@commute_and
+; CHECK-SAME: (i8 [[X:%.*]], i8 [[MASK:%.*]]) {
+; CHECK-NEXT:    ret i8 [[MASK]]
+;
+  %a = and i8 %mask, %x
+  %b = xor i8 %a, %mask
+  %sum = add i8 %a, %b
+  ret i8 %sum
+}
+
+define i8 @nowrap(i8 %x, i8 %mask) {
+; CHECK-LABEL: define {{[^@]+}}@nowrap
+; CHECK-SAME: (i8 [[X:%.*]], i8 [[MASK:%.*]]) {
+; CHECK-NEXT:    ret i8 [[MASK]]
+;
+  %a = and i8 %x, %mask
+  %b = xor i8 %a, %mask
+  %sum = add nuw nsw i8 %a, %b
+  ret i8 %sum
+}
+
+define <2 x i8> @vector(<2 x i8> %x, <2 x i8> %mask) {
+; CHECK-LABEL: define {{[^@]+}}@vector
+; CHECK-SAME: (<2 x i8> [[X:%.*]], <2 x i8> [[MASK:%.*]]) {
+; CHECK-NEXT:    ret <2 x i8> [[MASK]]
+;
+  %a = and <2 x i8> %x, %mask
+  %b = xor <2 x i8> %a, %mask
+  %sum = add <2 x i8> %a, %b
+  ret <2 x i8> %sum
+}
+
+define i1 @boolean(i1 %x, i1 %mask) {
+; CHECK-LABEL: define {{[^@]+}}@boolean
+; CHECK-SAME: (i1 [[X:%.*]], i1 [[MASK:%.*]]) {
+; CHECK-NEXT:    ret i1 [[MASK]]
+;
+  %a = and i1 %x, %mask
+  %b = xor i1 %a, %mask
+  %sum = add i1 %a, %b
+  ret i1 %sum
+}
+
+define i8 @different_mask(i8 %x, i8 %mask, i8 %other) {
+; CHECK-LABEL: define {{[^@]+}}@different_mask
+; CHECK-SAME: (i8 [[X:%.*]], i8 [[MASK:%.*]], i8 [[OTHER:%.*]]) {
+; CHECK-NEXT:    [[A:%.*]] = and i8 [[X]], [[MASK]]
+; CHECK-NEXT:    [[B:%.*]] = xor i8 [[A]], [[OTHER]]
+; CHECK-NEXT:    [[SUM:%.*]] = add i8 [[A]], [[B]]
+; CHECK-NEXT:    ret i8 [[SUM]]
+;
+  %a = and i8 %x, %mask
+  %b = xor i8 %a, %other
+  %sum = add i8 %a, %b
+  ret i8 %sum
+}
+
+define i8 @different_addend(i8 %x, i8 %mask, i8 %other) {
+; CHECK-LABEL: define {{[^@]+}}@different_addend
+; CHECK-SAME: (i8 [[X:%.*]], i8 [[MASK:%.*]], i8 [[OTHER:%.*]]) {
+; CHECK-NEXT:    [[A:%.*]] = and i8 [[X]], [[MASK]]
+; CHECK-NEXT:    [[B:%.*]] = xor i8 [[A]], [[MASK]]
+; CHECK-NEXT:    [[SUM:%.*]] = add i8 [[OTHER]], [[B]]
+; CHECK-NEXT:    ret i8 [[SUM]]
+;
+  %a = and i8 %x, %mask
+  %b = xor i8 %a, %mask
+  %sum = add i8 %other, %b
+  ret i8 %sum
+}
+
+define i8 @or_instead_of_and(i8 %x, i8 %mask) {
+; CHECK-LABEL: define {{[^@]+}}@or_instead_of_and
+; CHECK-SAME: (i8 [[X:%.*]], i8 [[MASK:%.*]]) {
+; CHECK-NEXT:    [[A:%.*]] = or i8 [[X]], [[MASK]]
+; CHECK-NEXT:    [[B:%.*]] = xor i8 [[A]], [[MASK]]
+; CHECK-NEXT:    [[SUM:%.*]] = add i8 [[A]], [[B]]
+; CHECK-NEXT:    ret i8 [[SUM]]
+;
+  %a = or i8 %x, %mask
+  %b = xor i8 %a, %mask
+  %sum = add i8 %a, %b
+  ret i8 %sum
+}

``````````

</details>


https://github.com/llvm/llvm-project/pull/221920


More information about the llvm-commits mailing list