[llvm] [ValueTracking] Strip zero tests through bitop-or self (PR #207938)
Jaeuk Lee via llvm-commits
llvm-commits at lists.llvm.org
Tue Jul 7 02:04:10 PDT 2026
https://github.com/skku970412 created https://github.com/llvm/llvm-project/pull/207938
## Summary
Extend `stripNullTest` so InstCombine can simplify zero tests of:
```llvm
or (bitop X), X
```
when `bitop` is a unary integer intrinsic that is zero iff its operand is zero.
This turns:
```llvm
icmp eq/ne (or (ctpop X), X), 0
```
into:
```llvm
icmp eq/ne X, 0
```
The initial scope is limited to `llvm.ctpop`, `llvm.bitreverse`, and
`llvm.bswap`, all with the intrinsic operand exactly matching the other `or`
operand. The commuted `or X, bitop(X)` spelling is handled too.
Fixes #207926.
## Tests
Added InstCombine coverage for:
- issue-style `ctpop(X) | X` in an `icmp ne 0` bool context;
- `icmp eq 0`;
- commuted `or`;
- vector `ctpop`;
- `bitreverse` and `bswap`;
- negative case where the intrinsic operand differs from the other `or`
operand.
Local verification:
```bash
ninja -C ../build -j20 opt
llvm/utils/update_test_checks.py --opt-binary ../build/bin/opt llvm/test/Transforms/InstCombine/icmp.ll
../build/bin/llvm-lit -sv llvm/test/Transforms/InstCombine/icmp.ll
../build/bin/llvm-lit -sv llvm/test/Transforms/InstCombine
../build/bin/llvm-lit -sv llvm/test/Analysis
../build/bin/opt -passes=instcombine -S /tmp/pr207926.ll
git diff --check
```
## AI Tool Disclosure
Assisted-by: OpenAI Codex
Codex was used for code navigation, testcase exploration, implementation
drafting, and local verification command execution. I manually reviewed the
implementation, tests, and generated code before submission.
>From 67ec2bb1cc5ce1bae4e2d66ead2d1ff029f0dc8e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=EC=9D=B4=EC=9E=AC=EC=9A=B1?=
<126692701+skku970412 at users.noreply.github.com>
Date: Tue, 7 Jul 2026 18:03:29 +0900
Subject: [PATCH] [ValueTracking] Strip zero tests through bitop-or self
---
llvm/lib/Analysis/ValueTracking.cpp | 25 +++++++
llvm/test/Transforms/InstCombine/icmp.ll | 89 ++++++++++++++++++++++++
2 files changed, 114 insertions(+)
diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index 0271a59fc4450..18eb1d1b16440 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -10742,6 +10742,22 @@ void llvm::findValuesAffectedByCondition(
}
const Value *llvm::stripNullTest(const Value *V) {
+ auto IsZeroIffZeroIntrinsicOf = [](const Value *V,
+ const Value *X) -> bool {
+ const auto *II = dyn_cast<IntrinsicInst>(V);
+ if (!II || II->arg_size() != 1 || II->getArgOperand(0) != X)
+ return false;
+
+ switch (II->getIntrinsicID()) {
+ case Intrinsic::bswap:
+ case Intrinsic::bitreverse:
+ case Intrinsic::ctpop:
+ return true;
+ default:
+ return false;
+ }
+ };
+
// (X >> C) or/add (X & mask(C) != 0)
if (const auto *BO = dyn_cast<BinaryOperator>(V)) {
if (BO->getOpcode() == Instruction::Add ||
@@ -10756,6 +10772,15 @@ const Value *llvm::stripNullTest(const Value *V) {
C2->popcount() == C1->getZExtValue())
return X;
}
+
+ if (BO->getOpcode() == Instruction::Or) {
+ const Value *A = BO->getOperand(0);
+ const Value *B = BO->getOperand(1);
+ if (IsZeroIffZeroIntrinsicOf(A, B))
+ return B;
+ if (IsZeroIffZeroIntrinsicOf(B, A))
+ return A;
+ }
}
return nullptr;
}
diff --git a/llvm/test/Transforms/InstCombine/icmp.ll b/llvm/test/Transforms/InstCombine/icmp.ll
index 61ae46ba08d2d..f932c2770ff3b 100644
--- a/llvm/test/Transforms/InstCombine/icmp.ll
+++ b/llvm/test/Transforms/InstCombine/icmp.ll
@@ -4560,6 +4560,95 @@ define <8 x i1> @bitreverse_vec_ne(<8 x i16> %x, <8 x i16> %y) {
ret <8 x i1> %cmp
}
+declare i64 @llvm.ctpop.i64(i64)
+declare <2 x i32> @llvm.ctpop.v2i32(<2 x i32>)
+
+define i1 @or_ctpop_x_ne_zero(i64 %x) {
+; CHECK-LABEL: define i1 @or_ctpop_x_ne_zero(
+; CHECK-SAME: i64 [[X:%.*]]) {
+; CHECK-NEXT: [[CMP:%.*]] = icmp ne i64 [[X]], 0
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %pop = call i64 @llvm.ctpop.i64(i64 %x)
+ %or = or i64 %pop, %x
+ %cmp = icmp ne i64 %or, 0
+ ret i1 %cmp
+}
+
+define i1 @or_ctpop_x_eq_zero(i64 %x) {
+; CHECK-LABEL: define i1 @or_ctpop_x_eq_zero(
+; CHECK-SAME: i64 [[X:%.*]]) {
+; CHECK-NEXT: [[CMP:%.*]] = icmp eq i64 [[X]], 0
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %pop = call i64 @llvm.ctpop.i64(i64 %x)
+ %or = or i64 %pop, %x
+ %cmp = icmp eq i64 %or, 0
+ ret i1 %cmp
+}
+
+define i1 @or_ctpop_x_ne_zero_commuted(i64 %x) {
+; CHECK-LABEL: define i1 @or_ctpop_x_ne_zero_commuted(
+; CHECK-SAME: i64 [[X:%.*]]) {
+; CHECK-NEXT: [[CMP:%.*]] = icmp ne i64 [[X]], 0
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %pop = call i64 @llvm.ctpop.i64(i64 %x)
+ %or = or i64 %x, %pop
+ %cmp = icmp ne i64 %or, 0
+ ret i1 %cmp
+}
+
+define <2 x i1> @or_ctpop_x_ne_zero_vec(<2 x i32> %x) {
+; CHECK-LABEL: define <2 x i1> @or_ctpop_x_ne_zero_vec(
+; CHECK-SAME: <2 x i32> [[X:%.*]]) {
+; CHECK-NEXT: [[CMP:%.*]] = icmp ne <2 x i32> [[X]], zeroinitializer
+; CHECK-NEXT: ret <2 x i1> [[CMP]]
+;
+ %pop = call <2 x i32> @llvm.ctpop.v2i32(<2 x i32> %x)
+ %or = or <2 x i32> %pop, %x
+ %cmp = icmp ne <2 x i32> %or, zeroinitializer
+ ret <2 x i1> %cmp
+}
+
+define i1 @or_bitreverse_x_ne_zero(i64 %x) {
+; CHECK-LABEL: define i1 @or_bitreverse_x_ne_zero(
+; CHECK-SAME: i64 [[X:%.*]]) {
+; CHECK-NEXT: [[CMP:%.*]] = icmp ne i64 [[X]], 0
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %rev = call i64 @llvm.bitreverse.i64(i64 %x)
+ %or = or i64 %rev, %x
+ %cmp = icmp ne i64 %or, 0
+ ret i1 %cmp
+}
+
+define i1 @or_bswap_x_eq_zero(i32 %x) {
+; CHECK-LABEL: define i1 @or_bswap_x_eq_zero(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT: [[CMP:%.*]] = icmp eq i32 [[X]], 0
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %swap = call i32 @llvm.bswap.i32(i32 %x)
+ %or = or i32 %swap, %x
+ %cmp = icmp eq i32 %or, 0
+ ret i1 %cmp
+}
+
+define i1 @or_ctpop_y_x_ne_zero(i64 %x, i64 %y) {
+; CHECK-LABEL: define i1 @or_ctpop_y_x_ne_zero(
+; CHECK-SAME: i64 [[X:%.*]], i64 [[Y:%.*]]) {
+; CHECK-NEXT: [[POP:%.*]] = call range(i64 0, 65) i64 @llvm.ctpop.i64(i64 [[Y]])
+; CHECK-NEXT: [[OR:%.*]] = or i64 [[POP]], [[X]]
+; CHECK-NEXT: [[CMP:%.*]] = icmp ne i64 [[OR]], 0
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %pop = call i64 @llvm.ctpop.i64(i64 %y)
+ %or = or i64 %pop, %x
+ %cmp = icmp ne i64 %or, 0
+ ret i1 %cmp
+}
+
; These perform a comparison of a value known to be between 4 and 5 with a value between 5 and 7.
; They should all simplify to equality compares.
define i1 @knownbits1(i8 %a, i8 %b) {
More information about the llvm-commits
mailing list