[llvm] [InstCombine] Fold xor(zext(or disjoint X, C1), C2) (PR #214970)

via llvm-commits llvm-commits at lists.llvm.org
Sat Aug 8 07:45:01 PDT 2026


=?utf-8?b?7KGw7JiB7KeEKEpveW91bmdqaW4p?Message-ID:
In-Reply-To: <llvm.org/llvm/llvm-project/pull/214970 at github.com>


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-transforms

Author: 조영진(Joyoungjin) (hunterhhunter)

<details>
<summary>Changes</summary>

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


---
Full diff: https://github.com/llvm/llvm-project/pull/214970.diff


2 Files Affected:

- (modified) llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp (+21) 
- (modified) llvm/test/Transforms/InstCombine/xor.ll (+86) 


``````````diff
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 3abaf74285cc0..17cfe2791fda1 100644
--- a/llvm/test/Transforms/InstCombine/xor.ll
+++ b/llvm/test/Transforms/InstCombine/xor.ll
@@ -1664,3 +1664,89 @@ 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:    [[Z:%.*]] = zext i8 [[INPUT:%.*]] to i32
+; CHECK-NEXT:    [[R:%.*]] = xor i32 [[Z]], 267
+; 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:    [[Z:%.*]] = zext i32 [[X:%.*]] to i64
+; CHECK-NEXT:    [[R:%.*]] = xor i64 [[Z]], 2611923443488327890
+; 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:    [[TMP1:%.*]] = zext i32 [[X:%.*]] to i64
+; CHECK-NEXT:    [[R:%.*]] = xor i64 [[TMP1]], 7640891576939301128
+; 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
+}

``````````

</details>


https://github.com/llvm/llvm-project/pull/214970


More information about the llvm-commits mailing list