[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