[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 08:40:47 PDT 2026
https://github.com/Jinlock9 updated https://github.com/llvm/llvm-project/pull/191638
>From d0d478a6d75687f21958a73eeb8bd1a8bca6b429 Mon Sep 17 00:00:00 2001
From: Jinlock <jinlock99 at gmail.com>
Date: Sat, 11 Apr 2026 23:11:01 +0800
Subject: [PATCH] [InstCombine] Fold (X ^ (or disjoint Y, C1)) ^ C2 to (X ^ Y)
^ (C1 ^ C2)
When the inner `or` has the `disjoint` flag, the operands share no
bits, so `or disjoint Y, C1` is equivalent to `xor Y, C1`. This
allows the XOR constants to be folded together.
Fixes #191169
---
.../InstCombine/InstCombineAndOrXor.cpp | 6 ++
llvm/test/Transforms/InstCombine/xor.ll | 79 +++++++++++++++++++
2 files changed, 85 insertions(+)
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
index 02aa1b381e15f..6fa966854f116 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
@@ -5440,6 +5440,12 @@ Instruction *InstCombinerImpl::visitXor(BinaryOperator &I) {
MaskedValueIsZero(X, *C, &I))
return BinaryOperator::CreateXor(X, ConstantInt::get(Ty, *C ^ *RHSC));
+ // (X ^ (or disjoint Y, C1)) ^ C2 --> (X ^ Y) ^ (C1 ^ C2)
+ if (match(Op0, m_OneUse(m_c_Xor(m_Value(X),
+ m_DisjointOr(m_Value(Y), m_APInt(C))))))
+ return BinaryOperator::CreateXor(Builder.CreateXor(X, Y),
+ ConstantInt::get(Ty, *C ^ *RHSC));
+
// When X is a power-of-two or zero and zero input is poison:
// ctlz(i32 X) ^ 31 --> cttz(X)
// cttz(i32 X) ^ 31 --> ctlz(X)
diff --git a/llvm/test/Transforms/InstCombine/xor.ll b/llvm/test/Transforms/InstCombine/xor.ll
index 3abaf74285cc0..80e983930ec0d 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,81 @@ 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
+}
+
+; Negative test: inner xor has multiple uses
+define i32 @xor_disjoint_or_fold_multiuse(i32 %x, i32 %y) {
+; CHECK-LABEL: @xor_disjoint_or_fold_multiuse(
+; CHECK-NEXT: [[OD:%.*]] = or disjoint i32 [[Y:%.*]], 1
+; CHECK-NEXT: [[XOR1:%.*]] = xor i32 [[X:%.*]], [[OD]]
+; CHECK-NEXT: call void @use_i32(i32 [[XOR1]])
+; CHECK-NEXT: [[XOR2:%.*]] = xor i32 [[XOR1]], 16
+; 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
+}
More information about the llvm-commits
mailing list