Add funnel_sh{l,r} functions and intrinsics

- Add a fallback implementation for the intrinsics
 - Add LLVM backend support for funnel shifts

Co-Authored-By: folkertdev <folkert@folkertdev.nl>
This commit is contained in:
sayantn 2025-08-21 04:47:05 +05:30
parent f5703d5dd3
commit 62b4347e80
No known key found for this signature in database
GPG key ID: B60412E056614AA4
16 changed files with 338 additions and 9 deletions

View file

@ -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;

View file

@ -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))
}

View file

@ -2269,6 +2269,8 @@ symbols! {
unboxed_closures,
unchecked_add,
unchecked_div,
unchecked_funnel_shl,
unchecked_funnel_shr,
unchecked_mul,
unchecked_rem,
unchecked_shl,

View file

@ -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
}

View file

@ -2102,6 +2102,61 @@ pub const fn saturating_add<T: Copy>(a: T, b: T) -> T;
#[rustc_intrinsic]
pub const fn saturating_sub<T: Copy>(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<T: [const] fallback::FunnelShift>(
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<T: [const] fallback::FunnelShift>(
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.
///

View file

@ -156,6 +156,7 @@
#![feature(f128)]
#![feature(freeze_impls)]
#![feature(fundamental)]
#![feature(funnel_shifts)]
#![feature(if_let_guard)]
#![feature(intra_doc_pointers)]
#![feature(intrinsics)]

View file

@ -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",

View file

@ -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

View file

@ -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)]

View file

@ -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;

View file

@ -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)]

View file

@ -0,0 +1,7 @@
#![feature(core_intrinsics, funnel_shifts)]
fn main() {
unsafe {
std::intrinsics::unchecked_funnel_shl(1_u32, 2, 32); //~ ERROR: Undefined Behavior
}
}

View file

@ -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

View file

@ -0,0 +1,7 @@
#![feature(core_intrinsics, funnel_shifts)]
fn main() {
unsafe {
std::intrinsics::unchecked_funnel_shr(1_u32, 2, 32); //~ ERROR: Undefined Behavior
}
}

View file

@ -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

View file

@ -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);
}
}