| use either::Either; |
| 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, ScalarInt, SimdAlign}; |
| use rustc_middle::{bug, err_ub_format, mir, span_bug, throw_unsup_format, ty}; |
| use rustc_span::{Symbol, sym}; |
| use tracing::trace; |
| |
| use super::{ |
| ImmTy, InterpCx, InterpResult, Machine, MinMax, MulAddType, OpTy, PlaceTy, Provenance, Scalar, |
| Size, TyAndLayout, assert_matches, interp_ok, throw_ub_format, |
| }; |
| use crate::interpret::Writeable; |
| |
| impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { |
| /// Returns `true` if emulation happened. |
| /// Here we implement the intrinsics that are common to all CTFE instances; individual machines can add their own |
| /// intrinsic handling. |
| pub fn eval_simd_intrinsic( |
| &mut self, |
| intrinsic_name: Symbol, |
| generic_args: ty::GenericArgsRef<'tcx>, |
| args: &[OpTy<'tcx, M::Provenance>], |
| dest: &PlaceTy<'tcx, M::Provenance>, |
| ret: Option<mir::BasicBlock>, |
| ) -> InterpResult<'tcx, bool> { |
| let dest = dest.force_mplace(self)?; |
| |
| match intrinsic_name { |
| sym::simd_insert => { |
| let index = u64::from(self.read_scalar(&args[1])?.to_u32()?); |
| let elem = &args[2]; |
| let (input, input_len) = self.project_to_simd(&args[0])?; |
| let (dest, dest_len) = self.project_to_simd(&dest)?; |
| assert_eq!(input_len, dest_len, "Return vector length must match input length"); |
| // Bounds are not checked by typeck so we have to do it ourselves. |
| if index >= input_len { |
| throw_ub_format!( |
| "`simd_insert` index {index} is out-of-bounds of vector with length {input_len}" |
| ); |
| } |
| |
| for i in 0..dest_len { |
| let place = self.project_index(&dest, i)?; |
| let value = |
| if i == index { elem.clone() } else { self.project_index(&input, i)? }; |
| self.copy_op(&value, &place)?; |
| } |
| } |
| sym::simd_extract => { |
| let index = u64::from(self.read_scalar(&args[1])?.to_u32()?); |
| let (input, input_len) = self.project_to_simd(&args[0])?; |
| // Bounds are not checked by typeck so we have to do it ourselves. |
| if index >= input_len { |
| throw_ub_format!( |
| "`simd_extract` index {index} is out-of-bounds of vector with length {input_len}" |
| ); |
| } |
| self.copy_op(&self.project_index(&input, index)?, &dest)?; |
| } |
| sym::simd_neg |
| | sym::simd_fabs |
| | sym::simd_ceil |
| | sym::simd_floor |
| | sym::simd_round |
| | sym::simd_round_ties_even |
| | sym::simd_trunc |
| | sym::simd_ctlz |
| | sym::simd_ctpop |
| | sym::simd_cttz |
| | sym::simd_bswap |
| | sym::simd_bitreverse => { |
| let (op, op_len) = self.project_to_simd(&args[0])?; |
| let (dest, dest_len) = self.project_to_simd(&dest)?; |
| |
| assert_eq!(dest_len, op_len); |
| |
| #[derive(Copy, Clone)] |
| enum Op { |
| MirOp(mir::UnOp), |
| Abs, |
| Round(rustc_apfloat::Round), |
| Numeric(Symbol), |
| } |
| let which = match intrinsic_name { |
| sym::simd_neg => Op::MirOp(mir::UnOp::Neg), |
| sym::simd_fabs => Op::Abs, |
| sym::simd_ceil => Op::Round(rustc_apfloat::Round::TowardPositive), |
| sym::simd_floor => Op::Round(rustc_apfloat::Round::TowardNegative), |
| sym::simd_round => Op::Round(rustc_apfloat::Round::NearestTiesToAway), |
| sym::simd_round_ties_even => Op::Round(rustc_apfloat::Round::NearestTiesToEven), |
| sym::simd_trunc => Op::Round(rustc_apfloat::Round::TowardZero), |
| sym::simd_ctlz => Op::Numeric(sym::ctlz), |
| sym::simd_ctpop => Op::Numeric(sym::ctpop), |
| sym::simd_cttz => Op::Numeric(sym::cttz), |
| sym::simd_bswap => Op::Numeric(sym::bswap), |
| sym::simd_bitreverse => Op::Numeric(sym::bitreverse), |
| _ => unreachable!(), |
| }; |
| |
| for i in 0..dest_len { |
| let op = self.read_immediate(&self.project_index(&op, i)?)?; |
| let dest = self.project_index(&dest, i)?; |
| let val = match which { |
| Op::MirOp(mir_op) => { |
| // this already does NaN adjustments |
| self.unary_op(mir_op, &op)?.to_scalar() |
| } |
| Op::Abs => { |
| // Works for f32 and f64. |
| let ty::Float(float_ty) = op.layout.ty.kind() else { |
| span_bug!( |
| self.cur_span(), |
| "{} operand is not a float", |
| intrinsic_name |
| ) |
| }; |
| let op = op.to_scalar(); |
| // "Bitwise" operation, no NaN adjustments |
| match float_ty { |
| FloatTy::F16 => Scalar::from_f16(op.to_f16()?.abs()), |
| FloatTy::F32 => Scalar::from_f32(op.to_f32()?.abs()), |
| FloatTy::F64 => Scalar::from_f64(op.to_f64()?.abs()), |
| FloatTy::F128 => Scalar::from_f128(op.to_f128()?.abs()), |
| } |
| } |
| Op::Round(rounding) => { |
| let ty::Float(float_ty) = op.layout.ty.kind() else { |
| span_bug!( |
| self.cur_span(), |
| "{} operand is not a float", |
| intrinsic_name |
| ) |
| }; |
| let op = op.to_scalar(); |
| match float_ty { |
| FloatTy::F16 => self.float_round::<Half>(op, rounding)?, |
| FloatTy::F32 => self.float_round::<Single>(op, rounding)?, |
| FloatTy::F64 => self.float_round::<Double>(op, rounding)?, |
| FloatTy::F128 => self.float_round::<Quad>(op, rounding)?, |
| } |
| } |
| Op::Numeric(name) => { |
| self.numeric_intrinsic(name, op.to_scalar(), op.layout, op.layout)? |
| } |
| }; |
| self.write_scalar(val, &dest)?; |
| } |
| } |
| sym::simd_add |
| | sym::simd_sub |
| | sym::simd_mul |
| | sym::simd_div |
| | sym::simd_rem |
| | sym::simd_shl |
| | sym::simd_shr |
| | sym::simd_and |
| | sym::simd_or |
| | sym::simd_xor |
| | sym::simd_eq |
| | sym::simd_ne |
| | sym::simd_lt |
| | sym::simd_le |
| | sym::simd_gt |
| | sym::simd_ge |
| | sym::simd_fmax |
| | sym::simd_fmin |
| | sym::simd_saturating_add |
| | sym::simd_saturating_sub |
| | sym::simd_arith_offset => { |
| use mir::BinOp; |
| |
| let (left, left_len) = self.project_to_simd(&args[0])?; |
| let (right, right_len) = self.project_to_simd(&args[1])?; |
| let (dest, dest_len) = self.project_to_simd(&dest)?; |
| |
| assert_eq!(dest_len, left_len); |
| assert_eq!(dest_len, right_len); |
| |
| enum Op { |
| MirOp(BinOp), |
| SaturatingOp(BinOp), |
| FMinMax(MinMax), |
| WrappingOffset, |
| } |
| let which = match intrinsic_name { |
| sym::simd_add => Op::MirOp(BinOp::Add), |
| sym::simd_sub => Op::MirOp(BinOp::Sub), |
| sym::simd_mul => Op::MirOp(BinOp::Mul), |
| sym::simd_div => Op::MirOp(BinOp::Div), |
| sym::simd_rem => Op::MirOp(BinOp::Rem), |
| sym::simd_shl => Op::MirOp(BinOp::ShlUnchecked), |
| sym::simd_shr => Op::MirOp(BinOp::ShrUnchecked), |
| sym::simd_and => Op::MirOp(BinOp::BitAnd), |
| sym::simd_or => Op::MirOp(BinOp::BitOr), |
| sym::simd_xor => Op::MirOp(BinOp::BitXor), |
| sym::simd_eq => Op::MirOp(BinOp::Eq), |
| sym::simd_ne => Op::MirOp(BinOp::Ne), |
| sym::simd_lt => Op::MirOp(BinOp::Lt), |
| sym::simd_le => Op::MirOp(BinOp::Le), |
| sym::simd_gt => Op::MirOp(BinOp::Gt), |
| sym::simd_ge => Op::MirOp(BinOp::Ge), |
| sym::simd_fmax => Op::FMinMax(MinMax::MaximumNumber), |
| sym::simd_fmin => Op::FMinMax(MinMax::MinimumNumber), |
| sym::simd_saturating_add => Op::SaturatingOp(BinOp::Add), |
| sym::simd_saturating_sub => Op::SaturatingOp(BinOp::Sub), |
| sym::simd_arith_offset => Op::WrappingOffset, |
| _ => unreachable!(), |
| }; |
| |
| for i in 0..dest_len { |
| let left = self.read_immediate(&self.project_index(&left, i)?)?; |
| let right = self.read_immediate(&self.project_index(&right, i)?)?; |
| let dest = self.project_index(&dest, i)?; |
| let val = match which { |
| Op::MirOp(mir_op) => { |
| // this does NaN adjustments. |
| let val = self.binary_op(mir_op, &left, &right).map_err_kind(|kind| { |
| match kind { |
| InterpErrorKind::UndefinedBehavior(UndefinedBehaviorInfo::ShiftOverflow { shift_amount, .. }) => { |
| // this resets the interpreter backtrace, but it's not worth avoiding that. |
| let shift_amount = match shift_amount { |
| Either::Left(v) => v.to_string(), |
| Either::Right(v) => v.to_string(), |
| }; |
| err_ub_format!("overflowing shift by {shift_amount} in `{intrinsic_name}` in lane {i}") |
| } |
| kind => kind |
| } |
| })?; |
| if matches!( |
| mir_op, |
| BinOp::Eq |
| | BinOp::Ne |
| | BinOp::Lt |
| | BinOp::Le |
| | BinOp::Gt |
| | BinOp::Ge |
| ) { |
| // Special handling for boolean-returning operations |
| assert_eq!(val.layout.ty, self.tcx.types.bool); |
| let val = val.to_scalar().to_bool().unwrap(); |
| bool_to_simd_element(val, dest.layout.size) |
| } else { |
| assert_ne!(val.layout.ty, self.tcx.types.bool); |
| assert_eq!(val.layout.ty, dest.layout.ty); |
| val.to_scalar() |
| } |
| } |
| Op::SaturatingOp(mir_op) => self.saturating_arith(mir_op, &left, &right)?, |
| Op::WrappingOffset => { |
| let ptr = left.to_scalar().to_pointer(self)?; |
| let offset_count = right.to_scalar().to_target_isize(self)?; |
| let pointee_ty = left.layout.ty.builtin_deref(true).unwrap(); |
| |
| let pointee_size = |
| i64::try_from(self.layout_of(pointee_ty)?.size.bytes()).unwrap(); |
| let offset_bytes = offset_count.wrapping_mul(pointee_size); |
| let offset_ptr = ptr.wrapping_signed_offset(offset_bytes, self); |
| Scalar::from_maybe_pointer(offset_ptr, self) |
| } |
| Op::FMinMax(op) => self.fminmax_op(op, &left, &right)?, |
| }; |
| self.write_scalar(val, &dest)?; |
| } |
| } |
| sym::simd_reduce_and |
| | sym::simd_reduce_or |
| | sym::simd_reduce_xor |
| | sym::simd_reduce_any |
| | sym::simd_reduce_all |
| | sym::simd_reduce_max |
| | sym::simd_reduce_min => { |
| use mir::BinOp; |
| |
| let (op, op_len) = self.project_to_simd(&args[0])?; |
| |
| let imm_from_bool = |b| { |
| ImmTy::from_scalar( |
| Scalar::from_bool(b), |
| self.layout_of(self.tcx.types.bool).unwrap(), |
| ) |
| }; |
| |
| enum Op { |
| MirOp(BinOp), |
| MirOpBool(BinOp), |
| MinMax(MinMax), |
| } |
| let which = match intrinsic_name { |
| sym::simd_reduce_and => Op::MirOp(BinOp::BitAnd), |
| sym::simd_reduce_or => Op::MirOp(BinOp::BitOr), |
| sym::simd_reduce_xor => Op::MirOp(BinOp::BitXor), |
| sym::simd_reduce_any => Op::MirOpBool(BinOp::BitOr), |
| sym::simd_reduce_all => Op::MirOpBool(BinOp::BitAnd), |
| sym::simd_reduce_max => Op::MinMax(MinMax::MaximumNumber), |
| sym::simd_reduce_min => Op::MinMax(MinMax::MinimumNumber), |
| _ => unreachable!(), |
| }; |
| |
| // Initialize with first lane, then proceed with the rest. |
| let mut res = self.read_immediate(&self.project_index(&op, 0)?)?; |
| if matches!(which, Op::MirOpBool(_)) { |
| // Convert to `bool` scalar. |
| res = imm_from_bool(simd_element_to_bool(res)?); |
| } |
| for i in 1..op_len { |
| let op = self.read_immediate(&self.project_index(&op, i)?)?; |
| res = match which { |
| Op::MirOp(mir_op) => self.binary_op(mir_op, &res, &op)?, |
| Op::MirOpBool(mir_op) => { |
| let op = imm_from_bool(simd_element_to_bool(op)?); |
| self.binary_op(mir_op, &res, &op)? |
| } |
| Op::MinMax(mmop) => { |
| if matches!(res.layout.ty.kind(), ty::Float(_)) { |
| ImmTy::from_scalar(self.fminmax_op(mmop, &res, &op)?, res.layout) |
| } else { |
| // Just boring integers, no NaNs to worry about. |
| let mirop = match mmop { |
| MinMax::MinimumNumber | MinMax::Minimum => BinOp::Le, |
| MinMax::MaximumNumber | MinMax::Maximum => BinOp::Ge, |
| }; |
| if self.binary_op(mirop, &res, &op)?.to_scalar().to_bool()? { |
| res |
| } else { |
| op |
| } |
| } |
| } |
| }; |
| } |
| self.write_immediate(*res, &dest)?; |
| } |
| sym::simd_reduce_add_ordered | sym::simd_reduce_mul_ordered => { |
| use mir::BinOp; |
| |
| let (op, op_len) = self.project_to_simd(&args[0])?; |
| let init = self.read_immediate(&args[1])?; |
| |
| let mir_op = match intrinsic_name { |
| sym::simd_reduce_add_ordered => BinOp::Add, |
| sym::simd_reduce_mul_ordered => BinOp::Mul, |
| _ => unreachable!(), |
| }; |
| |
| let mut res = init; |
| for i in 0..op_len { |
| let op = self.read_immediate(&self.project_index(&op, i)?)?; |
| res = self.binary_op(mir_op, &res, &op)?; |
| } |
| self.write_immediate(*res, &dest)?; |
| } |
| sym::simd_select => { |
| let (mask, mask_len) = self.project_to_simd(&args[0])?; |
| let (yes, yes_len) = self.project_to_simd(&args[1])?; |
| let (no, no_len) = self.project_to_simd(&args[2])?; |
| let (dest, dest_len) = self.project_to_simd(&dest)?; |
| |
| assert_eq!(dest_len, mask_len); |
| assert_eq!(dest_len, yes_len); |
| assert_eq!(dest_len, no_len); |
| |
| for i in 0..dest_len { |
| let mask = self.read_immediate(&self.project_index(&mask, i)?)?; |
| let yes = self.read_immediate(&self.project_index(&yes, i)?)?; |
| let no = self.read_immediate(&self.project_index(&no, i)?)?; |
| let dest = self.project_index(&dest, i)?; |
| |
| let val = if simd_element_to_bool(mask)? { yes } else { no }; |
| self.write_immediate(*val, &dest)?; |
| } |
| } |
| // Variant of `select` that takes a bitmask rather than a "vector of bool". |
| sym::simd_select_bitmask => { |
| let mask = &args[0]; |
| let (yes, yes_len) = self.project_to_simd(&args[1])?; |
| let (no, no_len) = self.project_to_simd(&args[2])?; |
| let (dest, dest_len) = self.project_to_simd(&dest)?; |
| let bitmask_len = dest_len.next_multiple_of(8); |
| if bitmask_len > 64 { |
| throw_unsup_format!( |
| "simd_select_bitmask: vectors larger than 64 elements are currently not supported" |
| ); |
| } |
| |
| assert_eq!(dest_len, yes_len); |
| assert_eq!(dest_len, no_len); |
| |
| // Read the mask, either as an integer or as an array. |
| let mask: u64 = match mask.layout.ty.kind() { |
| ty::Uint(_) => { |
| // Any larger integer type is fine. |
| assert!(mask.layout.size.bits() >= bitmask_len); |
| self.read_scalar(mask)?.to_bits(mask.layout.size)?.try_into().unwrap() |
| } |
| ty::Array(elem, _len) if elem == &self.tcx.types.u8 => { |
| // The array must have exactly the right size. |
| assert_eq!(mask.layout.size.bits(), bitmask_len); |
| // Read the raw bytes. |
| let mask = mask.assert_mem_place(); // arrays cannot be immediate |
| let mask_bytes = |
| self.read_bytes_ptr_strip_provenance(mask.ptr(), mask.layout.size)?; |
| // Turn them into a `u64` in the right way. |
| let mask_size = mask.layout.size.bytes_usize(); |
| let mut mask_arr = [0u8; 8]; |
| match self.tcx.data_layout.endian { |
| Endian::Little => { |
| // Fill the first N bytes. |
| mask_arr[..mask_size].copy_from_slice(mask_bytes); |
| u64::from_le_bytes(mask_arr) |
| } |
| Endian::Big => { |
| // Fill the last N bytes. |
| let i = mask_arr.len().strict_sub(mask_size); |
| mask_arr[i..].copy_from_slice(mask_bytes); |
| u64::from_be_bytes(mask_arr) |
| } |
| } |
| } |
| _ => bug!("simd_select_bitmask: invalid mask type {}", mask.layout.ty), |
| }; |
| |
| let dest_len = u32::try_from(dest_len).unwrap(); |
| for i in 0..dest_len { |
| let bit_i = simd_bitmask_index(i, dest_len, self.tcx.data_layout.endian); |
| let mask = mask & 1u64.strict_shl(bit_i); |
| let yes = self.read_immediate(&self.project_index(&yes, i.into())?)?; |
| let no = self.read_immediate(&self.project_index(&no, i.into())?)?; |
| let dest = self.project_index(&dest, i.into())?; |
| |
| let val = if mask != 0 { yes } else { no }; |
| self.write_immediate(*val, &dest)?; |
| } |
| // The remaining bits of the mask are ignored. |
| } |
| // Converts a "vector of bool" into a bitmask. |
| sym::simd_bitmask => { |
| let (op, op_len) = self.project_to_simd(&args[0])?; |
| let bitmask_len = op_len.next_multiple_of(8); |
| if bitmask_len > 64 { |
| throw_unsup_format!( |
| "simd_bitmask: vectors larger than 64 elements are currently not supported" |
| ); |
| } |
| |
| let op_len = u32::try_from(op_len).unwrap(); |
| let mut res = 0u64; |
| for i in 0..op_len { |
| let op = self.read_immediate(&self.project_index(&op, i.into())?)?; |
| if simd_element_to_bool(op)? { |
| let bit_i = simd_bitmask_index(i, op_len, self.tcx.data_layout.endian); |
| res |= 1u64.strict_shl(bit_i); |
| } |
| } |
| // Write the result, depending on the `dest` type. |
| // Returns either an unsigned integer or array of `u8`. |
| match dest.layout.ty.kind() { |
| ty::Uint(_) => { |
| // Any larger integer type is fine, it will be zero-extended. |
| assert!(dest.layout.size.bits() >= bitmask_len); |
| self.write_scalar(Scalar::from_uint(res, dest.layout.size), &dest)?; |
| } |
| ty::Array(elem, _len) if elem == &self.tcx.types.u8 => { |
| // The array must have exactly the right size. |
| assert_eq!(dest.layout.size.bits(), bitmask_len); |
| // We have to write the result byte-for-byte. |
| let res_size = dest.layout.size.bytes_usize(); |
| let res_bytes; |
| let res_bytes_slice = match self.tcx.data_layout.endian { |
| Endian::Little => { |
| res_bytes = res.to_le_bytes(); |
| &res_bytes[..res_size] // take the first N bytes |
| } |
| Endian::Big => { |
| res_bytes = res.to_be_bytes(); |
| &res_bytes[res_bytes.len().strict_sub(res_size)..] // take the last N bytes |
| } |
| }; |
| self.write_bytes_ptr(dest.ptr(), res_bytes_slice.iter().cloned())?; |
| } |
| _ => bug!("simd_bitmask: invalid return type {}", dest.layout.ty), |
| } |
| } |
| sym::simd_cast |
| | sym::simd_as |
| | sym::simd_cast_ptr |
| | sym::simd_with_exposed_provenance => { |
| let (op, op_len) = self.project_to_simd(&args[0])?; |
| let (dest, dest_len) = self.project_to_simd(&dest)?; |
| |
| assert_eq!(dest_len, op_len); |
| |
| let unsafe_cast = intrinsic_name == sym::simd_cast; |
| let safe_cast = intrinsic_name == sym::simd_as; |
| let ptr_cast = intrinsic_name == sym::simd_cast_ptr; |
| let from_exposed_cast = intrinsic_name == sym::simd_with_exposed_provenance; |
| |
| for i in 0..dest_len { |
| let op = self.read_immediate(&self.project_index(&op, i)?)?; |
| let dest = self.project_index(&dest, i)?; |
| |
| let val = match (op.layout.ty.kind(), dest.layout.ty.kind()) { |
| // Int-to-(int|float): always safe |
| (ty::Int(_) | ty::Uint(_), ty::Int(_) | ty::Uint(_) | ty::Float(_)) |
| if safe_cast || unsafe_cast => |
| self.int_to_int_or_float(&op, dest.layout)?, |
| // Float-to-float: always safe |
| (ty::Float(_), ty::Float(_)) if safe_cast || unsafe_cast => |
| self.float_to_float_or_int(&op, dest.layout)?, |
| // Float-to-int in safe mode |
| (ty::Float(_), ty::Int(_) | ty::Uint(_)) if safe_cast => |
| self.float_to_float_or_int(&op, dest.layout)?, |
| // Float-to-int in unchecked mode |
| (ty::Float(_), ty::Int(_) | ty::Uint(_)) if unsafe_cast => { |
| self.float_to_int_checked(&op, dest.layout, Round::TowardZero)? |
| .ok_or_else(|| { |
| err_ub_format!( |
| "`simd_cast` intrinsic called on {op} which cannot be represented in target type `{:?}`", |
| dest.layout.ty |
| ) |
| })? |
| } |
| // Ptr-to-ptr cast |
| (ty::RawPtr(..), ty::RawPtr(..)) if ptr_cast => |
| self.ptr_to_ptr(&op, dest.layout)?, |
| // Int->Ptr casts |
| (ty::Int(_) | ty::Uint(_), ty::RawPtr(..)) if from_exposed_cast => |
| self.pointer_with_exposed_provenance_cast(&op, dest.layout)?, |
| // Error otherwise |
| _ => |
| throw_unsup_format!( |
| "Unsupported SIMD cast from element type {from_ty} to {to_ty}", |
| from_ty = op.layout.ty, |
| to_ty = dest.layout.ty, |
| ), |
| }; |
| self.write_immediate(*val, &dest)?; |
| } |
| } |
| sym::simd_shuffle_const_generic => { |
| let (left, left_len) = self.project_to_simd(&args[0])?; |
| let (right, right_len) = self.project_to_simd(&args[1])?; |
| let (dest, dest_len) = self.project_to_simd(&dest)?; |
| |
| let index = generic_args[2].expect_const().to_branch(); |
| let index_len = index.len(); |
| |
| assert_eq!(left_len, right_len); |
| assert_eq!(u64::try_from(index_len).unwrap(), dest_len); |
| |
| for i in 0..dest_len { |
| let src_index: u64 = |
| index[usize::try_from(i).unwrap()].to_leaf().to_u32().into(); |
| let dest = self.project_index(&dest, i)?; |
| |
| let val = if src_index < left_len { |
| self.read_immediate(&self.project_index(&left, src_index)?)? |
| } else if src_index < left_len.strict_add(right_len) { |
| let right_idx = src_index.strict_sub(left_len); |
| self.read_immediate(&self.project_index(&right, right_idx)?)? |
| } else { |
| throw_ub_format!( |
| "`simd_shuffle_const_generic` index {src_index} is out-of-bounds for 2 vectors with length {dest_len}" |
| ); |
| }; |
| self.write_immediate(*val, &dest)?; |
| } |
| } |
| sym::simd_shuffle => { |
| let (left, left_len) = self.project_to_simd(&args[0])?; |
| let (right, right_len) = self.project_to_simd(&args[1])?; |
| let (index, index_len) = self.project_to_simd(&args[2])?; |
| let (dest, dest_len) = self.project_to_simd(&dest)?; |
| |
| assert_eq!(left_len, right_len); |
| assert_eq!(index_len, dest_len); |
| |
| for i in 0..dest_len { |
| let src_index: u64 = self |
| .read_immediate(&self.project_index(&index, i)?)? |
| .to_scalar() |
| .to_u32()? |
| .into(); |
| let dest = self.project_index(&dest, i)?; |
| |
| let val = if src_index < left_len { |
| self.read_immediate(&self.project_index(&left, src_index)?)? |
| } else if src_index < left_len.strict_add(right_len) { |
| let right_idx = src_index.strict_sub(left_len); |
| self.read_immediate(&self.project_index(&right, right_idx)?)? |
| } else { |
| throw_ub_format!( |
| "`simd_shuffle` index {src_index} is out-of-bounds for 2 vectors with length {dest_len}" |
| ); |
| }; |
| self.write_immediate(*val, &dest)?; |
| } |
| } |
| sym::simd_gather => { |
| let (passthru, passthru_len) = self.project_to_simd(&args[0])?; |
| let (ptrs, ptrs_len) = self.project_to_simd(&args[1])?; |
| let (mask, mask_len) = self.project_to_simd(&args[2])?; |
| let (dest, dest_len) = self.project_to_simd(&dest)?; |
| |
| assert_eq!(dest_len, passthru_len); |
| assert_eq!(dest_len, ptrs_len); |
| assert_eq!(dest_len, mask_len); |
| |
| for i in 0..dest_len { |
| let passthru = self.read_immediate(&self.project_index(&passthru, i)?)?; |
| let ptr = self.read_immediate(&self.project_index(&ptrs, i)?)?; |
| let mask = self.read_immediate(&self.project_index(&mask, i)?)?; |
| let dest = self.project_index(&dest, i)?; |
| |
| let val = if simd_element_to_bool(mask)? { |
| let place = self.deref_pointer(&ptr)?; |
| self.read_immediate(&place)? |
| } else { |
| passthru |
| }; |
| self.write_immediate(*val, &dest)?; |
| } |
| } |
| sym::simd_scatter => { |
| let (value, value_len) = self.project_to_simd(&args[0])?; |
| let (ptrs, ptrs_len) = self.project_to_simd(&args[1])?; |
| let (mask, mask_len) = self.project_to_simd(&args[2])?; |
| |
| assert_eq!(ptrs_len, value_len); |
| assert_eq!(ptrs_len, mask_len); |
| |
| for i in 0..ptrs_len { |
| let value = self.read_immediate(&self.project_index(&value, i)?)?; |
| let ptr = self.read_immediate(&self.project_index(&ptrs, i)?)?; |
| let mask = self.read_immediate(&self.project_index(&mask, i)?)?; |
| |
| if simd_element_to_bool(mask)? { |
| let place = self.deref_pointer(&ptr)?; |
| self.write_immediate(*value, &place)?; |
| } |
| } |
| } |
| sym::simd_masked_load => { |
| let dest_layout = dest.layout; |
| |
| let (mask, mask_len) = self.project_to_simd(&args[0])?; |
| let ptr = self.read_pointer(&args[1])?; |
| let (default, default_len) = self.project_to_simd(&args[2])?; |
| let (dest, dest_len) = self.project_to_simd(&dest)?; |
| |
| assert_eq!(dest_len, mask_len); |
| assert_eq!(dest_len, default_len); |
| |
| self.check_simd_ptr_alignment( |
| ptr, |
| dest_layout, |
| generic_args[3].expect_const().to_branch()[0].to_leaf().to_simd_alignment(), |
| )?; |
| |
| for i in 0..dest_len { |
| let mask = self.read_immediate(&self.project_index(&mask, i)?)?; |
| let default = self.read_immediate(&self.project_index(&default, i)?)?; |
| let dest = self.project_index(&dest, i)?; |
| |
| let val = if simd_element_to_bool(mask)? { |
| // Size * u64 is implemented as always checked |
| let ptr = ptr.wrapping_offset(dest.layout.size * i, self); |
| // we have already checked the alignment requirements |
| let place = self.ptr_to_mplace_unaligned(ptr, dest.layout); |
| self.read_immediate(&place)? |
| } else { |
| default |
| }; |
| self.write_immediate(*val, &dest)?; |
| } |
| } |
| sym::simd_masked_store => { |
| let (mask, mask_len) = self.project_to_simd(&args[0])?; |
| let ptr = self.read_pointer(&args[1])?; |
| let (vals, vals_len) = self.project_to_simd(&args[2])?; |
| |
| assert_eq!(mask_len, vals_len); |
| |
| self.check_simd_ptr_alignment( |
| ptr, |
| args[2].layout, |
| generic_args[3].expect_const().to_branch()[0].to_leaf().to_simd_alignment(), |
| )?; |
| |
| for i in 0..vals_len { |
| let mask = self.read_immediate(&self.project_index(&mask, i)?)?; |
| let val = self.read_immediate(&self.project_index(&vals, i)?)?; |
| |
| if simd_element_to_bool(mask)? { |
| // Size * u64 is implemented as always checked |
| let ptr = ptr.wrapping_offset(val.layout.size * i, self); |
| // we have already checked the alignment requirements |
| let place = self.ptr_to_mplace_unaligned(ptr, val.layout); |
| self.write_immediate(*val, &place)? |
| }; |
| } |
| } |
| sym::simd_fma | sym::simd_relaxed_fma => { |
| // `simd_fma` should always deterministically use `mul_add`, whereas `relaxed_fma` |
| // is non-deterministic, and can use either `mul_add` or `a * b + c` |
| let typ = match intrinsic_name { |
| sym::simd_fma => MulAddType::Fused, |
| sym::simd_relaxed_fma => MulAddType::Nondeterministic, |
| _ => unreachable!(), |
| }; |
| |
| let (a, a_len) = self.project_to_simd(&args[0])?; |
| let (b, b_len) = self.project_to_simd(&args[1])?; |
| let (c, c_len) = self.project_to_simd(&args[2])?; |
| let (dest, dest_len) = self.project_to_simd(&dest)?; |
| |
| assert_eq!(dest_len, a_len); |
| assert_eq!(dest_len, b_len); |
| assert_eq!(dest_len, c_len); |
| |
| for i in 0..dest_len { |
| let a = self.read_scalar(&self.project_index(&a, i)?)?; |
| let b = self.read_scalar(&self.project_index(&b, i)?)?; |
| let c = self.read_scalar(&self.project_index(&c, i)?)?; |
| let dest = self.project_index(&dest, i)?; |
| |
| let ty::Float(float_ty) = dest.layout.ty.kind() else { |
| span_bug!(self.cur_span(), "{} operand is not a float", intrinsic_name) |
| }; |
| |
| let val = match float_ty { |
| FloatTy::F16 => self.float_muladd::<Half>(a, b, c, typ)?, |
| FloatTy::F32 => self.float_muladd::<Single>(a, b, c, typ)?, |
| FloatTy::F64 => self.float_muladd::<Double>(a, b, c, typ)?, |
| FloatTy::F128 => self.float_muladd::<Quad>(a, b, c, typ)?, |
| }; |
| 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), |
| } |
| |
| trace!("{:?}", self.dump_place(&dest.clone().into())); |
| self.return_to_block(ret)?; |
| interp_ok(true) |
| } |
| |
| fn fminmax_op( |
| &self, |
| op: MinMax, |
| left: &ImmTy<'tcx, M::Provenance>, |
| right: &ImmTy<'tcx, M::Provenance>, |
| ) -> InterpResult<'tcx, Scalar<M::Provenance>> { |
| assert_eq!(left.layout.ty, right.layout.ty); |
| let ty::Float(float_ty) = left.layout.ty.kind() else { |
| bug!("fmax operand is not a float") |
| }; |
| let left = left.to_scalar(); |
| let right = right.to_scalar(); |
| interp_ok(match float_ty { |
| FloatTy::F16 => self.float_minmax::<Half>(left, right, op)?, |
| FloatTy::F32 => self.float_minmax::<Single>(left, right, op)?, |
| FloatTy::F64 => self.float_minmax::<Double>(left, right, op)?, |
| FloatTy::F128 => self.float_minmax::<Quad>(left, right, op)?, |
| }) |
| } |
| |
| fn check_simd_ptr_alignment( |
| &self, |
| ptr: Pointer<Option<M::Provenance>>, |
| vector_layout: TyAndLayout<'tcx>, |
| alignment: SimdAlign, |
| ) -> InterpResult<'tcx> { |
| assert_matches!(vector_layout.backend_repr, BackendRepr::SimdVector { .. }); |
| |
| let align = match alignment { |
| ty::SimdAlign::Unaligned => { |
| // The pointer is supposed to be unaligned, so no check is required. |
| return interp_ok(()); |
| } |
| ty::SimdAlign::Element => { |
| // Take the alignment of the only field, which is an array and therefore has the same |
| // alignment as the element type. |
| vector_layout.field(self, 0).align.abi |
| } |
| ty::SimdAlign::Vector => vector_layout.align.abi, |
| }; |
| |
| self.check_ptr_align(ptr, align) |
| } |
| } |
| |
| fn simd_bitmask_index(idx: u32, vec_len: u32, endianness: Endian) -> u32 { |
| assert!(idx < vec_len); |
| match endianness { |
| Endian::Little => idx, |
| #[expect(clippy::arithmetic_side_effects)] // idx < vec_len |
| Endian::Big => vec_len - 1 - idx, // reverse order of bits |
| } |
| } |
| |
| fn bool_to_simd_element<Prov: Provenance>(b: bool, size: Size) -> Scalar<Prov> { |
| // SIMD uses all-1 as pattern for "true". In two's complement, |
| // -1 has all its bits set to one and `from_int` will truncate or |
| // sign-extend it to `size` as required. |
| let val = if b { -1 } else { 0 }; |
| Scalar::from_int(val, size) |
| } |
| |
| fn simd_element_to_bool<Prov: Provenance>(elem: ImmTy<'_, Prov>) -> InterpResult<'_, bool> { |
| assert!( |
| matches!(elem.layout.ty.kind(), ty::Int(_) | ty::Uint(_)), |
| "SIMD mask element type must be an integer, but this is `{}`", |
| elem.layout.ty |
| ); |
| let val = elem.to_scalar().to_int(elem.layout.size)?; |
| interp_ok(match val { |
| 0 => false, |
| -1 => true, |
| _ => throw_ub_format!("each element of a SIMD mask must be all-0-bits or all-1-bits"), |
| }) |
| } |