[llvm] [InstCombine] Fold xor(zext(or disjoint X, C1), C2) (PR #214970)
Joyoungjin via llvm-commits
llvm-commits at lists.llvm.org
Sat Aug 8 07:44:05 PDT 2026
https://github.com/hunterhhunter created https://github.com/llvm/llvm-project/pull/214970
Fold the constant from a disjoint `or` into the outer `xor` after zero-extension.
For example, InstCombine currently leaves:
```llvm
define i64 @src(i32 %x) {
%or = or disjoint i32 %x, 1
%z = zext i32 %or to i64
%r = xor i64 %z, 2611923443488327891
ret i64 %r
}
```
This can be simplified to:
```llvm
define i64 @tgt(i32 %x) {
%z = zext i32 %x to i64
%r = xor i64 %z, 2611923443488327890
ret i64 %r
}
```
More generally:
```text
xor(zext(or disjoint X, C1), C2)
-> xor(zext(X), C2 ^ zext(C1))
```
Since `or disjoint` guarantees that `X` and `C1` have no overlapping set bits,
`X | C1` is equivalent to `X ^ C1`. After zero-extension, the constant
introduced by the inner `or` can therefore be absorbed into the outer XOR
constant.
This removes the intermediate `or`, reducing the IR sequence from three
instructions to two.
The initial implementation is limited to scalar integer constants and requires
both the `or disjoint` and `zext` to have one use.
For `zext nneg`, the replacement `zext` does not preserve `nneg`, since the new
source `X` is not independently known to be non-negative.
The patch is split into two commits:
1. Precommit tests capturing the existing InstCombine output.
2. The fold implementation together with the updated optimized CHECK lines.
AI assistance: ChatGPT was used during analysis and implementation discussion.
I reviewed and verified the submitted code and tests and understand the changes.
Proof: https://alive2.llvm.org/ce/z/zsnvLo
Fixes #214652
>From 4f206bb209f174ce8f7473321dfa6f121dba87e8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=EC=A1=B0=EC=98=81=EC=A7=84=28Joyoungjin=29?=
<slugger613 at naver.com>
Date: Sat, 8 Aug 2026 21:33:14 +0900
Subject: [PATCH 1/2] [InstCombine] Add zext or disjoint xor fold tests
---
llvm/test/Transforms/InstCombine/xor.ll | 89 +++++++++++++++++++++++++
1 file changed, 89 insertions(+)
diff --git a/llvm/test/Transforms/InstCombine/xor.ll b/llvm/test/Transforms/InstCombine/xor.ll
index 3abaf74285cc0..2d860f1727c8e 100644
--- a/llvm/test/Transforms/InstCombine/xor.ll
+++ b/llvm/test/Transforms/InstCombine/xor.ll
@@ -1664,3 +1664,92 @@ entry:
%or = or <2 x i32> %add, %c
ret <2 x i32> %or
}
+
+; xor(zext(or disjoint X, NarrowC), WideC) ->
+; xor(zext(X), WideC ^ zext(NarrowC))
+
+define i32 @fold_zext_or_disjoint_xor_i8_to_i32(i8 %input) {
+; CHECK-LABEL: @fold_zext_or_disjoint_xor_i8_to_i32(
+; CHECK-NEXT: [[INPUT:%.*]] = or disjoint i8 [[INPUT1:%.*]], 10
+; CHECK-NEXT: [[Z:%.*]] = zext i8 [[INPUT]] to i32
+; CHECK-NEXT: [[R:%.*]] = xor i32 [[Z]], 257
+; CHECK-NEXT: ret i32 [[R]]
+;
+ %or = or disjoint i8 %input, 10
+ %z = zext i8 %or to i32
+ %r = xor i32 %z, 257
+ ret i32 %r
+}
+
+define i64 @fold_zext_or_disjoint_xor_i32_to_i64(i32 %x) {
+; CHECK-LABEL: @fold_zext_or_disjoint_xor_i32_to_i64(
+; CHECK-NEXT: [[X:%.*]] = or disjoint i32 [[X1:%.*]], 1
+; CHECK-NEXT: [[Z:%.*]] = zext i32 [[X]] to i64
+; CHECK-NEXT: [[R:%.*]] = xor i64 [[Z]], 2611923443488327891
+; CHECK-NEXT: ret i64 [[R]]
+;
+ %or = or disjoint i32 %x, 1
+ %z = zext i32 %or to i64
+ %r = xor i64 %z, 2611923443488327891
+ ret i64 %r
+}
+
+define i64 @fold_zext_or_disjoint_xor_nneg(i32 %x) {
+; CHECK-LABEL: @fold_zext_or_disjoint_xor_nneg(
+; CHECK-NEXT: [[OR:%.*]] = or disjoint i32 [[X:%.*]], 16842752
+; CHECK-NEXT: [[Z:%.*]] = zext nneg i32 [[OR]] to i64
+; CHECK-NEXT: [[R:%.*]] = xor i64 [[Z]], 7640891576956012808
+; CHECK-NEXT: ret i64 [[R]]
+;
+ %or = or disjoint i32 %x, 16842752
+ %z = zext nneg i32 %or to i64
+ %r = xor i64 %z, 7640891576956012808
+ ret i64 %r
+}
+
+define i32 @no_fold_zext_plain_or_xor(i8 %x) {
+; CHECK-LABEL: @no_fold_zext_plain_or_xor(
+; CHECK-NEXT: [[OR:%.*]] = or i8 [[X:%.*]], 2
+; CHECK-NEXT: [[Z:%.*]] = zext i8 [[OR]] to i32
+; CHECK-NEXT: [[R:%.*]] = xor i32 [[Z]], 257
+; CHECK-NEXT: ret i32 [[R]]
+;
+ %or = or i8 %x, 2
+ %z = zext i8 %or to i32
+ %r = xor i32 %z, 257
+ ret i32 %r
+}
+
+define i32 @no_fold_zext_multi_use(i8 %x) {
+; CHECK-LABEL: @no_fold_zext_multi_use(
+; CHECK-NEXT: [[OR:%.*]] = or disjoint i8 [[X:%.*]], 2
+; CHECK-NEXT: [[Z:%.*]] = zext i8 [[OR]] to i32
+; CHECK-NEXT: [[R:%.*]] = xor i32 [[Z]], 257
+; CHECK-NEXT: [[USE:%.*]] = add nuw nsw i32 [[Z]], 1
+; CHECK-NEXT: [[RESULT:%.*]] = add nuw nsw i32 [[R]], [[USE]]
+; CHECK-NEXT: ret i32 [[RESULT]]
+;
+ %or = or disjoint i8 %x, 2
+ %z = zext i8 %or to i32
+ %r = xor i32 %z, 257
+ %use = add i32 %z, 1
+ %result = add i32 %r, %use
+ ret i32 %result
+}
+
+define i32 @no_fold_inner_or_multi_use(i8 %x) {
+; CHECK-LABEL: @no_fold_inner_or_multi_use(
+; CHECK-NEXT: [[OR:%.*]] = or disjoint i8 [[X:%.*]], 2
+; CHECK-NEXT: [[Z:%.*]] = zext i8 [[OR]] to i32
+; CHECK-NEXT: [[R:%.*]] = xor i32 [[Z]], 257
+; CHECK-NEXT: [[EXTRA:%.*]] = zext i8 [[OR]] to i32
+; CHECK-NEXT: [[RESULT:%.*]] = add nuw nsw i32 [[R]], [[EXTRA]]
+; CHECK-NEXT: ret i32 [[RESULT]]
+;
+ %or = or disjoint i8 %x, 2
+ %z = zext i8 %or to i32
+ %r = xor i32 %z, 257
+ %extra = zext i8 %or to i32
+ %result = add i32 %r, %extra
+ ret i32 %result
+}
>From 56fae9cc914db9aac2887b1a5c9770a464a05a00 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=EC=A1=B0=EC=98=81=EC=A7=84=28Joyoungjin=29?=
<slugger613 at naver.com>
Date: Sat, 8 Aug 2026 21:47:37 +0900
Subject: [PATCH 2/2] [InstCombine] Fold xor(zext(or disjoint X, C1), C2)
Fold a constant from a disjoint or into the outer xor after zero-extension.
This removes the intermediate or while preserving the widened xor.
Proof: https://alive2.llvm.org/ce/z/zsnvLo
Fixes #214652
---
.../InstCombine/InstCombineAndOrXor.cpp | 21 +++++++++++++++++++
llvm/test/Transforms/InstCombine/xor.ll | 15 ++++++-------
2 files changed, 27 insertions(+), 9 deletions(-)
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
index b6f4a55c07e8a..9cb6def2e6d8c 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
@@ -1775,6 +1775,27 @@ static Instruction *foldLogicCastConstant(BinaryOperator &Logic, CastInst *Cast,
Type *DestTy = Logic.getType();
Type *SrcTy = Cast->getSrcTy();
+ // xor (zext (or disjoint X, NarrowC)), WideC
+ // -> xor (zext X), WideC ^ zext(NarrowC)
+ if (LogicOpc == Instruction::Xor && SrcTy->isIntegerTy() &&
+ DestTy->isIntegerTy()) {
+ Value *X;
+ ConstantInt *NarrowC, *WideC;
+
+ if (match(C, m_ConstantInt(WideC)) &&
+ match(Cast,
+ m_OneUse(m_ZExt(m_OneUse(
+ m_c_DisjointOr(m_Value(X), m_ConstantInt(NarrowC))))))) {
+ APInt NewC =
+ WideC->getValue() ^
+ NarrowC->getValue().zext(WideC->getBitWidth());
+
+ Value *NewZExt = IC.Builder.CreateZExt(X, DestTy);
+ return BinaryOperator::CreateXor(NewZExt,
+ ConstantInt::get(DestTy, NewC));
+ }
+ }
+
// Move the logic operation ahead of a zext or sext if the constant is
// unchanged in the smaller source type. Performing the logic in a smaller
// type may provide more information to later folds, and the smaller logic
diff --git a/llvm/test/Transforms/InstCombine/xor.ll b/llvm/test/Transforms/InstCombine/xor.ll
index 2d860f1727c8e..17cfe2791fda1 100644
--- a/llvm/test/Transforms/InstCombine/xor.ll
+++ b/llvm/test/Transforms/InstCombine/xor.ll
@@ -1670,9 +1670,8 @@ entry:
define i32 @fold_zext_or_disjoint_xor_i8_to_i32(i8 %input) {
; CHECK-LABEL: @fold_zext_or_disjoint_xor_i8_to_i32(
-; CHECK-NEXT: [[INPUT:%.*]] = or disjoint i8 [[INPUT1:%.*]], 10
-; CHECK-NEXT: [[Z:%.*]] = zext i8 [[INPUT]] to i32
-; CHECK-NEXT: [[R:%.*]] = xor i32 [[Z]], 257
+; CHECK-NEXT: [[Z:%.*]] = zext i8 [[INPUT:%.*]] to i32
+; CHECK-NEXT: [[R:%.*]] = xor i32 [[Z]], 267
; CHECK-NEXT: ret i32 [[R]]
;
%or = or disjoint i8 %input, 10
@@ -1683,9 +1682,8 @@ define i32 @fold_zext_or_disjoint_xor_i8_to_i32(i8 %input) {
define i64 @fold_zext_or_disjoint_xor_i32_to_i64(i32 %x) {
; CHECK-LABEL: @fold_zext_or_disjoint_xor_i32_to_i64(
-; CHECK-NEXT: [[X:%.*]] = or disjoint i32 [[X1:%.*]], 1
-; CHECK-NEXT: [[Z:%.*]] = zext i32 [[X]] to i64
-; CHECK-NEXT: [[R:%.*]] = xor i64 [[Z]], 2611923443488327891
+; CHECK-NEXT: [[Z:%.*]] = zext i32 [[X:%.*]] to i64
+; CHECK-NEXT: [[R:%.*]] = xor i64 [[Z]], 2611923443488327890
; CHECK-NEXT: ret i64 [[R]]
;
%or = or disjoint i32 %x, 1
@@ -1696,9 +1694,8 @@ define i64 @fold_zext_or_disjoint_xor_i32_to_i64(i32 %x) {
define i64 @fold_zext_or_disjoint_xor_nneg(i32 %x) {
; CHECK-LABEL: @fold_zext_or_disjoint_xor_nneg(
-; CHECK-NEXT: [[OR:%.*]] = or disjoint i32 [[X:%.*]], 16842752
-; CHECK-NEXT: [[Z:%.*]] = zext nneg i32 [[OR]] to i64
-; CHECK-NEXT: [[R:%.*]] = xor i64 [[Z]], 7640891576956012808
+; CHECK-NEXT: [[TMP1:%.*]] = zext i32 [[X:%.*]] to i64
+; CHECK-NEXT: [[R:%.*]] = xor i64 [[TMP1]], 7640891576939301128
; CHECK-NEXT: ret i64 [[R]]
;
%or = or disjoint i32 %x, 16842752
More information about the llvm-commits
mailing list