[llvm] [InstCombine] Reuse existing xor for xor of disjoint or with constant (PR #230999)
via llvm-commits
llvm-commits at lists.llvm.org
Sun Oct 11 08:42:06 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-llvm-transforms
Author: Naaz30
<details>
<summary>Changes</summary>
Folds (X | C) ^ Y --> (X ^ Y) ^ C when the or is disjoint and only has one use. A disjoint or is equivalent to xor, so this regrouping does not increase the instruction count, and it exposes X ^ Y so CSE can share it with existing xors.
Fixes : #<!-- -->230720
---
Full diff: https://github.com/llvm/llvm-project/pull/230999.diff
2 Files Affected:
- (modified) llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp (+14)
- (added) llvm/test/Transforms/InstCombine/xor-disjoint-or-shared.ll (+150)
``````````diff
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
index ef3ccf8c647bf..db6031cda79b3 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
@@ -5804,6 +5804,20 @@ Instruction *InstCombinerImpl::visitXor(BinaryOperator &I) {
m_Value(Y))))
return BinaryOperator::CreateXor(Builder.CreateXor(X, Y), C1);
+ // (X | C) ^ Y --> (X ^ Y) ^ C iff X & C == 0 (disjoint or) and X ^ Y
+ // already exists. Reusing the existing xor removes the or and shares the
+ // common subexpression, no rewrite when there is nothing to share.
+ if (match(&I, m_c_Xor(m_OneUse(m_DisjointOr(m_Value(X), m_ImmConstant(C1))),
+ m_Value(Y))) &&
+ !isa<Constant>(X)) {
+ for (User *U : X->users()) {
+ auto *UI = dyn_cast<Instruction>(U);
+ if (UI && match(UI, m_c_Xor(m_Specific(X), m_Specific(Y))) &&
+ DT.dominates(UI, &I))
+ return BinaryOperator::CreateXor(UI, C1);
+ }
+ }
+
if (Instruction *R = reassociateForUses(I, Builder))
return R;
diff --git a/llvm/test/Transforms/InstCombine/xor-disjoint-or-shared.ll b/llvm/test/Transforms/InstCombine/xor-disjoint-or-shared.ll
new file mode 100644
index 0000000000000..0c6d1cbc642da
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/xor-disjoint-or-shared.ll
@@ -0,0 +1,150 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+
+; Tests for reassociating xor X, (or disjoint P, C) --> xor (xor X, P), C
+; so that a shared X ^ P is exposed.
+
+declare void @use(i32)
+
+; Positive: the example from the issue. x ^ phase is shared by all three
+; results once the disjoint ors are reassociated.
+define { i32, i32, i32 } @shared_xor(i32 %x, i32 %y) {
+; CHECK-LABEL: @shared_xor(
+; CHECK-NEXT: [[PHASE:%.*]] = and i32 [[Y:%.*]], 48
+; CHECK-NEXT: [[BASE:%.*]] = xor i32 [[X:%.*]], [[PHASE]]
+; CHECK-NEXT: [[V4:%.*]] = xor i32 [[BASE]], 4
+; CHECK-NEXT: [[V8:%.*]] = xor i32 [[BASE]], 8
+; CHECK-NEXT: [[R0:%.*]] = insertvalue { i32, i32, i32 } poison, i32 [[BASE]], 0
+; CHECK-NEXT: [[R1:%.*]] = insertvalue { i32, i32, i32 } [[R0]], i32 [[V4]], 1
+; CHECK-NEXT: [[R2:%.*]] = insertvalue { i32, i32, i32 } [[R1]], i32 [[V8]], 2
+; CHECK-NEXT: ret { i32, i32, i32 } [[R2]]
+;
+ %phase = and i32 %y, 48
+ %base = xor i32 %x, %phase
+ %p4 = or disjoint i32 %phase, 4
+ %v4 = xor i32 %x, %p4
+ %p8 = or disjoint i32 %phase, 8
+ %v8 = xor i32 %x, %p8
+ %r0 = insertvalue { i32, i32, i32 } poison, i32 %base, 0
+ %r1 = insertvalue { i32, i32, i32 } %r0, i32 %v4, 1
+ %r2 = insertvalue { i32, i32, i32 } %r1, i32 %v8, 2
+ ret { i32, i32, i32 } %r2
+}
+
+; Positive: operands of the outer xor commuted.
+define { i32, i32 } @shared_xor_commuted(i32 %x, i32 %y) {
+; CHECK-LABEL: @shared_xor_commuted(
+; CHECK-NEXT: [[PHASE:%.*]] = and i32 [[Y:%.*]], 48
+; CHECK-NEXT: [[BASE:%.*]] = xor i32 [[PHASE]], [[X:%.*]]
+; CHECK-NEXT: [[V4:%.*]] = xor i32 [[BASE]], 4
+; CHECK-NEXT: [[R0:%.*]] = insertvalue { i32, i32 } poison, i32 [[BASE]], 0
+; CHECK-NEXT: [[R1:%.*]] = insertvalue { i32, i32 } [[R0]], i32 [[V4]], 1
+; CHECK-NEXT: ret { i32, i32 } [[R1]]
+;
+ %phase = and i32 %y, 48
+ %base = xor i32 %phase, %x
+ %p4 = or disjoint i32 %phase, 4
+ %v4 = xor i32 %p4, %x
+ %r0 = insertvalue { i32, i32 } poison, i32 %base, 0
+ %r1 = insertvalue { i32, i32 } %r0, i32 %v4, 1
+ ret { i32, i32 } %r1
+}
+
+; Positive: vector with a splat constant.
+define { <2 x i32>, <2 x i32> } @shared_xor_vec(<2 x i32> %x, <2 x i32> %y) {
+; CHECK-LABEL: @shared_xor_vec(
+; CHECK-NEXT: [[PHASE:%.*]] = and <2 x i32> [[Y:%.*]], splat (i32 48)
+; CHECK-NEXT: [[BASE:%.*]] = xor <2 x i32> [[X:%.*]], [[PHASE]]
+; CHECK-NEXT: [[V4:%.*]] = xor <2 x i32> [[BASE]], splat (i32 4)
+; CHECK-NEXT: [[R0:%.*]] = insertvalue { <2 x i32>, <2 x i32> } poison, <2 x i32> [[BASE]], 0
+; CHECK-NEXT: [[R1:%.*]] = insertvalue { <2 x i32>, <2 x i32> } [[R0]], <2 x i32> [[V4]], 1
+; CHECK-NEXT: ret { <2 x i32>, <2 x i32> } [[R1]]
+;
+ %phase = and <2 x i32> %y, splat (i32 48)
+ %base = xor <2 x i32> %x, %phase
+ %p4 = or disjoint <2 x i32> %phase, splat (i32 4)
+ %v4 = xor <2 x i32> %x, %p4
+ %r0 = insertvalue { <2 x i32>, <2 x i32> } poison, <2 x i32> %base, 0
+ %r1 = insertvalue { <2 x i32>, <2 x i32> } %r0, <2 x i32> %v4, 1
+ ret { <2 x i32>, <2 x i32> } %r1
+}
+
+; Negative: the or has another use, so it can't be removed and the
+; rewrite would add an instruction.
+define { i32, i32 } @shared_xor_multiuse_or(i32 %x, i32 %y) {
+; CHECK-LABEL: @shared_xor_multiuse_or(
+; CHECK-NEXT: [[PHASE:%.*]] = and i32 [[Y:%.*]], 48
+; CHECK-NEXT: [[BASE:%.*]] = xor i32 [[X:%.*]], [[PHASE]]
+; CHECK-NEXT: [[P4:%.*]] = or disjoint i32 [[PHASE]], 4
+; CHECK-NEXT: call void @use(i32 [[P4]])
+; CHECK-NEXT: [[V4:%.*]] = xor i32 [[X]], [[P4]]
+; CHECK-NEXT: [[R0:%.*]] = insertvalue { i32, i32 } poison, i32 [[BASE]], 0
+; CHECK-NEXT: [[R1:%.*]] = insertvalue { i32, i32 } [[R0]], i32 [[V4]], 1
+; CHECK-NEXT: ret { i32, i32 } [[R1]]
+;
+ %phase = and i32 %y, 48
+ %base = xor i32 %x, %phase
+ %p4 = or disjoint i32 %phase, 4
+ call void @use(i32 %p4)
+ %v4 = xor i32 %x, %p4
+ %r0 = insertvalue { i32, i32 } poison, i32 %base, 0
+ %r1 = insertvalue { i32, i32 } %r0, i32 %v4, 1
+ ret { i32, i32 } %r1
+}
+
+; Negative: the or is not known disjoint (%a may already have bit 2 set),
+; so or is not equivalent to xor.
+define { i32, i32 } @shared_xor_not_disjoint(i32 %x, i32 %a) {
+; CHECK-LABEL: @shared_xor_not_disjoint(
+; CHECK-NEXT: [[BASE:%.*]] = xor i32 [[X:%.*]], [[A:%.*]]
+; CHECK-NEXT: [[P4:%.*]] = or i32 [[A]], 4
+; CHECK-NEXT: [[V4:%.*]] = xor i32 [[X]], [[P4]]
+; CHECK-NEXT: [[R0:%.*]] = insertvalue { i32, i32 } poison, i32 [[BASE]], 0
+; CHECK-NEXT: [[R1:%.*]] = insertvalue { i32, i32 } [[R0]], i32 [[V4]], 1
+; CHECK-NEXT: ret { i32, i32 } [[R1]]
+;
+ %base = xor i32 %x, %a
+ %p4 = or i32 %a, 4
+ %v4 = xor i32 %x, %p4
+ %r0 = insertvalue { i32, i32 } poison, i32 %base, 0
+ %r1 = insertvalue { i32, i32 } %r0, i32 %v4, 1
+ ret { i32, i32 } %r1
+}
+
+; Negative: the or operand is not a constant.
+define { i32, i32 } @shared_xor_or_not_constant(i32 %x, i32 %y, i32 %z) {
+; CHECK-LABEL: @shared_xor_or_not_constant(
+; CHECK-NEXT: [[PHASE:%.*]] = and i32 [[Y:%.*]], 48
+; CHECK-NEXT: [[ZLOW:%.*]] = and i32 [[Z:%.*]], 3
+; CHECK-NEXT: [[BASE:%.*]] = xor i32 [[X:%.*]], [[PHASE]]
+; CHECK-NEXT: [[P:%.*]] = or disjoint i32 [[PHASE]], [[ZLOW]]
+; CHECK-NEXT: [[V:%.*]] = xor i32 [[X]], [[P]]
+; CHECK-NEXT: [[R0:%.*]] = insertvalue { i32, i32 } poison, i32 [[BASE]], 0
+; CHECK-NEXT: [[R1:%.*]] = insertvalue { i32, i32 } [[R0]], i32 [[V]], 1
+; CHECK-NEXT: ret { i32, i32 } [[R1]]
+;
+ %phase = and i32 %y, 48
+ %zlow = and i32 %z, 3
+ %base = xor i32 %x, %phase
+ %p = or disjoint i32 %phase, %zlow
+ %v = xor i32 %x, %p
+ %r0 = insertvalue { i32, i32 } poison, i32 %base, 0
+ %r1 = insertvalue { i32, i32 } %r0, i32 %v, 1
+ ret { i32, i32 } %r1
+}
+
+; No shared xor exists. Documents what the fold does when there is nothing
+; to share (it reassociates if it canonicalizes unconditionally, and leaves
+; the code alone if it only fires when x ^ phase already exists).
+define i32 @no_shared_xor(i32 %x, i32 %y) {
+; CHECK-LABEL: @no_shared_xor(
+; CHECK-NEXT: [[PHASE:%.*]] = and i32 [[Y:%.*]], 48
+; CHECK-NEXT: [[P4:%.*]] = or disjoint i32 [[PHASE]], 4
+; CHECK-NEXT: [[V4:%.*]] = xor i32 [[X:%.*]], [[P4]]
+; CHECK-NEXT: ret i32 [[V4]]
+;
+ %phase = and i32 %y, 48
+ %p4 = or disjoint i32 %phase, 4
+ %v4 = xor i32 %x, %p4
+ ret i32 %v4
+}
``````````
</details>
https://github.com/llvm/llvm-project/pull/230999
More information about the llvm-commits
mailing list