Skip to content

Commit d9cc57e

Browse files
committed
optimize: checked_ilog when base is a power of two
if base == 2 ** k, then log(base, n) == log(2, n) / k
1 parent 2286e5d commit d9cc57e

File tree

2 files changed

+48
-3
lines changed

2 files changed

+48
-3
lines changed

library/core/src/num/uint_macros.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1555,6 +1555,10 @@ macro_rules! uint_impl {
15551555
///
15561556
/// ```
15571557
#[doc = concat!("assert_eq!(5", stringify!($SelfT), ".checked_ilog(5), Some(1));")]
1558+
#[doc = concat!("assert_eq!(4", stringify!($SelfT), ".checked_ilog(5), Some(0));")]
1559+
#[doc = concat!("assert_eq!(5", stringify!($SelfT), ".checked_ilog(0), None);")]
1560+
#[doc = concat!("assert_eq!(5", stringify!($SelfT), ".checked_ilog(1), None);")]
1561+
#[doc = concat!("assert_eq!(0", stringify!($SelfT), ".checked_ilog(1), None);")]
15581562
/// ```
15591563
#[stable(feature = "int_log", since = "1.67.0")]
15601564
#[rustc_const_stable(feature = "int_log", since = "1.67.0")]
@@ -1569,9 +1573,14 @@ macro_rules! uint_impl {
15691573
// applied by the compiler. If you want those specific bases,
15701574
// use `.checked_ilog2()` or `.checked_ilog10()` directly.
15711575
if core::intrinsics::is_val_statically_known(base) {
1572-
if base == 2 {
1573-
return self.checked_ilog2();
1574-
} else if base == 10 {
1576+
// change of base:
1577+
// if base == 2 ** k, then
1578+
// log(base, n) == log(2, n) / k
1579+
if base.is_power_of_two() && base > 1 {
1580+
let k = base.ilog2();
1581+
return Some(try_opt!(self.checked_ilog2()) / k);
1582+
}
1583+
if base == 10 {
15751584
return self.checked_ilog10();
15761585
}
15771586
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
//@ compile-flags: -Copt-level=3
2+
// Test that `checked_ilog` can use a faster implementation when `base` is a
3+
// known power of two
4+
5+
#![crate_type = "lib"]
6+
7+
// CHECK-LABEL: @checked_ilog2
8+
#[no_mangle]
9+
pub fn checked_ilog2(val: u32) -> Option<u32> {
10+
// CHECK: %[[ICMP:.+]] = icmp ne i32 %val, 0
11+
// CHECK: %[[CTZ:.+]] = tail call range(i32 0, 33) i32 @llvm.ctlz.i32(i32 %val, i1 true)
12+
// CHECK: %[[LOG2:.+]] = xor i32 %[[CTZ]], 31
13+
val.checked_ilog(2)
14+
}
15+
16+
// log(4, x) == log(2, x) / 2
17+
// CHECK-LABEL: @checked_ilog4
18+
#[no_mangle]
19+
pub fn checked_ilog4(val: u32) -> Option<u32> {
20+
// CHECK: %[[ICMP:.+]] = icmp ne i32 %val, 0
21+
// CHECK: %[[CTZ:.+]] = tail call range(i32 0, 33) i32 @llvm.ctlz.i32(i32 %val, i1 true)
22+
// CHECK: %[[DIV2:.+]] = lshr i32 %[[CTZ]], 1
23+
// CHECK: %[[LOG4:.+]] = xor i32 %[[DIV2]], 15
24+
val.checked_ilog(4)
25+
}
26+
27+
// log(16, x) == log(2, x) / 4
28+
// CHECK-LABEL: @checked_ilog16
29+
#[no_mangle]
30+
pub fn checked_ilog16(val: u32) -> Option<u32> {
31+
// CHECK: %[[ICMP:.+]] = icmp ne i32 %val, 0
32+
// CHECK: %[[CTZ:.+]] = tail call range(i32 0, 33) i32 @llvm.ctlz.i32(i32 %val, i1 true)
33+
// CHECK: %[[DIV4:.+]] = lshr i32 %[[CTZ]], 2
34+
// CHECK: %[[LOG16:.+]] = xor i32 %[[DIV2]], 7
35+
val.checked_ilog(16)
36+
}

0 commit comments

Comments
 (0)