[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
Sat Apr 11 21:44:58 PDT 2026


https://github.com/Jinlock9 updated https://github.com/llvm/llvm-project/pull/191638

>From e541149dd7c99c7c33dc685284803aaf36b657f6 Mon Sep 17 00:00:00 2001
From: Jinlock <jinlock99 at gmail.com>
Date: Sun, 12 Apr 2026 00:46:19 +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       |  3 +
 .../Transforms/InstCombine/icmp-of-xor-x.ll   | 12 +--
 llvm/test/Transforms/InstCombine/xor.ll       | 95 +++++++++++++++++++
 3 files changed, 104 insertions(+), 6 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
index 02aa1b381e15f..3d03517a55419 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
@@ -5634,10 +5634,13 @@ 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_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..34d24a35c059c 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,97 @@ 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
+}



More information about the llvm-commits mailing list