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 empty if it *only* contains Zero. + return getEmpty(); + } + + 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.abs(/*ZeroIsPoison=*/true); }, + [](const APInt &N) -> std::optional { + 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);