[llvm] [ValueTracking] Handle assume(!(trunc X)) in computeKnownBitsFromContext (PR #219854)

Rohan Jha via llvm-commits llvm-commits at lists.llvm.org
Sun Sep 13 12:28:25 PDT 2026


https://github.com/rohan11235813 updated https://github.com/llvm/llvm-project/pull/219854

>From 1c9624fac2c8c5e7311391705826b6af64ed3058 Mon Sep 17 00:00:00 2001
From: rohan11235813 <rohanj at quadric.io>
Date: Sat, 29 Aug 2026 14:46:12 -0700
Subject: [PATCH] [ValueTracking] Handle assume(!(trunc X)) in
 computeKnownBitsFromContext

---
 llvm/lib/Analysis/ValueTracking.cpp          | 14 +++++
 llvm/test/Transforms/InstCombine/assume.ll   | 63 ++++++++++++++++++++
 llvm/test/Transforms/InstSimplify/compare.ll |  3 +-
 3 files changed, 78 insertions(+), 2 deletions(-)

diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index ac49dbc8d76d3a..c18184cdb41f88 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -1145,6 +1145,20 @@ void llvm::computeKnownBitsFromContext(const Value *V, KnownBits &Known,
       Known.One.setBit(0);
       return;
     }
+    // assume(!(trunc V)) -> the low bit of V is zero.
+    Value *NotOp;
+    if (match(Arg, m_Not(m_Value(NotOp, m_Trunc(m_Specific(V))))) &&
+        (Q.AllowEphemerals ||
+         (Q.CxtI != Arg && Q.CxtI != NotOp && Q.CxtI != V)) &&
+        isValidAssumeForContext(I, Q.allowEphemerals(true))) {
+      auto *NotTrunc = cast<TruncInst>(NotOp);
+      if (NotTrunc->hasNoUnsignedWrap()) {
+        Known = KnownBits::makeConstant(APInt(BitWidth, 0));
+        return;
+      }
+      Known.Zero.setBit(0);
+      return;
+    }
 
     // The remaining tests are all recursive, so bail out if we hit the limit.
     if (Depth == MaxAnalysisRecursionDepth)
diff --git a/llvm/test/Transforms/InstCombine/assume.ll b/llvm/test/Transforms/InstCombine/assume.ll
index ff59947939a60e..7cb76176886875 100644
--- a/llvm/test/Transforms/InstCombine/assume.ll
+++ b/llvm/test/Transforms/InstCombine/assume.ll
@@ -1332,6 +1332,69 @@ define i1 @neg_assume_trunc_eq_one(i8 %x) {
   ret i1 %q
 }
 
+define i1 @assume_not_trunc_nuw_eq_zero(i8 %x) {
+; CHECK-LABEL: @assume_not_trunc_nuw_eq_zero(
+; CHECK-NEXT:    [[A:%.*]] = trunc nuw i8 [[X:%.*]] to i1
+; CHECK-NEXT:    [[N:%.*]] = xor i1 [[A]], true
+; CHECK-NEXT:    call void @llvm.assume(i1 [[N]])
+; CHECK-NEXT:    ret i1 true
+;
+  %a = trunc nuw i8 %x to i1
+  %n = xor i1 %a, true
+  call void @llvm.assume(i1 %n)
+  %q = icmp eq i8 %x, 0
+  ret i1 %q
+}
+
+define i1 @neg_assume_not_trunc_eq_zero(i8 %x) {
+; CHECK-LABEL: @neg_assume_not_trunc_eq_zero(
+; CHECK-NEXT:    [[A:%.*]] = trunc i8 [[X:%.*]] to i1
+; CHECK-NEXT:    [[N:%.*]] = xor i1 [[A]], true
+; CHECK-NEXT:    call void @llvm.assume(i1 [[N]])
+; CHECK-NEXT:    [[Q:%.*]] = icmp eq i8 [[X]], 0
+; CHECK-NEXT:    ret i1 [[Q]]
+;
+  %a = trunc i8 %x to i1
+  %n = xor i1 %a, true
+  call void @llvm.assume(i1 %n)
+  %q = icmp eq i8 %x, 0
+  ret i1 %q
+}
+
+define i8 @assume_not_trunc_low_bit(i8 %x) {
+; CHECK-LABEL: @assume_not_trunc_low_bit(
+; CHECK-NEXT:    [[A:%.*]] = trunc i8 [[X:%.*]] to i1
+; CHECK-NEXT:    [[N:%.*]] = xor i1 [[A]], true
+; CHECK-NEXT:    call void @llvm.assume(i1 [[N]])
+; CHECK-NEXT:    ret i8 0
+;
+  %a = trunc i8 %x to i1
+  %n = xor i1 %a, true
+  call void @llvm.assume(i1 %n)
+  %q = and i8 %x, 1
+  ret i8 %q
+}
+
+; The condition has a use other than the assume. The fact is still available to
+; %q, which is not one of the assumption's ephemeral values, and the assume
+; itself is preserved -- simplifying %a or %n with it would make the assumption
+; vacuous and delete it.
+define i8 @assume_not_trunc_multi_use(i8 %x) {
+; CHECK-LABEL: @assume_not_trunc_multi_use(
+; CHECK-NEXT:    [[A:%.*]] = trunc i8 [[X:%.*]] to i1
+; CHECK-NEXT:    [[N:%.*]] = xor i1 [[A]], true
+; CHECK-NEXT:    call void @llvm.assume(i1 [[N]])
+; CHECK-NEXT:    call void @use(i1 [[N]])
+; CHECK-NEXT:    ret i8 0
+;
+  %a = trunc i8 %x to i1
+  %n = xor i1 %a, true
+  call void @llvm.assume(i1 %n)
+  call void @use(i1 %n)
+  %q = and i8 %x, 1
+  ret i8 %q
+}
+
 define void @assume_dereferenceable_0(ptr %ptr) {
 ; CHECK-LABEL: @assume_dereferenceable_0(
 ; CHECK-NEXT:    ret void
diff --git a/llvm/test/Transforms/InstSimplify/compare.ll b/llvm/test/Transforms/InstSimplify/compare.ll
index 18768a9d24e311..5b037e51a119a0 100644
--- a/llvm/test/Transforms/InstSimplify/compare.ll
+++ b/llvm/test/Transforms/InstSimplify/compare.ll
@@ -3545,8 +3545,7 @@ define i1 @icmp_eq_false_by_trunc(i8 %x) {
 ; CHECK-NEXT:    [[TRUNC:%.*]] = trunc i8 [[X:%.*]] to i1
 ; CHECK-NEXT:    [[NOT:%.*]] = xor i1 [[TRUNC]], true
 ; CHECK-NEXT:    call void @llvm.assume(i1 [[NOT]])
-; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i8 [[X]], 1
-; CHECK-NEXT:    ret i1 [[CMP]]
+; CHECK-NEXT:    ret i1 false
 ;
   %trunc = trunc i8 %x to i1
   %not = xor i1 %trunc, true



More information about the llvm-commits mailing list