[llvm] [InstCombine] Fold (X ^ (or disjoint Y, C1)) ^ C2 to (X ^ Y) ^ (C1 ^ C2) (PR #191638)
via llvm-commits
llvm-commits at lists.llvm.org
Fri Jul 24 22:46:22 PDT 2026
https://github.com/Jinlock9 updated https://github.com/llvm/llvm-project/pull/191638
>From acbfb3d0bec90e2069276fb4c2f3eece5bc9e2a4 Mon Sep 17 00:00:00 2001
From: Jinlock <jinlock99 at gmail.com>
Date: Sat, 25 Jul 2026 13:45:55 +0800
Subject: [PATCH] [InstCombine] Fold (or disjoint X, C) ^ Y to (X ^ Y) ^ C
Extend the existing xor-by-constant hoisting pattern to also handle
or disjoint. Since or disjoint guarantees no overlapping bits, it is
equivalent to xor, so the constant can be hoisted out the same way.
The existing SimplifyAssociativeOrCommutative then folds any resulting
((X ^ Y) ^ C1) ^ C2 into (X ^ Y) ^ (C1 ^ C2).
Fixes #191169
---
.../InstCombine/InstCombineAndOrXor.cpp | 5 +
.../Transforms/InstCombine/icmp-of-xor-x.ll | 12 +-
llvm/test/Transforms/InstCombine/xor.ll | 107 ++++++++++++++++++
3 files changed, 118 insertions(+), 6 deletions(-)
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
index b6f4a55c07e8a..bebf169ccd5ad 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
@@ -5653,10 +5653,15 @@ Instruction *InstCombinerImpl::visitXor(BinaryOperator &I) {
// Otherwise, if all else failed, try to hoist the xor-by-constant:
// (X ^ C) ^ Y --> (X ^ Y) ^ C
+ // (or disjoint X, C) ^ Y --> (X ^ Y) ^ C
// Just like we do in other places, we completely avoid the fold
// for constantexprs, at least to avoid endless combine loop.
if (match(&I, m_c_Xor(m_OneUse(m_Xor(m_Value(X, m_Unless(m_ConstantExpr())),
m_ImmConstant(C1))),
+ m_Value(Y))) ||
+ match(&I, m_c_Xor(m_OneUse(m_DisjointOr(
+ m_Value(X, m_Unless(m_ConstantExpr())),
+ m_ImmConstant(C1))),
m_Value(Y))))
return BinaryOperator::CreateXor(Builder.CreateXor(X, Y), C1);
diff --git a/llvm/test/Transforms/InstCombine/icmp-of-xor-x.ll b/llvm/test/Transforms/InstCombine/icmp-of-xor-x.ll
index 2260f77ea5f44..bb9b568ec45c7 100644
--- a/llvm/test/Transforms/InstCombine/icmp-of-xor-x.ll
+++ b/llvm/test/Transforms/InstCombine/icmp-of-xor-x.ll
@@ -701,8 +701,8 @@ define i1 @xor_ugt_2(i8 %xx, i8 %y, i8 %z) {
; CHECK-LABEL: @xor_ugt_2(
; CHECK-NEXT: [[X:%.*]] = add i8 [[XX:%.*]], [[Z:%.*]]
; CHECK-NEXT: [[YZ:%.*]] = and i8 [[Y:%.*]], 63
-; CHECK-NEXT: [[Y1:%.*]] = or disjoint i8 [[YZ]], 64
-; CHECK-NEXT: [[XOR:%.*]] = xor i8 [[X]], [[Y1]]
+; CHECK-NEXT: [[TMP1:%.*]] = xor i8 [[YZ]], [[X]]
+; CHECK-NEXT: [[XOR:%.*]] = xor i8 [[TMP1]], 64
; CHECK-NEXT: [[R:%.*]] = icmp ugt i8 [[X]], [[XOR]]
; CHECK-NEXT: ret i1 [[R]]
;
@@ -728,8 +728,8 @@ define i1 @xor_ult(i8 %x) {
define <2 x i1> @xor_sgt(<2 x i8> %x, <2 x i8> %y) {
; CHECK-LABEL: @xor_sgt(
; CHECK-NEXT: [[YZ:%.*]] = and <2 x i8> [[Y:%.*]], splat (i8 31)
-; CHECK-NEXT: [[Y1:%.*]] = or disjoint <2 x i8> [[YZ]], splat (i8 64)
-; CHECK-NEXT: [[XOR:%.*]] = xor <2 x i8> [[X:%.*]], [[Y1]]
+; CHECK-NEXT: [[TMP1:%.*]] = xor <2 x i8> [[YZ]], [[X:%.*]]
+; CHECK-NEXT: [[XOR:%.*]] = xor <2 x i8> [[TMP1]], splat (i8 64)
; CHECK-NEXT: [[R:%.*]] = icmp sgt <2 x i8> [[XOR]], [[X]]
; CHECK-NEXT: ret <2 x i1> [[R]]
;
@@ -743,8 +743,8 @@ define <2 x i1> @xor_sgt(<2 x i8> %x, <2 x i8> %y) {
define <2 x i1> @xor_sgt_fail_no_known_msb(<2 x i8> %x, <2 x i8> %y) {
; CHECK-LABEL: @xor_sgt_fail_no_known_msb(
; CHECK-NEXT: [[YZ:%.*]] = and <2 x i8> [[Y:%.*]], splat (i8 55)
-; CHECK-NEXT: [[Y1:%.*]] = or disjoint <2 x i8> [[YZ]], splat (i8 8)
-; CHECK-NEXT: [[XOR:%.*]] = xor <2 x i8> [[X:%.*]], [[Y1]]
+; CHECK-NEXT: [[TMP1:%.*]] = xor <2 x i8> [[YZ]], [[X:%.*]]
+; CHECK-NEXT: [[XOR:%.*]] = xor <2 x i8> [[TMP1]], splat (i8 8)
; CHECK-NEXT: [[R:%.*]] = icmp sgt <2 x i8> [[XOR]], [[X]]
; CHECK-NEXT: ret <2 x i1> [[R]]
;
diff --git a/llvm/test/Transforms/InstCombine/xor.ll b/llvm/test/Transforms/InstCombine/xor.ll
index 3abaf74285cc0..a1b895c784285 100644
--- a/llvm/test/Transforms/InstCombine/xor.ll
+++ b/llvm/test/Transforms/InstCombine/xor.ll
@@ -7,6 +7,7 @@
declare i32 @llvm.ctlz.i32(i32, i1)
declare <2 x i8> @llvm.cttz.v2i8(<2 x i8>, i1)
declare void @use(i8)
+declare void @use_i32(i32)
define i1 @test0(i1 %A) {
; CHECK-LABEL: @test0(
@@ -1664,3 +1665,109 @@ entry:
%or = or <2 x i32> %add, %c
ret <2 x i32> %or
}
+
+define i32 @xor_disjoint_or_fold_basic(i32 %x, i32 %y) {
+; CHECK-LABEL: @xor_disjoint_or_fold_basic(
+; CHECK-NEXT: [[TMP1:%.*]] = xor i32 [[X:%.*]], [[Y:%.*]]
+; CHECK-NEXT: [[XOR2:%.*]] = xor i32 [[TMP1]], 17
+; CHECK-NEXT: ret i32 [[XOR2]]
+;
+ %od = or disjoint i32 %y, 1
+ %xor1 = xor i32 %x, %od
+ %xor2 = xor i32 %xor1, 16
+ ret i32 %xor2
+}
+
+define i32 @xor_disjoint_or_fold_commuted(i32 %x, i32 %y) {
+; CHECK-LABEL: @xor_disjoint_or_fold_commuted(
+; CHECK-NEXT: [[TMP1:%.*]] = xor i32 [[X:%.*]], [[Y:%.*]]
+; CHECK-NEXT: [[XOR2:%.*]] = xor i32 [[TMP1]], 17
+; CHECK-NEXT: ret i32 [[XOR2]]
+;
+ %od = or disjoint i32 %y, 1
+ %xor1 = xor i32 %od, %x
+ %xor2 = xor i32 %xor1, 16
+ ret i32 %xor2
+}
+
+define <4 x i32> @xor_disjoint_or_fold_vec(<4 x i32> %x, <4 x i32> %y) {
+; CHECK-LABEL: @xor_disjoint_or_fold_vec(
+; CHECK-NEXT: [[TMP1:%.*]] = xor <4 x i32> [[X:%.*]], [[Y:%.*]]
+; CHECK-NEXT: [[XOR2:%.*]] = xor <4 x i32> [[TMP1]], splat (i32 17)
+; CHECK-NEXT: ret <4 x i32> [[XOR2]]
+;
+ %od = or disjoint <4 x i32> %y, splat (i32 1)
+ %xor1 = xor <4 x i32> %x, %od
+ %xor2 = xor <4 x i32> %xor1, splat (i32 16)
+ ret <4 x i32> %xor2
+}
+
+define i32 @xor_disjoint_or_fold_different_consts(i32 %x, i32 %y) {
+; CHECK-LABEL: @xor_disjoint_or_fold_different_consts(
+; CHECK-NEXT: [[TMP1:%.*]] = xor i32 [[X:%.*]], [[Y:%.*]]
+; CHECK-NEXT: [[XOR2:%.*]] = xor i32 [[TMP1]], 11
+; CHECK-NEXT: ret i32 [[XOR2]]
+;
+ %od = or disjoint i32 %y, 3
+ %xor1 = xor i32 %x, %od
+ %xor2 = xor i32 %xor1, 8
+ ret i32 %xor2
+}
+
+; Negative test: plain or without disjoint flag
+define i32 @xor_or_no_disjoint_flag(i32 %x, i32 %y) {
+; CHECK-LABEL: @xor_or_no_disjoint_flag(
+; CHECK-NEXT: [[OD:%.*]] = or i32 [[Y:%.*]], 1
+; CHECK-NEXT: [[XOR1:%.*]] = xor i32 [[X:%.*]], [[OD]]
+; CHECK-NEXT: [[XOR2:%.*]] = xor i32 [[XOR1]], 16
+; CHECK-NEXT: ret i32 [[XOR2]]
+;
+ %od = or i32 %y, 1
+ %xor1 = xor i32 %x, %od
+ %xor2 = xor i32 %xor1, 16
+ ret i32 %xor2
+}
+
+; Positive test: inner xor has multiple uses (oneuse is on or disjoint, not xor)
+define i32 @xor_disjoint_or_fold_multiuse_xor(i32 %x, i32 %y) {
+; CHECK-LABEL: @xor_disjoint_or_fold_multiuse_xor(
+; CHECK-NEXT: [[TMP1:%.*]] = xor i32 [[Y:%.*]], [[X:%.*]]
+; CHECK-NEXT: [[XOR1:%.*]] = xor i32 [[TMP1]], 1
+; CHECK-NEXT: call void @use_i32(i32 [[XOR1]])
+; CHECK-NEXT: [[XOR2:%.*]] = xor i32 [[TMP1]], 17
+; CHECK-NEXT: ret i32 [[XOR2]]
+;
+ %od = or disjoint i32 %y, 1
+ %xor1 = xor i32 %x, %od
+ call void @use_i32(i32 %xor1)
+ %xor2 = xor i32 %xor1, 16
+ ret i32 %xor2
+}
+
+; Negative test: or disjoint has multiple uses
+define i32 @xor_disjoint_or_fold_multiuse_or(i32 %x, i32 %y) {
+; CHECK-LABEL: @xor_disjoint_or_fold_multiuse_or(
+; CHECK-NEXT: [[OD:%.*]] = or disjoint i32 [[Y:%.*]], 1
+; CHECK-NEXT: call void @use_i32(i32 [[OD]])
+; CHECK-NEXT: [[XOR1:%.*]] = xor i32 [[X:%.*]], [[OD]]
+; CHECK-NEXT: [[XOR2:%.*]] = xor i32 [[XOR1]], 16
+; CHECK-NEXT: ret i32 [[XOR2]]
+;
+ %od = or disjoint i32 %y, 1
+ call void @use_i32(i32 %od)
+ %xor1 = xor i32 %x, %od
+ %xor2 = xor i32 %xor1, 16
+ ret i32 %xor2
+}
+
+; Negative test: avoid hoisting with ConstantExpr operands.
+define i32 @xor_disjoint_or_fold_constantexpr(i32 %x) {
+; CHECK-LABEL: @xor_disjoint_or_fold_constantexpr(
+; CHECK-NOT: xor i32 {{.*}}, 17
+; CHECK: ret i32
+;
+ %od = or disjoint i32 ptrtoint (ptr @G1 to i32), 1
+ %xor1 = xor i32 %x, %od
+ %xor2 = xor i32 %xor1, 16
+ ret i32 %xor2
+}
More information about the llvm-commits
mailing list