| //! This module implements the [WebAssembly `SIMD128` ISA]. |
| //! |
| //! [WebAssembly `SIMD128` ISA]: |
| //! https://github.com/WebAssembly/simd/blob/master/proposals/simd/SIMD.md |
| // |
| // This files is structured as follows: |
| // * first the types are defined |
| // * then macros implementing the different APIs are provided |
| // * finally the API of each type is implements |
| // |
| #![allow(non_camel_case_types)] |
| |
| #[cfg(test)] |
| use stdsimd_test::assert_instr; |
| #[cfg(test)] |
| use wasm_bindgen_test::wasm_bindgen_test; |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // Types |
| |
| /// A single unconstrained byte (0-255). |
| pub type ImmByte = u8; |
| /// A byte with values in the range 0–1 identifying a lane. |
| pub type LaneIdx2 = u8; |
| /// A byte with values in the range 0–3 identifying a lane. |
| pub type LaneIdx4 = u8; |
| /// A byte with values in the range 0–7 identifying a lane. |
| pub type LaneIdx8 = u8; |
| /// A byte with values in the range 0–15 identifying a lane. |
| pub type LaneIdx16 = u8; |
| /// A byte with values in the range 0–31 identifying a lane. |
| pub type LaneIdx32 = u8; |
| |
| types! { |
| /// WASM-specific 128-bit wide SIMD vector type |
| pub struct v128(i128); |
| } |
| |
| mod sealed { |
| types! { |
| /// 128-bit wide SIMD vector type with 8 16-bit wide signed lanes |
| pub struct v8x16( |
| pub i8, pub i8, pub i8, pub i8, pub i8, pub i8, pub i8, pub i8, |
| pub i8, pub i8, pub i8, pub i8, pub i8, pub i8, pub i8, pub i8, |
| ); |
| /// 128-bit wide SIMD vector type with 8 16-bit wide signed lanes |
| pub struct v16x8( |
| pub i16, pub i16, pub i16, pub i16, |
| pub i16, pub i16, pub i16, pub i16 |
| ); |
| /// 128-bit wide SIMD vector type with 4 32-bit wide signed lanes |
| pub struct v32x4(pub i32, pub i32, pub i32, pub i32); |
| /// 128-bit wide SIMD vector type with 2 64-bit wide signed lanes |
| pub struct v64x2(pub i64, pub i64); |
| |
| /// 128-bit wide SIMD vector type with 8 16-bit wide unsigned lanes |
| pub struct u8x16( |
| pub u8, pub u8, pub u8, pub u8, pub u8, pub u8, pub u8, pub u8, |
| pub u8, pub u8, pub u8, pub u8, pub u8, pub u8, pub u8, pub u8, |
| ); |
| /// 128-bit wide SIMD vector type with 8 16-bit wide unsigned lanes |
| pub struct u16x8( |
| pub u16, pub u16, pub u16, pub u16, |
| pub u16, pub u16, pub u16, pub u16 |
| ); |
| /// 128-bit wide SIMD vector type with 4 32-bit wide unsigned lanes |
| pub struct u32x4(pub u32, pub u32, pub u32, pub u32); |
| /// 128-bit wide SIMD vector type with 2 64-bit wide unsigned lanes |
| pub struct u64x2(pub u64, pub u64); |
| |
| /// 128-bit wide SIMD vector type with 4 32-bit wide floating-point lanes |
| pub struct f32x4(pub f32, pub f32, pub f32, pub f32); |
| /// 128-bit wide SIMD vector type with 2 64-bit wide floating-point lanes |
| pub struct f64x2(pub f64, pub f64); |
| } |
| |
| #[allow(improper_ctypes)] |
| extern "C" { |
| #[link_name = "llvm.fabs.v4f32"] |
| fn abs_v4f32(x: f32x4) -> f32x4; |
| #[link_name = "llvm.fabs.v2f64"] |
| fn abs_v2f64(x: f64x2) -> f64x2; |
| #[link_name = "llvm.sqrt.v4f32"] |
| fn sqrt_v4f32(x: f32x4) -> f32x4; |
| #[link_name = "llvm.sqrt.v2f64"] |
| fn sqrt_v2f64(x: f64x2) -> f64x2; |
| #[link_name = "shufflevector"] |
| pub fn shufflevector_v16i8(x: v8x16, y: v8x16, i: v8x16) -> v8x16; |
| |
| } |
| impl f32x4 { |
| #[inline(always)] |
| pub unsafe fn abs(self) -> Self { |
| abs_v4f32(self) |
| } |
| #[inline(always)] |
| pub unsafe fn sqrt(self) -> Self { |
| sqrt_v4f32(self) |
| } |
| } |
| impl f64x2 { |
| #[inline(always)] |
| pub unsafe fn abs(self) -> Self { |
| abs_v2f64(self) |
| } |
| #[inline(always)] |
| pub unsafe fn sqrt(self) -> Self { |
| sqrt_v2f64(self) |
| } |
| } |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // Macros implementing the spec APIs: |
| |
| macro_rules! impl_splat { |
| ($id:ident[$ivec_ty:ident : $elem_ty:ident] <= $x_ty:ident | $($lane_id:ident),*) => { |
| /// Create vector with identical lanes |
| /// |
| /// Construct a vector with `x` replicated to all lanes. |
| #[inline] |
| // #[target_feature(enable = "simd128")] |
| // FIXME: #[cfg_attr(test, assert_instr($ident.splat))] |
| pub const unsafe fn splat(x: $x_ty) -> v128 { |
| union U { |
| vec: self::sealed::$ivec_ty, |
| res: v128 |
| } |
| U { vec: self::sealed::$ivec_ty($({ struct $lane_id; x as $elem_ty}),*) }.res |
| } |
| } |
| } |
| |
| macro_rules! impl_extract_lane { |
| ($id:ident[$ivec_ty:ident : $selem_ty:ident|$uelem_ty:ident]($lane_idx:ty) |
| => $x_ty:ident) => { |
| /// Extract lane as a scalar (sign-extend) |
| /// |
| /// Extract the scalar value of lane specified in the immediate |
| /// mode operand `imm` from `a` by sign-extending it. |
| #[inline] |
| // #[target_feature(enable = "simd128")] |
| // FIXME: #[cfg_attr(test, assert_instr($id.extract_lane_s, imm = |
| // 0))] |
| #[rustc_args_required_const(1)] |
| pub unsafe fn extract_lane_s(a: v128, imm: $lane_idx) -> $x_ty { |
| use coresimd::simd_llvm::simd_extract; |
| union U { |
| vec: self::sealed::$ivec_ty, |
| a: v128, |
| } |
| // the vectors store a signed integer => extract into it |
| let v: $selem_ty = simd_extract( |
| U { a }.vec, |
| imm as u32, /* zero-extends index */ |
| ); |
| v as $x_ty |
| } |
| |
| /// Extract lane as a scalar (zero-extend) |
| /// |
| /// Extract the scalar value of lane specified in the immediate |
| /// mode operand `imm` from `a` by zero-extending it. |
| #[inline] |
| // #[target_feature(enable = "simd128")] |
| // FIXME: #[cfg_attr(test, assert_instr($id.extract_lane_u, imm = |
| // 0))] |
| #[rustc_args_required_const(1)] |
| pub unsafe fn extract_lane_u(a: v128, imm: $lane_idx) -> $x_ty { |
| use coresimd::simd_llvm::simd_extract; |
| union U { |
| vec: self::sealed::$ivec_ty, |
| a: v128, |
| } |
| // the vectors store a signed integer => extract into it |
| let v: $selem_ty = simd_extract( |
| U { a }.vec, |
| imm as u32, /* zero-extends index */ |
| ); |
| // re-interpret the signed integer as an unsigned one of the |
| // same size (no-op) |
| let v: $uelem_ty = ::mem::transmute(v); |
| // cast the internal unsigned integer to a larger signed |
| // integer (zero-extends) |
| v as $x_ty |
| } |
| }; |
| ($id:ident[$ivec_ty:ident]($lane_idx:ty) => $x_ty:ident) => { |
| /// Extract lane as a scalar |
| /// |
| /// Extract the scalar value of lane specified in the immediate |
| /// mode operand `imm` from `a`. |
| #[inline] |
| // #[target_feature(enable = "simd128")] |
| // FIXME: #[cfg_attr(test, assert_instr($id.extract_lane_u, imm = |
| // 0))] |
| #[rustc_args_required_const(1)] |
| pub unsafe fn extract_lane(a: v128, imm: $lane_idx) -> $x_ty { |
| use coresimd::simd_llvm::simd_extract; |
| union U { |
| vec: self::sealed::$ivec_ty, |
| a: v128, |
| } |
| // the vectors store a signed integer => extract into it |
| simd_extract(U { a }.vec, imm as u32 /* zero-extends index */) |
| } |
| }; |
| } |
| |
| macro_rules! impl_replace_lane { |
| ($id:ident[$ivec_ty:ident:$ielem_ty:ident]($lane_idx:ty) <= $x_ty:ident) => { |
| /// Replace lane value |
| /// |
| /// Return a new vector with lanes identical to `a`, except for |
| /// lane specified in the immediate mode argument `i` which |
| /// has the value `x`. |
| #[inline] |
| // #[target_feature(enable = "simd128")] |
| // FIXME: #[cfg_attr(test, assert_instr($id.extract_lane_u))] |
| #[rustc_args_required_const(1)] |
| pub unsafe fn replace_lane(a: v128, imm: $lane_idx, x: $x_ty) -> v128 { |
| use coresimd::simd_llvm::simd_insert; |
| union U { |
| vec: self::sealed::$ivec_ty, |
| a: v128, |
| } |
| // the vectors store a signed integer => extract into it |
| ::mem::transmute(simd_insert( |
| U { a }.vec, |
| imm as u32, /* zero-extends index */ |
| x as $ielem_ty, |
| )) |
| } |
| }; |
| } |
| |
| macro_rules! impl_wrapping_add_sub_neg { |
| ($id:ident[$ivec_ty:ident]) => { |
| /// Lane-wise wrapping integer addition |
| #[inline] |
| // #[target_feature(enable = "simd128")] |
| // FIXME: #[cfg_attr(test, assert_instr($id.add))] |
| pub unsafe fn add(a: v128, b: v128) -> v128 { |
| use coresimd::simd_llvm::simd_add; |
| let a: sealed::$ivec_ty = ::mem::transmute(a); |
| let b: sealed::$ivec_ty = ::mem::transmute(b); |
| ::mem::transmute(simd_add(a, b)) |
| } |
| |
| /// Lane-wise wrapping integer subtraction |
| #[inline] |
| // #[target_feature(enable = "simd128")] |
| // FIXME: #[cfg_attr(test, assert_instr($id.sub))] |
| pub unsafe fn sub(a: v128, b: v128) -> v128 { |
| use coresimd::simd_llvm::simd_sub; |
| let a: sealed::$ivec_ty = ::mem::transmute(a); |
| let b: sealed::$ivec_ty = ::mem::transmute(b); |
| ::mem::transmute(simd_sub(a, b)) |
| } |
| |
| /// Lane-wise wrapping integer negation |
| #[inline] |
| // #[target_feature(enable = "simd128")] |
| // FIXME: #[cfg_attr(test, assert_instr($id.neg))] |
| pub unsafe fn neg(a: v128) -> v128 { |
| use coresimd::simd_llvm::simd_mul; |
| let a: sealed::$ivec_ty = ::mem::transmute(a); |
| let b: sealed::$ivec_ty = ::mem::transmute($id::splat(-1)); |
| ::mem::transmute(simd_mul(b, a)) |
| } |
| |
| // note: multiplication explicitly omitted because i64x2 does |
| // not implement it |
| }; |
| } |
| |
| // TODO: Saturating integer arithmetic |
| // need to add intrinsics to rustc |
| |
| // note: multiplication explicitly implemented separately because i64x2 does |
| // not implement it |
| |
| macro_rules! impl_wrapping_mul { |
| ($id:ident[$ivec_ty:ident]) => { |
| /// Lane-wise wrapping integer multiplication |
| #[inline] |
| // #[target_feature(enable = "simd128")] |
| // FIXME: #[cfg_attr(test, assert_instr($id.mul))] |
| pub unsafe fn mul(a: v128, b: v128) -> v128 { |
| use coresimd::simd_llvm::simd_mul; |
| let a: sealed::$ivec_ty = ::mem::transmute(a); |
| let b: sealed::$ivec_ty = ::mem::transmute(b); |
| ::mem::transmute(simd_mul(a, b)) |
| } |
| }; |
| } |
| |
| macro_rules! impl_shl_scalar { |
| ($id:ident[$ivec_ty:ident : $t:ty]) => { |
| /// Left shift by scalar. |
| /// |
| /// Shift the bits in each lane to the left by the same amount. |
| /// Only the low bits of the shift amount are used. |
| #[inline] |
| // #[target_feature(enable = "simd128")] |
| // FIXME: #[cfg_attr(test, assert_instr($id.shl))] |
| pub unsafe fn shl(a: v128, y: i32) -> v128 { |
| use coresimd::simd_llvm::simd_shl; |
| let a: sealed::$ivec_ty = ::mem::transmute(a); |
| let b: sealed::$ivec_ty = ::mem::transmute($id::splat(y as $t)); |
| ::mem::transmute(simd_shl(a, b)) |
| } |
| }; |
| } |
| |
| macro_rules! impl_shr_scalar { |
| ($id:ident[$svec_ty:ident : $uvec_ty:ident : $t:ty]) => { |
| /// Arithmetic right shift by scalar. |
| /// |
| /// Shift the bits in each lane to the right by the same amount. |
| #[inline] |
| // #[target_feature(enable = "simd128")] |
| // FIXME: #[cfg_attr(test, assert_instr($id.shr))] |
| pub unsafe fn shr_s(a: v128, y: i32) -> v128 { |
| use coresimd::simd_llvm::simd_shr; |
| let a: sealed::$svec_ty = ::mem::transmute(a); |
| let b: sealed::$svec_ty = ::mem::transmute($id::splat(y as $t)); |
| ::mem::transmute(simd_shr(a, b)) |
| } |
| |
| /// Logical right shift by scalar. |
| /// |
| /// Shift the bits in each lane to the right by the same amount. |
| #[inline] |
| // #[target_feature(enable = "simd128")] |
| // FIXME: #[cfg_attr(test, assert_instr($id.shr))] |
| pub unsafe fn shr_u(a: v128, y: i32) -> v128 { |
| use coresimd::simd_llvm::simd_shr; |
| let a: sealed::$uvec_ty = ::mem::transmute(a); |
| let b: sealed::$uvec_ty = ::mem::transmute($id::splat(y as $t)); |
| ::mem::transmute(simd_shr(a, b)) |
| } |
| }; |
| } |
| |
| macro_rules! impl_boolean_reduction { |
| ($id:ident[$ivec_ty:ident]) => { |
| /// Any lane true |
| /// |
| /// Returns `1` if any lane in `a` is non-zero, `0` otherwise. |
| #[inline] |
| // #[target_feature(enable = "simd128")] |
| // FIXME: #[cfg_attr(test, assert_instr($id.any_true))] |
| pub unsafe fn any_true(a: v128) -> i32 { |
| use coresimd::simd_llvm::simd_reduce_any; |
| let a: sealed::$ivec_ty = ::mem::transmute(a); |
| if simd_reduce_any(a) { |
| 1 |
| } else { |
| 0 |
| } |
| } |
| |
| /// All lanes true |
| /// |
| /// Returns `1` if all lanes in `a` are non-zero, `0` otherwise. |
| #[inline] |
| // #[target_feature(enable = "simd128")] |
| // FIXME: #[cfg_attr(test, assert_instr($id.all_true))] |
| pub unsafe fn all_true(a: v128) -> i32 { |
| use coresimd::simd_llvm::simd_reduce_all; |
| let a: sealed::$ivec_ty = ::mem::transmute(a); |
| if simd_reduce_all(a) { |
| 1 |
| } else { |
| 0 |
| } |
| } |
| }; |
| } |
| |
| macro_rules! impl_comparisons { |
| ($id:ident[$ivec_ty:ident]) => { |
| impl_comparisons!($id[$ivec_ty=>$ivec_ty]); |
| }; |
| ($id:ident[$ivec_ty:ident=>$rvec_ty:ident]) => { |
| /// Equality |
| #[inline] |
| // #[target_feature(enable = "simd128")] |
| // FIXME: #[cfg_attr(test, assert_instr($id.eq))] |
| pub unsafe fn eq(a: v128, b: v128) -> v128 { |
| use coresimd::simd_llvm::simd_eq; |
| let a: sealed::$ivec_ty = ::mem::transmute(a); |
| let b: sealed::$ivec_ty = ::mem::transmute(b); |
| let c: sealed::$rvec_ty = simd_eq(a, b); |
| ::mem::transmute(c) |
| } |
| /// Non-Equality |
| #[inline] |
| // #[target_feature(enable = "simd128")] |
| // FIXME: #[cfg_attr(test, assert_instr($id.ne))] |
| pub unsafe fn ne(a: v128, b: v128) -> v128 { |
| use coresimd::simd_llvm::simd_ne; |
| let a: sealed::$ivec_ty = ::mem::transmute(a); |
| let b: sealed::$ivec_ty = ::mem::transmute(b); |
| let c: sealed::$rvec_ty = simd_ne(a, b); |
| ::mem::transmute(c) |
| } |
| /// Less-than |
| #[inline] |
| // #[target_feature(enable = "simd128")] |
| // FIXME: #[cfg_attr(test, assert_instr($id.lt))] |
| pub unsafe fn lt(a: v128, b: v128) -> v128 { |
| use coresimd::simd_llvm::simd_lt; |
| let a: sealed::$ivec_ty = ::mem::transmute(a); |
| let b: sealed::$ivec_ty = ::mem::transmute(b); |
| let c: sealed::$rvec_ty = simd_lt(a, b); |
| ::mem::transmute(c) |
| } |
| /// Less-than or equal |
| #[inline] |
| // #[target_feature(enable = "simd128")] |
| // FIXME: #[cfg_attr(test, assert_instr($id.le))] |
| pub unsafe fn le(a: v128, b: v128) -> v128 { |
| use coresimd::simd_llvm::simd_le; |
| let a: sealed::$ivec_ty = ::mem::transmute(a); |
| let b: sealed::$ivec_ty = ::mem::transmute(b); |
| let c: sealed::$rvec_ty = simd_le(a, b); |
| ::mem::transmute(c) |
| } |
| /// Greater-than |
| #[inline] |
| // #[target_feature(enable = "simd128")] |
| // FIXME: #[cfg_attr(test, assert_instr($id.gt))] |
| pub unsafe fn gt(a: v128, b: v128) -> v128 { |
| use coresimd::simd_llvm::simd_gt; |
| let a: sealed::$ivec_ty = ::mem::transmute(a); |
| let b: sealed::$ivec_ty = ::mem::transmute(b); |
| let c: sealed::$rvec_ty = simd_gt(a, b); |
| ::mem::transmute(c) |
| } |
| /// Greater-than or equal |
| #[inline] |
| // #[target_feature(enable = "simd128")] |
| // FIXME: #[cfg_attr(test, assert_instr($id.ge))] |
| pub unsafe fn ge(a: v128, b: v128) -> v128 { |
| use coresimd::simd_llvm::simd_ge; |
| let a: sealed::$ivec_ty = ::mem::transmute(a); |
| let b: sealed::$ivec_ty = ::mem::transmute(b); |
| let c: sealed::$rvec_ty = simd_ge(a, b); |
| ::mem::transmute(c) |
| } |
| } |
| } |
| |
| // Floating-point operations |
| macro_rules! impl_floating_point_ops { |
| ($id:ident) => { |
| /// Negation |
| /// |
| /// Apply the IEEE `negate(x)` function to each lane. This simply |
| /// inverts the sign bit, preserving all other bits, even for `NaN` |
| /// inputs. |
| #[inline] |
| // #[target_feature(enable = "simd128")] |
| // FIXME: #[cfg_attr(test, assert_instr($id.neg))] |
| pub unsafe fn neg(a: v128) -> v128 { |
| use coresimd::simd_llvm::simd_mul; |
| let a: sealed::$id = ::mem::transmute(a); |
| let b: sealed::$id = ::mem::transmute($id::splat(-1.)); |
| ::mem::transmute(simd_mul(b, a)) |
| } |
| /// Absolute value |
| /// |
| /// Apply the IEEE `abs(x)` function to each lane. This simply |
| /// clears the sign bit, preserving all other bits, even for `NaN` |
| /// inputs. |
| #[inline] |
| // #[target_feature(enable = "simd128")] |
| // FIXME: #[cfg_attr(test, assert_instr($id.abs))] |
| pub unsafe fn abs(a: v128) -> v128 { |
| let a: sealed::$id = ::mem::transmute(a); |
| ::mem::transmute(a.abs()) |
| } |
| /// NaN-propagating minimum |
| /// |
| /// Lane-wise minimum value, propagating `NaN`s. |
| #[inline] |
| // #[target_feature(enable = "simd128")] |
| // FIXME: #[cfg_attr(test, assert_instr($id.min))] |
| pub unsafe fn min(a: v128, b: v128) -> v128 { |
| v128::bitselect(a, b, $id::lt(a, b)) |
| } |
| /// NaN-propagating maximum |
| /// |
| /// Lane-wise maximum value, propagating `NaN`s. |
| #[inline] |
| // #[target_feature(enable = "simd128")] |
| // FIXME: #[cfg_attr(test, assert_instr($id.max))] |
| pub unsafe fn max(a: v128, b: v128) -> v128 { |
| v128::bitselect(a, b, $id::gt(a, b)) |
| } |
| /// Square-root |
| /// |
| /// Lane-wise square-root. |
| #[inline] |
| // #[target_feature(enable = "simd128")] |
| // FIXME: #[cfg_attr(test, assert_instr($id.sqrt))] |
| pub unsafe fn sqrt(a: v128) -> v128 { |
| let a: sealed::$id = ::mem::transmute(a); |
| ::mem::transmute(a.sqrt()) |
| } |
| /// Lane-wise addition |
| #[inline] |
| // #[target_feature(enable = "simd128")] |
| // FIXME: #[cfg_attr(test, assert_instr($id.add))] |
| pub unsafe fn add(a: v128, b: v128) -> v128 { |
| use coresimd::simd_llvm::simd_add; |
| let a: sealed::$id = ::mem::transmute(a); |
| let b: sealed::$id = ::mem::transmute(b); |
| ::mem::transmute(simd_add(a, b)) |
| } |
| /// Lane-wise subtraction |
| #[inline] |
| // #[target_feature(enable = "simd128")] |
| // FIXME: #[cfg_attr(test, assert_instr($id.sub))] |
| pub unsafe fn sub(a: v128, b: v128) -> v128 { |
| use coresimd::simd_llvm::simd_sub; |
| let a: sealed::$id = ::mem::transmute(a); |
| let b: sealed::$id = ::mem::transmute(b); |
| ::mem::transmute(simd_sub(a, b)) |
| } |
| /// Lane-wise multiplication |
| #[inline] |
| // #[target_feature(enable = "simd128")] |
| // FIXME: #[cfg_attr(test, assert_instr($id.mul))] |
| pub unsafe fn mul(a: v128, b: v128) -> v128 { |
| use coresimd::simd_llvm::simd_mul; |
| let a: sealed::$id = ::mem::transmute(a); |
| let b: sealed::$id = ::mem::transmute(b); |
| ::mem::transmute(simd_mul(a, b)) |
| } |
| /// Lane-wise division |
| #[inline] |
| // #[target_feature(enable = "simd128")] |
| // FIXME: #[cfg_attr(test, assert_instr($id.div))] |
| pub unsafe fn div(a: v128, b: v128) -> v128 { |
| use coresimd::simd_llvm::simd_div; |
| let a: sealed::$id = ::mem::transmute(a); |
| let b: sealed::$id = ::mem::transmute(b); |
| ::mem::transmute(simd_div(a, b)) |
| } |
| }; |
| } |
| |
| macro_rules! impl_conversion { |
| ($conversion:ident[$instr:expr]: $from_ty:ident => $to_ty:ident | $id:ident) => { |
| #[inline] |
| // #[target_feature(enable = "simd128")] |
| // FIXME: #[cfg_attr(test, assert_instr($instr))] |
| pub unsafe fn $conversion(a: v128) -> v128 { |
| use coresimd::simd_llvm::simd_cast; |
| let a: sealed::$from_ty = ::mem::transmute(a); |
| let b: sealed::$to_ty = simd_cast(a); |
| ::mem::transmute(b) |
| } |
| }; |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // Implementations: |
| |
| // v128 |
| impl v128 { |
| /////////////////////////////////////////////////////////////////////////// |
| // Const constructor: |
| |
| /// Materialize a constant SIMD value from the immediate operands. |
| /// |
| /// The `v128.const` instruction is encoded with 16 immediate bytes |
| /// `imm` which provide the bits of the vector directly. |
| #[inline] |
| // #[target_feature(enable = "simd128")] |
| // FIXME: #[cfg_attr(test, assert_instr(v128.const, imm = |
| // [ImmByte::new(42); 16]))] |
| #[rustc_args_required_const(0)] |
| pub const unsafe fn const_(imm: [ImmByte; 16]) -> v128 { |
| union U { |
| imm: [ImmByte; 16], |
| vec: v128, |
| } |
| U { imm }.vec |
| } |
| |
| /////////////////////////////////////////////////////////////////////////// |
| // Bitwise logical operations: |
| |
| /// Bitwise logical and |
| #[inline] |
| // #[target_feature(enable = "simd128")] |
| // FIXME: #[cfg_attr(test, assert_instr($id.and))] |
| pub unsafe fn and(a: v128, b: v128) -> v128 { |
| use coresimd::simd_llvm::simd_and; |
| simd_and(a, b) |
| } |
| |
| /// Bitwise logical or |
| #[inline] |
| // #[target_feature(enable = "simd128")] |
| // FIXME: #[cfg_attr(test, assert_instr($id.or))] |
| pub unsafe fn or(a: v128, b: v128) -> v128 { |
| use coresimd::simd_llvm::simd_or; |
| simd_or(a, b) |
| } |
| |
| /// Bitwise logical xor |
| #[inline] |
| // #[target_feature(enable = "simd128")] |
| // FIXME: #[cfg_attr(test, assert_instr($id.xor))] |
| pub unsafe fn xor(a: v128, b: v128) -> v128 { |
| use coresimd::simd_llvm::simd_xor; |
| simd_xor(a, b) |
| } |
| |
| /// Bitwise logical not |
| #[inline] |
| // #[target_feature(enable = "simd128")] |
| // FIXME: #[cfg_attr(test, assert_instr($id.not))] |
| pub unsafe fn not(a: v128) -> v128 { |
| union U { |
| v: u128, |
| c: [ImmByte; 16], |
| } |
| // FIXME: https://github.com/rust-lang/rust/issues/53193 |
| const C: [ImmByte; 16] = unsafe { |
| U { |
| v: ::_core::u128::MAX, |
| }.c |
| }; |
| Self::xor(v128::const_(C), a) |
| } |
| |
| /// Bitwise select |
| /// |
| /// Use the bits in the control mask `c` to select the corresponding bit |
| /// from `v1` when `1` and `v2` when `0`. |
| #[inline] |
| // #[target_feature(enable = "simd128")] |
| // FIXME: #[cfg_attr(test, assert_instr($id.bitselect))] |
| pub unsafe fn bitselect(v1: v128, v2: v128, c: v128) -> v128 { |
| // FIXME: use llvm.select instead - we need to add a `simd_bitselect` |
| // intrinsic to rustc that converts a v128 vector into a i1x128. The |
| // `simd_select` intrinsic converts e.g. a i8x16 into a i1x16 which is |
| // not what we want here: |
| Self::or(Self::and(v1, c), Self::and(v2, Self::not(c))) |
| } |
| |
| /////////////////////////////////////////////////////////////////////////// |
| // Memory load/stores: |
| |
| /// Load a `v128` vector from the given heap address. |
| #[inline] |
| // #[target_feature(enable = "simd128")] |
| // FIXME: #[cfg_attr(test, assert_instr($id.load))] |
| pub unsafe fn load(m: *const v128) -> v128 { |
| ::_core::ptr::read(m) |
| } |
| |
| /// Store a `v128` vector to the given heap address. |
| #[inline] |
| // #[target_feature(enable = "simd128")] |
| // FIXME: #[cfg_attr(test, assert_instr($id.store))] |
| pub unsafe fn store(m: *mut v128, a: v128) { |
| ::_core::ptr::write(m, a) |
| } |
| } |
| |
| pub use self::sealed::v8x16 as __internal_v8x16; |
| pub use coresimd::simd_llvm::simd_shuffle16 as __internal_v8x16_shuffle; |
| /// Shuffle lanes |
| /// |
| /// Create vector with lanes selected from the lanes of two input vectors |
| /// `a` and `b` by the indices specified in the immediate mode operand |
| /// `imm`. Each index selects an element of the result vector, where the |
| /// indices `i` in range `[0, 15]` select the `i`-th elements of `a`, and |
| /// the indices in range `[16, 31]` select the `i - 16`-th element of `b`. |
| #[macro_export] |
| macro_rules! v8x16_shuffle { |
| ($a:expr, $b:expr, [ |
| $imm0:expr, $imm1:expr, $imm2:expr, $imm3:expr, |
| $imm4:expr, $imm5:expr, $imm6:expr, $imm7:expr, |
| $imm8:expr, $imm9:expr, $imm10:expr, $imm11:expr, |
| $imm12:expr, $imm13:expr, $imm14:expr, $imm15:expr |
| ]) => { |
| #[allow(unused_unsafe)] |
| unsafe { |
| let a: $crate::arch::wasm32::v128 = $a; |
| let b: $crate::arch::wasm32::v128 = $b; |
| union U { |
| e: v128, |
| i: $crate::arch::wasm32::__internal_v8x16, |
| } |
| let a = U { e: a }.i; |
| let b = U { e: b }.i; |
| |
| let r: $crate::arch::wasm32::__internal_v8x16 = |
| $crate::arch::wasm32::__internal_v8x16_shuffle( |
| a, |
| b, |
| [ |
| $imm0 as u32, |
| $imm1, |
| $imm2, |
| $imm3, |
| $imm4, |
| $imm5, |
| $imm6, |
| $imm7, |
| $imm8, |
| $imm9, |
| $imm10, |
| $imm11, |
| $imm12, |
| $imm13, |
| $imm14, |
| $imm15, |
| ], |
| ); |
| U { i: r }.e |
| } |
| }; |
| } |
| |
| /// WASM-specific v8x16 instructions with modulo-arithmetic semantics |
| pub mod i8x16 { |
| use super::*; |
| impl_splat!( |
| i8x16[v8x16: i8] <= i32 | x0, |
| x1, |
| x2, |
| x3, |
| x4, |
| x5, |
| x6, |
| x7, |
| x8, |
| x9, |
| x10, |
| x11, |
| x12, |
| x13, |
| x14, |
| x15 |
| ); |
| impl_extract_lane!(i8x16[v8x16:i8|u8](LaneIdx16) => i32); |
| impl_replace_lane!(i8x16[v8x16: i8](LaneIdx16) <= i32); |
| impl_wrapping_add_sub_neg!(i8x16[v8x16]); |
| impl_wrapping_mul!(i8x16[v8x16]); |
| impl_shl_scalar!(i8x16[v8x16: i32]); |
| impl_shr_scalar!(i8x16[v8x16: u8x16: i32]); |
| impl_boolean_reduction!(i8x16[v8x16]); |
| impl_comparisons!(i8x16[v8x16]); |
| } |
| |
| /// WASM-specific v16x8 instructions with modulo-arithmetic semantics |
| pub mod i16x8 { |
| use super::*; |
| impl_splat!(i16x8[v16x8: i16] <= i32 | x0, x1, x2, x3, x4, x5, x6, x7); |
| impl_extract_lane!(i16x8[v16x8:i16|u16](LaneIdx8) => i32); |
| impl_replace_lane!(i16x8[v16x8: i16](LaneIdx8) <= i32); |
| impl_wrapping_add_sub_neg!(i16x8[v16x8]); |
| impl_wrapping_mul!(i16x8[v16x8]); |
| impl_shl_scalar!(i16x8[v16x8: i32]); |
| impl_shr_scalar!(i16x8[v16x8: u16x8: i32]); |
| impl_boolean_reduction!(i16x8[v16x8]); |
| impl_comparisons!(i16x8[v16x8]); |
| } |
| |
| /// WASM-specific v32x4 instructions with modulo-arithmetic semantics |
| pub mod i32x4 { |
| use super::*; |
| impl_splat!(i32x4[v32x4: i32] <= i32 | x0, x1, x2, x3); |
| impl_extract_lane!(i32x4[v32x4](LaneIdx4) => i32); |
| impl_replace_lane!(i32x4[v32x4: i32](LaneIdx4) <= i32); |
| impl_wrapping_add_sub_neg!(i32x4[v32x4]); |
| impl_wrapping_mul!(i32x4[v32x4]); |
| impl_shl_scalar!(i32x4[v32x4: i32]); |
| impl_shr_scalar!(i32x4[v32x4: u32x4: i32]); |
| impl_boolean_reduction!(i32x4[v32x4]); |
| impl_comparisons!(i32x4[v32x4]); |
| impl_conversion!(trunc_s_f32x4_sat["i32x4.trunc_s/f32x4:sat"]: f32x4 => v32x4 | i32x4); |
| impl_conversion!(trunc_u_f32x4_sat["i32x4.trunc_s/f32x4:sat"]: f32x4 => u32x4 | i32x4); |
| } |
| |
| /// WASM-specific v64x2 instructions with modulo-arithmetic semantics |
| pub mod i64x2 { |
| use super::*; |
| impl_splat!(i64x2[v64x2: i64] <= i64 | x0, x1); |
| impl_extract_lane!(i64x2[v64x2](LaneIdx2) => i64); |
| impl_replace_lane!(i64x2[v64x2: i64](LaneIdx2) <= i64); |
| impl_wrapping_add_sub_neg!(i64x2[v64x2]); |
| // note: wrapping multiplication for i64x2 is not part of the spec |
| impl_shl_scalar!(i64x2[v64x2: i64]); |
| impl_shr_scalar!(i64x2[v64x2: u64x2: i64]); |
| impl_boolean_reduction!(i64x2[v64x2]); |
| impl_comparisons!(i64x2[v64x2]); |
| impl_conversion!(trunc_s_f64x2_sat["i64x2.trunc_s/f64x2:sat"]: f64x2 => v64x2 | i64x2); |
| impl_conversion!(trunc_u_f64x2_sat["i64x2.trunc_s/f64x2:sat"]: f64x2 => u64x2 | i64x2); |
| } |
| |
| /// WASM-specific v32x4 floating-point instructions |
| pub mod f32x4 { |
| use super::*; |
| impl_splat!(f32x4[f32x4: f32] <= f32 | x0, x1, x2, x3); |
| impl_extract_lane!(f32x4[f32x4](LaneIdx4) => f32); |
| impl_replace_lane!(f32x4[f32x4: f32](LaneIdx4) <= f32); |
| impl_comparisons!(f32x4[f32x4=>v32x4]); |
| impl_floating_point_ops!(f32x4); |
| impl_conversion!(convert_s_i32x4["f32x4.convert_s/i32x4"]: v32x4 => f32x4 | f32x4); |
| impl_conversion!(convert_u_i32x4["f32x4.convert_u/i32x4"]: u32x4 => f32x4 | f32x4); |
| |
| } |
| |
| /// WASM-specific v64x2 floating-point instructions |
| pub mod f64x2 { |
| use super::*; |
| impl_splat!(f64x2[f64x2: f64] <= f64 | x0, x1); |
| impl_extract_lane!(f64x2[f64x2](LaneIdx2) => f64); |
| impl_replace_lane!(f64x2[f64x2: f64](LaneIdx2) <= f64); |
| impl_comparisons!(f64x2[f64x2=>v64x2]); |
| impl_floating_point_ops!(f64x2); |
| impl_conversion!(convert_s_i64x2["f64x2.convert_s/i64x2"]: v64x2 => f64x2 | f64x2); |
| impl_conversion!(convert_u_i64x2["f64x2.convert_u/i64x2"]: u64x2 => f64x2 | f64x2); |
| } |
| |
| #[cfg(test)] |
| pub mod tests { |
| use super::*; |
| use std; |
| use std::mem; |
| use std::prelude::v1::*; |
| use wasm_bindgen_test::*; |
| |
| fn compare_bytes(a: v128, b: v128) { |
| let a: [u8; 16] = unsafe { mem::transmute(a) }; |
| let b: [u8; 16] = unsafe { mem::transmute(b) }; |
| assert_eq!(a, b); |
| } |
| |
| #[wasm_bindgen_test] |
| fn v128_const() { |
| const A: v128 = unsafe { |
| v128::const_([ |
| 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, |
| ]) |
| }; |
| compare_bytes(A, A); |
| } |
| |
| macro_rules! test_splat { |
| ($test_id:ident: $id:ident($val:expr) => $($vals:expr),*) => { |
| #[wasm_bindgen_test] |
| fn $test_id() { |
| const A: v128 = unsafe { |
| $id::splat($val) |
| }; |
| const B: v128 = unsafe { |
| v128::const_([$($vals),*]) |
| }; |
| compare_bytes(A, B); |
| } |
| } |
| } |
| |
| test_splat!(i8x16_splat: i8x16(42) => 42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42); |
| test_splat!(i16x8_splat: i16x8(42) => 42, 0, 42, 0, 42, 0, 42, 0, 42, 0, 42, 0, 42, 0, 42, 0); |
| test_splat!(i32x4_splat: i32x4(42) => 42, 0, 0, 0, 42, 0, 0, 0, 42, 0, 0, 0, 42, 0, 0, 0); |
| test_splat!(i64x2_splat: i64x2(42) => 42, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0); |
| test_splat!(f32x4_splat: f32x4(42.) => 0, 0, 40, 66, 0, 0, 40, 66, 0, 0, 40, 66, 0, 0, 40, 66); |
| test_splat!(f64x2_splat: f64x2(42.) => 0, 0, 0, 0, 0, 0, 69, 64, 0, 0, 0, 0, 0, 0, 69, 64); |
| |
| // tests extract and replace lanes |
| macro_rules! test_extract { |
| ($test_id:ident: $id:ident[$ety:ident] => $extract_fn:ident | [$val:expr; $count:expr] |
| | [$($vals:expr),*] => ($other:expr) |
| | $($ids:expr),*) => { |
| #[wasm_bindgen_test] |
| fn $test_id() { |
| unsafe { |
| // splat vector and check that all indices contain the same value |
| // splatted: |
| const A: v128 = unsafe { |
| $id::splat($val) |
| }; |
| $( |
| assert_eq!($id::$extract_fn(A, $ids) as $ety, $val); |
| )*; |
| |
| // create a vector from array and check that the indices contain |
| // the same values as in the array: |
| let arr: [$ety; $count] = [$($vals),*]; |
| let mut vec: v128 = mem::transmute(arr); |
| $( |
| assert_eq!($id::$extract_fn(vec, $ids) as $ety, arr[$ids]); |
| )*; |
| |
| // replace lane 0 with another value |
| vec = $id::replace_lane(vec, 0, $other); |
| assert_ne!($id::$extract_fn(vec, 0) as $ety, arr[0]); |
| assert_eq!($id::$extract_fn(vec, 0) as $ety, $other); |
| } |
| } |
| } |
| } |
| |
| test_extract!(i8x16_extract_u: i8x16[u8] => extract_lane_u | [255; 16] |
| | [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] => (42) |
| | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 |
| ); |
| test_extract!(i8x16_extract_s: i8x16[i8] => extract_lane_s | [-122; 16] |
| | [0, -1, 2, -3, 4, -5, 6, -7, 8, -9, 10, -11, 12, -13, 14, -15] => (-42) |
| | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 |
| ); |
| |
| test_extract!(i16x8_extract_u: i16x8[u16] => extract_lane_u | [255; 8] |
| | [0, 1, 2, 3, 4, 5, 6, 7] => (42) | 0, 1, 2, 3, 4, 5, 6, 7 |
| ); |
| test_extract!(i16x8_extract_s: i16x8[i16] => extract_lane_s | [-122; 8] |
| | [0, -1, 2, -3, 4, -5, 6, -7] => (-42) | 0, 1, 2, 3, 4, 5, 6, 7 |
| ); |
| test_extract!(i32x4_extract: i32x4[i32] => extract_lane | [-122; 4] |
| | [0, -1, 2, -3] => (42) | 0, 1, 2, 3 |
| ); |
| test_extract!(i64x2_extract: i64x2[i64] => extract_lane | [-122; 2] |
| | [0, -1] => (42) | 0, 1 |
| ); |
| test_extract!(f32x4_extract: f32x4[f32] => extract_lane | [-122.; 4] |
| | [0., -1., 2., -3.] => (42.) | 0, 1, 2, 3 |
| ); |
| test_extract!(f64x2_extract: f64x2[f64] => extract_lane | [-122.; 2] |
| | [0., -1.] => (42.) | 0, 1 |
| ); |
| |
| #[wasm_bindgen_test] |
| fn v8x16_shuffle() { |
| unsafe { |
| let a = [0_u8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; |
| let b = [ |
| 16_u8, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, |
| 31, |
| ]; |
| |
| let vec_a: v128 = mem::transmute(a); |
| let vec_b: v128 = mem::transmute(b); |
| |
| let vec_r = v8x16_shuffle!( |
| vec_a, |
| vec_b, |
| [0, 16, 2, 18, 4, 20, 6, 22, 8, 24, 10, 26, 12, 28, 14, 30] |
| ); |
| |
| let e = |
| [0_u8, 16, 2, 18, 4, 20, 6, 22, 8, 24, 10, 26, 12, 28, 14, 30]; |
| let vec_e: v128 = mem::transmute(e); |
| compare_bytes(vec_r, vec_e); |
| } |
| } |
| |
| macro_rules! floating_point { |
| (f32) => { |
| true |
| }; |
| (f64) => { |
| true |
| }; |
| ($id:ident) => { |
| false |
| }; |
| } |
| |
| trait IsNan: Sized { |
| fn is_nan(self) -> bool { |
| false |
| } |
| } |
| impl IsNan for i8 {} |
| impl IsNan for i16 {} |
| impl IsNan for i32 {} |
| impl IsNan for i64 {} |
| |
| macro_rules! test_bop { |
| ($id:ident[$ety:ident; $ecount:expr] | |
| $binary_op:ident [$op_test_id:ident] : |
| ([$($in_a:expr),*], [$($in_b:expr),*]) => [$($out:expr),*]) => { |
| test_bop!( |
| $id[$ety; $ecount] => $ety | $binary_op [ $op_test_id ]: |
| ([$($in_a),*], [$($in_b),*]) => [$($out),*] |
| ); |
| |
| }; |
| ($id:ident[$ety:ident; $ecount:expr] => $oty:ident | |
| $binary_op:ident [$op_test_id:ident] : |
| ([$($in_a:expr),*], [$($in_b:expr),*]) => [$($out:expr),*]) => { |
| #[wasm_bindgen_test] |
| fn $op_test_id() { |
| unsafe { |
| let a_input: [$ety; $ecount] = [$($in_a),*]; |
| let b_input: [$ety; $ecount] = [$($in_b),*]; |
| let output: [$oty; $ecount] = [$($out),*]; |
| |
| let a_vec_in: v128 = mem::transmute(a_input); |
| let b_vec_in: v128 = mem::transmute(b_input); |
| let vec_res: v128 = $id::$binary_op(a_vec_in, b_vec_in); |
| |
| let res: [$oty; $ecount] = mem::transmute(vec_res); |
| |
| if !floating_point!($ety) { |
| assert_eq!(res, output); |
| } else { |
| for i in 0..$ecount { |
| let r = res[i]; |
| let o = output[i]; |
| assert_eq!(r.is_nan(), o.is_nan()); |
| if !r.is_nan() { |
| assert_eq!(r, o); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| macro_rules! test_bops { |
| ($id:ident[$ety:ident; $ecount:expr] | |
| $binary_op:ident [$op_test_id:ident]: |
| ([$($in_a:expr),*], $in_b:expr) => [$($out:expr),*]) => { |
| #[wasm_bindgen_test] |
| fn $op_test_id() { |
| unsafe { |
| let a_input: [$ety; $ecount] = [$($in_a),*]; |
| let output: [$ety; $ecount] = [$($out),*]; |
| |
| let a_vec_in: v128 = mem::transmute(a_input); |
| let vec_res: v128 = $id::$binary_op(a_vec_in, $in_b); |
| |
| let res: [$ety; $ecount] = mem::transmute(vec_res); |
| assert_eq!(res, output); |
| } |
| } |
| } |
| } |
| |
| macro_rules! test_uop { |
| ($id:ident[$ety:ident; $ecount:expr] | |
| $unary_op:ident [$op_test_id:ident]: [$($in_a:expr),*] => [$($out:expr),*]) => { |
| #[wasm_bindgen_test] |
| fn $op_test_id() { |
| unsafe { |
| let a_input: [$ety; $ecount] = [$($in_a),*]; |
| let output: [$ety; $ecount] = [$($out),*]; |
| |
| let a_vec_in: v128 = mem::transmute(a_input); |
| let vec_res: v128 = $id::$unary_op(a_vec_in); |
| |
| let res: [$ety; $ecount] = mem::transmute(vec_res); |
| assert_eq!(res, output); |
| } |
| } |
| } |
| } |
| |
| test_bop!(i8x16[i8; 16] | add[i8x16_add_test]: |
| ([0, -1, 2, 3, 4, 5, 6, i8::max_value(), 1, 1, 1, 1, 1, 1, 1, 1], |
| [8, i8::min_value(), 10, 11, 12, 13, 14, 1, 1, 1, 1, 1, 1, 1, 1, 1]) => |
| [8, i8::max_value(), 12, 14, 16, 18, 20, i8::min_value(), 2, 2, 2, 2, 2, 2, 2, 2]); |
| test_bop!(i8x16[i8; 16] | sub[i8x16_sub_test]: |
| ([0, -1, 2, 3, 4, 5, 6, -1, 1, 1, 1, 1, 1, 1, 1, 1], |
| [8, i8::min_value(), 10, 11, 12, 13, 14, i8::max_value(), 1, 1, 1, 1, 1, 1, 1, 1]) => |
| [-8, i8::max_value(), -8, -8, -8, -8, -8, i8::min_value(), 0, 0, 0, 0, 0, 0, 0, 0]); |
| test_bop!(i8x16[i8; 16] | mul[i8x16_mul_test]: |
| ([0, -2, 2, 3, 4, 5, 6, 2, 1, 1, 1, 1, 1, 1, 1, 1], |
| [8, i8::min_value(), 10, 11, 12, 13, 14, i8::max_value(), 1, 1, 1, 1, 1, 1, 1, 1]) => |
| [0, 0, 20, 33, 48, 65, 84, -2, 1, 1, 1, 1, 1, 1, 1, 1]); |
| test_uop!(i8x16[i8; 16] | neg[i8x16_neg_test]: |
| [8, i8::min_value(), 10, 11, 12, 13, 14, i8::max_value(), 1, 1, 1, 1, 1, 1, 1, 1] => |
| [-8, i8::min_value(), -10, -11, -12, -13, -14, i8::min_value() + 1, -1, -1, -1, -1, -1, -1, -1, -1]); |
| |
| test_bop!(i16x8[i16; 8] | add[i16x8_add_test]: |
| ([0, -1, 2, 3, 4, 5, 6, i16::max_value()], |
| [8, i16::min_value(), 10, 11, 12, 13, 14, 1]) => |
| [8, i16::max_value(), 12, 14, 16, 18, 20, i16::min_value()]); |
| test_bop!(i16x8[i16; 8] | sub[i16x8_sub_test]: |
| ([0, -1, 2, 3, 4, 5, 6, -1], |
| [8, i16::min_value(), 10, 11, 12, 13, 14, i16::max_value()]) => |
| [-8, i16::max_value(), -8, -8, -8, -8, -8, i16::min_value()]); |
| test_bop!(i16x8[i16; 8] | mul[i16x8_mul_test]: |
| ([0, -2, 2, 3, 4, 5, 6, 2], |
| [8, i16::min_value(), 10, 11, 12, 13, 14, i16::max_value()]) => |
| [0, 0, 20, 33, 48, 65, 84, -2]); |
| test_uop!(i16x8[i16; 8] | neg[i16x8_neg_test]: |
| [8, i16::min_value(), 10, 11, 12, 13, 14, i16::max_value()] => |
| [-8, i16::min_value(), -10, -11, -12, -13, -14, i16::min_value() + 1]); |
| |
| test_bop!(i32x4[i32; 4] | add[i32x4_add_test]: |
| ([0, -1, 2, i32::max_value()], |
| [8, i32::min_value(), 10, 1]) => |
| [8, i32::max_value(), 12, i32::min_value()]); |
| test_bop!(i32x4[i32; 4] | sub[i32x4_sub_test]: |
| ([0, -1, 2, -1], |
| [8, i32::min_value(), 10, i32::max_value()]) => |
| [-8, i32::max_value(), -8, i32::min_value()]); |
| test_bop!(i32x4[i32; 4] | mul[i32x4_mul_test]: |
| ([0, -2, 2, 2], |
| [8, i32::min_value(), 10, i32::max_value()]) => |
| [0, 0, 20, -2]); |
| test_uop!(i32x4[i32; 4] | neg[i32x4_neg_test]: |
| [8, i32::min_value(), 10, i32::max_value()] => |
| [-8, i32::min_value(), -10, i32::min_value() + 1]); |
| |
| test_bop!(i64x2[i64; 2] | add[i64x2_add_test]: |
| ([-1, i64::max_value()], |
| [i64::min_value(), 1]) => |
| [i64::max_value(), i64::min_value()]); |
| test_bop!(i64x2[i64; 2] | sub[i64x2_sub_test]: |
| ([-1, -1], |
| [i64::min_value(), i64::max_value()]) => |
| [ i64::max_value(), i64::min_value()]); |
| // note: mul for i64x2 is not part of the spec |
| test_uop!(i64x2[i64; 2] | neg[i64x2_neg_test]: |
| [i64::min_value(), i64::max_value()] => |
| [i64::min_value(), i64::min_value() + 1]); |
| |
| test_bops!(i8x16[i8; 16] | shl[i8x16_shl_test]: |
| ([0, -1, 2, 3, 4, 5, 6, i8::max_value(), 1, 1, 1, 1, 1, 1, 1, 1], 1) => |
| [0, -2, 4, 6, 8, 10, 12, -2, 2, 2, 2, 2, 2, 2, 2, 2]); |
| test_bops!(i16x8[i16; 8] | shl[i16x8_shl_test]: |
| ([0, -1, 2, 3, 4, 5, 6, i16::max_value()], 1) => |
| [0, -2, 4, 6, 8, 10, 12, -2]); |
| test_bops!(i32x4[i32; 4] | shl[i32x4_shl_test]: |
| ([0, -1, 2, 3], 1) => [0, -2, 4, 6]); |
| test_bops!(i64x2[i64; 2] | shl[i64x2_shl_test]: |
| ([0, -1], 1) => [0, -2]); |
| |
| test_bops!(i8x16[i8; 16] | shr_s[i8x16_shr_s_test]: |
| ([0, -1, 2, 3, 4, 5, 6, i8::max_value(), 1, 1, 1, 1, 1, 1, 1, 1], 1) => |
| [0, -1, 1, 1, 2, 2, 3, 63, 0, 0, 0, 0, 0, 0, 0, 0]); |
| test_bops!(i16x8[i16; 8] | shr_s[i16x8_shr_s_test]: |
| ([0, -1, 2, 3, 4, 5, 6, i16::max_value()], 1) => |
| [0, -1, 1, 1, 2, 2, 3, i16::max_value() / 2]); |
| test_bops!(i32x4[i32; 4] | shr_s[i32x4_shr_s_test]: |
| ([0, -1, 2, 3], 1) => [0, -1, 1, 1]); |
| test_bops!(i64x2[i64; 2] | shr_s[i64x2_shr_s_test]: |
| ([0, -1], 1) => [0, -1]); |
| |
| test_bops!(i8x16[i8; 16] | shr_u[i8x16_uhr_u_test]: |
| ([0, -1, 2, 3, 4, 5, 6, i8::max_value(), 1, 1, 1, 1, 1, 1, 1, 1], 1) => |
| [0, i8::max_value(), 1, 1, 2, 2, 3, 63, 0, 0, 0, 0, 0, 0, 0, 0]); |
| test_bops!(i16x8[i16; 8] | shr_u[i16x8_uhr_u_test]: |
| ([0, -1, 2, 3, 4, 5, 6, i16::max_value()], 1) => |
| [0, i16::max_value(), 1, 1, 2, 2, 3, i16::max_value() / 2]); |
| test_bops!(i32x4[i32; 4] | shr_u[i32x4_uhr_u_test]: |
| ([0, -1, 2, 3], 1) => [0, i32::max_value(), 1, 1]); |
| test_bops!(i64x2[i64; 2] | shr_u[i64x2_uhr_u_test]: |
| ([0, -1], 1) => [0, i64::max_value()]); |
| |
| #[wasm_bindgen_test] |
| fn v128_bitwise_logical_ops() { |
| unsafe { |
| let a: [u32; 4] = [u32::max_value(), 0, u32::max_value(), 0]; |
| let b: [u32; 4] = [u32::max_value(); 4]; |
| let c: [u32; 4] = [0; 4]; |
| |
| let vec_a: v128 = mem::transmute(a); |
| let vec_b: v128 = mem::transmute(b); |
| let vec_c: v128 = mem::transmute(c); |
| |
| let r: v128 = v128::and(vec_a, vec_a); |
| compare_bytes(r, vec_a); |
| let r: v128 = v128::and(vec_a, vec_b); |
| compare_bytes(r, vec_a); |
| let r: v128 = v128::or(vec_a, vec_b); |
| compare_bytes(r, vec_b); |
| let r: v128 = v128::not(vec_b); |
| compare_bytes(r, vec_c); |
| let r: v128 = v128::xor(vec_a, vec_c); |
| compare_bytes(r, vec_a); |
| |
| let r: v128 = v128::bitselect(vec_b, vec_c, vec_b); |
| compare_bytes(r, vec_b); |
| let r: v128 = v128::bitselect(vec_b, vec_c, vec_c); |
| compare_bytes(r, vec_c); |
| let r: v128 = v128::bitselect(vec_b, vec_c, vec_a); |
| compare_bytes(r, vec_a); |
| } |
| } |
| |
| macro_rules! test_bool_red { |
| ($id:ident[$test_id:ident] | [$($true:expr),*] | [$($false:expr),*] | [$($alt:expr),*]) => { |
| #[wasm_bindgen_test] |
| fn $test_id() { |
| unsafe { |
| let vec_a: v128 = mem::transmute([$($true),*]); // true |
| let vec_b: v128 = mem::transmute([$($false),*]); // false |
| let vec_c: v128 = mem::transmute([$($alt),*]); // alternating |
| |
| assert_eq!($id::any_true(vec_a), 1); |
| assert_eq!($id::any_true(vec_b), 0); |
| assert_eq!($id::any_true(vec_c), 1); |
| |
| assert_eq!($id::all_true(vec_a), 1); |
| assert_eq!($id::all_true(vec_b), 0); |
| assert_eq!($id::all_true(vec_c), 0); |
| } |
| } |
| } |
| } |
| |
| test_bool_red!( |
| i8x16[i8x16_boolean_reductions] |
| | [1_i8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] |
| | [0_i8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] |
| | [1_i8, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0] |
| ); |
| test_bool_red!( |
| i16x8[i16x8_boolean_reductions] |
| | [1_i16, 1, 1, 1, 1, 1, 1, 1] |
| | [0_i16, 0, 0, 0, 0, 0, 0, 0] |
| | [1_i16, 0, 1, 0, 1, 0, 1, 0] |
| ); |
| test_bool_red!( |
| i32x4[i32x4_boolean_reductions] |
| | [1_i32, 1, 1, 1] |
| | [0_i32, 0, 0, 0] |
| | [1_i32, 0, 1, 0] |
| ); |
| test_bool_red!( |
| i64x2[i64x2_boolean_reductions] | [1_i64, 1] | [0_i64, 0] | [1_i64, 0] |
| ); |
| |
| test_bop!(i8x16[i8; 16] | eq[i8x16_eq_test]: |
| ([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], |
| [0, 2, 2, 4, 4, 6, 6, 7, 8, 10, 10, 12, 12, 14, 14, 15]) => |
| [-1, 0, -1, 0 ,-1, 0, -1, -1, -1, 0, -1, 0 ,-1, 0, -1, -1]); |
| test_bop!(i16x8[i16; 8] | eq[i16x8_eq_test]: |
| ([0, 1, 2, 3, 4, 5, 6, 7], [0, 2, 2, 4, 4, 6, 6, 7]) => |
| [-1, 0, -1, 0 ,-1, 0, -1, -1]); |
| test_bop!(i32x4[i32; 4] | eq[i32x4_eq_test]: |
| ([0, 1, 2, 3], [0, 2, 2, 4]) => [-1, 0, -1, 0]); |
| test_bop!(i64x2[i64; 2] | eq[i64x2_eq_test]: ([0, 1], [0, 2]) => [-1, 0]); |
| test_bop!(f32x4[f32; 4] => i32 | eq[f32x4_eq_test]: |
| ([0., 1., 2., 3.], [0., 2., 2., 4.]) => [-1, 0, -1, 0]); |
| test_bop!(f64x2[f64; 2] => i64 | eq[f64x2_eq_test]: ([0., 1.], [0., 2.]) => [-1, 0]); |
| |
| test_bop!(i8x16[i8; 16] | ne[i8x16_ne_test]: |
| ([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], |
| [0, 2, 2, 4, 4, 6, 6, 7, 8, 10, 10, 12, 12, 14, 14, 15]) => |
| [0, -1, 0, -1 ,0, -1, 0, 0, 0, -1, 0, -1 ,0, -1, 0, 0]); |
| test_bop!(i16x8[i16; 8] | ne[i16x8_ne_test]: |
| ([0, 1, 2, 3, 4, 5, 6, 7], [0, 2, 2, 4, 4, 6, 6, 7]) => |
| [0, -1, 0, -1 ,0, -1, 0, 0]); |
| test_bop!(i32x4[i32; 4] | ne[i32x4_ne_test]: |
| ([0, 1, 2, 3], [0, 2, 2, 4]) => [0, -1, 0, -1]); |
| test_bop!(i64x2[i64; 2] | ne[i64x2_ne_test]: ([0, 1], [0, 2]) => [0, -1]); |
| test_bop!(f32x4[f32; 4] => i32 | ne[f32x4_ne_test]: |
| ([0., 1., 2., 3.], [0., 2., 2., 4.]) => [0, -1, 0, -1]); |
| test_bop!(f64x2[f64; 2] => i64 | ne[f64x2_ne_test]: ([0., 1.], [0., 2.]) => [0, -1]); |
| |
| test_bop!(i8x16[i8; 16] | lt[i8x16_lt_test]: |
| ([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], |
| [0, 2, 2, 4, 4, 6, 6, 7, 8, 10, 10, 12, 12, 14, 14, 15]) => |
| [0, -1, 0, -1 ,0, -1, 0, 0, 0, -1, 0, -1 ,0, -1, 0, 0]); |
| test_bop!(i16x8[i16; 8] | lt[i16x8_lt_test]: |
| ([0, 1, 2, 3, 4, 5, 6, 7], [0, 2, 2, 4, 4, 6, 6, 7]) => |
| [0, -1, 0, -1 ,0, -1, 0, 0]); |
| test_bop!(i32x4[i32; 4] | lt[i32x4_lt_test]: |
| ([0, 1, 2, 3], [0, 2, 2, 4]) => [0, -1, 0, -1]); |
| test_bop!(i64x2[i64; 2] | lt[i64x2_lt_test]: ([0, 1], [0, 2]) => [0, -1]); |
| test_bop!(f32x4[f32; 4] => i32 | lt[f32x4_lt_test]: |
| ([0., 1., 2., 3.], [0., 2., 2., 4.]) => [0, -1, 0, -1]); |
| test_bop!(f64x2[f64; 2] => i64 | lt[f64x2_lt_test]: ([0., 1.], [0., 2.]) => [0, -1]); |
| |
| test_bop!(i8x16[i8; 16] | gt[i8x16_gt_test]: |
| ([0, 2, 2, 4, 4, 6, 6, 7, 8, 10, 10, 12, 12, 14, 14, 15], |
| [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]) => |
| [0, -1, 0, -1 ,0, -1, 0, 0, 0, -1, 0, -1 ,0, -1, 0, 0]); |
| test_bop!(i16x8[i16; 8] | gt[i16x8_gt_test]: |
| ([0, 2, 2, 4, 4, 6, 6, 7], [0, 1, 2, 3, 4, 5, 6, 7]) => |
| [0, -1, 0, -1 ,0, -1, 0, 0]); |
| test_bop!(i32x4[i32; 4] | gt[i32x4_gt_test]: |
| ([0, 2, 2, 4], [0, 1, 2, 3]) => [0, -1, 0, -1]); |
| test_bop!(i64x2[i64; 2] | gt[i64x2_gt_test]: ([0, 2], [0, 1]) => [0, -1]); |
| test_bop!(f32x4[f32; 4] => i32 | gt[f32x4_gt_test]: |
| ([0., 2., 2., 4.], [0., 1., 2., 3.]) => [0, -1, 0, -1]); |
| test_bop!(f64x2[f64; 2] => i64 | gt[f64x2_gt_test]: ([0., 2.], [0., 1.]) => [0, -1]); |
| |
| test_bop!(i8x16[i8; 16] | ge[i8x16_ge_test]: |
| ([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], |
| [0, 2, 2, 4, 4, 6, 6, 7, 8, 10, 10, 12, 12, 14, 14, 15]) => |
| [-1, 0, -1, 0 ,-1, 0, -1, -1, -1, 0, -1, 0 ,-1, 0, -1, -1]); |
| test_bop!(i16x8[i16; 8] | ge[i16x8_ge_test]: |
| ([0, 1, 2, 3, 4, 5, 6, 7], [0, 2, 2, 4, 4, 6, 6, 7]) => |
| [-1, 0, -1, 0 ,-1, 0, -1, -1]); |
| test_bop!(i32x4[i32; 4] | ge[i32x4_ge_test]: |
| ([0, 1, 2, 3], [0, 2, 2, 4]) => [-1, 0, -1, 0]); |
| test_bop!(i64x2[i64; 2] | ge[i64x2_ge_test]: ([0, 1], [0, 2]) => [-1, 0]); |
| test_bop!(f32x4[f32; 4] => i32 | ge[f32x4_ge_test]: |
| ([0., 1., 2., 3.], [0., 2., 2., 4.]) => [-1, 0, -1, 0]); |
| test_bop!(f64x2[f64; 2] => i64 | ge[f64x2_ge_test]: ([0., 1.], [0., 2.]) => [-1, 0]); |
| |
| test_bop!(i8x16[i8; 16] | le[i8x16_le_test]: |
| ([0, 2, 2, 4, 4, 6, 6, 7, 8, 10, 10, 12, 12, 14, 14, 15], |
| [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] |
| ) => |
| [-1, 0, -1, 0 ,-1, 0, -1, -1, -1, 0, -1, 0 ,-1, 0, -1, -1]); |
| test_bop!(i16x8[i16; 8] | le[i16x8_le_test]: |
| ([0, 2, 2, 4, 4, 6, 6, 7], [0, 1, 2, 3, 4, 5, 6, 7]) => |
| [-1, 0, -1, 0 ,-1, 0, -1, -1]); |
| test_bop!(i32x4[i32; 4] | le[i32x4_le_test]: |
| ([0, 2, 2, 4], [0, 1, 2, 3]) => [-1, 0, -1, 0]); |
| test_bop!(i64x2[i64; 2] | le[i64x2_le_test]: ([0, 2], [0, 1]) => [-1, 0]); |
| test_bop!(f32x4[f32; 4] => i32 | le[f32x4_le_test]: |
| ([0., 2., 2., 4.], [0., 1., 2., 3.]) => [-1, 0, -1, -0]); |
| test_bop!(f64x2[f64; 2] => i64 | le[f64x2_le_test]: ([0., 2.], [0., 1.]) => [-1, 0]); |
| |
| #[wasm_bindgen_test] |
| fn v128_bitwise_load_store() { |
| unsafe { |
| let mut arr: [i32; 4] = [0, 1, 2, 3]; |
| |
| let vec = v128::load(arr.as_ptr() as *const v128); |
| let vec = i32x4::add(vec, vec); |
| v128::store(arr.as_mut_ptr() as *mut v128, vec); |
| |
| assert_eq!(arr, [0, 2, 4, 6]); |
| } |
| } |
| |
| test_uop!(f32x4[f32; 4] | neg[f32x4_neg_test]: [0., 1., 2., 3.] => [ 0., -1., -2., -3.]); |
| test_uop!(f32x4[f32; 4] | abs[f32x4_abs_test]: [0., -1., 2., -3.] => [ 0., 1., 2., 3.]); |
| test_bop!(f32x4[f32; 4] | min[f32x4_min_test]: |
| ([0., -1., 7., 8.], [1., -3., -4., 10.]) => [0., -3., -4., 8.]); |
| test_bop!(f32x4[f32; 4] | min[f32x4_min_test_nan]: |
| ([0., -1., 7., 8.], [1., -3., -4., std::f32::NAN]) |
| => [0., -3., -4., std::f32::NAN]); |
| test_bop!(f32x4[f32; 4] | max[f32x4_max_test]: |
| ([0., -1., 7., 8.], [1., -3., -4., 10.]) => [1., -1., 7., 10.]); |
| test_bop!(f32x4[f32; 4] | max[f32x4_max_test_nan]: |
| ([0., -1., 7., 8.], [1., -3., -4., std::f32::NAN]) |
| => [1., -1., 7., std::f32::NAN]); |
| test_bop!(f32x4[f32; 4] | add[f32x4_add_test]: |
| ([0., -1., 7., 8.], [1., -3., -4., 10.]) => [1., -4., 3., 18.]); |
| test_bop!(f32x4[f32; 4] | sub[f32x4_sub_test]: |
| ([0., -1., 7., 8.], [1., -3., -4., 10.]) => [-1., 2., 11., -2.]); |
| test_bop!(f32x4[f32; 4] | mul[f32x4_mul_test]: |
| ([0., -1., 7., 8.], [1., -3., -4., 10.]) => [0., 3., -28., 80.]); |
| test_bop!(f32x4[f32; 4] | div[f32x4_div_test]: |
| ([0., -8., 70., 8.], [1., 4., 10., 2.]) => [0., -2., 7., 4.]); |
| |
| test_uop!(f64x2[f64; 2] | neg[f64x2_neg_test]: [0., 1.] => [ 0., -1.]); |
| test_uop!(f64x2[f64; 2] | abs[f64x2_abs_test]: [0., -1.] => [ 0., 1.]); |
| test_bop!(f64x2[f64; 2] | min[f64x2_min_test]: |
| ([0., -1.], [1., -3.]) => [0., -3.]); |
| test_bop!(f64x2[f64; 2] | min[f64x2_min_test_nan]: |
| ([7., 8.], [-4., std::f64::NAN]) |
| => [ -4., std::f64::NAN]); |
| test_bop!(f64x2[f64; 2] | max[f64x2_max_test]: |
| ([0., -1.], [1., -3.]) => [1., -1.]); |
| test_bop!(f64x2[f64; 2] | max[f64x2_max_test_nan]: |
| ([7., 8.], [ -4., std::f64::NAN]) |
| => [7., std::f64::NAN]); |
| test_bop!(f64x2[f64; 2] | add[f64x2_add_test]: |
| ([0., -1.], [1., -3.]) => [1., -4.]); |
| test_bop!(f64x2[f64; 2] | sub[f64x2_sub_test]: |
| ([0., -1.], [1., -3.]) => [-1., 2.]); |
| test_bop!(f64x2[f64; 2] | mul[f64x2_mul_test]: |
| ([0., -1.], [1., -3.]) => [0., 3.]); |
| test_bop!(f64x2[f64; 2] | div[f64x2_div_test]: |
| ([0., -8.], [1., 4.]) => [0., -2.]); |
| |
| macro_rules! test_conv { |
| ($test_id:ident | $conv_id:ident | $to_ty:ident | $from:expr, $to:expr) => { |
| #[wasm_bindgen_test] |
| fn $test_id() { |
| unsafe { |
| let from: v128 = mem::transmute($from); |
| let to: v128 = mem::transmute($to); |
| |
| let r: v128 = $to_ty::$conv_id(from); |
| |
| compare_bytes(r, to); |
| } |
| } |
| }; |
| } |
| |
| test_conv!( |
| f32x4_convert_s_i32x4 | convert_s_i32x4 | f32x4 | [1_i32, 2, 3, 4], |
| [1_f32, 2., 3., 4.] |
| ); |
| test_conv!( |
| f32x4_convert_u_i32x4 |
| | convert_u_i32x4 |
| | f32x4 |
| | [u32::max_value(), 2, 3, 4], |
| [u32::max_value() as f32, 2., 3., 4.] |
| ); |
| test_conv!( |
| f64x2_convert_s_i64x2 | convert_s_i64x2 | f64x2 | [1_i64, 2], |
| [1_f64, 2.] |
| ); |
| test_conv!( |
| f64x2_convert_u_i64x2 |
| | convert_u_i64x2 |
| | f64x2 |
| | [u64::max_value(), 2], |
| [18446744073709552000.0, 2.] |
| ); |
| |
| // FIXME: this fails, and produces -2147483648 instead of saturating at |
| // i32::max_value() test_conv!(i32x4_trunc_s_f32x4_sat | trunc_s_f32x4_sat |
| // | i32x4 | [1_f32, 2., (i32::max_value() as f32 + 1.), 4.], |
| // [1_i32, 2, i32::max_value(), 4]); FIXME: add other saturating tests |
| } |