Index: llvm/include/llvm/ADT/APInt.h
===================================================================
--- llvm/include/llvm/ADT/APInt.h
+++ llvm/include/llvm/ADT/APInt.h
@@ -1737,6 +1737,9 @@
     return *this;
   }
 
+  /// Get the leading zeroes of a value.
+  APInt ctlz() const { return APInt(BitWidth, this->countLeadingZeros()); }
+
   /// \returns the multiplicative inverse for a given modulo.
   APInt multiplicativeInverse(const APInt &modulo) const;
 
Index: llvm/include/llvm/IR/ConstantRange.h
===================================================================
--- llvm/include/llvm/IR/ConstantRange.h
+++ llvm/include/llvm/IR/ConstantRange.h
@@ -526,6 +526,11 @@
   /// \p IntMinIsPoison is false.
   ConstantRange abs(bool IntMinIsPoison = false) const;
 
+  /// Calculate absolute value range. If the original range contains zero, then
+  /// the resulting range will be bounded if and only if \p ZeroIsPoison is
+  /// false.
+  ConstantRange ctlz(bool ZeroIsPoison = false) const;
+
   /// Represents whether an operation on the given constant range is known to
   /// always or never overflow.
   enum class OverflowResult {
Index: llvm/lib/IR/ConstantRange.cpp
===================================================================
--- llvm/lib/IR/ConstantRange.cpp
+++ llvm/lib/IR/ConstantRange.cpp
@@ -945,6 +945,7 @@
   case Intrinsic::smin:
   case Intrinsic::smax:
   case Intrinsic::abs:
+  case Intrinsic::ctlz:
     return true;
   default:
     return false;
@@ -976,6 +977,12 @@
     assert(IntMinIsPoison->getBitWidth() == 1 && "Must be boolean");
     return Ops[0].abs(IntMinIsPoison->getBoolValue());
   }
+  case Intrinsic::ctlz: {
+    const APInt *ZeroIsPoison = Ops[1].getSingleElement();
+    assert(ZeroIsPoison && "Must be known (immarg)");
+    assert(ZeroIsPoison->getBitWidth() == 1 && "Must be boolean");
+    return Ops[0].ctlz(ZeroIsPoison->getBoolValue());
+  }
   default:
     assert(!isIntrinsicSupported(IntrinsicID) && "Shouldn't be supported");
     llvm_unreachable("Unsupported intrinsic");
@@ -1667,6 +1674,36 @@
                                     APIntOps::umax(-SMin, SMax) + 1);
 }
 
+ConstantRange ConstantRange::ctlz(bool ZeroIsPoison) const {
+  if (isEmptySet())
+    return getEmpty();
+
+  APInt Zero = APInt::getZero(getBitWidth());
+  if (ZeroIsPoison && contains(Zero)) {
+    // The range may become full if it *only* contains Zero.
+    return getFull();
+  }
+
+  if (isWrappedSet() || isFullSet()) {
+    // The range wraps, therefore it includes the two extreme encodings, all
+    // zeros and all ones. The only way we can express this [0, BitWidth + 1).
+    return ConstantRange(Zero, APInt(getBitWidth(), (getBitWidth() + 1)));
+  }
+
+  // Zero is either safe or not in the range. The output range is composed by
+  // the result of countLeadingZero of the two extremes, sorted.
+  APInt Lower = APInt(getBitWidth(), getLower().countLeadingZeros());
+  APInt Upper = APInt(getBitWidth(), (getUpper() - 1).countLeadingZeros());
+  if (Lower.eq(Upper)) {
+    return ConstantRange(std::move(Lower), Upper + 1);
+  } else {
+    if (Lower.ugt(Upper))
+      std::swap(Lower, Upper);
+    ++Upper;
+    return ConstantRange(std::move(Lower), std::move(Upper));
+  }
+}
+
 ConstantRange::OverflowResult ConstantRange::unsignedAddMayOverflow(
     const ConstantRange &Other) const {
   if (isEmptySet() || Other.isEmptySet())
Index: llvm/test/Analysis/LazyValueAnalysis/lvi-for-ctlz.ll
===================================================================
--- /dev/null
+++ llvm/test/Analysis/LazyValueAnalysis/lvi-for-ctlz.ll
@@ -0,0 +1,79 @@
+; RUN: opt < %s -passes=jump-threading -print-lvi-after-jump-threading -disable-output 2>&1 | FileCheck %s
+
+; Ensures that LazyValueInfo correctly propagates the constant range lattice values to ctlz intrinsic.
+
+; Constant range value may be bounded on [28,31] interval,
+; as forward.0 is taken only if %val is within [1,9].
+define i32 @test_ctlz_1(i32 %val) {
+entry:
+  %add = add i32 %val, -1
+  %cond.0 = icmp ult i32 %add, 9
+  br i1 %cond.0, label %forward.0, label %forward.1
+
+forward.0:                                        ; preds = %entry
+; CHECK-LABEL: forward.0:
+; CHECK: ; LatticeVal for: 'i32 %val' is: constantrange<1, 10>
+; CHECK: ; LatticeVal for: '  %ret_val.0 = tail call i32 @llvm.ctlz.i32(i32 %val, i1 true), !range !0' in BB: '%forward.0' is: constantrange<28, 32>
+; CHECK-NOT: ; LatticeVal for: '  %ret_val.0 = tail call i32 @llvm.ctlz.i32(i32 %val, i1 true), !range !0' in BB: '%forward.0' is: constantrange<0, 33>
+; CHECK-NEXT: %ret_val.0 = tail call i32 @llvm.ctlz.i32(i32 %val, i1 true), !range !0
+  %ret_val.0 = tail call i32 @llvm.ctlz.i32(i32 %val, i1 true), !range !0
+  ret i32 %ret_val.0
+
+forward.1:                                        ; preds = %entry
+  %cond.1 = icmp ugt i32 %val, 20
+  %ret_val.1 = select i1 %cond.1, i32 10, i32 0
+  ret i32 %ret_val.1
+}
+
+@g_unsigned_char = global i8 0
+
+; Constant range value may be bounded on [23,32] interval, as it takes
+; the sum of %val, which is within [0, 10], and g_unsigned_char, which is
+; within [0, 255], and ZeroIsPoison is set to false.
+define i32 @test_ctlz_2(i32 %val) {
+entry:
+  %cond.0 = icmp ult i32 %val, 10
+  br i1 %cond.0, label %forward.0, label %forward.1
+
+forward.0:                                        ; preds = %entry
+; CHECK-LABEL: forward.0:
+; CHECK-NEXT: ; LatticeVal for: 'i32 %val' is: constantrange<0, 10>
+  %load = load i8, i8* @g_unsigned_char
+  %zext = zext i8 %load to i32
+  %add = add nuw nsw i32 %zext, %val
+; CHECK: ; LatticeVal for: '  %res = tail call i32 @llvm.ctlz.i32(i32 %add, i1 false), !range !0' in BB: '%forward.0' is: constantrange<23, 33>
+; CHECK-NEXT: %res = tail call i32 @llvm.ctlz.i32(i32 %add, i1 false), !range !0
+  %res = tail call i32 @llvm.ctlz.i32(i32 %add, i1 false), !range !0
+  br label %forward.1
+
+forward.1:                                         ; preds = %entry, %forward.0
+  %ret_val = phi i32 [ %res, %forward.0 ], [ 0, %entry ]
+  ret i32 %ret_val
+}
+
+; Same as test_ctlz_2, this time with ZeroIsPoison set to true.
+; Hence, constant range value may be unknown.
+define i32 @test_ctlz_3(i32 %val) {
+entry:
+  %cond.0 = icmp ult i32 %val, 10
+  br i1 %cond.0, label %forward.0, label %forward.1
+
+forward.0:                                        ; preds = %entry
+; CHECK-LABEL: forward.0:
+; CHECK-NEXT: ; LatticeVal for: 'i32 %val' is: constantrange<0, 10>
+  %load = load i8, i8* @g_unsigned_char
+  %zext = zext i8 %load to i32
+  %add = add nuw nsw i32 %zext, %val
+; CHECK: ; LatticeVal for: '  %res = tail call i32 @llvm.ctlz.i32(i32 %add, i1 true), !range !0' in BB: '%forward.0' is: unknown
+; CHECK-NEXT: %res = tail call i32 @llvm.ctlz.i32(i32 %add, i1 true), !range !0
+  %res = tail call i32 @llvm.ctlz.i32(i32 %add, i1 true), !range !0
+  br label %forward.1
+
+forward.1:                                         ; preds = %entry, %forward.0
+  %ret_val = phi i32 [ %res, %forward.0 ], [ 0, %entry ]
+  ret i32 %ret_val
+}
+
+declare i32 @llvm.ctlz.i32(i32, i1 immarg) nounwind willreturn
+
+!0 = !{i32 0, i32 33}
Index: llvm/unittests/IR/ConstantRangeTest.cpp
===================================================================
--- llvm/unittests/IR/ConstantRangeTest.cpp
+++ llvm/unittests/IR/ConstantRangeTest.cpp
@@ -2397,6 +2397,19 @@
       });
 }
 
+TEST_F(ConstantRangeTest, Ctlz) {
+  TestUnaryOpExhaustive([](const ConstantRange &CR) { return CR.ctlz(); },
+                        [](const APInt &N) { return N.ctlz(); });
+
+  TestUnaryOpExhaustive(
+      [](const ConstantRange &CR) { return CR.ctlz(/*ZeroIsPoison=*/true); },
+      [](const APInt &N) -> std::optional<APInt> {
+        if (N.isZero())
+          return std::nullopt;
+        return N.ctlz();
+      });
+}
+
 TEST_F(ConstantRangeTest, castOps) {
   ConstantRange A(APInt(16, 66), APInt(16, 128));
   ConstantRange FpToI8 = A.castOp(Instruction::FPToSI, 8);