[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