Skip to content

Commit 4baa8c2

Browse files
Merge pull request rust-lang#34 from rust-lang/feature/round
Feature/round
2 parents 285fff0 + 3870633 commit 4baa8c2

File tree

8 files changed

+305
-14
lines changed

8 files changed

+305
-14
lines changed

.github/workflows/ci.yml

+2-1
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,8 @@ jobs:
121121
# for NaNs which makes it worth testing on despite that.
122122
- mips-unknown-linux-gnu
123123
- mips64-unknown-linux-gnuabi64
124-
- riscv64gc-unknown-linux-gnu
124+
# TODO: reenable pending https://github.com/rust-lang/rust/issues/77866
125+
# - riscv64gc-unknown-linux-gnu
125126

126127
steps:
127128
- uses: actions/checkout@v2

crates/core_simd/src/intrinsics.rs

+3
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,7 @@ extern "platform-intrinsic" {
3636

3737
/// xor
3838
pub(crate) fn simd_xor<T>(x: T, y: T) -> T;
39+
40+
/// fptoui/fptosi/uitofp/sitofp
41+
pub(crate) fn simd_cast<T, U>(x: T) -> U;
3942
}

crates/core_simd/src/lib.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#![no_std]
2-
#![feature(repr_simd, platform_intrinsics)]
2+
#![feature(repr_simd, platform_intrinsics, link_llvm_intrinsics, simd_ffi)]
33
#![warn(missing_docs)]
44
//! Portable SIMD module.
55
@@ -56,3 +56,5 @@ mod vectors_mask128;
5656
pub use vectors_mask128::*;
5757
mod vectors_masksize;
5858
pub use vectors_masksize::*;
59+
60+
mod round;

crates/core_simd/src/round.rs

+153
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
macro_rules! implement {
2+
{
3+
impl $type:ident {
4+
int_type = $int_type:ident,
5+
floor = $floor_intrinsic:literal,
6+
ceil = $ceil_intrinsic:literal,
7+
round = $round_intrinsic:literal,
8+
trunc = $trunc_intrinsic:literal,
9+
}
10+
} => {
11+
mod $type {
12+
#[allow(improper_ctypes)]
13+
extern "C" {
14+
#[link_name = $floor_intrinsic]
15+
fn floor_intrinsic(x: crate::$type) -> crate::$type;
16+
#[link_name = $ceil_intrinsic]
17+
fn ceil_intrinsic(x: crate::$type) -> crate::$type;
18+
#[link_name = $round_intrinsic]
19+
fn round_intrinsic(x: crate::$type) -> crate::$type;
20+
#[link_name = $trunc_intrinsic]
21+
fn trunc_intrinsic(x: crate::$type) -> crate::$type;
22+
}
23+
24+
impl crate::$type {
25+
/// Returns the largest integer less than or equal to each lane.
26+
#[must_use = "method returns a new vector and does not mutate the original value"]
27+
#[inline]
28+
pub fn floor(self) -> Self {
29+
unsafe { floor_intrinsic(self) }
30+
}
31+
32+
/// Returns the smallest integer greater than or equal to each lane.
33+
#[must_use = "method returns a new vector and does not mutate the original value"]
34+
#[inline]
35+
pub fn ceil(self) -> Self {
36+
unsafe { ceil_intrinsic(self) }
37+
}
38+
39+
/// Returns the nearest integer to each lane. Round half-way cases away from 0.0.
40+
#[must_use = "method returns a new vector and does not mutate the original value"]
41+
#[inline]
42+
pub fn round(self) -> Self {
43+
unsafe { round_intrinsic(self) }
44+
}
45+
46+
/// Returns the integer part of each lane.
47+
#[must_use = "method returns a new vector and does not mutate the original value"]
48+
#[inline]
49+
pub fn trunc(self) -> Self {
50+
unsafe { trunc_intrinsic(self) }
51+
}
52+
53+
/// Returns the fractional part of each lane.
54+
#[must_use = "method returns a new vector and does not mutate the original value"]
55+
#[inline]
56+
pub fn fract(self) -> Self {
57+
self - self.trunc()
58+
}
59+
60+
/// Rounds toward zero and converts to the same-width integer type, assuming that
61+
/// the value is finite and fits in that type.
62+
///
63+
/// # Safety
64+
/// The value must:
65+
///
66+
/// * Not be NaN
67+
/// * Not be infinite
68+
/// * Be representable in the return type, after truncating off its fractional part
69+
#[inline]
70+
pub unsafe fn to_int_unchecked(self) -> crate::$int_type {
71+
crate::intrinsics::simd_cast(self)
72+
}
73+
74+
/// Creates a floating-point vector from an integer vector. Rounds values that are
75+
/// not exactly representable.
76+
#[inline]
77+
pub fn round_from_int(value: crate::$int_type) -> Self {
78+
unsafe { crate::intrinsics::simd_cast(value) }
79+
}
80+
}
81+
}
82+
}
83+
}
84+
85+
implement! {
86+
impl f32x2 {
87+
int_type = i32x2,
88+
floor = "llvm.floor.v2f32",
89+
ceil = "llvm.ceil.v2f32",
90+
round = "llvm.round.v2f32",
91+
trunc = "llvm.trunc.v2f32",
92+
}
93+
}
94+
95+
implement! {
96+
impl f32x4 {
97+
int_type = i32x4,
98+
floor = "llvm.floor.v4f32",
99+
ceil = "llvm.ceil.v4f32",
100+
round = "llvm.round.v4f32",
101+
trunc = "llvm.trunc.v4f32",
102+
}
103+
}
104+
105+
implement! {
106+
impl f32x8 {
107+
int_type = i32x8,
108+
floor = "llvm.floor.v8f32",
109+
ceil = "llvm.ceil.v8f32",
110+
round = "llvm.round.v8f32",
111+
trunc = "llvm.trunc.v8f32",
112+
}
113+
}
114+
115+
implement! {
116+
impl f32x16 {
117+
int_type = i32x16,
118+
floor = "llvm.floor.v16f32",
119+
ceil = "llvm.ceil.v16f32",
120+
round = "llvm.round.v16f32",
121+
trunc = "llvm.trunc.v16f32",
122+
}
123+
}
124+
125+
implement! {
126+
impl f64x2 {
127+
int_type = i64x2,
128+
floor = "llvm.floor.v2f64",
129+
ceil = "llvm.ceil.v2f64",
130+
round = "llvm.round.v2f64",
131+
trunc = "llvm.trunc.v2f64",
132+
}
133+
}
134+
135+
implement! {
136+
impl f64x4 {
137+
int_type = i64x4,
138+
floor = "llvm.floor.v4f64",
139+
ceil = "llvm.ceil.v4f64",
140+
round = "llvm.round.v4f64",
141+
trunc = "llvm.trunc.v4f64",
142+
}
143+
}
144+
145+
implement! {
146+
impl f64x8 {
147+
int_type = i64x8,
148+
floor = "llvm.floor.v8f64",
149+
ceil = "llvm.ceil.v8f64",
150+
round = "llvm.round.v8f64",
151+
trunc = "llvm.trunc.v8f64",
152+
}
153+
}

crates/core_simd/tests/helpers/lanewise.rs

+9-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
1-
pub fn apply_unary_lanewise<T: Copy, V: AsMut<[T]> + Default>(mut x: V, f: impl Fn(T) -> T) -> V {
2-
for lane in x.as_mut() {
3-
*lane = f(*lane)
1+
pub fn apply_unary_lanewise<T1: Copy, T2: Copy, V1: AsRef<[T1]>, V2: AsMut<[T2]> + Default>(
2+
x: V1,
3+
f: impl Fn(T1) -> T2,
4+
) -> V2 {
5+
let mut y = V2::default();
6+
assert_eq!(x.as_ref().len(), y.as_mut().len());
7+
for (x, y) in x.as_ref().iter().zip(y.as_mut().iter_mut()) {
8+
*y = f(*x);
49
}
5-
x
10+
y
611
}
712

813
pub fn apply_binary_lanewise<T: Copy, V: AsRef<[T]> + AsMut<[T]> + Default>(
+4-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use super::helpers;
22

3-
float_tests! { f32x2, f32 }
4-
float_tests! { f32x4, f32 }
5-
float_tests! { f32x8, f32 }
6-
float_tests! { f32x16, f32 }
3+
float_tests! { f32x2, f32, i32x2, i32 }
4+
float_tests! { f32x4, f32, i32x4, i32 }
5+
float_tests! { f32x8, f32, i32x8, i32 }
6+
float_tests! { f32x16, f32, i32x16, i32 }
+3-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use super::helpers;
22

3-
float_tests! { f64x2, f64 }
4-
float_tests! { f64x4, f64 }
5-
float_tests! { f64x8, f64 }
3+
float_tests! { f64x2, f64, i64x2, i64 }
4+
float_tests! { f64x4, f64, i64x4, i64 }
5+
float_tests! { f64x8, f64, i64x8, i64 }

crates/core_simd/tests/ops_impl/float_macros.rs

+128-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
macro_rules! float_tests {
2-
{ $vector:ident, $scalar:ident } => {
2+
{ $vector:ident, $scalar:ident, $int_vector:ident, $int_scalar:ident } => {
33
#[cfg(test)]
44
mod $vector {
55
use super::*;
@@ -24,6 +24,18 @@ macro_rules! float_tests {
2424
slice.chunks_exact(lanes).map(from_slice)
2525
}
2626

27+
fn from_slice_int(slice: &[$int_scalar]) -> core_simd::$int_vector {
28+
let mut value = core_simd::$int_vector::default();
29+
let value_slice: &mut [_] = value.as_mut();
30+
value_slice.copy_from_slice(&slice[0..value_slice.len()]);
31+
value
32+
}
33+
34+
fn slice_chunks_int(slice: &[$int_scalar]) -> impl Iterator<Item = core_simd::$int_vector> + '_ {
35+
let lanes = core::mem::size_of::<core_simd::$int_vector>() / core::mem::size_of::<$int_scalar>();
36+
slice.chunks_exact(lanes).map(from_slice_int)
37+
}
38+
2739
const A: [$scalar; 16] = [0., 1., 2., 3., 4., 5., 6., 7., 8., 9., 10., 11., 12., 13., 14., 15.];
2840
const B: [$scalar; 16] = [16., 17., 18., 19., 20., 21., 22., 23., 24., 25., 26., 27., 28., 29., 30., 31.];
2941
const C: [$scalar; 16] = [
@@ -322,6 +334,121 @@ macro_rules! float_tests {
322334
assert_biteq!(v.abs(), expected);
323335
}
324336
}
337+
338+
#[test]
339+
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
340+
fn ceil_odd_floats() {
341+
for v in slice_chunks(&C) {
342+
let expected = apply_unary_lanewise(v, <$scalar>::ceil);
343+
assert_biteq!(v.ceil(), expected);
344+
}
345+
}
346+
347+
#[test]
348+
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
349+
fn floor_odd_floats() {
350+
for v in slice_chunks(&C) {
351+
let expected = apply_unary_lanewise(v, <$scalar>::floor);
352+
assert_biteq!(v.floor(), expected);
353+
}
354+
}
355+
356+
#[test]
357+
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
358+
fn round_odd_floats() {
359+
for v in slice_chunks(&C) {
360+
let expected = apply_unary_lanewise(v, <$scalar>::round);
361+
assert_biteq!(v.round(), expected);
362+
}
363+
}
364+
365+
#[test]
366+
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
367+
fn round_mode() {
368+
assert_biteq!(core_simd::$vector::splat(1.5).round(), core_simd::$vector::splat(2.0));
369+
assert_biteq!(core_simd::$vector::splat(2.5).round(), core_simd::$vector::splat(3.0));
370+
assert_biteq!(core_simd::$vector::splat(-1.5).round(), core_simd::$vector::splat(-2.0));
371+
assert_biteq!(core_simd::$vector::splat(-2.5).round(), core_simd::$vector::splat(-3.0));
372+
}
373+
374+
#[test]
375+
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
376+
fn trunc_odd_floats() {
377+
for v in slice_chunks(&C) {
378+
let expected = apply_unary_lanewise(v, <$scalar>::trunc);
379+
assert_biteq!(v.trunc(), expected);
380+
}
381+
}
382+
383+
#[test]
384+
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
385+
fn fract_odd_floats() {
386+
for v in slice_chunks(&C) {
387+
let expected = apply_unary_lanewise(v, <$scalar>::fract);
388+
assert_biteq!(v.fract(), expected);
389+
}
390+
}
391+
392+
#[test]
393+
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
394+
fn to_int_unchecked() {
395+
// The maximum integer that can be represented by the equivalently sized float has
396+
// all of the mantissa digits set to 1, pushed up to the MSB.
397+
const ALL_MANTISSA_BITS: $int_scalar = ((1 << <$scalar>::MANTISSA_DIGITS) - 1);
398+
const MAX_REPRESENTABLE_VALUE: $int_scalar =
399+
ALL_MANTISSA_BITS << (core::mem::size_of::<$scalar>() * 8 - <$scalar>::MANTISSA_DIGITS as usize - 1);
400+
const VALUES: [$scalar; 16] = [
401+
-0.0,
402+
0.0,
403+
-1.0,
404+
1.0,
405+
ALL_MANTISSA_BITS as $scalar,
406+
-ALL_MANTISSA_BITS as $scalar,
407+
MAX_REPRESENTABLE_VALUE as $scalar,
408+
-MAX_REPRESENTABLE_VALUE as $scalar,
409+
(MAX_REPRESENTABLE_VALUE / 2) as $scalar,
410+
(-MAX_REPRESENTABLE_VALUE / 2) as $scalar,
411+
<$scalar>::MIN_POSITIVE,
412+
-<$scalar>::MIN_POSITIVE,
413+
<$scalar>::EPSILON,
414+
-<$scalar>::EPSILON,
415+
100.0 / 3.0,
416+
-100.0 / 3.0,
417+
];
418+
419+
for v in slice_chunks(&VALUES) {
420+
let expected = apply_unary_lanewise(v, |x| unsafe { x.to_int_unchecked() });
421+
assert_biteq!(unsafe { v.to_int_unchecked() }, expected);
422+
}
423+
}
424+
425+
#[test]
426+
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
427+
fn round_from_int() {
428+
const VALUES: [$int_scalar; 16] = [
429+
0,
430+
0,
431+
1,
432+
-1,
433+
100,
434+
-100,
435+
200,
436+
-200,
437+
413,
438+
-413,
439+
1017,
440+
-1017,
441+
1234567,
442+
-1234567,
443+
<$int_scalar>::MAX,
444+
<$int_scalar>::MIN,
445+
];
446+
447+
for v in slice_chunks_int(&VALUES) {
448+
let expected = apply_unary_lanewise(v, |x| x as $scalar);
449+
assert_biteq!(core_simd::$vector::round_from_int(v), expected);
450+
}
451+
}
325452
}
326453
}
327454
}

0 commit comments

Comments
 (0)