[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
Sun Aug 16 04:43:04 PDT 2026


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

>From 4459f17ccf4ff130d1387b27de243e1be51c41b3 Mon Sep 17 00:00:00 2001
From: Jinlock Choi <jinlock99 at gmail.com>
Date: Sun, 16 Aug 2026 19:42:42 +0800
Subject: [PATCH] [InstCombine] Hoist xor constant through disjoint or

Extend the existing xor-by-constant hoist to treat `or disjoint X, C`
like `X ^ C`, enabling folds such as:

(X ^ (or disjoint Y, C1)) ^ C2 -> (X ^ Y) ^ (C1 ^ C2)

Keep the existing ConstantExpr exclusion for the hoisted value operand to avoid
combine loops.

Fixes #191169
---
 .../InstCombine/InstCombineAndOrXor.cpp       |  5 +
 .../Transforms/InstCombine/icmp-of-xor-x.ll   | 12 +--
 llvm/test/Transforms/InstCombine/xor.ll       | 94 +++++++++++++++++++
 3 files changed, 105 insertions(+), 6 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
index b6f4a55c07e8a..3ee340b74c743 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..6ec8431bc61cc 100644
--- a/llvm/test/Transforms/InstCombine/xor.ll
+++ b/llvm/test/Transforms/InstCombine/xor.ll
@@ -1664,3 +1664,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
+}
+
+; 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 i8 @xor_disjoint_or_fold_multiuse_xor(i8 %x, i8 %y) {
+; CHECK-LABEL: @xor_disjoint_or_fold_multiuse_xor(
+; CHECK-NEXT:    [[TMP1:%.*]] = xor i8 [[Y:%.*]], [[X:%.*]]
+; CHECK-NEXT:    [[XOR1:%.*]] = xor i8 [[TMP1]], 1
+; CHECK-NEXT:    call void @use(i8 [[XOR1]])
+; CHECK-NEXT:    [[XOR2:%.*]] = xor i8 [[TMP1]], 17
+; CHECK-NEXT:    ret i8 [[XOR2]]
+;
+  %od = or disjoint i8 %y, 1
+  %xor1 = xor i8 %x, %od
+  call void @use(i8 %xor1)
+  %xor2 = xor i8 %xor1, 16
+  ret i8 %xor2
+}
+
+; Negative test: or disjoint has multiple uses
+define i8 @xor_disjoint_or_fold_multiuse_or(i8 %x, i8 %y) {
+; CHECK-LABEL: @xor_disjoint_or_fold_multiuse_or(
+; CHECK-NEXT:    [[OD:%.*]] = or disjoint i8 [[Y:%.*]], 1
+; CHECK-NEXT:    call void @use(i8 [[OD]])
+; CHECK-NEXT:    [[XOR1:%.*]] = xor i8 [[X:%.*]], [[OD]]
+; CHECK-NEXT:    [[XOR2:%.*]] = xor i8 [[XOR1]], 16
+; CHECK-NEXT:    ret i8 [[XOR2]]
+;
+  %od = or disjoint i8 %y, 1
+  call void @use(i8 %od)
+  %xor1 = xor i8 %x, %od
+  %xor2 = xor i8 %xor1, 16
+  ret i8 %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