[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