Skip to content

Commit 6143bde

Browse files
Merge #243 from ./no-overflow-panic
Remove overflow panic from divrem and add basic docs to Simd<T, N> This finishes normalizing Simd<T, N> to being approximately equivalent to Simd<Wrapping<T>, N> for all implemented operations I can think of. It also documents this fact, allowing this to close #56.
2 parents 4910274 + 5d52455 commit 6143bde

File tree

4 files changed

+75
-26
lines changed

4 files changed

+75
-26
lines changed

crates/core_simd/src/intrinsics.rs

+9
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,15 @@ extern "platform-intrinsic" {
1717
pub(crate) fn simd_mul<T>(x: T, y: T) -> T;
1818

1919
/// udiv/sdiv/fdiv
20+
/// ints and uints: {s,u}div incur UB if division by zero occurs.
21+
/// ints: sdiv is UB for int::MIN / -1.
22+
/// floats: fdiv is never UB, but may create NaNs or infinities.
2023
pub(crate) fn simd_div<T>(x: T, y: T) -> T;
2124

2225
/// urem/srem/frem
26+
/// ints and uints: {s,u}rem incur UB if division by zero occurs.
27+
/// ints: srem is UB for int::MIN / -1.
28+
/// floats: frem is equivalent to libm::fmod in the "default" floating point environment, sans errno.
2329
pub(crate) fn simd_rem<T>(x: T, y: T) -> T;
2430

2531
/// shl
@@ -45,6 +51,9 @@ extern "platform-intrinsic" {
4551
pub(crate) fn simd_as<T, U>(x: T) -> U;
4652

4753
/// neg/fneg
54+
/// ints: ultimately becomes a call to cg_ssa's BuilderMethods::neg. cg_llvm equates this to `simd_sub(Simd::splat(0), x)`.
55+
/// floats: LLVM's fneg, which changes the floating point sign bit. Some arches have instructions for it.
56+
/// Rust panics for Neg::neg(int::MIN) due to overflow, but it is not UB in LLVM without `nsw`.
4857
pub(crate) fn simd_neg<T>(x: T) -> T;
4958

5059
/// fabs

crates/core_simd/src/ops.rs

+24-15
Original file line numberDiff line numberDiff line change
@@ -57,29 +57,40 @@ macro_rules! wrap_bitshift {
5757
};
5858
}
5959

60-
// Division by zero is poison, according to LLVM.
61-
// So is dividing the MIN value of a signed integer by -1,
62-
// since that would return MAX + 1.
63-
// FIXME: Rust allows <SInt>::MIN / -1,
64-
// so we should probably figure out how to make that safe.
60+
/// SAFETY: This macro must only be used to impl Div or Rem and given the matching intrinsic.
61+
/// It guards against LLVM's UB conditions for integer div or rem using masks and selects,
62+
/// thus guaranteeing a Rust value returns instead.
63+
///
64+
/// | | LLVM | Rust
65+
/// | :--------------: | :--- | :----------
66+
/// | N {/,%} 0 | UB | panic!()
67+
/// | <$int>::MIN / -1 | UB | <$int>::MIN
68+
/// | <$int>::MIN % -1 | UB | 0
69+
///
6570
macro_rules! int_divrem_guard {
6671
( $lhs:ident,
6772
$rhs:ident,
6873
{ const PANIC_ZERO: &'static str = $zero:literal;
69-
const PANIC_OVERFLOW: &'static str = $overflow:literal;
7074
$simd_call:ident
7175
},
7276
$int:ident ) => {
7377
if $rhs.lanes_eq(Simd::splat(0)).any() {
7478
panic!($zero);
75-
} else if <$int>::MIN != 0
76-
&& ($lhs.lanes_eq(Simd::splat(<$int>::MIN))
77-
// type inference can break here, so cut an SInt to size
78-
& $rhs.lanes_eq(Simd::splat(-1i64 as _))).any()
79-
{
80-
panic!($overflow);
8179
} else {
82-
unsafe { $crate::simd::intrinsics::$simd_call($lhs, $rhs) }
80+
// Prevent otherwise-UB overflow on the MIN / -1 case.
81+
let rhs = if <$int>::MIN != 0 {
82+
// This should, at worst, optimize to a few branchless logical ops
83+
// Ideally, this entire conditional should evaporate
84+
// Fire LLVM and implement those manually if it doesn't get the hint
85+
($lhs.lanes_eq(Simd::splat(<$int>::MIN))
86+
// type inference can break here, so cut an SInt to size
87+
& $rhs.lanes_eq(Simd::splat(-1i64 as _)))
88+
.select(Simd::splat(1), $rhs)
89+
} else {
90+
// Nice base case to make it easy to const-fold away the other branch.
91+
$rhs
92+
};
93+
unsafe { $crate::simd::intrinsics::$simd_call($lhs, rhs) }
8394
}
8495
};
8596
}
@@ -183,15 +194,13 @@ for_base_ops! {
183194
impl Div::div {
184195
int_divrem_guard {
185196
const PANIC_ZERO: &'static str = "attempt to divide by zero";
186-
const PANIC_OVERFLOW: &'static str = "attempt to divide with overflow";
187197
simd_div
188198
}
189199
}
190200

191201
impl Rem::rem {
192202
int_divrem_guard {
193203
const PANIC_ZERO: &'static str = "attempt to calculate the remainder with a divisor of zero";
194-
const PANIC_OVERFLOW: &'static str = "attempt to calculate the remainder with overflow";
195204
simd_rem
196205
}
197206
}

crates/core_simd/src/vector.rs

+32-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,38 @@ pub(crate) mod ptr;
1212
use crate::simd::intrinsics;
1313
use crate::simd::{LaneCount, Mask, MaskElement, SupportedLaneCount};
1414

15-
/// A SIMD vector of `LANES` elements of type `T`.
15+
/// A SIMD vector of `LANES` elements of type `T`. `Simd<T, N>` has the same shape as [`[T; N]`](array), but operates like `T`.
16+
///
17+
/// Two vectors of the same type and length will, by convention, support the operators (+, *, etc.) that `T` does.
18+
/// These take the lanes at each index on the left-hand side and right-hand side, perform the operation,
19+
/// and return the result in the same lane in a vector of equal size. For a given operator, this is equivalent to zipping
20+
/// the two arrays together and mapping the operator over each lane.
21+
///
22+
/// ```rust
23+
/// # #![feature(array_zip, portable_simd)]
24+
/// # use core::simd::{Simd};
25+
/// let a0: [i32; 4] = [-2, 0, 2, 4];
26+
/// let a1 = [10, 9, 8, 7];
27+
/// let zm_add = a0.zip(a1).map(|(lhs, rhs)| lhs + rhs);
28+
/// let zm_mul = a0.zip(a1).map(|(lhs, rhs)| lhs * rhs);
29+
///
30+
/// // `Simd<T, N>` implements `From<[T; N]>
31+
/// let (v0, v1) = (Simd::from(a0), Simd::from(a1));
32+
/// // Which means arrays implement `Into<Simd<T, N>>`.
33+
/// assert_eq!(v0 + v1, zm_add.into());
34+
/// assert_eq!(v0 * v1, zm_mul.into());
35+
/// ```
36+
///
37+
/// `Simd` with integers has the quirk that these operations are also inherently wrapping, as if `T` was [`Wrapping<T>`].
38+
/// Thus, `Simd` does not implement `wrapping_add`, because that is the default behavior.
39+
/// This means there is no warning on overflows, even in "debug" builds.
40+
/// For most applications where `Simd` is appropriate, it is "not a bug" to wrap,
41+
/// and even "debug builds" are unlikely to tolerate the loss of performance.
42+
/// You may want to consider using explicitly checked arithmetic if such is required.
43+
/// Division by zero still causes a panic, so you may want to consider using floating point numbers if that is unacceptable.
44+
///
45+
/// [`Wrapping<T>`]: core::num::Wrapping
46+
///
1647
#[repr(simd)]
1748
pub struct Simd<T, const LANES: usize>([T; LANES])
1849
where

crates/core_simd/tests/ops_macros.rs

+10-10
Original file line numberDiff line numberDiff line change
@@ -210,15 +210,21 @@ macro_rules! impl_signed_tests {
210210
)
211211
}
212212

213-
}
213+
fn div_min_may_overflow<const LANES: usize>() {
214+
let a = Vector::<LANES>::splat(Scalar::MIN);
215+
let b = Vector::<LANES>::splat(-1);
216+
assert_eq!(a / b, a);
217+
}
214218

215-
test_helpers::test_lanes_panic! {
216-
fn div_min_overflow_panics<const LANES: usize>() {
219+
fn rem_min_may_overflow<const LANES: usize>() {
217220
let a = Vector::<LANES>::splat(Scalar::MIN);
218221
let b = Vector::<LANES>::splat(-1);
219-
let _ = a / b;
222+
assert_eq!(a % b, Vector::<LANES>::splat(0));
220223
}
221224

225+
}
226+
227+
test_helpers::test_lanes_panic! {
222228
fn div_by_all_zeros_panics<const LANES: usize>() {
223229
let a = Vector::<LANES>::splat(42);
224230
let b = Vector::<LANES>::splat(0);
@@ -232,12 +238,6 @@ macro_rules! impl_signed_tests {
232238
let _ = a / b;
233239
}
234240

235-
fn rem_min_overflow_panic<const LANES: usize>() {
236-
let a = Vector::<LANES>::splat(Scalar::MIN);
237-
let b = Vector::<LANES>::splat(-1);
238-
let _ = a % b;
239-
}
240-
241241
fn rem_zero_panic<const LANES: usize>() {
242242
let a = Vector::<LANES>::splat(42);
243243
let b = Vector::<LANES>::splat(0);

0 commit comments

Comments
 (0)