diff --git a/compiler/rustc_codegen_llvm/src/intrinsic.rs b/compiler/rustc_codegen_llvm/src/intrinsic.rs index 49d3dedbeabdf..85f71f331a491 100644 --- a/compiler/rustc_codegen_llvm/src/intrinsic.rs +++ b/compiler/rustc_codegen_llvm/src/intrinsic.rs @@ -383,7 +383,9 @@ impl<'ll, 'tcx> IntrinsicCallBuilderMethods<'tcx> for Builder<'_, 'll, 'tcx> { | sym::rotate_left | sym::rotate_right | sym::saturating_add - | sym::saturating_sub => { + | sym::saturating_sub + | sym::unchecked_funnel_shl + | sym::unchecked_funnel_shr => { let ty = args[0].layout.ty; if !ty.is_integral() { tcx.dcx().emit_err(InvalidMonomorphization::BasicIntegerType { @@ -424,18 +426,26 @@ impl<'ll, 'tcx> IntrinsicCallBuilderMethods<'tcx> for Builder<'_, 'll, 'tcx> { sym::bitreverse => { self.call_intrinsic("llvm.bitreverse", &[llty], &[args[0].immediate()]) } - sym::rotate_left | sym::rotate_right => { - let is_left = name == sym::rotate_left; - let val = args[0].immediate(); - let raw_shift = args[1].immediate(); - // rotate = funnel shift with first two args the same + sym::rotate_left + | sym::rotate_right + | sym::unchecked_funnel_shl + | sym::unchecked_funnel_shr => { + let is_left = name == sym::rotate_left || name == sym::unchecked_funnel_shl; + let lhs = args[0].immediate(); + let (rhs, raw_shift) = + if name == sym::rotate_left || name == sym::rotate_right { + // rotate = funnel shift with first two args the same + (lhs, args[1].immediate()) + } else { + (args[1].immediate(), args[2].immediate()) + }; let llvm_name = format!("llvm.fsh{}", if is_left { 'l' } else { 'r' }); // llvm expects shift to be the same type as the values, but rust // always uses `u32`. - let raw_shift = self.intcast(raw_shift, self.val_ty(val), false); + let raw_shift = self.intcast(raw_shift, self.val_ty(lhs), false); - self.call_intrinsic(llvm_name, &[llty], &[val, val, raw_shift]) + self.call_intrinsic(llvm_name, &[llty], &[lhs, rhs, raw_shift]) } sym::saturating_add | sym::saturating_sub => { let is_add = name == sym::saturating_add; diff --git a/compiler/rustc_hir_analysis/src/check/intrinsic.rs b/compiler/rustc_hir_analysis/src/check/intrinsic.rs index cfc6bc2f3a0a9..aa2d27ab80945 100644 --- a/compiler/rustc_hir_analysis/src/check/intrinsic.rs +++ b/compiler/rustc_hir_analysis/src/check/intrinsic.rs @@ -449,6 +449,9 @@ pub(crate) fn check_intrinsic_type( } sym::unchecked_shl | sym::unchecked_shr => (2, 0, vec![param(0), param(1)], param(0)), sym::rotate_left | sym::rotate_right => (1, 0, vec![param(0), tcx.types.u32], param(0)), + sym::unchecked_funnel_shl | sym::unchecked_funnel_shr => { + (1, 0, vec![param(0), param(0), tcx.types.u32], param(0)) + } sym::unchecked_add | sym::unchecked_sub | sym::unchecked_mul => { (1, 0, vec![param(0), param(0)], param(0)) } diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index dcb1becc957db..64662d481371a 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -2269,6 +2269,8 @@ symbols! { unboxed_closures, unchecked_add, unchecked_div, + unchecked_funnel_shl, + unchecked_funnel_shr, unchecked_mul, unchecked_rem, unchecked_shl, diff --git a/library/core/src/intrinsics/fallback.rs b/library/core/src/intrinsics/fallback.rs index eec5c4d646d07..96928325328a9 100644 --- a/library/core/src/intrinsics/fallback.rs +++ b/library/core/src/intrinsics/fallback.rs @@ -148,3 +148,76 @@ impl_disjoint_bitor! { u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize, } + +#[const_trait] +#[rustc_const_unstable(feature = "core_intrinsics_fallbacks", issue = "none")] +pub trait FunnelShift: Copy + 'static { + /// See [`super::unchecked_funnel_shl`]; we just need the trait indirection to handle + /// different types since calling intrinsics with generics doesn't work. + unsafe fn unchecked_funnel_shl(self, rhs: Self, shift: u32) -> Self; + + /// See [`super::unchecked_funnel_shr`]; we just need the trait indirection to handle + /// different types since calling intrinsics with generics doesn't work. + unsafe fn unchecked_funnel_shr(self, rhs: Self, shift: u32) -> Self; +} + +macro_rules! impl_funnel_shifts { + ($($type:ident),*) => {$( + #[rustc_const_unstable(feature = "core_intrinsics_fallbacks", issue = "none")] + impl const FunnelShift for $type { + #[cfg_attr(miri, track_caller)] + #[inline] + unsafe fn unchecked_funnel_shl(self, rhs: Self, shift: u32) -> Self { + // This implementation is also used by Miri so we have to check the precondition. + // SAFETY: this is guaranteed by the caller + unsafe { super::assume(shift < $type::BITS) }; + if shift == 0 { + self + } else { + // SAFETY: + // - `shift < T::BITS`, which satisfies `unchecked_shl` + // - this also ensures that `T::BITS - shift < T::BITS` (shift = 0 is checked + // above), which satisfies `unchecked_shr` + // - because the types are unsigned, the combination are disjoint bits (this is + // not true if they're signed, since SHR will fill in the empty space with a + // sign bit, not zero) + unsafe { + super::disjoint_bitor( + super::unchecked_shl(self, shift), + super::unchecked_shr(rhs, $type::BITS - shift), + ) + } + } + } + + #[cfg_attr(miri, track_caller)] + #[inline] + unsafe fn unchecked_funnel_shr(self, rhs: Self, shift: u32) -> Self { + // This implementation is also used by Miri so we have to check the precondition. + // SAFETY: this is guaranteed by the caller + unsafe { super::assume(shift < $type::BITS) }; + if shift == 0 { + rhs + } else { + // SAFETY: + // - `shift < T::BITS`, which satisfies `unchecked_shr` + // - this also ensures that `T::BITS - shift < T::BITS` (shift = 0 is checked + // above), which satisfies `unchecked_shl` + // - because the types are unsigned, the combination are disjoint bits (this is + // not true if they're signed, since SHR will fill in the empty space with a + // sign bit, not zero) + unsafe { + super::disjoint_bitor( + super::unchecked_shl(self, $type::BITS - shift), + super::unchecked_shr(rhs, shift), + ) + } + } + } + } + )*}; +} + +impl_funnel_shifts! { + u8, u16, u32, u64, u128, usize +} diff --git a/library/core/src/intrinsics/mod.rs b/library/core/src/intrinsics/mod.rs index 904aa52c7845b..bffffbc29c1eb 100644 --- a/library/core/src/intrinsics/mod.rs +++ b/library/core/src/intrinsics/mod.rs @@ -2102,6 +2102,61 @@ pub const fn saturating_add(a: T, b: T) -> T; #[rustc_intrinsic] pub const fn saturating_sub(a: T, b: T) -> T; +/// Funnel Shift left. +/// +/// Concatenates `a` and `b` (with `a` in the most significant half), +/// creating an integer twice as wide. Then shift this integer left +/// by `shift`), and extract the most significant half. If `a` and `b` +/// are the same, this is equivalent to a rotate left operation. +/// +/// It is undefined behavior if `shift` is greater than or equal to the +/// bit size of `T`. +/// +/// Safe versions of this intrinsic are available on the integer primitives +/// via the `funnel_shl` method. For example, [`u32::funnel_shl`]. +#[rustc_intrinsic] +#[rustc_nounwind] +#[rustc_const_unstable(feature = "funnel_shifts", issue = "145686")] +#[unstable(feature = "funnel_shifts", issue = "145686")] +#[track_caller] +#[miri::intrinsic_fallback_is_spec] +pub const unsafe fn unchecked_funnel_shl( + a: T, + b: T, + shift: u32, +) -> T { + // SAFETY: caller ensures that `shift` is in-range + unsafe { a.unchecked_funnel_shl(b, shift) } +} + +/// Funnel Shift right. +/// +/// Concatenates `a` and `b` (with `a` in the most significant half), +/// creating an integer twice as wide. Then shift this integer right +/// by `shift` (taken modulo the bit size of `T`), and extract the +/// least significant half. If `a` and `b` are the same, this is equivalent +/// to a rotate right operation. +/// +/// It is undefined behavior if `shift` is greater than or equal to the +/// bit size of `T`. +/// +/// Safer versions of this intrinsic are available on the integer primitives +/// via the `funnel_shr` method. For example, [`u32::funnel_shr`] +#[rustc_intrinsic] +#[rustc_nounwind] +#[rustc_const_unstable(feature = "funnel_shifts", issue = "145686")] +#[unstable(feature = "funnel_shifts", issue = "145686")] +#[track_caller] +#[miri::intrinsic_fallback_is_spec] +pub const unsafe fn unchecked_funnel_shr( + a: T, + b: T, + shift: u32, +) -> T { + // SAFETY: caller ensures that `shift` is in-range + unsafe { a.unchecked_funnel_shr(b, shift) } +} + /// This is an implementation detail of [`crate::ptr::read`] and should /// not be used anywhere else. See its comments for why this exists. /// diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs index 71abd707374cf..fb6cacb0e0076 100644 --- a/library/core/src/lib.rs +++ b/library/core/src/lib.rs @@ -156,6 +156,7 @@ #![feature(f128)] #![feature(freeze_impls)] #![feature(fundamental)] +#![feature(funnel_shifts)] #![feature(if_let_guard)] #![feature(intra_doc_pointers)] #![feature(intrinsics)] diff --git a/library/core/src/num/mod.rs b/library/core/src/num/mod.rs index acfe38b7a37b5..e2987b0b7e59d 100644 --- a/library/core/src/num/mod.rs +++ b/library/core/src/num/mod.rs @@ -454,6 +454,9 @@ impl u8 { rot = 2, rot_op = "0x82", rot_result = "0xa", + fsh_op = "0x36", + fshl_result = "0x8", + fshr_result = "0x8d", swap_op = "0x12", swapped = "0x12", reversed = "0x48", @@ -1088,6 +1091,9 @@ impl u16 { rot = 4, rot_op = "0xa003", rot_result = "0x3a", + fsh_op = "0x2de", + fshl_result = "0x30", + fshr_result = "0x302d", swap_op = "0x1234", swapped = "0x3412", reversed = "0x2c48", @@ -1135,6 +1141,9 @@ impl u32 { rot = 8, rot_op = "0x10000b3", rot_result = "0xb301", + fsh_op = "0x2fe78e45", + fshl_result = "0xb32f", + fshr_result = "0xb32fe78e", swap_op = "0x12345678", swapped = "0x78563412", reversed = "0x1e6a2c48", @@ -1158,6 +1167,9 @@ impl u64 { rot = 12, rot_op = "0xaa00000000006e1", rot_result = "0x6e10aa", + fsh_op = "0x2fe78e45983acd98", + fshl_result = "0x6e12fe", + fshr_result = "0x6e12fe78e45983ac", swap_op = "0x1234567890123456", swapped = "0x5634129078563412", reversed = "0x6a2c48091e6a2c48", @@ -1181,6 +1193,9 @@ impl u128 { rot = 16, rot_op = "0x13f40000000000000000000000004f76", rot_result = "0x4f7613f4", + fsh_op = "0x2fe78e45983acd98039000008736273", + fshl_result = "0x4f7602fe", + fshr_result = "0x4f7602fe78e45983acd9803900000873", swap_op = "0x12345678901234567890123456789012", swapped = "0x12907856341290785634129078563412", reversed = "0x48091e6a2c48091e6a2c48091e6a2c48", @@ -1207,6 +1222,9 @@ impl usize { rot = 4, rot_op = "0xa003", rot_result = "0x3a", + fsh_op = "0x2fe78e45983acd98039000008736273", + fshl_result = "0x4f7602fe", + fshr_result = "0x4f7602fe78e45983acd9803900000873", swap_op = "0x1234", swapped = "0x3412", reversed = "0x2c48", @@ -1231,6 +1249,9 @@ impl usize { rot = 8, rot_op = "0x10000b3", rot_result = "0xb301", + fsh_op = "0x2fe78e45", + fshl_result = "0xb32f", + fshr_result = "0xb32fe78e", swap_op = "0x12345678", swapped = "0x78563412", reversed = "0x1e6a2c48", @@ -1255,6 +1276,9 @@ impl usize { rot = 12, rot_op = "0xaa00000000006e1", rot_result = "0x6e10aa", + fsh_op = "0x2fe78e45983acd98", + fshl_result = "0x6e12fe", + fshr_result = "0x6e12fe78e45983ac", swap_op = "0x1234567890123456", swapped = "0x5634129078563412", reversed = "0x6a2c48091e6a2c48", diff --git a/library/core/src/num/uint_macros.rs b/library/core/src/num/uint_macros.rs index 10d9498d15e45..1fef3c822b720 100644 --- a/library/core/src/num/uint_macros.rs +++ b/library/core/src/num/uint_macros.rs @@ -14,6 +14,9 @@ macro_rules! uint_impl { rot = $rot:literal, rot_op = $rot_op:literal, rot_result = $rot_result:literal, + fsh_op = $fsh_op:literal, + fshl_result = $fshl_result:literal, + fshr_result = $fshr_result:literal, swap_op = $swap_op:literal, swapped = $swapped:literal, reversed = $reversed:literal, @@ -375,6 +378,76 @@ macro_rules! uint_impl { return intrinsics::rotate_right(self, n); } + /// Performs a left funnel shift (concatenates `self` with `rhs`, with `self` + /// making up the most significant half, then shifts the combined value left + /// by `n`, and most significant half is extracted to produce the result). + /// + /// Please note this isn't the same operation as the `<<` shifting operator or + /// [`rotate_left`](Self::rotate_left), although `a.funnel_shl(a, n)` is *equivalent* + /// to `a.rotate_left(n)`. + /// + /// # Panics + /// + /// If `n` is greater than or equal to the number of bits in `self` + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// #![feature(funnel_shifts)] + #[doc = concat!("let a = ", $rot_op, stringify!($SelfT), ";")] + #[doc = concat!("let b = ", $fsh_op, stringify!($SelfT), ";")] + #[doc = concat!("let m = ", $fshl_result, ";")] + /// + #[doc = concat!("assert_eq!(a.funnel_shl(b, ", $rot, "), m);")] + /// ``` + #[rustc_const_unstable(feature = "funnel_shifts", issue = "145686")] + #[unstable(feature = "funnel_shifts", issue = "145686")] + #[must_use = "this returns the result of the operation, \ + without modifying the original"] + #[inline(always)] + pub const fn funnel_shl(self, rhs: Self, n: u32) -> Self { + assert!(n < Self::BITS, "attempt to funnel shift left with overflow"); + // SAFETY: just checked that `shift` is in-range + unsafe { intrinsics::unchecked_funnel_shl(self, rhs, n) } + } + + /// Performs a right funnel shift (concatenates `self` and `rhs`, with `self` + /// making up the most significant half, then shifts the combined value right + /// by `n`, and least significant half is extracted to produce the result). + /// + /// Please note this isn't the same operation as the `>>` shifting operator or + /// [`rotate_right`](Self::rotate_right), although `a.funnel_shr(a, n)` is *equivalent* + /// to `a.rotate_right(n)`. + /// + /// # Panics + /// + /// If `n` is greater than or equal to the number of bits in `self` + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// #![feature(funnel_shifts)] + #[doc = concat!("let a = ", $rot_op, stringify!($SelfT), ";")] + #[doc = concat!("let b = ", $fsh_op, stringify!($SelfT), ";")] + #[doc = concat!("let m = ", $fshr_result, ";")] + /// + #[doc = concat!("assert_eq!(a.funnel_shr(b, ", $rot, "), m);")] + /// ``` + #[rustc_const_unstable(feature = "funnel_shifts", issue = "145686")] + #[unstable(feature = "funnel_shifts", issue = "145686")] + #[must_use = "this returns the result of the operation, \ + without modifying the original"] + #[inline(always)] + pub const fn funnel_shr(self, rhs: Self, n: u32) -> Self { + assert!(n < Self::BITS, "attempt to funnel shift right with overflow"); + // SAFETY: just checked that `shift` is in-range + unsafe { intrinsics::unchecked_funnel_shr(self, rhs, n) } + } + /// Reverses the byte order of the integer. /// /// # Examples diff --git a/library/coretests/tests/lib.rs b/library/coretests/tests/lib.rs index d2281b1df2ffc..c16c344776dd0 100644 --- a/library/coretests/tests/lib.rs +++ b/library/coretests/tests/lib.rs @@ -50,6 +50,7 @@ #![feature(fmt_internals)] #![feature(formatting_options)] #![feature(freeze)] +#![feature(funnel_shifts)] #![feature(future_join)] #![feature(generic_assert_internals)] #![feature(hasher_prefixfree_extras)] diff --git a/library/coretests/tests/num/uint_macros.rs b/library/coretests/tests/num/uint_macros.rs index c7d10ea4d880a..63be8a45b5cfd 100644 --- a/library/coretests/tests/num/uint_macros.rs +++ b/library/coretests/tests/num/uint_macros.rs @@ -104,6 +104,19 @@ macro_rules! uint_module { assert_eq_const_safe!($T: C.rotate_left(128), C); } + fn test_funnel_shift() { + // Shifting by 0 should have no effect + assert_eq_const_safe!($T: <$T>::funnel_shl(A, B, 0), A); + assert_eq_const_safe!($T: <$T>::funnel_shr(A, B, 0), B); + + assert_eq_const_safe!($T: <$T>::funnel_shl(_0, _1, 4), 0b1111); + assert_eq_const_safe!($T: <$T>::funnel_shr(_0, _1, 4), _1 >> 4); + assert_eq_const_safe!($T: <$T>::funnel_shl(_1, _0, 4), _1 << 4); + + assert_eq_const_safe!($T: <$T>::funnel_shl(_1, _1, 4), <$T>::rotate_left(_1, 4)); + assert_eq_const_safe!($T: <$T>::funnel_shr(_1, _1, 4), <$T>::rotate_right(_1, 4)); + } + fn test_swap_bytes() { assert_eq_const_safe!($T: A.swap_bytes().swap_bytes(), A); assert_eq_const_safe!($T: B.swap_bytes().swap_bytes(), B); @@ -150,6 +163,29 @@ macro_rules! uint_module { } } + #[test] + #[should_panic = "attempt to funnel shift left with overflow"] + fn test_funnel_shl_overflow() { + let _ = <$T>::funnel_shl(A, B, $T::BITS); + } + + #[test] + #[should_panic = "attempt to funnel shift right with overflow"] + fn test_funnel_shr_overflow() { + let _ = <$T>::funnel_shr(A, B, $T::BITS); + } + + #[test] + fn test_funnel_shifts_runtime() { + for i in 0..$T::BITS - 1 { + assert_eq!(<$T>::funnel_shl(A, 0, i), A << i); + assert_eq!(<$T>::funnel_shl(A, A, i), A.rotate_left(i)); + + assert_eq!(<$T>::funnel_shr(0, A, i), A >> i); + assert_eq!(<$T>::funnel_shr(A, A, i), A.rotate_right(i)); + } + } + #[test] fn test_isolate_highest_one() { const BITS: $T = <$T>::MAX; diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index ab417b6c72f9b..75018424c4e92 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -296,6 +296,7 @@ #![feature(f128)] #![feature(ffi_const)] #![feature(formatting_options)] +#![feature(funnel_shifts)] #![feature(hash_map_internals)] #![feature(hash_map_macro)] #![feature(if_let_guard)] diff --git a/src/tools/miri/tests/fail/intrinsics/funnel_shl.rs b/src/tools/miri/tests/fail/intrinsics/funnel_shl.rs new file mode 100644 index 0000000000000..40b61f65ebe88 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/funnel_shl.rs @@ -0,0 +1,7 @@ +#![feature(core_intrinsics, funnel_shifts)] + +fn main() { + unsafe { + std::intrinsics::unchecked_funnel_shl(1_u32, 2, 32); //~ ERROR: Undefined Behavior + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/funnel_shl.stderr b/src/tools/miri/tests/fail/intrinsics/funnel_shl.stderr new file mode 100644 index 0000000000000..fc828021b132c --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/funnel_shl.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: `assume` called with `false` + --> tests/fail/intrinsics/funnel_shl.rs:LL:CC + | +LL | std::intrinsics::unchecked_funnel_shl(1_u32, 2, 32); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at tests/fail/intrinsics/funnel_shl.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/funnel_shr.rs b/src/tools/miri/tests/fail/intrinsics/funnel_shr.rs new file mode 100644 index 0000000000000..9582265639716 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/funnel_shr.rs @@ -0,0 +1,7 @@ +#![feature(core_intrinsics, funnel_shifts)] + +fn main() { + unsafe { + std::intrinsics::unchecked_funnel_shr(1_u32, 2, 32); //~ ERROR: Undefined Behavior + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/funnel_shr.stderr b/src/tools/miri/tests/fail/intrinsics/funnel_shr.stderr new file mode 100644 index 0000000000000..7361df1b4c7aa --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/funnel_shr.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: `assume` called with `false` + --> tests/fail/intrinsics/funnel_shr.rs:LL:CC + | +LL | std::intrinsics::unchecked_funnel_shr(1_u32, 2, 32); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at tests/fail/intrinsics/funnel_shr.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/pass/intrinsics/integer.rs b/src/tools/miri/tests/pass/intrinsics/integer.rs index 13e7bd8e1b944..8727b6d3c87ec 100644 --- a/src/tools/miri/tests/pass/intrinsics/integer.rs +++ b/src/tools/miri/tests/pass/intrinsics/integer.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 // SPDX-FileCopyrightText: The Rust Project Developers (see https://thanks.rust-lang.org) -#![feature(core_intrinsics)] +#![feature(core_intrinsics, funnel_shifts)] use std::intrinsics::*; pub fn main() { @@ -143,5 +143,11 @@ pub fn main() { assert_eq!(unchecked_mul(6u8, 7), 42); assert_eq!(unchecked_mul(13, -5), -65); + + assert_eq!(unchecked_funnel_shl(1_u32, 2, 5), 32); + assert_eq!(unchecked_funnel_shl(1_u32, 2, 31), 0x80000001); + + assert_eq!(unchecked_funnel_shr(1_u32, 2, 5), 0x08000000); + assert_eq!(unchecked_funnel_shr(1_u32, 2, 31), 2); } }