[llvm] [InstCombine] Reassociate XOR to expose complemented-mask fold (PR #195430)
via llvm-commits
llvm-commits at lists.llvm.org
Sat May 2 01:15:21 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-llvm-transforms
Author: yiiiz (ZY546)
<details>
<summary>Changes</summary>
Reassociate XOR expressions of the form:
(A ^ (X & ~C)) ^ C
into:
((X & ~C) ^ C) ^ A
This exposes the existing demanded-bits fold that simplifies:
(X & ~C) ^ C -> X | C
Fixed: #<!-- -->195019
---
Full diff: https://github.com/llvm/llvm-project/pull/195430.diff
2 Files Affected:
- (modified) llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp (+23)
- (added) llvm/test/Transforms/InstCombine/xor-reassociate-and-mask.ll (+115)
``````````diff
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
index d81ed56290476..fdbfa14c39213 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
@@ -5631,6 +5631,29 @@ Instruction *InstCombinerImpl::visitXor(BinaryOperator &I) {
if (Instruction *Abs = canonicalizeAbs(I, Builder))
return Abs;
+ const APInt *AndC, *XorC;
+ // Reassociate to expose the existing demanded-bits fold:
+ // (A ^ (X & ~C)) ^ C --> ((X & ~C) ^ C) ^ A
+ // ((X & ~C) ^ C) --> (X | C)
+ if (match(&I,
+ m_c_Xor(m_OneUse(m_c_Xor(m_Value(A, m_Unless(m_ConstantExpr())),
+ m_Value(B, m_Unless(m_ConstantExpr())))),
+ m_APInt(XorC)))) {
+ Value *Masked = B, *Other = A;
+ if (!match(Masked, m_OneUse(m_And(m_Value(X), m_APInt(AndC)))) ||
+ *AndC != ~*XorC) {
+ Masked = A;
+ Other = B;
+ if (!match(Masked, m_OneUse(m_And(m_Value(X), m_APInt(AndC)))) ||
+ *AndC != ~*XorC)
+ Masked = nullptr;
+ }
+ if (Masked)
+ return BinaryOperator::CreateXor(
+ Builder.CreateXor(Masked, ConstantInt::get(I.getType(), *XorC)),
+ Other);
+ }
+
// Otherwise, if all else failed, try to hoist the xor-by-constant:
// (X ^ C) ^ Y --> (X ^ Y) ^ C
// Just like we do in other places, we completely avoid the fold
diff --git a/llvm/test/Transforms/InstCombine/xor-reassociate-and-mask.ll b/llvm/test/Transforms/InstCombine/xor-reassociate-and-mask.ll
new file mode 100644
index 0000000000000..9ac80f07eb6bd
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/xor-reassociate-and-mask.ll
@@ -0,0 +1,115 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+
+define i32 @basic(i32 %a, i32 %x) {
+; CHECK-LABEL: @basic(
+; CHECK-NEXT: [[TMP1:%.*]] = or i32 [[X:%.*]], 1
+; CHECK-NEXT: [[R:%.*]] = xor i32 [[TMP1]], [[A:%.*]]
+; CHECK-NEXT: ret i32 [[R]]
+;
+ %masked = and i32 %x, -2
+ %xor0 = xor i32 %a, %masked
+ %r = xor i32 %xor0, 1
+ ret i32 %r
+}
+
+define i32 @outer_xor_commuted(i32 %a, i32 %x) {
+; CHECK-LABEL: @outer_xor_commuted(
+; CHECK-NEXT: [[TMP1:%.*]] = or i32 [[X:%.*]], 1
+; CHECK-NEXT: [[R:%.*]] = xor i32 [[TMP1]], [[A:%.*]]
+; CHECK-NEXT: ret i32 [[R]]
+;
+ %masked = and i32 %x, -2
+ %xor0 = xor i32 %a, %masked
+ %r = xor i32 1, %xor0
+ ret i32 %r
+}
+
+define i32 @multi_bit_mask(i32 %a, i32 %x) {
+; CHECK-LABEL: @multi_bit_mask(
+; CHECK-NEXT: [[TMP1:%.*]] = or i32 [[X:%.*]], 42
+; CHECK-NEXT: [[R:%.*]] = xor i32 [[TMP1]], [[A:%.*]]
+; CHECK-NEXT: ret i32 [[R]]
+;
+ %masked = and i32 %x, -43
+ %xor0 = xor i32 %a, %masked
+ %r = xor i32 %xor0, 42
+ ret i32 %r
+}
+
+define i64 @wide_constant(i64 %a, i64 %x) {
+; CHECK-LABEL: @wide_constant(
+; CHECK-NEXT: [[TMP1:%.*]] = or i64 [[X:%.*]], 4294967296
+; CHECK-NEXT: [[R:%.*]] = xor i64 [[TMP1]], [[A:%.*]]
+; CHECK-NEXT: ret i64 [[R]]
+;
+ %masked = and i64 %x, -4294967297
+ %xor0 = xor i64 %a, %masked
+ %r = xor i64 %xor0, 4294967296
+ ret i64 %r
+}
+
+define <2 x i32> @splat_vector(<2 x i32> %a, <2 x i32> %x) {
+; CHECK-LABEL: @splat_vector(
+; CHECK-NEXT: [[TMP1:%.*]] = or <2 x i32> [[X:%.*]], splat (i32 1)
+; CHECK-NEXT: [[R:%.*]] = xor <2 x i32> [[TMP1]], [[A:%.*]]
+; CHECK-NEXT: ret <2 x i32> [[R]]
+;
+ %masked = and <2 x i32> %x, <i32 -2, i32 -2>
+ %xor0 = xor <2 x i32> %a, %masked
+ %r = xor <2 x i32> %xor0, <i32 1, i32 1>
+ ret <2 x i32> %r
+}
+
+define i32 @inner_xor_commuted(i32 %a, i32 %x) {
+; CHECK-LABEL: @inner_xor_commuted(
+; CHECK-NEXT: [[TMP1:%.*]] = or i32 [[X:%.*]], 1
+; CHECK-NEXT: [[R:%.*]] = xor i32 [[TMP1]], [[A:%.*]]
+; CHECK-NEXT: ret i32 [[R]]
+;
+ %masked = and i32 %x, -2
+ %xor0 = xor i32 %masked, %a
+ %r = xor i32 %xor0, 1
+ ret i32 %r
+}
+
+define i32 @extra_use_of_inner_xor_negative(i32 %a, i32 %x, ptr %p) {
+; CHECK-LABEL: @extra_use_of_inner_xor_negative(
+; CHECK-NEXT: [[MASKED:%.*]] = and i32 [[X:%.*]], -2
+; CHECK-NEXT: [[XOR0:%.*]] = xor i32 [[A:%.*]], [[MASKED]]
+; CHECK-NEXT: store i32 [[XOR0]], ptr [[P:%.*]], align 4
+; CHECK-NEXT: [[R:%.*]] = xor i32 [[XOR0]], 1
+; CHECK-NEXT: ret i32 [[R]]
+;
+ %masked = and i32 %x, -2
+ %xor0 = xor i32 %a, %masked
+ store i32 %xor0, ptr %p, align 4
+ %r = xor i32 %xor0, 1
+ ret i32 %r
+}
+
+define i32 @non_complement_mask_negative(i32 %a, i32 %x) {
+; CHECK-LABEL: @non_complement_mask_negative(
+; CHECK-NEXT: [[MASKED:%.*]] = and i32 [[X:%.*]], -4
+; CHECK-NEXT: [[XOR0:%.*]] = xor i32 [[A:%.*]], [[MASKED]]
+; CHECK-NEXT: [[R:%.*]] = xor i32 [[XOR0]], 1
+; CHECK-NEXT: ret i32 [[R]]
+;
+ %masked = and i32 %x, -4
+ %xor0 = xor i32 %a, %masked
+ %r = xor i32 %xor0, 1
+ ret i32 %r
+}
+
+define i32 @wrong_xor_constant_negative(i32 %a, i32 %x) {
+; CHECK-LABEL: @wrong_xor_constant_negative(
+; CHECK-NEXT: [[MASKED:%.*]] = and i32 [[X:%.*]], -2
+; CHECK-NEXT: [[XOR0:%.*]] = xor i32 [[A:%.*]], [[MASKED]]
+; CHECK-NEXT: [[R:%.*]] = xor i32 [[XOR0]], 2
+; CHECK-NEXT: ret i32 [[R]]
+;
+ %masked = and i32 %x, -2
+ %xor0 = xor i32 %a, %masked
+ %r = xor i32 %xor0, 2
+ ret i32 %r
+}
``````````
</details>
https://github.com/llvm/llvm-project/pull/195430
More information about the llvm-commits
mailing list