From a7aedeb5c8895c470cb2f693115f10f0961ab40d Mon Sep 17 00:00:00 2001 From: sayantn Date: Thu, 9 Oct 2025 17:08:17 +0530 Subject: [PATCH] implement SIMD funnel shifts in const-eval --- .../src/interpret/intrinsics/simd.rs | 54 ++++++++++++++++++- .../intrinsics/simd-funnel_shl-too-far.rs | 12 +++++ .../intrinsics/simd-funnel_shl-too-far.stderr | 13 +++++ .../intrinsics/simd-funnel_shr-too-far.rs | 12 +++++ .../intrinsics/simd-funnel_shr-too-far.stderr | 13 +++++ .../tests/pass/intrinsics/portable-simd.rs | 16 +++++- 6 files changed, 117 insertions(+), 3 deletions(-) create mode 100644 src/tools/miri/tests/fail/intrinsics/simd-funnel_shl-too-far.rs create mode 100644 src/tools/miri/tests/fail/intrinsics/simd-funnel_shl-too-far.stderr create mode 100644 src/tools/miri/tests/fail/intrinsics/simd-funnel_shr-too-far.rs create mode 100644 src/tools/miri/tests/fail/intrinsics/simd-funnel_shr-too-far.stderr diff --git a/compiler/rustc_const_eval/src/interpret/intrinsics/simd.rs b/compiler/rustc_const_eval/src/interpret/intrinsics/simd.rs index 751674f3f068d..bae423840ee1b 100644 --- a/compiler/rustc_const_eval/src/interpret/intrinsics/simd.rs +++ b/compiler/rustc_const_eval/src/interpret/intrinsics/simd.rs @@ -3,7 +3,7 @@ use rustc_abi::{BackendRepr, Endian}; use rustc_apfloat::ieee::{Double, Half, Quad, Single}; use rustc_apfloat::{Float, Round}; use rustc_middle::mir::interpret::{InterpErrorKind, Pointer, UndefinedBehaviorInfo}; -use rustc_middle::ty::{FloatTy, SimdAlign}; +use rustc_middle::ty::{FloatTy, ScalarInt, SimdAlign}; use rustc_middle::{bug, err_ub_format, mir, span_bug, throw_unsup_format, ty}; use rustc_span::{Symbol, sym}; use tracing::trace; @@ -744,6 +744,58 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { self.write_scalar(val, &dest)?; } } + sym::simd_funnel_shl | sym::simd_funnel_shr => { + let (left, _) = self.project_to_simd(&args[0])?; + let (right, _) = self.project_to_simd(&args[1])?; + let (shift, _) = self.project_to_simd(&args[2])?; + let (dest, _) = self.project_to_simd(&dest)?; + + let (len, elem_ty) = args[0].layout.ty.simd_size_and_type(*self.tcx); + let (elem_size, _signed) = elem_ty.int_size_and_signed(*self.tcx); + let elem_size_bits = u128::from(elem_size.bits()); + + let is_left = intrinsic_name == sym::simd_funnel_shl; + + for i in 0..len { + let left = + self.read_scalar(&self.project_index(&left, i)?)?.to_bits(elem_size)?; + let right = + self.read_scalar(&self.project_index(&right, i)?)?.to_bits(elem_size)?; + let shift_bits = + self.read_scalar(&self.project_index(&shift, i)?)?.to_bits(elem_size)?; + + if shift_bits >= elem_size_bits { + throw_ub_format!( + "overflowing shift by {shift_bits} in `{intrinsic_name}` in lane {i}" + ); + } + let inv_shift_bits = u32::try_from(elem_size_bits - shift_bits).unwrap(); + + // A funnel shift left by S can be implemented as `(x << S) | y.unbounded_shr(SIZE - S)`. + // The `unbounded_shr` is needed because otherwise if `S = 0`, it would be `x | y` + // when it should be `x`. + // + // This selects the least-significant `SIZE - S` bits of `x`, followed by the `S` most + // significant bits of `y`. As `left` and `right` both occupy the lower `SIZE` bits, + // we can treat the lower `SIZE` bits as an integer of the right width and use + // the same implementation, but on a zero-extended `x` and `y`. This works because + // `x << S` just pushes the `SIZE-S` MSBs out, and `y >> (SIZE - S)` shifts in + // zeros, as it is zero-extended. To the lower `SIZE` bits, this looks just like a + // funnel shift left. + // + // Note that the `unbounded_sh{l,r}`s are needed only in case we are using this on + // `u128xN` and `inv_shift_bits == 128`. + let result_bits = if is_left { + (left << shift_bits) | right.unbounded_shr(inv_shift_bits) + } else { + left.unbounded_shl(inv_shift_bits) | (right >> shift_bits) + }; + let (result, _overflow) = ScalarInt::truncate_from_uint(result_bits, elem_size); + + let dest = self.project_index(&dest, i)?; + self.write_scalar(result, &dest)?; + } + } // Unsupported intrinsic: skip the return_to_block below. _ => return interp_ok(false), diff --git a/src/tools/miri/tests/fail/intrinsics/simd-funnel_shl-too-far.rs b/src/tools/miri/tests/fail/intrinsics/simd-funnel_shl-too-far.rs new file mode 100644 index 0000000000000..54cecc23f9e71 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/simd-funnel_shl-too-far.rs @@ -0,0 +1,12 @@ +#![feature(core_intrinsics, portable_simd)] + +use std::intrinsics::simd::simd_funnel_shl; +use std::simd::*; + +fn main() { + unsafe { + let x = i32x2::from_array([1, 1]); + let y = i32x2::from_array([100, 0]); + simd_funnel_shl(x, x, y); //~ERROR: overflowing shift by 100 in `simd_funnel_shl` in lane 0 + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/simd-funnel_shl-too-far.stderr b/src/tools/miri/tests/fail/intrinsics/simd-funnel_shl-too-far.stderr new file mode 100644 index 0000000000000..00c7c39a8856f --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/simd-funnel_shl-too-far.stderr @@ -0,0 +1,13 @@ +error: Undefined Behavior: overflowing shift by 100 in `simd_funnel_shl` in lane 0 + --> tests/fail/intrinsics/simd-funnel_shl-too-far.rs:LL:CC + | +LL | simd_funnel_shl(x, x, y); + | ^^^^^^^^^^^^^^^^^^^^^^^^ 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: 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/simd-funnel_shr-too-far.rs b/src/tools/miri/tests/fail/intrinsics/simd-funnel_shr-too-far.rs new file mode 100644 index 0000000000000..6fb2daa488484 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/simd-funnel_shr-too-far.rs @@ -0,0 +1,12 @@ +#![feature(core_intrinsics, portable_simd)] + +use std::intrinsics::simd::simd_funnel_shr; +use std::simd::*; + +fn main() { + unsafe { + let x = i32x2::from_array([1, 1]); + let y = i32x2::from_array([20, 40]); + simd_funnel_shr(x, x, y); //~ERROR: overflowing shift by 40 in `simd_funnel_shr` in lane 1 + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/simd-funnel_shr-too-far.stderr b/src/tools/miri/tests/fail/intrinsics/simd-funnel_shr-too-far.stderr new file mode 100644 index 0000000000000..2f6c6cd71ea6d --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/simd-funnel_shr-too-far.stderr @@ -0,0 +1,13 @@ +error: Undefined Behavior: overflowing shift by 40 in `simd_funnel_shr` in lane 1 + --> tests/fail/intrinsics/simd-funnel_shr-too-far.rs:LL:CC + | +LL | simd_funnel_shr(x, x, y); + | ^^^^^^^^^^^^^^^^^^^^^^^^ 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: 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/portable-simd.rs b/src/tools/miri/tests/pass/intrinsics/portable-simd.rs index 4ecbe167b5253..961a4b82a7e9b 100644 --- a/src/tools/miri/tests/pass/intrinsics/portable-simd.rs +++ b/src/tools/miri/tests/pass/intrinsics/portable-simd.rs @@ -62,7 +62,7 @@ impl PackedSimd { #[rustc_nounwind] pub unsafe fn simd_shuffle_const_generic(x: T, y: T) -> U; -pub fn simd_ops_f16() { +fn simd_ops_f16() { use intrinsics::*; // small hack to make type inference better @@ -273,7 +273,7 @@ fn simd_ops_f64() { assert_eq!(f64x2::from_array([f64::NAN, 0.0]).reduce_min(), 0.0); } -pub fn simd_ops_f128() { +fn simd_ops_f128() { use intrinsics::*; // small hack to make type inference better @@ -454,6 +454,18 @@ fn simd_ops_i32() { 0x3fffffffu32 as i32 ]) ); + + // these values are taken from the doctests of `u32::funnel_shl` and `u32::funnel_shr` + let c = u32x4::splat(0x010000b3); + let d = u32x4::splat(0x2fe78e45); + + unsafe { + assert_eq!(intrinsics::simd_funnel_shl(c, d, u32x4::splat(0)), c); + assert_eq!(intrinsics::simd_funnel_shl(c, d, u32x4::splat(8)), u32x4::splat(0x0000b32f)); + + assert_eq!(intrinsics::simd_funnel_shr(c, d, u32x4::splat(0)), d); + assert_eq!(intrinsics::simd_funnel_shr(c, d, u32x4::splat(8)), u32x4::splat(0xb32fe78e)); + } } fn simd_mask() {