[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