Skip to content

Commit

Permalink
Merge pull request rust-lang#34 from rust-lang/feature/round
Browse files Browse the repository at this point in the history
Feature/round
  • Loading branch information
calebzulawski committed Oct 13, 2020
2 parents 285fff0 + 3870633 commit 4baa8c2
Show file tree
Hide file tree
Showing 8 changed files with 305 additions and 14 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,8 @@ jobs:
# for NaNs which makes it worth testing on despite that.
- mips-unknown-linux-gnu
- mips64-unknown-linux-gnuabi64
- riscv64gc-unknown-linux-gnu
# TODO: reenable pending https://github.com/rust-lang/rust/issues/77866
# - riscv64gc-unknown-linux-gnu

steps:
- uses: actions/checkout@v2
Expand Down
3 changes: 3 additions & 0 deletions crates/core_simd/src/intrinsics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,7 @@ extern "platform-intrinsic" {

/// xor
pub(crate) fn simd_xor<T>(x: T, y: T) -> T;

/// fptoui/fptosi/uitofp/sitofp
pub(crate) fn simd_cast<T, U>(x: T) -> U;
}
4 changes: 3 additions & 1 deletion crates/core_simd/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#![no_std]
#![feature(repr_simd, platform_intrinsics)]
#![feature(repr_simd, platform_intrinsics, link_llvm_intrinsics, simd_ffi)]
#![warn(missing_docs)]
//! Portable SIMD module.

Expand Down Expand Up @@ -56,3 +56,5 @@ mod vectors_mask128;
pub use vectors_mask128::*;
mod vectors_masksize;
pub use vectors_masksize::*;

mod round;
153 changes: 153 additions & 0 deletions crates/core_simd/src/round.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
macro_rules! implement {
{
impl $type:ident {
int_type = $int_type:ident,
floor = $floor_intrinsic:literal,
ceil = $ceil_intrinsic:literal,
round = $round_intrinsic:literal,
trunc = $trunc_intrinsic:literal,
}
} => {
mod $type {
#[allow(improper_ctypes)]
extern "C" {
#[link_name = $floor_intrinsic]
fn floor_intrinsic(x: crate::$type) -> crate::$type;
#[link_name = $ceil_intrinsic]
fn ceil_intrinsic(x: crate::$type) -> crate::$type;
#[link_name = $round_intrinsic]
fn round_intrinsic(x: crate::$type) -> crate::$type;
#[link_name = $trunc_intrinsic]
fn trunc_intrinsic(x: crate::$type) -> crate::$type;
}

impl crate::$type {
/// Returns the largest integer less than or equal to each lane.
#[must_use = "method returns a new vector and does not mutate the original value"]
#[inline]
pub fn floor(self) -> Self {
unsafe { floor_intrinsic(self) }
}

/// Returns the smallest integer greater than or equal to each lane.
#[must_use = "method returns a new vector and does not mutate the original value"]
#[inline]
pub fn ceil(self) -> Self {
unsafe { ceil_intrinsic(self) }
}

/// Returns the nearest integer to each lane. Round half-way cases away from 0.0.
#[must_use = "method returns a new vector and does not mutate the original value"]
#[inline]
pub fn round(self) -> Self {
unsafe { round_intrinsic(self) }
}

/// Returns the integer part of each lane.
#[must_use = "method returns a new vector and does not mutate the original value"]
#[inline]
pub fn trunc(self) -> Self {
unsafe { trunc_intrinsic(self) }
}

/// Returns the fractional part of each lane.
#[must_use = "method returns a new vector and does not mutate the original value"]
#[inline]
pub fn fract(self) -> Self {
self - self.trunc()
}

/// Rounds toward zero and converts to the same-width integer type, assuming that
/// the value is finite and fits in that type.
///
/// # Safety
/// The value must:
///
/// * Not be NaN
/// * Not be infinite
/// * Be representable in the return type, after truncating off its fractional part
#[inline]
pub unsafe fn to_int_unchecked(self) -> crate::$int_type {
crate::intrinsics::simd_cast(self)
}

/// Creates a floating-point vector from an integer vector. Rounds values that are
/// not exactly representable.
#[inline]
pub fn round_from_int(value: crate::$int_type) -> Self {
unsafe { crate::intrinsics::simd_cast(value) }
}
}
}
}
}

implement! {
impl f32x2 {
int_type = i32x2,
floor = "llvm.floor.v2f32",
ceil = "llvm.ceil.v2f32",
round = "llvm.round.v2f32",
trunc = "llvm.trunc.v2f32",
}
}

implement! {
impl f32x4 {
int_type = i32x4,
floor = "llvm.floor.v4f32",
ceil = "llvm.ceil.v4f32",
round = "llvm.round.v4f32",
trunc = "llvm.trunc.v4f32",
}
}

implement! {
impl f32x8 {
int_type = i32x8,
floor = "llvm.floor.v8f32",
ceil = "llvm.ceil.v8f32",
round = "llvm.round.v8f32",
trunc = "llvm.trunc.v8f32",
}
}

implement! {
impl f32x16 {
int_type = i32x16,
floor = "llvm.floor.v16f32",
ceil = "llvm.ceil.v16f32",
round = "llvm.round.v16f32",
trunc = "llvm.trunc.v16f32",
}
}

implement! {
impl f64x2 {
int_type = i64x2,
floor = "llvm.floor.v2f64",
ceil = "llvm.ceil.v2f64",
round = "llvm.round.v2f64",
trunc = "llvm.trunc.v2f64",
}
}

implement! {
impl f64x4 {
int_type = i64x4,
floor = "llvm.floor.v4f64",
ceil = "llvm.ceil.v4f64",
round = "llvm.round.v4f64",
trunc = "llvm.trunc.v4f64",
}
}

implement! {
impl f64x8 {
int_type = i64x8,
floor = "llvm.floor.v8f64",
ceil = "llvm.ceil.v8f64",
round = "llvm.round.v8f64",
trunc = "llvm.trunc.v8f64",
}
}
13 changes: 9 additions & 4 deletions crates/core_simd/tests/helpers/lanewise.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
pub fn apply_unary_lanewise<T: Copy, V: AsMut<[T]> + Default>(mut x: V, f: impl Fn(T) -> T) -> V {
for lane in x.as_mut() {
*lane = f(*lane)
pub fn apply_unary_lanewise<T1: Copy, T2: Copy, V1: AsRef<[T1]>, V2: AsMut<[T2]> + Default>(
x: V1,
f: impl Fn(T1) -> T2,
) -> V2 {
let mut y = V2::default();
assert_eq!(x.as_ref().len(), y.as_mut().len());
for (x, y) in x.as_ref().iter().zip(y.as_mut().iter_mut()) {
*y = f(*x);
}
x
y
}

pub fn apply_binary_lanewise<T: Copy, V: AsRef<[T]> + AsMut<[T]> + Default>(
Expand Down
8 changes: 4 additions & 4 deletions crates/core_simd/tests/ops_impl/f32.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::helpers;

float_tests! { f32x2, f32 }
float_tests! { f32x4, f32 }
float_tests! { f32x8, f32 }
float_tests! { f32x16, f32 }
float_tests! { f32x2, f32, i32x2, i32 }
float_tests! { f32x4, f32, i32x4, i32 }
float_tests! { f32x8, f32, i32x8, i32 }
float_tests! { f32x16, f32, i32x16, i32 }
6 changes: 3 additions & 3 deletions crates/core_simd/tests/ops_impl/f64.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use super::helpers;

float_tests! { f64x2, f64 }
float_tests! { f64x4, f64 }
float_tests! { f64x8, f64 }
float_tests! { f64x2, f64, i64x2, i64 }
float_tests! { f64x4, f64, i64x4, i64 }
float_tests! { f64x8, f64, i64x8, i64 }
129 changes: 128 additions & 1 deletion crates/core_simd/tests/ops_impl/float_macros.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
macro_rules! float_tests {
{ $vector:ident, $scalar:ident } => {
{ $vector:ident, $scalar:ident, $int_vector:ident, $int_scalar:ident } => {
#[cfg(test)]
mod $vector {
use super::*;
Expand All @@ -24,6 +24,18 @@ macro_rules! float_tests {
slice.chunks_exact(lanes).map(from_slice)
}

fn from_slice_int(slice: &[$int_scalar]) -> core_simd::$int_vector {
let mut value = core_simd::$int_vector::default();
let value_slice: &mut [_] = value.as_mut();
value_slice.copy_from_slice(&slice[0..value_slice.len()]);
value
}

fn slice_chunks_int(slice: &[$int_scalar]) -> impl Iterator<Item = core_simd::$int_vector> + '_ {
let lanes = core::mem::size_of::<core_simd::$int_vector>() / core::mem::size_of::<$int_scalar>();
slice.chunks_exact(lanes).map(from_slice_int)
}

const A: [$scalar; 16] = [0., 1., 2., 3., 4., 5., 6., 7., 8., 9., 10., 11., 12., 13., 14., 15.];
const B: [$scalar; 16] = [16., 17., 18., 19., 20., 21., 22., 23., 24., 25., 26., 27., 28., 29., 30., 31.];
const C: [$scalar; 16] = [
Expand Down Expand Up @@ -322,6 +334,121 @@ macro_rules! float_tests {
assert_biteq!(v.abs(), expected);
}
}

#[test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
fn ceil_odd_floats() {
for v in slice_chunks(&C) {
let expected = apply_unary_lanewise(v, <$scalar>::ceil);
assert_biteq!(v.ceil(), expected);
}
}

#[test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
fn floor_odd_floats() {
for v in slice_chunks(&C) {
let expected = apply_unary_lanewise(v, <$scalar>::floor);
assert_biteq!(v.floor(), expected);
}
}

#[test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
fn round_odd_floats() {
for v in slice_chunks(&C) {
let expected = apply_unary_lanewise(v, <$scalar>::round);
assert_biteq!(v.round(), expected);
}
}

#[test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
fn round_mode() {
assert_biteq!(core_simd::$vector::splat(1.5).round(), core_simd::$vector::splat(2.0));
assert_biteq!(core_simd::$vector::splat(2.5).round(), core_simd::$vector::splat(3.0));
assert_biteq!(core_simd::$vector::splat(-1.5).round(), core_simd::$vector::splat(-2.0));
assert_biteq!(core_simd::$vector::splat(-2.5).round(), core_simd::$vector::splat(-3.0));
}

#[test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
fn trunc_odd_floats() {
for v in slice_chunks(&C) {
let expected = apply_unary_lanewise(v, <$scalar>::trunc);
assert_biteq!(v.trunc(), expected);
}
}

#[test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
fn fract_odd_floats() {
for v in slice_chunks(&C) {
let expected = apply_unary_lanewise(v, <$scalar>::fract);
assert_biteq!(v.fract(), expected);
}
}

#[test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
fn to_int_unchecked() {
// The maximum integer that can be represented by the equivalently sized float has
// all of the mantissa digits set to 1, pushed up to the MSB.
const ALL_MANTISSA_BITS: $int_scalar = ((1 << <$scalar>::MANTISSA_DIGITS) - 1);
const MAX_REPRESENTABLE_VALUE: $int_scalar =
ALL_MANTISSA_BITS << (core::mem::size_of::<$scalar>() * 8 - <$scalar>::MANTISSA_DIGITS as usize - 1);
const VALUES: [$scalar; 16] = [
-0.0,
0.0,
-1.0,
1.0,
ALL_MANTISSA_BITS as $scalar,
-ALL_MANTISSA_BITS as $scalar,
MAX_REPRESENTABLE_VALUE as $scalar,
-MAX_REPRESENTABLE_VALUE as $scalar,
(MAX_REPRESENTABLE_VALUE / 2) as $scalar,
(-MAX_REPRESENTABLE_VALUE / 2) as $scalar,
<$scalar>::MIN_POSITIVE,
-<$scalar>::MIN_POSITIVE,
<$scalar>::EPSILON,
-<$scalar>::EPSILON,
100.0 / 3.0,
-100.0 / 3.0,
];

for v in slice_chunks(&VALUES) {
let expected = apply_unary_lanewise(v, |x| unsafe { x.to_int_unchecked() });
assert_biteq!(unsafe { v.to_int_unchecked() }, expected);
}
}

#[test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
fn round_from_int() {
const VALUES: [$int_scalar; 16] = [
0,
0,
1,
-1,
100,
-100,
200,
-200,
413,
-413,
1017,
-1017,
1234567,
-1234567,
<$int_scalar>::MAX,
<$int_scalar>::MIN,
];

for v in slice_chunks_int(&VALUES) {
let expected = apply_unary_lanewise(v, |x| x as $scalar);
assert_biteq!(core_simd::$vector::round_from_int(v), expected);
}
}
}
}
}

0 comments on commit 4baa8c2

Please sign in to comment.