Auto merge of #148478 - RalfJung:rotating-funnel, r=Mark-Simulacrum

use funnel shift as fallback impl for rotating shifts

That lets us remove this gnarly implementation from Miri and const-eval.

However, `rotate_left`/`rotate_right` are stable as const fn, so to do this we have to `rustc_allow_const_fn_unstable` a bunch of const trait stuff. Is that a bad idea? Cc `@oli-obk` `@fee1-dead`
This commit is contained in:
bors 2025-11-17 04:36:16 +00:00
commit 89fe96197d
6 changed files with 47 additions and 44 deletions

View file

@ -378,8 +378,6 @@ impl<'ll, 'tcx> IntrinsicCallBuilderMethods<'tcx> for Builder<'_, 'll, 'tcx> {
| sym::ctpop
| sym::bswap
| sym::bitreverse
| sym::rotate_left
| sym::rotate_right
| sym::saturating_add
| sym::saturating_sub
| sym::unchecked_funnel_shl
@ -424,19 +422,11 @@ 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
| sym::unchecked_funnel_shl
| sym::unchecked_funnel_shr => {
let is_left = name == sym::rotate_left || name == sym::unchecked_funnel_shl;
sym::unchecked_funnel_shl | sym::unchecked_funnel_shr => {
let is_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 rhs = args[1].immediate();
let raw_shift = 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

View file

@ -333,29 +333,6 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
let r = self.read_immediate(&args[1])?;
self.exact_div(&l, &r, dest)?;
}
sym::rotate_left | sym::rotate_right => {
// rotate_left: (X << (S % BW)) | (X >> ((BW - S) % BW))
// rotate_right: (X << ((BW - S) % BW)) | (X >> (S % BW))
let layout_val = self.layout_of(instance_args.type_at(0))?;
let val = self.read_scalar(&args[0])?;
let val_bits = val.to_bits(layout_val.size)?; // sign is ignored here
let layout_raw_shift = self.layout_of(self.tcx.types.u32)?;
let raw_shift = self.read_scalar(&args[1])?;
let raw_shift_bits = raw_shift.to_bits(layout_raw_shift.size)?;
let width_bits = u128::from(layout_val.size.bits());
let shift_bits = raw_shift_bits % width_bits;
let inv_shift_bits = (width_bits - shift_bits) % width_bits;
let result_bits = if intrinsic_name == sym::rotate_left {
(val_bits << shift_bits) | (val_bits >> inv_shift_bits)
} else {
(val_bits >> shift_bits) | (val_bits << inv_shift_bits)
};
let truncated_bits = layout_val.size.truncate(result_bits);
let result = Scalar::from_uint(truncated_bits, layout_val.size);
self.write_scalar(result, dest)?;
}
sym::copy => {
self.copy_intrinsic(&args[0], &args[1], &args[2], /*nonoverlapping*/ false)?;
}

View file

@ -56,7 +56,7 @@
use crate::ffi::va_list::{VaArgSafe, VaListImpl};
use crate::marker::{ConstParamTy, Destruct, DiscriminantKind, PointeeSized, Tuple};
use crate::ptr;
use crate::{mem, ptr};
mod bounds;
pub mod fallback;
@ -2013,7 +2013,14 @@ pub const unsafe fn unchecked_mul<T: Copy>(x: T, y: T) -> T;
#[rustc_intrinsic_const_stable_indirect]
#[rustc_nounwind]
#[rustc_intrinsic]
pub const fn rotate_left<T: Copy>(x: T, shift: u32) -> T;
#[rustc_allow_const_fn_unstable(const_trait_impl, funnel_shifts)]
#[miri::intrinsic_fallback_is_spec]
pub const fn rotate_left<T: [const] fallback::FunnelShift>(x: T, shift: u32) -> T {
// Make sure to call the intrinsic for `funnel_shl`, not the fallback impl.
// SAFETY: we modulo `shift` so that the result is definitely less than the size of
// `T` in bits.
unsafe { unchecked_funnel_shl(x, x, shift % (mem::size_of::<T>() as u32 * 8)) }
}
/// Performs rotate right.
///
@ -2028,7 +2035,14 @@ pub const fn rotate_left<T: Copy>(x: T, shift: u32) -> T;
#[rustc_intrinsic_const_stable_indirect]
#[rustc_nounwind]
#[rustc_intrinsic]
pub const fn rotate_right<T: Copy>(x: T, shift: u32) -> T;
#[rustc_allow_const_fn_unstable(const_trait_impl, funnel_shifts)]
#[miri::intrinsic_fallback_is_spec]
pub const fn rotate_right<T: [const] fallback::FunnelShift>(x: T, shift: u32) -> T {
// Make sure to call the intrinsic for `funnel_shr`, not the fallback impl.
// SAFETY: we modulo `shift` so that the result is definitely less than the size of
// `T` in bits.
unsafe { unchecked_funnel_shr(x, x, shift % (mem::size_of::<T>() as u32 * 8)) }
}
/// Returns (a + b) mod 2<sup>N</sup>, where N is the width of T in bits.
///

View file

@ -275,6 +275,10 @@ macro_rules! int_impl {
/// Shifts the bits to the left by a specified amount, `n`,
/// wrapping the truncated bits to the end of the resulting integer.
///
/// `rotate_left(n)` is equivalent to applying `rotate_left(1)` a total of `n` times. In
/// particular, a rotation by the number of bits in `self` returns the input value
/// unchanged.
///
/// Please note this isn't the same operation as the `<<` shifting operator!
///
/// # Examples
@ -284,6 +288,7 @@ macro_rules! int_impl {
#[doc = concat!("let m = ", $rot_result, ";")]
///
#[doc = concat!("assert_eq!(n.rotate_left(", $rot, "), m);")]
#[doc = concat!("assert_eq!(n.rotate_left(1024), n);")]
/// ```
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_const_stable(feature = "const_int_methods", since = "1.32.0")]
@ -298,6 +303,10 @@ macro_rules! int_impl {
/// wrapping the truncated bits to the beginning of the resulting
/// integer.
///
/// `rotate_right(n)` is equivalent to applying `rotate_right(1)` a total of `n` times. In
/// particular, a rotation by the number of bits in `self` returns the input value
/// unchanged.
///
/// Please note this isn't the same operation as the `>>` shifting operator!
///
/// # Examples
@ -307,6 +316,7 @@ macro_rules! int_impl {
#[doc = concat!("let m = ", $rot_op, ";")]
///
#[doc = concat!("assert_eq!(n.rotate_right(", $rot, "), m);")]
#[doc = concat!("assert_eq!(n.rotate_right(1024), n);")]
/// ```
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_const_stable(feature = "const_int_methods", since = "1.32.0")]

View file

@ -336,6 +336,10 @@ macro_rules! uint_impl {
/// Shifts the bits to the left by a specified amount, `n`,
/// wrapping the truncated bits to the end of the resulting integer.
///
/// `rotate_left(n)` is equivalent to applying `rotate_left(1)` a total of `n` times. In
/// particular, a rotation by the number of bits in `self` returns the input value
/// unchanged.
///
/// Please note this isn't the same operation as the `<<` shifting operator!
///
/// # Examples
@ -345,12 +349,14 @@ macro_rules! uint_impl {
#[doc = concat!("let m = ", $rot_result, ";")]
///
#[doc = concat!("assert_eq!(n.rotate_left(", $rot, "), m);")]
#[doc = concat!("assert_eq!(n.rotate_left(1024), n);")]
/// ```
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_const_stable(feature = "const_math", since = "1.32.0")]
#[must_use = "this returns the result of the operation, \
without modifying the original"]
#[inline(always)]
#[rustc_allow_const_fn_unstable(const_trait_impl)] // for the intrinsic fallback
pub const fn rotate_left(self, n: u32) -> Self {
return intrinsics::rotate_left(self, n);
}
@ -359,6 +365,10 @@ macro_rules! uint_impl {
/// wrapping the truncated bits to the beginning of the resulting
/// integer.
///
/// `rotate_right(n)` is equivalent to applying `rotate_right(1)` a total of `n` times. In
/// particular, a rotation by the number of bits in `self` returns the input value
/// unchanged.
///
/// Please note this isn't the same operation as the `>>` shifting operator!
///
/// # Examples
@ -368,12 +378,14 @@ macro_rules! uint_impl {
#[doc = concat!("let m = ", $rot_op, ";")]
///
#[doc = concat!("assert_eq!(n.rotate_right(", $rot, "), m);")]
#[doc = concat!("assert_eq!(n.rotate_right(1024), n);")]
/// ```
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_const_stable(feature = "const_math", since = "1.32.0")]
#[must_use = "this returns the result of the operation, \
without modifying the original"]
#[inline(always)]
#[rustc_allow_const_fn_unstable(const_trait_impl)] // for the intrinsic fallback
pub const fn rotate_right(self, n: u32) -> Self {
return intrinsics::rotate_right(self, n);
}

View file

@ -1,4 +1,4 @@
//@ compile-flags: -C no-prepopulate-passes
//@ compile-flags: -O
#![crate_type = "lib"]
#![feature(core_intrinsics)]
@ -9,7 +9,7 @@ use std::intrinsics::rotate_left;
#[no_mangle]
pub unsafe fn rotate_left_u16(x: u16, shift: u32) -> u16 {
// CHECK: %[[tmp:.*]] = trunc i32 %shift to i16
// CHECK: call i16 @llvm.fshl.i16(i16 %x, i16 %x, i16 %[[tmp]])
// CHECK: call noundef i16 @llvm.fshl.i16(i16 %x, i16 %x, i16 %[[tmp]])
rotate_left(x, shift)
}
@ -18,7 +18,7 @@ pub unsafe fn rotate_left_u16(x: u16, shift: u32) -> u16 {
pub unsafe fn rotate_left_u32(x: u32, shift: u32) -> u32 {
// CHECK-NOT: trunc
// CHECK-NOT: zext
// CHECK: call i32 @llvm.fshl.i32(i32 %x, i32 %x, i32 %shift)
// CHECK: call noundef i32 @llvm.fshl.i32(i32 %x, i32 %x, i32 %shift)
rotate_left(x, shift)
}
@ -26,6 +26,6 @@ pub unsafe fn rotate_left_u32(x: u32, shift: u32) -> u32 {
#[no_mangle]
pub unsafe fn rotate_left_u64(x: u64, shift: u32) -> u64 {
// CHECK: %[[tmp:.*]] = zext i32 %shift to i64
// CHECK: call i64 @llvm.fshl.i64(i64 %x, i64 %x, i64 %[[tmp]])
// CHECK: call noundef i64 @llvm.fshl.i64(i64 %x, i64 %x, i64 %[[tmp]])
rotate_left(x, shift)
}