[llvm] [InstCombine] Reassociate xor and disjoint or through zext (PR #214970)
Joyoungjin via llvm-commits
llvm-commits at lists.llvm.org
Wed Aug 12 06:41:54 PDT 2026
https://github.com/hunterhhunter updated https://github.com/llvm/llvm-project/pull/214970
>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 01/11] [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 02/11] [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
>From ec4c06fb27847b624136f188ed98161d4cb54362 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: Sun, 9 Aug 2026 11:14:36 +0900
Subject: [PATCH 03/11] [InstCombine] Address review comments
---
.../InstCombine/InstCombineAndOrXor.cpp | 33 +++++++------------
llvm/test/Transforms/InstCombine/xor.ll | 33 ++++++++++++-------
2 files changed, 33 insertions(+), 33 deletions(-)
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
index 9cb6def2e6d8c..59782e18f7f1b 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
@@ -1775,27 +1775,6 @@ 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
@@ -5467,6 +5446,18 @@ Instruction *InstCombinerImpl::visitXor(BinaryOperator &I) {
if (match(Op1, m_APInt(RHSC))) {
Value *X;
const APInt *C;
+
+ // xor (zext (or disjoint X, C)), RHSC
+ // -> xor (zext X), RHSC ^ zext(C)
+ if (match(Op0, m_OneUse(m_ZExt(m_OneUse(
+ m_DisjointOr(m_Value(X), m_APInt(C))))))) {
+ APInt NewC = *RHSC ^ C->zext(RHSC->getBitWidth());
+
+ Value *NewZExt = Builder.CreateZExt(X, Ty);
+ return BinaryOperator::CreateXor(
+ NewZExt, Constant::getIntegerValue(Ty, NewC));
+ }
+
// (C - X) ^ signmaskC --> (C + signmaskC) - X
if (RHSC->isSignMask() && match(Op0, m_Sub(m_APInt(C), m_Value(X))))
return BinaryOperator::CreateSub(ConstantInt::get(Ty, *C + *RHSC), X);
diff --git a/llvm/test/Transforms/InstCombine/xor.ll b/llvm/test/Transforms/InstCombine/xor.ll
index 17cfe2791fda1..511d29badc54a 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(
@@ -1704,6 +1705,18 @@ define i64 @fold_zext_or_disjoint_xor_nneg(i32 %x) {
ret i64 %r
}
+define <2 x i32> @fold_zext_or_disjoint_xor_splat_vector(<2 x i8> %x) {
+; CHECK-LABEL: @fold_zext_or_disjoint_xor_splat_vector(
+; CHECK-NEXT: [[TMP1:%.*]] = zext <2 x i8> [[X:%.*]] to <2 x i32>
+; CHECK-NEXT: [[R:%.*]] = xor <2 x i32> [[TMP1]], splat (i32 267)
+; CHECK-NEXT: ret <2 x i32> [[R]]
+;
+ %or = or disjoint <2 x i8> %x, <i8 10, i8 10>
+ %z = zext <2 x i8> %or to <2 x i32>
+ %r = xor <2 x i32> %z, <i32 257, i32 257>
+ ret <2 x i32> %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
@@ -1722,16 +1735,14 @@ define i32 @no_fold_zext_multi_use(i8 %x) {
; 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]]
+; CHECK-NEXT: call void @use_i32(i32 [[Z]])
+; CHECK-NEXT: ret i32 [[R]]
;
%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
+ call void @use_i32(i32 %z)
+ ret i32 %r
}
define i32 @no_fold_inner_or_multi_use(i8 %x) {
@@ -1739,14 +1750,12 @@ define i32 @no_fold_inner_or_multi_use(i8 %x) {
; 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]]
+; CHECK-NEXT: call void @use(i8 [[OR]])
+; CHECK-NEXT: ret i32 [[R]]
;
%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
+ call void @use(i8 %or)
+ ret i32 %r
}
>From 969ec8e2d9a5fc3a447ad3dcfc9839c8f8f7a15a 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: Sun, 9 Aug 2026 21:27:09 +0900
Subject: [PATCH 04/11] [InstCombine] Handle or disjoint in
simplifyAssocCastAssoc
---
.../InstCombine/InstCombineAndOrXor.cpp | 11 -----------
.../InstCombine/InstructionCombining.cpp | 19 ++++++++++++++++++-
llvm/test/Transforms/InstCombine/xor.ll | 2 +-
3 files changed, 19 insertions(+), 13 deletions(-)
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
index 59782e18f7f1b..31749910b0c5c 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
@@ -5447,17 +5447,6 @@ Instruction *InstCombinerImpl::visitXor(BinaryOperator &I) {
Value *X;
const APInt *C;
- // xor (zext (or disjoint X, C)), RHSC
- // -> xor (zext X), RHSC ^ zext(C)
- if (match(Op0, m_OneUse(m_ZExt(m_OneUse(
- m_DisjointOr(m_Value(X), m_APInt(C))))))) {
- APInt NewC = *RHSC ^ C->zext(RHSC->getBitWidth());
-
- Value *NewZExt = Builder.CreateZExt(X, Ty);
- return BinaryOperator::CreateXor(
- NewZExt, Constant::getIntegerValue(Ty, NewC));
- }
-
// (C - X) ^ signmaskC --> (C + signmaskC) - X
if (RHSC->isSignMask() && match(Op0, m_Sub(m_APInt(C), m_Value(X))))
return BinaryOperator::CreateSub(ConstantInt::get(Ty, *C + *RHSC), X);
diff --git a/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp b/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
index 1fd813fb856ad..bee896b2642fc 100644
--- a/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
@@ -432,7 +432,15 @@ static bool simplifyAssocCastAssoc(BinaryOperator *BinOp1,
auto AssocOpcode = BinOp1->getOpcode();
auto *BinOp2 = dyn_cast<BinaryOperator>(Cast->getOperand(0));
- if (!BinOp2 || !BinOp2->hasOneUse() || BinOp2->getOpcode() != AssocOpcode)
+ if (!BinOp2 || !BinOp2->hasOneUse())
+ return false;
+
+ // `or disjoint` is equivalent to xor.
+ bool IsDisjointOrAsXor =
+ AssocOpcode == Instruction::Xor &&
+ match(BinOp2, m_DisjointOr(m_Value(), m_Value()));
+
+ if (BinOp2->getOpcode() != AssocOpcode && !IsDisjointOrAsXor)
return false;
Constant *C1, *C2;
@@ -455,10 +463,19 @@ static bool simplifyAssocCastAssoc(BinaryOperator *BinOp1,
if (!FoldedC)
return false;
+ // If the original zext was nneg, it is safe to preserve nneg when
+ // removing an `or disjoint`: a non-negative (X | C) implies X is
+ // also non-negative.
+ bool PreserveNonNeg = IsDisjointOrAsXor && Cast->hasNonNeg();
+
IC.replaceOperand(*Cast, 0, BinOp2->getOperand(0));
IC.replaceOperand(*BinOp1, 1, FoldedC);
BinOp1->dropPoisonGeneratingFlags();
Cast->dropPoisonGeneratingFlags();
+
+ if (PreserveNonNeg)
+ Cast->setNonNeg();
+
return true;
}
diff --git a/llvm/test/Transforms/InstCombine/xor.ll b/llvm/test/Transforms/InstCombine/xor.ll
index 511d29badc54a..6839984c8c5cd 100644
--- a/llvm/test/Transforms/InstCombine/xor.ll
+++ b/llvm/test/Transforms/InstCombine/xor.ll
@@ -1695,7 +1695,7 @@ 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: [[TMP1:%.*]] = zext i32 [[X:%.*]] to i64
+; CHECK-NEXT: [[TMP1:%.*]] = zext nneg i32 [[X:%.*]] to i64
; CHECK-NEXT: [[R:%.*]] = xor i64 [[TMP1]], 7640891576939301128
; CHECK-NEXT: ret i64 [[R]]
;
>From 1aeeb7a8807e27f1bdb5ec336d33f5883978e3b2 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: Sun, 9 Aug 2026 21:30:54 +0900
Subject: [PATCH 05/11] [InstCombine] Remove unrelated whitespace
---
llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp | 1 -
1 file changed, 1 deletion(-)
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
index 31749910b0c5c..b6f4a55c07e8a 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
@@ -5446,7 +5446,6 @@ Instruction *InstCombinerImpl::visitXor(BinaryOperator &I) {
if (match(Op1, m_APInt(RHSC))) {
Value *X;
const APInt *C;
-
// (C - X) ^ signmaskC --> (C + signmaskC) - X
if (RHSC->isSignMask() && match(Op0, m_Sub(m_APInt(C), m_Value(X))))
return BinaryOperator::CreateSub(ConstantInt::get(Ty, *C + *RHSC), X);
>From 1c4f02cb04b79389b57ebe5553b1ad0d309525fc 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: Mon, 10 Aug 2026 19:35:58 +0900
Subject: [PATCH 06/11] [InstCombine] Precommit tests for disjoint or zext xor
fold
---
llvm/test/Transforms/InstCombine/or.ll | 71 ++++++++++++++++++++++++++
1 file changed, 71 insertions(+)
diff --git a/llvm/test/Transforms/InstCombine/or.ll b/llvm/test/Transforms/InstCombine/or.ll
index 4126d653d2ad1..8d8b5f3d9af60 100644
--- a/llvm/test/Transforms/InstCombine/or.ll
+++ b/llvm/test/Transforms/InstCombine/or.ll
@@ -4,6 +4,7 @@
target datalayout = "e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-f32:32:32-f64:32:64-v64:64:64-v128:128:128-a0:0:64-f80:128:128-n32:64"
declare void @use(i32)
+declare void @use_i8(i8)
; Should be eliminated
define i32 @test12(i32 %A) {
@@ -2386,3 +2387,73 @@ define i32 @signum_i32_or_wrong_ext(i32 %x) {
%r = or i32 %signbit, %sgt0ext
ret i32 %r
}
+
+; or disjoint(zext(xor X, C1), C2) -> xor(zext(X), C2 ^ zext(C1))
+define i32 @fold_disjoint_or_zext_xor(i8 %x) {
+; CHECK-LABEL: @fold_disjoint_or_zext_xor(
+; CHECK-NEXT: [[TMP1:%.*]] = and i8 [[X:%.*]], -2
+; CHECK-NEXT: [[TMP2:%.*]] = xor i8 [[TMP1]], 3
+; CHECK-NEXT: [[R:%.*]] = zext i8 [[TMP2]] to i32
+; CHECK-NEXT: ret i32 [[R]]
+;
+ %a = xor i8 %x, 2
+ %z = zext i8 %a to i32
+ %r = or disjoint i32 %z, 1
+ ret i32 %r
+}
+
+define <2 x i32> @fold_disjoint_or_zext_xor_splat(<2 x i8> %x) {
+; CHECK-LABEL: @fold_disjoint_or_zext_xor_splat(
+; CHECK-NEXT: [[TMP1:%.*]] = and <2 x i8> [[X:%.*]], splat (i8 -2)
+; CHECK-NEXT: [[TMP2:%.*]] = xor <2 x i8> [[TMP1]], splat (i8 3)
+; CHECK-NEXT: [[R:%.*]] = zext <2 x i8> [[TMP2]] to <2 x i32>
+; CHECK-NEXT: ret <2 x i32> [[R]]
+;
+ %a = xor <2 x i8> %x, splat (i8 2)
+ %z = zext <2 x i8> %a to <2 x i32>
+ %r = or disjoint <2 x i32> %z, splat (i32 1)
+ ret <2 x i32> %r
+}
+
+define i32 @no_fold_or_zext_xor(i8 %x) {
+; CHECK-LABEL: @no_fold_or_zext_xor(
+; CHECK-NEXT: [[TMP1:%.*]] = and i8 [[X:%.*]], -2
+; CHECK-NEXT: [[TMP2:%.*]] = xor i8 [[TMP1]], 3
+; CHECK-NEXT: [[R:%.*]] = zext i8 [[TMP2]] to i32
+; CHECK-NEXT: ret i32 [[R]]
+;
+ %a = xor i8 %x, 2
+ %z = zext i8 %a to i32
+ %r = or i32 %z, 1
+ ret i32 %r
+}
+
+define i32 @no_fold_disjoint_or_zext_xor_zext_multi_use(i8 %x) {
+; CHECK-LABEL: @no_fold_disjoint_or_zext_xor_zext_multi_use(
+; CHECK-NEXT: [[A:%.*]] = xor i8 [[X:%.*]], 2
+; CHECK-NEXT: [[Z:%.*]] = zext i8 [[A]] to i32
+; CHECK-NEXT: call void @use(i32 [[Z]])
+; CHECK-NEXT: [[R:%.*]] = or disjoint i32 [[Z]], 1
+; CHECK-NEXT: ret i32 [[R]]
+;
+ %a = xor i8 %x, 2
+ %z = zext i8 %a to i32
+ call void @use(i32 %z)
+ %r = or disjoint i32 %z, 1
+ ret i32 %r
+}
+
+define i32 @no_fold_disjoint_or_zext_xor_inner_multi_use(i8 %x) {
+; CHECK-LABEL: @no_fold_disjoint_or_zext_xor_inner_multi_use(
+; CHECK-NEXT: [[A:%.*]] = xor i8 [[X:%.*]], 2
+; CHECK-NEXT: call void @use_i8(i8 [[A]])
+; CHECK-NEXT: [[TMP1:%.*]] = or i8 [[A]], 1
+; CHECK-NEXT: [[R:%.*]] = zext i8 [[TMP1]] to i32
+; CHECK-NEXT: ret i32 [[R]]
+;
+ %a = xor i8 %x, 2
+ call void @use_i8(i8 %a)
+ %z = zext i8 %a to i32
+ %r = or disjoint i32 %z, 1
+ ret i32 %r
+}
>From ab364ee335298c5ae651b9d03b9be77a5482a33b 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: Mon, 10 Aug 2026 19:48:03 +0900
Subject: [PATCH 07/11] [InstCombine] Preserve nneg when reassociating or
through zext
---
.../Transforms/InstCombine/InstructionCombining.cpp | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp b/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
index bee896b2642fc..874cc3a1836ea 100644
--- a/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
@@ -436,9 +436,8 @@ static bool simplifyAssocCastAssoc(BinaryOperator *BinOp1,
return false;
// `or disjoint` is equivalent to xor.
- bool IsDisjointOrAsXor =
- AssocOpcode == Instruction::Xor &&
- match(BinOp2, m_DisjointOr(m_Value(), m_Value()));
+ bool IsDisjointOrAsXor = AssocOpcode == Instruction::Xor &&
+ match(BinOp2, m_DisjointOr(m_Value(), m_Value()));
if (BinOp2->getOpcode() != AssocOpcode && !IsDisjointOrAsXor)
return false;
@@ -464,9 +463,10 @@ static bool simplifyAssocCastAssoc(BinaryOperator *BinOp1,
return false;
// If the original zext was nneg, it is safe to preserve nneg when
- // removing an `or disjoint`: a non-negative (X | C) implies X is
- // also non-negative.
- bool PreserveNonNeg = IsDisjointOrAsXor && Cast->hasNonNeg();
+ // removing an `or`: a non-negative (X | C) implies X is also
+ // non-negative.
+ bool PreserveNonNeg =
+ Cast->hasNonNeg() && BinOp2->getOpcode() == Instruction::Or;
IC.replaceOperand(*Cast, 0, BinOp2->getOperand(0));
IC.replaceOperand(*BinOp1, 1, FoldedC);
>From b00a86e21bf572836c7e3660e3edcaa8c4fde72b 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: Mon, 10 Aug 2026 22:46:19 +0900
Subject: [PATCH 08/11] [InstCombine] Fold disjoint or with zext xor
---
.../InstCombine/InstCombineAndOrXor.cpp | 20 +++++++++++++++++++
llvm/test/Transforms/InstCombine/or.ll | 10 ++++------
2 files changed, 24 insertions(+), 6 deletions(-)
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
index b6f4a55c07e8a..e560e530971fa 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
@@ -13,6 +13,7 @@
#include "InstCombineInternal.h"
#include "llvm/ADT/SmallBitVector.h"
#include "llvm/Analysis/CmpInstAnalysis.h"
+#include "llvm/Analysis/ConstantFolding.h"
#include "llvm/Analysis/FloatingPointPredicateUtils.h"
#include "llvm/Analysis/InstructionSimplify.h"
#include "llvm/IR/ConstantRange.h"
@@ -3963,6 +3964,25 @@ Value *InstCombinerImpl::foldDisjointOr(Value *LHS, Value *RHS) {
if (Value *Res = foldIntegerRepackThroughZExt(LHS, RHS, Builder))
return Res;
+ Value *X;
+ Constant *C1, *C2;
+ if (match(LHS, m_OneUse(m_ZExt(m_OneUse(
+ m_Xor(m_Value(X), m_Constant(C1)))))) &&
+ match(RHS, m_Constant(C2))) {
+ Type *DestTy = C2->getType();
+ Constant *ExtC1 =
+ ConstantFoldCastOperand(Instruction::ZExt, C1, DestTy, DL);
+ if (!ExtC1)
+ return nullptr;
+
+ Constant *FoldedC = ConstantFoldBinaryOpOperands(
+ Instruction::Xor, C2, ExtC1, DL);
+ if (!FoldedC)
+ return nullptr;
+
+ return Builder.CreateXor(Builder.CreateZExt(X, DestTy), FoldedC);
+ }
+
return nullptr;
}
diff --git a/llvm/test/Transforms/InstCombine/or.ll b/llvm/test/Transforms/InstCombine/or.ll
index 8d8b5f3d9af60..49364214574aa 100644
--- a/llvm/test/Transforms/InstCombine/or.ll
+++ b/llvm/test/Transforms/InstCombine/or.ll
@@ -2391,9 +2391,8 @@ define i32 @signum_i32_or_wrong_ext(i32 %x) {
; or disjoint(zext(xor X, C1), C2) -> xor(zext(X), C2 ^ zext(C1))
define i32 @fold_disjoint_or_zext_xor(i8 %x) {
; CHECK-LABEL: @fold_disjoint_or_zext_xor(
-; CHECK-NEXT: [[TMP1:%.*]] = and i8 [[X:%.*]], -2
-; CHECK-NEXT: [[TMP2:%.*]] = xor i8 [[TMP1]], 3
-; CHECK-NEXT: [[R:%.*]] = zext i8 [[TMP2]] to i32
+; CHECK-NEXT: [[TMP1:%.*]] = xor i8 [[X:%.*]], 3
+; CHECK-NEXT: [[R:%.*]] = zext i8 [[TMP1]] to i32
; CHECK-NEXT: ret i32 [[R]]
;
%a = xor i8 %x, 2
@@ -2404,9 +2403,8 @@ define i32 @fold_disjoint_or_zext_xor(i8 %x) {
define <2 x i32> @fold_disjoint_or_zext_xor_splat(<2 x i8> %x) {
; CHECK-LABEL: @fold_disjoint_or_zext_xor_splat(
-; CHECK-NEXT: [[TMP1:%.*]] = and <2 x i8> [[X:%.*]], splat (i8 -2)
-; CHECK-NEXT: [[TMP2:%.*]] = xor <2 x i8> [[TMP1]], splat (i8 3)
-; CHECK-NEXT: [[R:%.*]] = zext <2 x i8> [[TMP2]] to <2 x i32>
+; CHECK-NEXT: [[TMP1:%.*]] = xor <2 x i8> [[X:%.*]], splat (i8 3)
+; CHECK-NEXT: [[R:%.*]] = zext <2 x i8> [[TMP1]] to <2 x i32>
; CHECK-NEXT: ret <2 x i32> [[R]]
;
%a = xor <2 x i8> %x, splat (i8 2)
>From 914adc72324077a691f79ae57f9c128725a5997c 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: Mon, 10 Aug 2026 22:57:09 +0900
Subject: [PATCH 09/11] [InstCombine] Document disjoint-or xor fold
---
llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp | 3 +++
1 file changed, 3 insertions(+)
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
index e560e530971fa..ba37384ea9801 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
@@ -3964,6 +3964,9 @@ Value *InstCombinerImpl::foldDisjointOr(Value *LHS, Value *RHS) {
if (Value *Res = foldIntegerRepackThroughZExt(LHS, RHS, Builder))
return Res;
+ // For defined inputs, `or disjoint` is equivalent to xor:
+ // or disjoint (zext (xor X, C1)), C2
+ // -> xor (zext X), C2 ^ zext(C1)
Value *X;
Constant *C1, *C2;
if (match(LHS, m_OneUse(m_ZExt(m_OneUse(
>From 37ba12f3307ba9f1aabbb363664f787306eddc7e 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: Mon, 10 Aug 2026 23:35:28 +0900
Subject: [PATCH 10/11] [InstCombine] Generalize disjoint-or cast reassociation
---
.../InstCombine/InstCombineAndOrXor.cpp | 23 --------------
.../InstCombine/InstructionCombining.cpp | 30 +++++++++++--------
2 files changed, 17 insertions(+), 36 deletions(-)
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
index ba37384ea9801..b6f4a55c07e8a 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
@@ -13,7 +13,6 @@
#include "InstCombineInternal.h"
#include "llvm/ADT/SmallBitVector.h"
#include "llvm/Analysis/CmpInstAnalysis.h"
-#include "llvm/Analysis/ConstantFolding.h"
#include "llvm/Analysis/FloatingPointPredicateUtils.h"
#include "llvm/Analysis/InstructionSimplify.h"
#include "llvm/IR/ConstantRange.h"
@@ -3964,28 +3963,6 @@ Value *InstCombinerImpl::foldDisjointOr(Value *LHS, Value *RHS) {
if (Value *Res = foldIntegerRepackThroughZExt(LHS, RHS, Builder))
return Res;
- // For defined inputs, `or disjoint` is equivalent to xor:
- // or disjoint (zext (xor X, C1)), C2
- // -> xor (zext X), C2 ^ zext(C1)
- Value *X;
- Constant *C1, *C2;
- if (match(LHS, m_OneUse(m_ZExt(m_OneUse(
- m_Xor(m_Value(X), m_Constant(C1)))))) &&
- match(RHS, m_Constant(C2))) {
- Type *DestTy = C2->getType();
- Constant *ExtC1 =
- ConstantFoldCastOperand(Instruction::ZExt, C1, DestTy, DL);
- if (!ExtC1)
- return nullptr;
-
- Constant *FoldedC = ConstantFoldBinaryOpOperands(
- Instruction::Xor, C2, ExtC1, DL);
- if (!FoldedC)
- return nullptr;
-
- return Builder.CreateXor(Builder.CreateZExt(X, DestTy), FoldedC);
- }
-
return nullptr;
}
diff --git a/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp b/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
index 874cc3a1836ea..a9919a4e57dcf 100644
--- a/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
@@ -435,12 +435,19 @@ static bool simplifyAssocCastAssoc(BinaryOperator *BinOp1,
if (!BinOp2 || !BinOp2->hasOneUse())
return false;
- // `or disjoint` is equivalent to xor.
- bool IsDisjointOrAsXor = AssocOpcode == Instruction::Xor &&
- match(BinOp2, m_DisjointOr(m_Value(), m_Value()));
+ if (BinOp2->getOpcode() != AssocOpcode) {
+ // For defined inputs, `or disjoint` is equivalent to xor.
+ bool IsMixedXorDisjointOr =
+ (AssocOpcode == Instruction::Xor &&
+ match(BinOp2, m_DisjointOr(m_Value(), m_Value()))) ||
+ (BinOp2->getOpcode() == Instruction::Xor &&
+ match(BinOp1, m_DisjointOr(m_Value(), m_Value())));
+
+ if (!IsMixedXorDisjointOr)
+ return false;
- if (BinOp2->getOpcode() != AssocOpcode && !IsDisjointOrAsXor)
- return false;
+ AssocOpcode = Instruction::Xor;
+ }
Constant *C1, *C2;
if (!match(BinOp1->getOperand(1), m_Constant(C1)) ||
@@ -462,20 +469,18 @@ static bool simplifyAssocCastAssoc(BinaryOperator *BinOp1,
if (!FoldedC)
return false;
- // If the original zext was nneg, it is safe to preserve nneg when
- // removing an `or`: a non-negative (X | C) implies X is also
- // non-negative.
+ // A non-negative (X | C) implies that X is also non-negative.
bool PreserveNonNeg =
- Cast->hasNonNeg() && BinOp2->getOpcode() == Instruction::Or;
+ Cast->hasNonNeg() && BinOp2->getOpcode() == Instruction::Or;
IC.replaceOperand(*Cast, 0, BinOp2->getOperand(0));
- IC.replaceOperand(*BinOp1, 1, FoldedC);
- BinOp1->dropPoisonGeneratingFlags();
Cast->dropPoisonGeneratingFlags();
if (PreserveNonNeg)
Cast->setNonNeg();
+ Value *NewBinOp = IC.Builder.CreateBinOp(AssocOpcode, Cast, FoldedC);
+ IC.replaceInstUsesWith(*BinOp1, NewBinOp);
return true;
}
@@ -598,9 +603,8 @@ bool InstCombinerImpl::SimplifyAssociativeOrCommutative(BinaryOperator &I) {
if (I.isAssociative() && I.isCommutative()) {
if (simplifyAssocCastAssoc(&I, *this)) {
- Changed = true;
++NumReassoc;
- continue;
+ return true;
}
// Transform: "(A op B) op C" ==> "(C op A) op B" if "C op A" simplifies.
>From 650a440ae377553ba804d09a89635f5293a8c9bd 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: Wed, 12 Aug 2026 22:38:19 +0900
Subject: [PATCH 11/11] [InstCombine] Add nneg test for or reassociation
---
llvm/test/Transforms/InstCombine/or.ll | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/llvm/test/Transforms/InstCombine/or.ll b/llvm/test/Transforms/InstCombine/or.ll
index 49364214574aa..6a9e90a1eda5a 100644
--- a/llvm/test/Transforms/InstCombine/or.ll
+++ b/llvm/test/Transforms/InstCombine/or.ll
@@ -2413,6 +2413,19 @@ define <2 x i32> @fold_disjoint_or_zext_xor_splat(<2 x i8> %x) {
ret <2 x i32> %r
}
+; or(zext nneg(or X, C1), C2) -> zext nneg(or X, C1 | trunc(C2))
+define i32 @fold_or_zext_or_nneg(i8 %x) {
+; CHECK-LABEL: @fold_or_zext_or_nneg(
+; CHECK-NEXT: [[TMP1:%.*]] = or i8 [[X:%.*]], 3
+; CHECK-NEXT: [[R:%.*]] = zext nneg i8 [[TMP1]] to i32
+; CHECK-NEXT: ret i32 [[R]]
+;
+ %a = or i8 %x, 2
+ %z = zext nneg i8 %a to i32
+ %r = or i32 %z, 1
+ ret i32 %r
+}
+
define i32 @no_fold_or_zext_xor(i8 %x) {
; CHECK-LABEL: @no_fold_or_zext_xor(
; CHECK-NEXT: [[TMP1:%.*]] = and i8 [[X:%.*]], -2
More information about the llvm-commits
mailing list