Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 64 additions & 41 deletions library/core/src/num/int_log10.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
//! These functions compute the integer logarithm of their type, assuming
//! that someone has already checked that the value is strictly positive.

use crate::num::NonZero;

// 0 < val <= u8::MAX
#[inline]
pub(super) const fn u8(val: u8) -> u32 {
const fn u8_impl(val: u8) -> u32 {
let val = val as u32;

// For better performance, avoid branches by assembling the solution
Expand Down Expand Up @@ -45,13 +47,13 @@ const fn less_than_5(val: u32) -> u32 {

// 0 < val <= u16::MAX
#[inline]
pub(super) const fn u16(val: u16) -> u32 {
const fn u16_impl(val: u16) -> u32 {
less_than_5(val as u32)
}

// 0 < val <= u32::MAX
#[inline]
pub(super) const fn u32(mut val: u32) -> u32 {
const fn u32_impl(mut val: u32) -> u32 {
let mut log = 0;
if val >= 100_000 {
val /= 100_000;
Expand All @@ -62,7 +64,7 @@ pub(super) const fn u32(mut val: u32) -> u32 {

// 0 < val <= u64::MAX
#[inline]
pub(super) const fn u64(mut val: u64) -> u32 {
const fn u64_impl(mut val: u64) -> u32 {
let mut log = 0;
if val >= 10_000_000_000 {
val /= 10_000_000_000;
Expand All @@ -77,66 +79,87 @@ pub(super) const fn u64(mut val: u64) -> u32 {

// 0 < val <= u128::MAX
#[inline]
pub(super) const fn u128(mut val: u128) -> u32 {
const fn u128_impl(mut val: u128) -> u32 {
let mut log = 0;
if val >= 100_000_000_000_000_000_000_000_000_000_000 {
val /= 100_000_000_000_000_000_000_000_000_000_000;
log += 32;
return log + u32(val as u32);
return log + u32_impl(val as u32);
}
if val >= 10_000_000_000_000_000 {
val /= 10_000_000_000_000_000;
log += 16;
}
log + u64(val as u64)
log + u64_impl(val as u64)
}

#[cfg(target_pointer_width = "16")]
#[inline]
pub(super) const fn usize(val: usize) -> u32 {
u16(val as _)
}
macro_rules! define_unsigned_ilog10 {
($($ty:ident => $impl_fn:ident,)*) => {$(
#[inline]
pub(super) const fn $ty(val: NonZero<$ty>) -> u32 {
let result = $impl_fn(val.get());

#[cfg(target_pointer_width = "32")]
#[inline]
pub(super) const fn usize(val: usize) -> u32 {
u32(val as _)
}
// SAFETY: Integer logarithm is monotonic non-decreasing, so the computed `result` cannot
// exceed the value produced for the maximum input.
unsafe { crate::hint::assert_unchecked(result <= const { $impl_fn($ty::MAX) }) };

#[cfg(target_pointer_width = "64")]
#[inline]
pub(super) const fn usize(val: usize) -> u32 {
u64(val as _)
result
}
)*};
}

// 0 < val <= i8::MAX
#[inline]
pub(super) const fn i8(val: i8) -> u32 {
u8(val as u8)
define_unsigned_ilog10! {
u8 => u8_impl,
u16 => u16_impl,
u32 => u32_impl,
u64 => u64_impl,
u128 => u128_impl,
}

// 0 < val <= i16::MAX
#[inline]
pub(super) const fn i16(val: i16) -> u32 {
u16(val as u16)
}
pub(super) const fn usize(val: NonZero<usize>) -> u32 {
#[cfg(target_pointer_width = "16")]
let impl_fn = u16;

// 0 < val <= i32::MAX
#[inline]
pub(super) const fn i32(val: i32) -> u32 {
u32(val as u32)
#[cfg(target_pointer_width = "32")]
let impl_fn = u32;

#[cfg(target_pointer_width = "64")]
let impl_fn = u64;

// SAFETY: We have selected the correct `impl_fn`, so the converting `val` to the argument is
// safe.
impl_fn(unsafe { NonZero::new_unchecked(val.get() as _) })
}

// 0 < val <= i64::MAX
#[inline]
pub(super) const fn i64(val: i64) -> u32 {
u64(val as u64)
macro_rules! define_signed_ilog10 {
($($ty:ident => $impl_fn:ident,)*) => {$(
// 0 < val <= $ty::MAX
#[inline]
pub(super) const fn $ty(val: $ty) -> Option<u32> {
if val > 0 {
let result = $impl_fn(val.cast_unsigned());

// SAFETY: Integer logarithm is monotonic non-decreasing, so the computed `result`
// cannot exceed the value produced for the maximum input.
unsafe {
crate::hint::assert_unchecked(result <= const { $impl_fn($ty::MAX.cast_unsigned()) });
}

Some(result)
} else {
None
}
}
)*};
}

// 0 < val <= i128::MAX
#[inline]
pub(super) const fn i128(val: i128) -> u32 {
u128(val as u128)
define_signed_ilog10! {
i8 => u8_impl,
i16 => u16_impl,
i32 => u32_impl,
i64 => u64_impl,
i128 => u128_impl,
}

/// Instantiate this panic logic once, rather than for all the ilog methods
Expand Down
6 changes: 1 addition & 5 deletions library/core/src/num/int_macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3527,11 +3527,7 @@ macro_rules! int_impl {
without modifying the original"]
#[inline]
pub const fn checked_ilog10(self) -> Option<u32> {
if self > 0 {
Some(int_log10::$ActualT(self as $ActualT))
} else {
None
}
int_log10::$ActualT(self as $ActualT)
}

/// Computes the absolute value of `self`.
Expand Down
2 changes: 1 addition & 1 deletion library/core/src/num/nonzero.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1657,7 +1657,7 @@ macro_rules! nonzero_integer_signedness_dependent_methods {
without modifying the original"]
#[inline]
pub const fn ilog10(self) -> u32 {
super::int_log10::$Int(self.get())
super::int_log10::$Int(self)
}

/// Calculates the midpoint (average) between `self` and `rhs`.
Expand Down
213 changes: 213 additions & 0 deletions tests/codegen-llvm/lib-optimizations/ilog10-range.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
//! Make sure the compiler knows the result range of `ilog10`.

//@ compile-flags: -O -Z merge-functions=disabled

#![crate_type = "lib"]

use std::num::NonZero;

// Signed integers.

#[no_mangle]
fn i8_ilog10_range(value: i8) {
const MAX_RESULT: u32 = i8::MAX.ilog10();

// CHECK-LABEL: @i8_ilog10_range(
// CHECK-NOT: panic
// CHECK: ret void
// CHECK-NEXT: }
assert!(value <= 0 || value.ilog10() <= MAX_RESULT);
assert!(value.checked_ilog10().is_none_or(|result| result <= MAX_RESULT));
}

#[no_mangle]
fn i16_ilog10_range(value: i16) {
const MAX_RESULT: u32 = i16::MAX.ilog10();

// CHECK-LABEL: @i16_ilog10_range(
// CHECK-NOT: panic
// CHECK: ret void
// CHECK-NEXT: }
assert!(value <= 0 || value.ilog10() <= MAX_RESULT);
assert!(value.checked_ilog10().is_none_or(|result| result <= MAX_RESULT));
}

#[no_mangle]
fn i32_ilog10_range(value: i32) {
const MAX_RESULT: u32 = i32::MAX.ilog10();

// CHECK-LABEL: @i32_ilog10_range(
// CHECK-NOT: panic
// CHECK: ret void
// CHECK-NEXT: }
assert!(value <= 0 || value.ilog10() <= MAX_RESULT);
assert!(value.checked_ilog10().is_none_or(|result| result <= MAX_RESULT));
}

#[no_mangle]
fn i64_ilog10_range(value: i64) {
const MAX_RESULT: u32 = i64::MAX.ilog10();

// CHECK-LABEL: @i64_ilog10_range(
// CHECK-NOT: panic
// CHECK: ret void
// CHECK-NEXT: }
assert!(value <= 0 || value.ilog10() <= MAX_RESULT);
assert!(value.checked_ilog10().is_none_or(|result| result <= MAX_RESULT));
}

#[no_mangle]
fn i128_ilog10_range(value: i128) {
const MAX_RESULT: u32 = i128::MAX.ilog10();

// CHECK-LABEL: @i128_ilog10_range(
// CHECK-NOT: panic
// CHECK: ret void
// CHECK-NEXT: }
assert!(value <= 0 || value.ilog10() <= MAX_RESULT);
assert!(value.checked_ilog10().is_none_or(|result| result <= MAX_RESULT));
}

#[no_mangle]
fn isize_ilog10_range(value: isize) {
const MAX_RESULT: u32 = isize::MAX.ilog10();

// CHECK-LABEL: @isize_ilog10_range(
// CHECK-NOT: panic
// CHECK: ret void
// CHECK-NEXT: }
assert!(value <= 0 || value.ilog10() <= MAX_RESULT);
assert!(value.checked_ilog10().is_none_or(|result| result <= MAX_RESULT));
}

// Unsigned integer types.

#[no_mangle]
fn u8_ilog10_range(value: u8) {
const MAX_RESULT: u32 = u8::MAX.ilog10();

// CHECK-LABEL: @u8_ilog10_range(
// CHECK-NOT: panic
// CHECK: ret void
// CHECK-NEXT: }
assert!(value == 0 || value.ilog10() <= MAX_RESULT);
assert!(value.checked_ilog10().is_none_or(|result| result <= MAX_RESULT));
}

#[no_mangle]
fn u16_ilog10_range(value: u16) {
const MAX_RESULT: u32 = u16::MAX.ilog10();

// CHECK-LABEL: @u16_ilog10_range(
// CHECK-NOT: panic
// CHECK: ret void
// CHECK-NEXT: }
assert!(value == 0 || value.ilog10() <= MAX_RESULT);
assert!(value.checked_ilog10().is_none_or(|result| result <= MAX_RESULT));
}

#[no_mangle]
fn u32_ilog10_range(value: u32) {
const MAX_RESULT: u32 = u32::MAX.ilog10();

// CHECK-LABEL: @u32_ilog10_range(
// CHECK-NOT: panic
// CHECK: ret void
// CHECK-NEXT: }
assert!(value == 0 || value.ilog10() <= MAX_RESULT);
assert!(value.checked_ilog10().is_none_or(|result| result <= MAX_RESULT));
}

#[no_mangle]
fn u64_ilog10_range(value: u64) {
const MAX_RESULT: u32 = u64::MAX.ilog10();

// CHECK-LABEL: @u64_ilog10_range(
// CHECK-NOT: panic
// CHECK: ret void
// CHECK-NEXT: }
assert!(value == 0 || value.ilog10() <= MAX_RESULT);
assert!(value.checked_ilog10().is_none_or(|result| result <= MAX_RESULT));
}

#[no_mangle]
fn u128_ilog10_range(value: u128) {
const MAX_RESULT: u32 = u128::MAX.ilog10();

// CHECK-LABEL: @u128_ilog10_range(
// CHECK-NOT: panic
// CHECK: ret void
// CHECK-NEXT: }
assert!(value == 0 || value.ilog10() <= MAX_RESULT);
assert!(value.checked_ilog10().is_none_or(|result| result <= MAX_RESULT));
}

#[no_mangle]
fn usize_ilog10_range(value: usize) {
const MAX_RESULT: u32 = usize::MAX.ilog10();

// CHECK-LABEL: @usize_ilog10_range(
// CHECK-NOT: panic
// CHECK: ret void
// CHECK-NEXT: }
assert!(value == 0 || value.ilog10() <= MAX_RESULT);
assert!(value.checked_ilog10().is_none_or(|result| result <= MAX_RESULT));
}

// Signed non-zero integers do not have `ilog10` methods.

// Unsigned non-zero integers.

#[no_mangle]
fn non_zero_u8_ilog10_range(value: NonZero<u8>) {
// CHECK-LABEL: @non_zero_u8_ilog10_range(
// CHECK-NOT: panic
// CHECK: ret void
// CHECK-NEXT: }
assert!(value.ilog10() <= const { u8::MAX.ilog10() });
}

#[no_mangle]
fn non_zero_u16_ilog10_range(value: NonZero<u16>) {
// CHECK-LABEL: @non_zero_u16_ilog10_range(
// CHECK-NOT: panic
// CHECK: ret void
// CHECK-NEXT: }
assert!(value.ilog10() <= const { u16::MAX.ilog10() });
}

#[no_mangle]
fn non_zero_u32_ilog10_range(value: NonZero<u32>) {
// CHECK-LABEL: @non_zero_u32_ilog10_range(
// CHECK-NOT: panic
// CHECK: ret void
// CHECK-NEXT: }
assert!(value.ilog10() <= const { u32::MAX.ilog10() });
}

#[no_mangle]
fn non_zero_u64_ilog10_range(value: NonZero<u64>) {
// CHECK-LABEL: @non_zero_u64_ilog10_range(
// CHECK-NOT: panic
// CHECK: ret void
// CHECK-NEXT: }
assert!(value.ilog10() <= const { u64::MAX.ilog10() });
}

#[no_mangle]
fn non_zero_u128_ilog10_range(value: NonZero<u128>) {
// CHECK-LABEL: @non_zero_u128_ilog10_range(
// CHECK-NOT: panic
// CHECK: ret void
// CHECK-NEXT: }
assert!(value.ilog10() <= const { u128::MAX.ilog10() });
}

#[no_mangle]
fn non_zero_usize_ilog10_range(value: NonZero<usize>) {
// CHECK-LABEL: @non_zero_usize_ilog10_range(
// CHECK-NOT: panic
// CHECK: ret void
// CHECK-NEXT: }
assert!(value.ilog10() <= const { usize::MAX.ilog10() });
}
Loading