From 7c61ed7f01f3c2b09c853767fbbf1f410ad7b327 Mon Sep 17 00:00:00 2001 From: Karl Meakin Date: Thu, 2 Oct 2025 01:29:00 +0100 Subject: [PATCH 1/5] optimize: `checked_ilog` when `base` is a power of two if base == 2 ** k, then log(base, n) == log(2, n) / k --- library/core/src/num/uint_macros.rs | 15 ++++++++--- tests/codegen-llvm/ilog_known_base.rs | 36 +++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 3 deletions(-) create mode 100644 tests/codegen-llvm/ilog_known_base.rs diff --git a/library/core/src/num/uint_macros.rs b/library/core/src/num/uint_macros.rs index ae8324c13f0cb..ee0c398c10b26 100644 --- a/library/core/src/num/uint_macros.rs +++ b/library/core/src/num/uint_macros.rs @@ -1682,6 +1682,10 @@ macro_rules! uint_impl { /// /// ``` #[doc = concat!("assert_eq!(5", stringify!($SelfT), ".checked_ilog(5), Some(1));")] + #[doc = concat!("assert_eq!(4", stringify!($SelfT), ".checked_ilog(5), Some(0));")] + #[doc = concat!("assert_eq!(5", stringify!($SelfT), ".checked_ilog(0), None);")] + #[doc = concat!("assert_eq!(5", stringify!($SelfT), ".checked_ilog(1), None);")] + #[doc = concat!("assert_eq!(0", stringify!($SelfT), ".checked_ilog(1), None);")] /// ``` #[stable(feature = "int_log", since = "1.67.0")] #[rustc_const_stable(feature = "int_log", since = "1.67.0")] @@ -1696,9 +1700,14 @@ macro_rules! uint_impl { // applied by the compiler. If you want those specific bases, // use `.checked_ilog2()` or `.checked_ilog10()` directly. if core::intrinsics::is_val_statically_known(base) { - if base == 2 { - return self.checked_ilog2(); - } else if base == 10 { + // change of base: + // if base == 2 ** k, then + // log(base, n) == log(2, n) / k + if base.is_power_of_two() && base > 1 { + let k = base.ilog2(); + return Some(try_opt!(self.checked_ilog2()) / k); + } + if base == 10 { return self.checked_ilog10(); } } diff --git a/tests/codegen-llvm/ilog_known_base.rs b/tests/codegen-llvm/ilog_known_base.rs new file mode 100644 index 0000000000000..3f0cdd551c28b --- /dev/null +++ b/tests/codegen-llvm/ilog_known_base.rs @@ -0,0 +1,36 @@ +//@ compile-flags: -Copt-level=3 +// Test that `checked_ilog` can use a faster implementation when `base` is a +// known power of two + +#![crate_type = "lib"] + +// CHECK-LABEL: @checked_ilog2 +#[no_mangle] +pub fn checked_ilog2(val: u32) -> Option { + // CHECK: %[[ICMP:.+]] = icmp ne i32 %val, 0 + // CHECK: %[[CTZ:.+]] = tail call range(i32 0, 33) i32 @llvm.ctlz.i32(i32 %val, i1 true) + // CHECK: %[[LOG2:.+]] = xor i32 %[[CTZ]], 31 + val.checked_ilog(2) +} + +// log(4, x) == log(2, x) / 2 +// CHECK-LABEL: @checked_ilog4 +#[no_mangle] +pub fn checked_ilog4(val: u32) -> Option { + // CHECK: %[[ICMP:.+]] = icmp ne i32 %val, 0 + // CHECK: %[[CTZ:.+]] = tail call range(i32 0, 33) i32 @llvm.ctlz.i32(i32 %val, i1 true) + // CHECK: %[[DIV2:.+]] = lshr i32 %[[CTZ]], 1 + // CHECK: %[[LOG4:.+]] = xor i32 %[[DIV2]], 15 + val.checked_ilog(4) +} + +// log(16, x) == log(2, x) / 4 +// CHECK-LABEL: @checked_ilog16 +#[no_mangle] +pub fn checked_ilog16(val: u32) -> Option { + // CHECK: %[[ICMP:.+]] = icmp ne i32 %val, 0 + // CHECK: %[[CTZ:.+]] = tail call range(i32 0, 33) i32 @llvm.ctlz.i32(i32 %val, i1 true) + // CHECK: %[[DIV4:.+]] = lshr i32 %[[CTZ]], 2 + // CHECK: %[[LOG16:.+]] = xor i32 %[[DIV2]], 7 + val.checked_ilog(16) +} From 7ae25637cac5261532035d3e9dbc1a11b32bd726 Mon Sep 17 00:00:00 2001 From: Karl Meakin Date: Sat, 22 Nov 2025 00:14:08 +0000 Subject: [PATCH 2/5] refactor: Increase test coverage for `pow` Increase test coverage to check all interesting edge cases and all variants. --- library/coretests/tests/num/int_macros.rs | 225 ++++++++++++++++++--- library/coretests/tests/num/uint_macros.rs | 117 ++++++++++- 2 files changed, 307 insertions(+), 35 deletions(-) diff --git a/library/coretests/tests/num/int_macros.rs b/library/coretests/tests/num/int_macros.rs index 37336f49ef1b6..bd5af0e85e191 100644 --- a/library/coretests/tests/num/int_macros.rs +++ b/library/coretests/tests/num/int_macros.rs @@ -316,50 +316,227 @@ macro_rules! int_module { } fn test_pow() { + { + const R: $T = 0; + assert_eq_const_safe!($T: R.wrapping_pow(0), 1 as $T); + assert_eq_const_safe!($T: R.wrapping_pow(1), 0 as $T); + assert_eq_const_safe!($T: R.wrapping_pow(2), 0 as $T); + assert_eq_const_safe!($T: R.wrapping_pow(128), 0 as $T); + assert_eq_const_safe!($T: R.wrapping_pow(129), 0 as $T); + + assert_eq_const_safe!(Option<$T>: R.checked_pow(0), Some(1 as $T)); + assert_eq_const_safe!(Option<$T>: R.checked_pow(1), Some(0 as $T)); + assert_eq_const_safe!(Option<$T>: R.checked_pow(2), Some(0 as $T)); + assert_eq_const_safe!(Option<$T>: R.checked_pow(128), Some(0 as $T)); + assert_eq_const_safe!(Option<$T>: R.checked_pow(129), Some(0 as $T)); + + assert_eq_const_safe!(($T, bool): R.overflowing_pow(0), (1 as $T, false)); + assert_eq_const_safe!(($T, bool): R.overflowing_pow(1), (0 as $T, false)); + assert_eq_const_safe!(($T, bool): R.overflowing_pow(2), (0 as $T, false)); + assert_eq_const_safe!(($T, bool): R.overflowing_pow(128), (0 as $T, false)); + assert_eq_const_safe!(($T, bool): R.overflowing_pow(129), (0 as $T, false)); + + assert_eq_const_safe!($T: R.saturating_pow(0), 1 as $T); + assert_eq_const_safe!($T: R.saturating_pow(1), 0 as $T); + assert_eq_const_safe!($T: R.saturating_pow(2), 0 as $T); + assert_eq_const_safe!($T: R.saturating_pow(128), 0 as $T); + assert_eq_const_safe!($T: R.saturating_pow(129), 0 as $T); + } + + { + const R: $T = 1; + assert_eq_const_safe!($T: R.wrapping_pow(0), 1 as $T); + assert_eq_const_safe!($T: R.wrapping_pow(1), 1 as $T); + assert_eq_const_safe!($T: R.wrapping_pow(2), 1 as $T); + assert_eq_const_safe!($T: R.wrapping_pow(128), 1 as $T); + assert_eq_const_safe!($T: R.wrapping_pow(129), 1 as $T); + + assert_eq_const_safe!(Option<$T>: R.checked_pow(0), Some(1 as $T)); + assert_eq_const_safe!(Option<$T>: R.checked_pow(1), Some(1 as $T)); + assert_eq_const_safe!(Option<$T>: R.checked_pow(2), Some(1 as $T)); + assert_eq_const_safe!(Option<$T>: R.checked_pow(128), Some(1 as $T)); + assert_eq_const_safe!(Option<$T>: R.checked_pow(129), Some(1 as $T)); + + assert_eq_const_safe!(($T, bool): R.overflowing_pow(0), (1 as $T, false)); + assert_eq_const_safe!(($T, bool): R.overflowing_pow(1), (1 as $T, false)); + assert_eq_const_safe!(($T, bool): R.overflowing_pow(2), (1 as $T, false)); + assert_eq_const_safe!(($T, bool): R.overflowing_pow(128), (1 as $T, false)); + assert_eq_const_safe!(($T, bool): R.overflowing_pow(129), (1 as $T, false)); + + assert_eq_const_safe!($T: R.saturating_pow(0), 1 as $T); + assert_eq_const_safe!($T: R.saturating_pow(1), 1 as $T); + assert_eq_const_safe!($T: R.saturating_pow(2), 1 as $T); + assert_eq_const_safe!($T: R.saturating_pow(128), 1 as $T); + assert_eq_const_safe!($T: R.saturating_pow(129), 1 as $T); + } + + { + const R: $T = -1; + assert_eq_const_safe!($T: R.wrapping_pow(0), 1 as $T); + assert_eq_const_safe!($T: R.wrapping_pow(1), -1 as $T); + assert_eq_const_safe!($T: R.wrapping_pow(2), 1 as $T); + assert_eq_const_safe!($T: R.wrapping_pow(128), 1 as $T); + assert_eq_const_safe!($T: R.wrapping_pow(129), -1 as $T); + + assert_eq_const_safe!(Option<$T>: R.checked_pow(0), Some(1 as $T)); + assert_eq_const_safe!(Option<$T>: R.checked_pow(1), Some(-1 as $T)); + assert_eq_const_safe!(Option<$T>: R.checked_pow(2), Some(1 as $T)); + assert_eq_const_safe!(Option<$T>: R.checked_pow(128), Some(1 as $T)); + assert_eq_const_safe!(Option<$T>: R.checked_pow(129), Some(-1 as $T)); + + assert_eq_const_safe!(($T, bool): R.overflowing_pow(0), (1 as $T, false)); + assert_eq_const_safe!(($T, bool): R.overflowing_pow(1), (-1 as $T, false)); + assert_eq_const_safe!(($T, bool): R.overflowing_pow(2), (1 as $T, false)); + assert_eq_const_safe!(($T, bool): R.overflowing_pow(128), (1 as $T, false)); + assert_eq_const_safe!(($T, bool): R.overflowing_pow(129), (-1 as $T, false)); + + assert_eq_const_safe!($T: R.saturating_pow(0), 1 as $T); + assert_eq_const_safe!($T: R.saturating_pow(1), -1 as $T); + assert_eq_const_safe!($T: R.saturating_pow(2), 1 as $T); + assert_eq_const_safe!($T: R.saturating_pow(128), 1 as $T); + assert_eq_const_safe!($T: R.saturating_pow(129), -1 as $T); + } + { const R: $T = 2; - assert_eq_const_safe!($T: R.pow(2), 4 as $T); - assert_eq_const_safe!($T: R.pow(0), 1 as $T); - assert_eq_const_safe!($T: R.wrapping_pow(2), 4 as $T); assert_eq_const_safe!($T: R.wrapping_pow(0), 1 as $T); - assert_eq_const_safe!(Option<$T>: R.checked_pow(2), Some(4 as $T)); + assert_eq_const_safe!($T: R.wrapping_pow(1), 2 as $T); + assert_eq_const_safe!($T: R.wrapping_pow(2), 4 as $T); + assert_eq_const_safe!($T: R.wrapping_pow(128), 0 as $T); + assert_eq_const_safe!($T: R.wrapping_pow(129), 0 as $T); + assert_eq_const_safe!(Option<$T>: R.checked_pow(0), Some(1 as $T)); - assert_eq_const_safe!(($T, bool): R.overflowing_pow(2), (4 as $T, false)); + assert_eq_const_safe!(Option<$T>: R.checked_pow(1), Some(2 as $T)); + assert_eq_const_safe!(Option<$T>: R.checked_pow(2), Some(4 as $T)); + assert_eq_const_safe!(Option<$T>: R.checked_pow(128), None); + assert_eq_const_safe!(Option<$T>: R.checked_pow(129), None); + assert_eq_const_safe!(($T, bool): R.overflowing_pow(0), (1 as $T, false)); + assert_eq_const_safe!(($T, bool): R.overflowing_pow(1), (2 as $T, false)); + assert_eq_const_safe!(($T, bool): R.overflowing_pow(2), (4 as $T, false)); + assert_eq_const_safe!(($T, bool): R.overflowing_pow(128), (0 as $T, true)); + assert_eq_const_safe!(($T, bool): R.overflowing_pow(129), (0 as $T, true)); + + assert_eq_const_safe!($T: R.saturating_pow(0), 1 as $T); + assert_eq_const_safe!($T: R.saturating_pow(1), 2 as $T); assert_eq_const_safe!($T: R.saturating_pow(2), 4 as $T); + assert_eq_const_safe!($T: R.saturating_pow(128), $T::MAX); + assert_eq_const_safe!($T: R.saturating_pow(129), $T::MAX); + } + + { + const R: $T = -2; + assert_eq_const_safe!($T: R.wrapping_pow(0), 1 as $T); + assert_eq_const_safe!($T: R.wrapping_pow(1), -2 as $T); + assert_eq_const_safe!($T: R.wrapping_pow(2), 4 as $T); + assert_eq_const_safe!($T: R.wrapping_pow($T::BITS - 1), $T::MIN); + assert_eq_const_safe!($T: R.wrapping_pow($T::BITS), 0 as $T); + assert_eq_const_safe!($T: R.wrapping_pow($T::BITS + 1), 0 as $T); + assert_eq_const_safe!($T: R.wrapping_pow(128), 0 as $T); + assert_eq_const_safe!($T: R.wrapping_pow(129), 0 as $T); + + assert_eq_const_safe!(Option<$T>: R.checked_pow(0), Some(1 as $T)); + assert_eq_const_safe!(Option<$T>: R.checked_pow(1), Some(-2 as $T)); + assert_eq_const_safe!(Option<$T>: R.checked_pow(2), Some(4 as $T)); + assert_eq_const_safe!(Option<$T>: R.checked_pow($T::BITS - 1), Some($T::MIN)); + assert_eq_const_safe!(Option<$T>: R.checked_pow($T::BITS), None); + assert_eq_const_safe!(Option<$T>: R.checked_pow($T::BITS + 1), None); + assert_eq_const_safe!(Option<$T>: R.checked_pow(128), None); + assert_eq_const_safe!(Option<$T>: R.checked_pow(129), None); + + assert_eq_const_safe!(($T, bool): R.overflowing_pow(0), (1 as $T, false)); + assert_eq_const_safe!(($T, bool): R.overflowing_pow(1), (-2 as $T, false)); + assert_eq_const_safe!(($T, bool): R.overflowing_pow(2), (4 as $T, false)); + assert_eq_const_safe!(($T, bool): R.overflowing_pow($T::BITS - 1), ($T::MIN, false)); + assert_eq_const_safe!(($T, bool): R.overflowing_pow($T::BITS), (0 as $T, true)); + assert_eq_const_safe!(($T, bool): R.overflowing_pow($T::BITS + 1), (0 as $T, true)); + assert_eq_const_safe!(($T, bool): R.overflowing_pow(128), (0 as $T, true)); + assert_eq_const_safe!(($T, bool): R.overflowing_pow(129), (0 as $T, true)); + assert_eq_const_safe!($T: R.saturating_pow(0), 1 as $T); + assert_eq_const_safe!($T: R.saturating_pow(1), -2 as $T); + assert_eq_const_safe!($T: R.saturating_pow(2), 4 as $T); + assert_eq_const_safe!($T: R.saturating_pow($T::BITS - 1), $T::MIN); + assert_eq_const_safe!($T: R.saturating_pow($T::BITS), $T::MAX); + assert_eq_const_safe!($T: R.saturating_pow($T::BITS + 1), $T::MIN); + assert_eq_const_safe!($T: R.saturating_pow(128), $T::MAX); + assert_eq_const_safe!($T: R.saturating_pow(129), $T::MIN); + } + + // Overflow in the shift caclculation should result in the final + // result being 0 rather than accidentally succeeding due to a + // shift within the word size. + // eg `4 ** 0x8000_0000` should give 0 rather than 1 << 0 + { + const R: $T = 4; + const HALF: u32 = u32::MAX / 2 + 1; + assert_eq_const_safe!($T: R.wrapping_pow(HALF), 0 as $T); + assert_eq_const_safe!(Option<$T>: R.checked_pow(HALF), None); + assert_eq_const_safe!(($T, bool): R.overflowing_pow(HALF), (0 as $T, true)); + assert_eq_const_safe!($T: R.saturating_pow(HALF), $T::MAX); } { - const R: $T = MAX; - // use `^` to represent .pow() with no overflow. - // if itest::MAX == 2^j-1, then itest is a `j` bit int, - // so that `itest::MAX*itest::MAX == 2^(2*j)-2^(j+1)+1`, - // thussaturating_pow the overflowing result is exactly 1. + const R: $T = -4; + const HALF: u32 = u32::MAX / 2 + 1; + assert_eq_const_safe!($T: R.wrapping_pow(HALF), 0 as $T); + assert_eq_const_safe!(Option<$T>: R.checked_pow(HALF), None); + assert_eq_const_safe!(($T, bool): R.overflowing_pow(HALF), (0 as $T, true)); + assert_eq_const_safe!($T: R.saturating_pow(HALF), $T::MAX); + } + + { + const R: $T = $T::MAX; + assert_eq_const_safe!($T: R.wrapping_pow(0), 1 as $T); + assert_eq_const_safe!($T: R.wrapping_pow(1), R as $T); assert_eq_const_safe!($T: R.wrapping_pow(2), 1 as $T); + assert_eq_const_safe!($T: R.wrapping_pow(128), 1 as $T); + assert_eq_const_safe!($T: R.wrapping_pow(129), R as $T); + + assert_eq_const_safe!(Option<$T>: R.checked_pow(0), Some(1 as $T)); + assert_eq_const_safe!(Option<$T>: R.checked_pow(1), Some(R as $T)); assert_eq_const_safe!(Option<$T>: R.checked_pow(2), None); + assert_eq_const_safe!(Option<$T>: R.checked_pow(128), None); + assert_eq_const_safe!(Option<$T>: R.checked_pow(129), None); + + assert_eq_const_safe!(($T, bool): R.overflowing_pow(0), (1 as $T, false)); + assert_eq_const_safe!(($T, bool): R.overflowing_pow(1), (R as $T, false)); assert_eq_const_safe!(($T, bool): R.overflowing_pow(2), (1 as $T, true)); - assert_eq_const_safe!($T: R.saturating_pow(2), MAX); + assert_eq_const_safe!(($T, bool): R.overflowing_pow(128), (1 as $T, true)); + assert_eq_const_safe!(($T, bool): R.overflowing_pow(129), (R as $T, true)); + + assert_eq_const_safe!($T: R.saturating_pow(0), 1 as $T); + assert_eq_const_safe!($T: R.saturating_pow(1), R as $T); + assert_eq_const_safe!($T: R.saturating_pow(2), $T::MAX); + assert_eq_const_safe!($T: R.saturating_pow(128), $T::MAX); + assert_eq_const_safe!($T: R.saturating_pow(129), $T::MAX); } { - // test for negative exponent. - const R: $T = -2; - assert_eq_const_safe!($T: R.pow(2), 4 as $T); - assert_eq_const_safe!($T: R.pow(3), -8 as $T); - assert_eq_const_safe!($T: R.pow(0), 1 as $T); - assert_eq_const_safe!($T: R.wrapping_pow(2), 4 as $T); - assert_eq_const_safe!($T: R.wrapping_pow(3), -8 as $T); + const R: $T = $T::MIN; assert_eq_const_safe!($T: R.wrapping_pow(0), 1 as $T); - assert_eq_const_safe!(Option<$T>: R.checked_pow(2), Some(4 as $T)); - assert_eq_const_safe!(Option<$T>: R.checked_pow(3), Some(-8 as $T)); + assert_eq_const_safe!($T: R.wrapping_pow(1), R as $T); + assert_eq_const_safe!($T: R.wrapping_pow(2), 0 as $T); + assert_eq_const_safe!($T: R.wrapping_pow(128), 0 as $T); + assert_eq_const_safe!($T: R.wrapping_pow(129), 0 as $T); + assert_eq_const_safe!(Option<$T>: R.checked_pow(0), Some(1 as $T)); - assert_eq_const_safe!(($T, bool): R.overflowing_pow(2), (4 as $T, false)); - assert_eq_const_safe!(($T, bool): R.overflowing_pow(3), (-8 as $T, false)); + assert_eq_const_safe!(Option<$T>: R.checked_pow(1), Some(R as $T)); + assert_eq_const_safe!(Option<$T>: R.checked_pow(2), None); + assert_eq_const_safe!(Option<$T>: R.checked_pow(128), None); + assert_eq_const_safe!(Option<$T>: R.checked_pow(129), None); + assert_eq_const_safe!(($T, bool): R.overflowing_pow(0), (1 as $T, false)); - assert_eq_const_safe!($T: R.saturating_pow(2), 4 as $T); - assert_eq_const_safe!($T: R.saturating_pow(3), -8 as $T); + assert_eq_const_safe!(($T, bool): R.overflowing_pow(1), (R as $T, false)); + assert_eq_const_safe!(($T, bool): R.overflowing_pow(2), (0 as $T, true)); + assert_eq_const_safe!(($T, bool): R.overflowing_pow(128), (0 as $T, true)); + assert_eq_const_safe!(($T, bool): R.overflowing_pow(129), (0 as $T, true)); + assert_eq_const_safe!($T: R.saturating_pow(0), 1 as $T); + assert_eq_const_safe!($T: R.saturating_pow(1), R as $T); + assert_eq_const_safe!($T: R.saturating_pow(2), $T::MAX); + assert_eq_const_safe!($T: R.saturating_pow(128), $T::MAX); + assert_eq_const_safe!($T: R.saturating_pow(129), $T::MIN); } } diff --git a/library/coretests/tests/num/uint_macros.rs b/library/coretests/tests/num/uint_macros.rs index 240c66fd5c715..dee300a283832 100644 --- a/library/coretests/tests/num/uint_macros.rs +++ b/library/coretests/tests/num/uint_macros.rs @@ -338,30 +338,125 @@ macro_rules! uint_module { } fn test_pow() { + { + const R: $T = 0; + assert_eq_const_safe!($T: R.wrapping_pow(0), 1 as $T); + assert_eq_const_safe!($T: R.wrapping_pow(1), 0 as $T); + assert_eq_const_safe!($T: R.wrapping_pow(2), 0 as $T); + assert_eq_const_safe!($T: R.wrapping_pow(128), 0 as $T); + assert_eq_const_safe!($T: R.wrapping_pow(129), 0 as $T); + + assert_eq_const_safe!(Option<$T>: R.checked_pow(0), Some(1 as $T)); + assert_eq_const_safe!(Option<$T>: R.checked_pow(1), Some(0 as $T)); + assert_eq_const_safe!(Option<$T>: R.checked_pow(2), Some(0 as $T)); + assert_eq_const_safe!(Option<$T>: R.checked_pow(128), Some(0 as $T)); + assert_eq_const_safe!(Option<$T>: R.checked_pow(129), Some(0 as $T)); + + assert_eq_const_safe!(($T, bool): R.overflowing_pow(0), (1 as $T, false)); + assert_eq_const_safe!(($T, bool): R.overflowing_pow(1), (0 as $T, false)); + assert_eq_const_safe!(($T, bool): R.overflowing_pow(2), (0 as $T, false)); + assert_eq_const_safe!(($T, bool): R.overflowing_pow(128), (0 as $T, false)); + assert_eq_const_safe!(($T, bool): R.overflowing_pow(129), (0 as $T, false)); + + assert_eq_const_safe!($T: R.saturating_pow(0), 1 as $T); + assert_eq_const_safe!($T: R.saturating_pow(1), 0 as $T); + assert_eq_const_safe!($T: R.saturating_pow(2), 0 as $T); + assert_eq_const_safe!($T: R.saturating_pow(128), 0 as $T); + assert_eq_const_safe!($T: R.saturating_pow(129), 0 as $T); + } + + { + const R: $T = 1; + assert_eq_const_safe!($T: R.wrapping_pow(0), 1 as $T); + assert_eq_const_safe!($T: R.wrapping_pow(1), 1 as $T); + assert_eq_const_safe!($T: R.wrapping_pow(2), 1 as $T); + assert_eq_const_safe!($T: R.wrapping_pow(128), 1 as $T); + assert_eq_const_safe!($T: R.wrapping_pow(129), 1 as $T); + + assert_eq_const_safe!(Option<$T>: R.checked_pow(0), Some(1 as $T)); + assert_eq_const_safe!(Option<$T>: R.checked_pow(1), Some(1 as $T)); + assert_eq_const_safe!(Option<$T>: R.checked_pow(2), Some(1 as $T)); + assert_eq_const_safe!(Option<$T>: R.checked_pow(128), Some(1 as $T)); + assert_eq_const_safe!(Option<$T>: R.checked_pow(129), Some(1 as $T)); + + assert_eq_const_safe!(($T, bool): R.overflowing_pow(0), (1 as $T, false)); + assert_eq_const_safe!(($T, bool): R.overflowing_pow(1), (1 as $T, false)); + assert_eq_const_safe!(($T, bool): R.overflowing_pow(2), (1 as $T, false)); + assert_eq_const_safe!(($T, bool): R.overflowing_pow(128), (1 as $T, false)); + assert_eq_const_safe!(($T, bool): R.overflowing_pow(129), (1 as $T, false)); + + assert_eq_const_safe!($T: R.saturating_pow(0), 1 as $T); + assert_eq_const_safe!($T: R.saturating_pow(1), 1 as $T); + assert_eq_const_safe!($T: R.saturating_pow(2), 1 as $T); + assert_eq_const_safe!($T: R.saturating_pow(128), 1 as $T); + assert_eq_const_safe!($T: R.saturating_pow(129), 1 as $T); + } + { const R: $T = 2; - assert_eq_const_safe!($T: R.pow(2), 4 as $T); - assert_eq_const_safe!($T: R.pow(0), 1 as $T); - assert_eq_const_safe!($T: R.wrapping_pow(2), 4 as $T); assert_eq_const_safe!($T: R.wrapping_pow(0), 1 as $T); - assert_eq_const_safe!(Option<$T>: R.checked_pow(2), Some(4 as $T)); + assert_eq_const_safe!($T: R.wrapping_pow(1), 2 as $T); + assert_eq_const_safe!($T: R.wrapping_pow(2), 4 as $T); + assert_eq_const_safe!($T: R.wrapping_pow(128), 0 as $T); + assert_eq_const_safe!($T: R.wrapping_pow(129), 0 as $T); + assert_eq_const_safe!(Option<$T>: R.checked_pow(0), Some(1 as $T)); - assert_eq_const_safe!(($T, bool): R.overflowing_pow(2), (4 as $T, false)); + assert_eq_const_safe!(Option<$T>: R.checked_pow(1), Some(2 as $T)); + assert_eq_const_safe!(Option<$T>: R.checked_pow(2), Some(4 as $T)); + assert_eq_const_safe!(Option<$T>: R.checked_pow(128), None); + assert_eq_const_safe!(Option<$T>: R.checked_pow(129), None); + assert_eq_const_safe!(($T, bool): R.overflowing_pow(0), (1 as $T, false)); - assert_eq_const_safe!($T: R.saturating_pow(2), 4 as $T); + assert_eq_const_safe!(($T, bool): R.overflowing_pow(1), (2 as $T, false)); + assert_eq_const_safe!(($T, bool): R.overflowing_pow(2), (4 as $T, false)); + assert_eq_const_safe!(($T, bool): R.overflowing_pow(128), (0 as $T, true)); + assert_eq_const_safe!(($T, bool): R.overflowing_pow(129), (0 as $T, true)); + assert_eq_const_safe!($T: R.saturating_pow(0), 1 as $T); + assert_eq_const_safe!($T: R.saturating_pow(1), 2 as $T); + assert_eq_const_safe!($T: R.saturating_pow(2), 4 as $T); + assert_eq_const_safe!($T: R.saturating_pow(128), $T::MAX); + assert_eq_const_safe!($T: R.saturating_pow(129), $T::MAX); + } + + // Overflow in the shift caclculation should result in the final + // result being 0 rather than accidentally succeeding due to a + // shift within the word size. + // eg `4 ** 0x8000_0000` should give 0 rather than 1 << 0 + { + const R: $T = 4; + const HALF: u32 = u32::MAX / 2 + 1; + assert_eq_const_safe!($T: R.wrapping_pow(HALF), 0 as $T); + assert_eq_const_safe!(Option<$T>: R.checked_pow(HALF), None); + assert_eq_const_safe!(($T, bool): R.overflowing_pow(HALF), (0 as $T, true)); + assert_eq_const_safe!($T: R.saturating_pow(HALF), $T::MAX); } { const R: $T = $T::MAX; - // use `^` to represent .pow() with no overflow. - // if itest::MAX == 2^j-1, then itest is a `j` bit int, - // so that `itest::MAX*itest::MAX == 2^(2*j)-2^(j+1)+1`, - // thussaturating_pow the overflowing result is exactly 1. + assert_eq_const_safe!($T: R.wrapping_pow(0), 1 as $T); + assert_eq_const_safe!($T: R.wrapping_pow(1), R as $T); assert_eq_const_safe!($T: R.wrapping_pow(2), 1 as $T); + assert_eq_const_safe!($T: R.wrapping_pow(128), 1 as $T); + assert_eq_const_safe!($T: R.wrapping_pow(129), R as $T); + + assert_eq_const_safe!(Option<$T>: R.checked_pow(0), Some(1 as $T)); + assert_eq_const_safe!(Option<$T>: R.checked_pow(1), Some(R as $T)); assert_eq_const_safe!(Option<$T>: R.checked_pow(2), None); + assert_eq_const_safe!(Option<$T>: R.checked_pow(128), None); + assert_eq_const_safe!(Option<$T>: R.checked_pow(129), None); + + assert_eq_const_safe!(($T, bool): R.overflowing_pow(0), (1 as $T, false)); + assert_eq_const_safe!(($T, bool): R.overflowing_pow(1), (R as $T, false)); assert_eq_const_safe!(($T, bool): R.overflowing_pow(2), (1 as $T, true)); - assert_eq_const_safe!($T: R.saturating_pow(2), MAX); + assert_eq_const_safe!(($T, bool): R.overflowing_pow(128), (1 as $T, true)); + assert_eq_const_safe!(($T, bool): R.overflowing_pow(129), (R as $T, true)); + + assert_eq_const_safe!($T: R.saturating_pow(0), 1 as $T); + assert_eq_const_safe!($T: R.saturating_pow(1), R as $T); + assert_eq_const_safe!($T: R.saturating_pow(2), $T::MAX); + assert_eq_const_safe!($T: R.saturating_pow(128), $T::MAX); + assert_eq_const_safe!($T: R.saturating_pow(129), $T::MAX); } } From c52c2eea4a0bc851e78a3733aaa71605b9ab009d Mon Sep 17 00:00:00 2001 From: Karl Meakin Date: Sat, 22 Nov 2025 00:44:01 +0000 Subject: [PATCH 3/5] refactor: Deduplicate `pow` implementations `strict_pow` can be implemented in terms of `checked_pow`, `wrapping_pow` can be implemented in terms of `overflowing_pow`, and `pow` can be implemented in terms of `strict_pow` or `wrapping_pow`. --- library/core/src/num/imp/overflow_panic.rs | 6 + library/core/src/num/int_macros.rs | 122 +++-------------- library/core/src/num/uint_macros.rs | 123 +++--------------- .../overflowing-pow-signed.rs | 2 +- .../overflowing-pow-unsigned.rs | 2 +- 5 files changed, 48 insertions(+), 207 deletions(-) diff --git a/library/core/src/num/imp/overflow_panic.rs b/library/core/src/num/imp/overflow_panic.rs index a9b91daba954b..5108dc7cb7a2e 100644 --- a/library/core/src/num/imp/overflow_panic.rs +++ b/library/core/src/num/imp/overflow_panic.rs @@ -49,3 +49,9 @@ pub(in crate::num) const fn shr() -> ! { pub(in crate::num) const fn shl() -> ! { panic!("attempt to shift left with overflow") } + +#[cold] +#[track_caller] +pub(in crate::num) const fn pow() -> ! { + panic!("attempt to exponentiate with overflow") +} diff --git a/library/core/src/num/int_macros.rs b/library/core/src/num/int_macros.rs index aba0eaf8d4842..df3fb7f4089e4 100644 --- a/library/core/src/num/int_macros.rs +++ b/library/core/src/num/int_macros.rs @@ -1807,23 +1807,10 @@ macro_rules! int_impl { without modifying the original"] #[inline] #[track_caller] - pub const fn strict_pow(self, mut exp: u32) -> Self { - if exp == 0 { - return 1; - } - let mut base = self; - let mut acc: Self = 1; - - loop { - if (exp & 1) == 1 { - acc = acc.strict_mul(base); - // since exp!=0, finally the exp must be 1. - if exp == 1 { - return acc; - } - } - exp /= 2; - base = base.strict_mul(base); + pub const fn strict_pow(self, exp: u32) -> Self { + match self.checked_pow(exp) { + Some(x) => x, + None => imp::overflow_panic::pow(), } } @@ -2438,43 +2425,9 @@ macro_rules! int_impl { #[must_use = "this returns the result of the operation, \ without modifying the original"] #[inline] - pub const fn wrapping_pow(self, mut exp: u32) -> Self { - if exp == 0 { - return 1; - } - let mut base = self; - let mut acc: Self = 1; - - if intrinsics::is_val_statically_known(exp) { - while exp > 1 { - if (exp & 1) == 1 { - acc = acc.wrapping_mul(base); - } - exp /= 2; - base = base.wrapping_mul(base); - } - - // since exp!=0, finally the exp must be 1. - // Deal with the final bit of the exponent separately, since - // squaring the base afterwards is not necessary. - acc.wrapping_mul(base) - } else { - // This is faster than the above when the exponent is not known - // at compile time. We can't use the same code for the constant - // exponent case because LLVM is currently unable to unroll - // this loop. - loop { - if (exp & 1) == 1 { - acc = acc.wrapping_mul(base); - // since exp!=0, finally the exp must be 1. - if exp == 1 { - return acc; - } - } - exp /= 2; - base = base.wrapping_mul(base); - } - } + pub const fn wrapping_pow(self, exp: u32) -> Self { + let (a, _) = self.overflowing_pow(exp); + a } /// Calculates `self` + `rhs`. @@ -3040,29 +2993,26 @@ macro_rules! int_impl { #[inline] pub const fn overflowing_pow(self, mut exp: u32) -> (Self, bool) { if exp == 0 { - return (1,false); + return (1, false); } + let mut base = self; let mut acc: Self = 1; - let mut overflown = false; - // Scratch space for storing results of overflowing_mul. - let mut r; + let mut overflow = false; + let mut tmp_overflow; loop { if (exp & 1) == 1 { - r = acc.overflowing_mul(base); + (acc, tmp_overflow) = acc.overflowing_mul(base); + overflow |= tmp_overflow; // since exp!=0, finally the exp must be 1. if exp == 1 { - r.1 |= overflown; - return r; + return (acc, overflow); } - acc = r.0; - overflown |= r.1; } exp /= 2; - r = base.overflowing_mul(base); - base = r.0; - overflown |= r.1; + (base, tmp_overflow) = base.overflowing_mul(base); + overflow |= tmp_overflow; } } @@ -3082,43 +3032,11 @@ macro_rules! int_impl { without modifying the original"] #[inline] #[rustc_inherit_overflow_checks] - pub const fn pow(self, mut exp: u32) -> Self { - if exp == 0 { - return 1; - } - let mut base = self; - let mut acc = 1; - - if intrinsics::is_val_statically_known(exp) { - while exp > 1 { - if (exp & 1) == 1 { - acc = acc * base; - } - exp /= 2; - base = base * base; - } - - // since exp!=0, finally the exp must be 1. - // Deal with the final bit of the exponent separately, since - // squaring the base afterwards is not necessary and may cause a - // needless overflow. - acc * base + pub const fn pow(self, exp: u32) -> Self { + if intrinsics::overflow_checks() { + self.strict_pow(exp) } else { - // This is faster than the above when the exponent is not known - // at compile time. We can't use the same code for the constant - // exponent case because LLVM is currently unable to unroll - // this loop. - loop { - if (exp & 1) == 1 { - acc = acc * base; - // since exp!=0, finally the exp must be 1. - if exp == 1 { - return acc; - } - } - exp /= 2; - base = base * base; - } + self.wrapping_pow(exp) } } diff --git a/library/core/src/num/uint_macros.rs b/library/core/src/num/uint_macros.rs index ee0c398c10b26..a596890630620 100644 --- a/library/core/src/num/uint_macros.rs +++ b/library/core/src/num/uint_macros.rs @@ -2272,23 +2272,10 @@ macro_rules! uint_impl { without modifying the original"] #[inline] #[track_caller] - pub const fn strict_pow(self, mut exp: u32) -> Self { - if exp == 0 { - return 1; - } - let mut base = self; - let mut acc: Self = 1; - - loop { - if (exp & 1) == 1 { - acc = acc.strict_mul(base); - // since exp!=0, finally the exp must be 1. - if exp == 1 { - return acc; - } - } - exp /= 2; - base = base.strict_mul(base); + pub const fn strict_pow(self, exp: u32) -> Self { + match self.checked_pow(exp) { + None => imp::overflow_panic::pow(), + Some(a) => a, } } @@ -2778,43 +2765,9 @@ macro_rules! uint_impl { #[must_use = "this returns the result of the operation, \ without modifying the original"] #[inline] - pub const fn wrapping_pow(self, mut exp: u32) -> Self { - if exp == 0 { - return 1; - } - let mut base = self; - let mut acc: Self = 1; - - if intrinsics::is_val_statically_known(exp) { - while exp > 1 { - if (exp & 1) == 1 { - acc = acc.wrapping_mul(base); - } - exp /= 2; - base = base.wrapping_mul(base); - } - - // since exp!=0, finally the exp must be 1. - // Deal with the final bit of the exponent separately, since - // squaring the base afterwards is not necessary. - acc.wrapping_mul(base) - } else { - // This is faster than the above when the exponent is not known - // at compile time. We can't use the same code for the constant - // exponent case because LLVM is currently unable to unroll - // this loop. - loop { - if (exp & 1) == 1 { - acc = acc.wrapping_mul(base); - // since exp!=0, finally the exp must be 1. - if exp == 1 { - return acc; - } - } - exp /= 2; - base = base.wrapping_mul(base); - } - } + pub const fn wrapping_pow(self, exp: u32) -> Self { + let (a, _) = self.overflowing_pow(exp); + a } /// Calculates `self` + `rhs`. @@ -3456,30 +3409,26 @@ macro_rules! uint_impl { without modifying the original"] #[inline] pub const fn overflowing_pow(self, mut exp: u32) -> (Self, bool) { - if exp == 0{ - return (1,false); + if exp == 0 { + return (1, false); } let mut base = self; let mut acc: Self = 1; - let mut overflown = false; - // Scratch space for storing results of overflowing_mul. - let mut r; + let mut overflow = false; + let mut tmp_overflow; loop { if (exp & 1) == 1 { - r = acc.overflowing_mul(base); + (acc, tmp_overflow) = acc.overflowing_mul(base); + overflow |= tmp_overflow; // since exp!=0, finally the exp must be 1. if exp == 1 { - r.1 |= overflown; - return r; + return (acc, overflow); } - acc = r.0; - overflown |= r.1; } exp /= 2; - r = base.overflowing_mul(base); - base = r.0; - overflown |= r.1; + (base, tmp_overflow) = base.overflowing_mul(base); + overflow |= tmp_overflow; } } @@ -3497,43 +3446,11 @@ macro_rules! uint_impl { without modifying the original"] #[inline] #[rustc_inherit_overflow_checks] - pub const fn pow(self, mut exp: u32) -> Self { - if exp == 0 { - return 1; - } - let mut base = self; - let mut acc = 1; - - if intrinsics::is_val_statically_known(exp) { - while exp > 1 { - if (exp & 1) == 1 { - acc = acc * base; - } - exp /= 2; - base = base * base; - } - - // since exp!=0, finally the exp must be 1. - // Deal with the final bit of the exponent separately, since - // squaring the base afterwards is not necessary and may cause a - // needless overflow. - acc * base + pub const fn pow(self, exp: u32) -> Self { + if intrinsics::overflow_checks() { + self.strict_pow(exp) } else { - // This is faster than the above when the exponent is not known - // at compile time. We can't use the same code for the constant - // exponent case because LLVM is currently unable to unroll - // this loop. - loop { - if (exp & 1) == 1 { - acc = acc * base; - // since exp!=0, finally the exp must be 1. - if exp == 1 { - return acc; - } - } - exp /= 2; - base = base * base; - } + self.wrapping_pow(exp) } } diff --git a/tests/ui/numbers-arithmetic/overflowing-pow-signed.rs b/tests/ui/numbers-arithmetic/overflowing-pow-signed.rs index 6db6682e16fd6..807e5a65c665a 100644 --- a/tests/ui/numbers-arithmetic/overflowing-pow-signed.rs +++ b/tests/ui/numbers-arithmetic/overflowing-pow-signed.rs @@ -1,6 +1,6 @@ //@ run-fail //@ regex-error-pattern: thread 'main'.*panicked -//@ error-pattern: attempt to multiply with overflow +//@ regex-error-pattern: attempt to exponentiate with overflow //@ needs-subprocess //@ compile-flags: -C debug-assertions diff --git a/tests/ui/numbers-arithmetic/overflowing-pow-unsigned.rs b/tests/ui/numbers-arithmetic/overflowing-pow-unsigned.rs index bde0de6f6f575..75672dd4b8c21 100644 --- a/tests/ui/numbers-arithmetic/overflowing-pow-unsigned.rs +++ b/tests/ui/numbers-arithmetic/overflowing-pow-unsigned.rs @@ -1,6 +1,6 @@ //@ run-fail //@ regex-error-pattern: thread 'main'.*panicked -//@ error-pattern: attempt to multiply with overflow +//@ regex-error-pattern: attempt to exponentiate with overflow //@ needs-subprocess //@ compile-flags: -C debug-assertions From 55e14077eb75ef9ccfc562d7753480bfab9aca1f Mon Sep 17 00:00:00 2001 From: Karl Meakin Date: Sat, 22 Nov 2025 02:30:21 +0000 Subject: [PATCH 4/5] optimize: `pow` if `exp` is statically known Copy the optimization that unrolls the loop from `pow` to `checked_pow` and `overflowing_pow`. --- library/core/src/num/int_macros.rs | 36 +++++++++++++++++++++++++++++ library/core/src/num/uint_macros.rs | 36 +++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/library/core/src/num/int_macros.rs b/library/core/src/num/int_macros.rs index df3fb7f4089e4..99251a2cf23cc 100644 --- a/library/core/src/num/int_macros.rs +++ b/library/core/src/num/int_macros.rs @@ -1767,6 +1767,22 @@ macro_rules! int_impl { let mut base = self; let mut acc: Self = 1; + if intrinsics::is_val_statically_known(exp) { + while exp > 1 { + if (exp & 1) == 1 { + acc = try_opt!(acc.checked_mul(base)); + } + exp /= 2; + base = try_opt!(base.checked_mul(base)); + } + + // since exp!=0, finally the exp must be 1. + // Deal with the final bit of the exponent separately, since + // squaring the base afterwards is not necessary and may cause a + // needless overflow. + return acc.checked_mul(base); + } + loop { if (exp & 1) == 1 { acc = try_opt!(acc.checked_mul(base)); @@ -3001,6 +3017,26 @@ macro_rules! int_impl { let mut overflow = false; let mut tmp_overflow; + if intrinsics::is_val_statically_known(exp) { + while exp > 1 { + if (exp & 1) == 1 { + (acc, tmp_overflow) = acc.overflowing_mul(base); + overflow |= tmp_overflow; + } + exp /= 2; + (base, tmp_overflow) = base.overflowing_mul(base); + overflow |= tmp_overflow; + } + + // since exp!=0, finally the exp must be 1. + // Deal with the final bit of the exponent separately, since + // squaring the base afterwards is not necessary and may cause a + // needless overflow. + (acc, tmp_overflow) = acc.overflowing_mul(base); + overflow |= tmp_overflow; + return (acc, overflow); + } + loop { if (exp & 1) == 1 { (acc, tmp_overflow) = acc.overflowing_mul(base); diff --git a/library/core/src/num/uint_macros.rs b/library/core/src/num/uint_macros.rs index a596890630620..c7f7945ad1a2d 100644 --- a/library/core/src/num/uint_macros.rs +++ b/library/core/src/num/uint_macros.rs @@ -2232,6 +2232,22 @@ macro_rules! uint_impl { let mut base = self; let mut acc: Self = 1; + if intrinsics::is_val_statically_known(exp) { + while exp > 1 { + if (exp & 1) == 1 { + acc = try_opt!(acc.checked_mul(base)); + } + exp /= 2; + base = try_opt!(base.checked_mul(base)); + } + + // since exp!=0, finally the exp must be 1. + // Deal with the final bit of the exponent separately, since + // squaring the base afterwards is not necessary and may cause a + // needless overflow. + return acc.checked_mul(base); + } + loop { if (exp & 1) == 1 { acc = try_opt!(acc.checked_mul(base)); @@ -3417,6 +3433,26 @@ macro_rules! uint_impl { let mut overflow = false; let mut tmp_overflow; + if intrinsics::is_val_statically_known(exp) { + while exp > 1 { + if (exp & 1) == 1 { + (acc, tmp_overflow) = acc.overflowing_mul(base); + overflow |= tmp_overflow; + } + exp /= 2; + (base, tmp_overflow) = base.overflowing_mul(base); + overflow |= tmp_overflow; + } + + // since exp!=0, finally the exp must be 1. + // Deal with the final bit of the exponent separately, since + // squaring the base afterwards is not necessary and may cause a + // needless overflow. + (acc, tmp_overflow) = acc.overflowing_mul(base); + overflow |= tmp_overflow; + return (acc, overflow); + } + loop { if (exp & 1) == 1 { (acc, tmp_overflow) = acc.overflowing_mul(base); From b0aca14316026ef52c848d72ccfc01d97a5633bb Mon Sep 17 00:00:00 2001 From: Karl Meakin Date: Thu, 2 Oct 2025 02:09:18 +0100 Subject: [PATCH 5/5] optimize: `pow` when `base` is a power of two if base == 2 ** k, then (2 ** k) ** n == 2 ** (k * n) == 1 << (k * n) --- library/core/src/num/int_macros.rs | 41 ++++++++++++++++--- library/core/src/num/uint_macros.rs | 36 ++++++++++++++--- tests/codegen-llvm/pow_known_base.rs | 59 ++++++++++++++++++++++++++++ 3 files changed, 125 insertions(+), 11 deletions(-) create mode 100644 tests/codegen-llvm/pow_known_base.rs diff --git a/library/core/src/num/int_macros.rs b/library/core/src/num/int_macros.rs index 99251a2cf23cc..1fb7f3a0c89ca 100644 --- a/library/core/src/num/int_macros.rs +++ b/library/core/src/num/int_macros.rs @@ -1761,11 +1761,25 @@ macro_rules! int_impl { without modifying the original"] #[inline] pub const fn checked_pow(self, mut exp: u32) -> Option { + let mut base = self; + let mut acc: Self = 1; + + if intrinsics::is_val_statically_known(base) { + if base.unsigned_abs().is_power_of_two() { + let k = base.unsigned_abs().ilog2(); + let shift = try_opt!(k.checked_mul(exp)); + let magnitude = try_opt!((1 as Self).checked_shl(shift)); + return if base < 0 && (exp % 2) == 1 { + Some(magnitude.wrapping_neg()) + } else { + Some(magnitude) + } + } + } + if exp == 0 { return Some(1); } - let mut base = self; - let mut acc: Self = 1; if intrinsics::is_val_statically_known(exp) { while exp > 1 { @@ -3008,15 +3022,30 @@ macro_rules! int_impl { without modifying the original"] #[inline] pub const fn overflowing_pow(self, mut exp: u32) -> (Self, bool) { - if exp == 0 { - return (1, false); - } - let mut base = self; let mut acc: Self = 1; let mut overflow = false; let mut tmp_overflow; + if intrinsics::is_val_statically_known(base) { + if base.unsigned_abs().is_power_of_two() { + let k = base.unsigned_abs().ilog2(); + let Some(shift) = k.checked_mul(exp) else { + return (0, true) + }; + let magnitude = (1 as Self).unbounded_shl(shift); + return if base < 0 && (exp % 2) == 1 { + (magnitude.wrapping_neg(), shift >= Self::BITS) + } else { + (magnitude, shift >= Self::BITS) + } + } + } + + if exp == 0 { + return (1, false); + } + if intrinsics::is_val_statically_known(exp) { while exp > 1 { if (exp & 1) == 1 { diff --git a/library/core/src/num/uint_macros.rs b/library/core/src/num/uint_macros.rs index c7f7945ad1a2d..c0ab252a5f5da 100644 --- a/library/core/src/num/uint_macros.rs +++ b/library/core/src/num/uint_macros.rs @@ -2226,11 +2226,23 @@ macro_rules! uint_impl { without modifying the original"] #[inline] pub const fn checked_pow(self, mut exp: u32) -> Option { + let mut base = self; + let mut acc: Self = 1; + + if intrinsics::is_val_statically_known(base) && base.is_power_of_two() { + // change of base: + // if base == 2 ** k, then + // (2 ** k) ** n + // == 2 ** (k * n) + // == 1 << (k * n) + let k = base.ilog2(); + let shift = try_opt!(k.checked_mul(exp)); + return (1 as Self).checked_shl(shift); + } + if exp == 0 { return Some(1); } - let mut base = self; - let mut acc: Self = 1; if intrinsics::is_val_statically_known(exp) { while exp > 1 { @@ -3425,14 +3437,28 @@ macro_rules! uint_impl { without modifying the original"] #[inline] pub const fn overflowing_pow(self, mut exp: u32) -> (Self, bool) { - if exp == 0 { - return (1, false); - } let mut base = self; let mut acc: Self = 1; let mut overflow = false; let mut tmp_overflow; + if intrinsics::is_val_statically_known(base) && base.is_power_of_two() { + // change of base: + // if base == 2 ** k, then + // (2 ** k) ** n + // == 2 ** (k * n) + // == 1 << (k * n) + let k = base.ilog2(); + let Some(shift) = k.checked_mul(exp) else { + return (0, true) + }; + return ((1 as Self).unbounded_shl(shift), shift >= Self::BITS) + } + + if exp == 0 { + return (1, false); + } + if intrinsics::is_val_statically_known(exp) { while exp > 1 { if (exp & 1) == 1 { diff --git a/tests/codegen-llvm/pow_known_base.rs b/tests/codegen-llvm/pow_known_base.rs new file mode 100644 index 0000000000000..3e463f7828ec4 --- /dev/null +++ b/tests/codegen-llvm/pow_known_base.rs @@ -0,0 +1,59 @@ +//@ compile-flags: -Copt-level=3 +// Test that `pow` can use a faster implementation when `base` is a +// known power of two + +#![crate_type = "lib"] + +// 2 ** n == 2 ** (1 * n) == 1 << (1 * n) +// CHECK-LABEL: @pow2 +#[no_mangle] +pub fn pow2(exp: u32) -> u32 { + // CHECK: %[[OVERFLOW:.+]] = icmp ult i32 %exp, 32 + // CHECK: %[[POW:.+]] = shl nuw i32 1, %exp + // CHECK: %[[RET:.+]] = select i1 %[[OVERFLOW]], i32 %[[POW]], i32 0 + // CHECK: ret i32 %[[RET]] + 2u32.pow(exp) +} + +// 4 ** n == 2 ** (2 * n) == 1 << (2 * n) +// CHECK-LABEL: @pow4 +#[no_mangle] +pub fn pow4(exp: u32) -> u32 { + // CHECK: %[[ICMP1:.+]] = icmp slt i32 %exp, 0 + // CHECK: %[[SHIFT_AMOUNT:.+]] = shl i32 %exp, 1 + // CHECK: %[[ICMP2:.+]] = icmp ult i32 %[[SHIFT_AMOUNT]], 32 + // CHECK: %[[POW:.+]] = shl nuw i32 1, %[[SHIFT_AMOUNT]] + // CHECK: %[[SEL:.+]] = select i1 %[[ICMP2]], i32 %[[POW]], i32 0 + // CHECK: %[[RET:.+]] = select i1 %[[ICMP1]], i32 0, i32 %[[SEL]] + // CHECK: ret i32 %[[RET]] + 4u32.pow(exp) +} + +// 16 ** n == 2 ** (4 * n) == 1 << (4 * n) +// CHECK-LABEL: @pow16 +#[no_mangle] +pub fn pow16(exp: u32) -> u32 { + // CHECK: %[[ICMP1:.+]] = icmp ugt i32 %exp, 1073741823 + // CHECK: %[[SHIFT_AMOUNT:.+]] = shl i32 %exp, 2 + // CHECK: %[[ICMP2:.+]] = icmp ult i32 %[[SHIFT_AMOUNT]], 32 + // CHECK: %[[POW:.+]] = shl nuw i32 1, %[[SHIFT_AMOUNT]] + // CHECK: %[[SEL:.+]] = select i1 %[[ICMP2]], i32 %[[POW]], i32 0 + // CHECK: %[[RET:.+]] = select i1 %[[ICMP1]], i32 0, i32 %[[SEL]] + // CHECK: ret i32 %[[RET]] + 16u32.pow(exp) +} + +// (-2) ** n == (-2) ** (1 * n) == 1 << (1 * n) +// CHECK-LABEL: @pow_minus_2 +#[no_mangle] +pub fn pow_minus_2(exp: u32) -> i32 { + // CHECK: %[[OVERFLOW:.+]] = icmp ult i32 %exp, 32 + // CHECK: %[[POW:.+]] = shl nuw i32 1, %exp + // CHECK: %[[MAGNITUDE:.+]] = select i1 %[[OVERFLOW]], i32 %[[POW]], i32 0 + // CHECK: %[[IS_ODD:.+]] = and i32 %exp, 1 + // CHECK: %[[IS_EVEN:.+]] = icmp eq i32 %[[IS_ODD]], 0 + // CHECK: %[[NEG:.+]] = sub i32 0, %[[MAGNITUDE]] + // CHECK: %[[RET:.+]] = select i1 %[[IS_EVEN]], i32 %[[MAGNITUDE]], i32 %[[NEG]] + // CHECK: ret i32 %[[RET]] + (-2i32).pow(exp) +}