Skip to content

Commit e188fc7

Browse files
fix : SIMD
1 parent 2d0a55e commit e188fc7

7 files changed

Lines changed: 279 additions & 149 deletions

File tree

src/simd/generic/avx512.rs

Lines changed: 103 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,104 @@
1-
//! AVX-512 SIMD backend — 16-wide f32 using __m512.
1+
//! AVX-512 SIMD backend — 16-wide f32 using `__m512`.
22
//!
3-
//! Enabled with `--features avx512` on x86_64 targets.
4-
//! Requires nightly Rust for AVX-512 intrinsics.
5-
6-
// AVX-512 support requires nightly and is a placeholder for future implementation.
7-
// The trait implementation follows the same pattern as AVX2 but with __m512 / 16 lanes.
8-
9-
// When stabilized, this will use:
10-
// - _mm512_load_ps, _mm512_store_ps
11-
// - _mm512_set1_ps
12-
// - _mm512_add_ps, _mm512_sub_ps, _mm512_mul_ps, _mm512_div_ps
13-
// - _mm512_fmadd_ps
14-
// - _mm512_max_ps, _mm512_min_ps
15-
// - _mm512_abs_ps
16-
// - _mm512_cmp_ps_mask + _mm512_mask_blend_ps
17-
18-
// For now, re-export scalar as a placeholder if AVX-512 intrinsics aren't available.
19-
#[cfg(not(all(target_arch = "x86_64", target_feature = "avx512f")))]
20-
pub type F32x16 = super::scalar::ScalarFloat;
3+
//! Requires the `avx512f` target feature at the call site (guarded by
4+
//! `#[target_feature]` + `is_x86_feature_detected!` in `crate::simd::x86_64`
5+
//! - an internal module - or by the `sim-avx512` opt-in feature assuming
6+
//! target hardware support in [`crate::sim`]).
7+
8+
use core::arch::x86_64::*;
9+
10+
use super::traits::SimdFloat;
11+
12+
/// 16-wide f32 SIMD type using AVX-512F.
13+
#[derive(Copy, Clone)]
14+
#[repr(transparent)]
15+
pub struct F32x16(__m512);
16+
17+
impl SimdFloat for F32x16 {
18+
const WIDTH: usize = 16;
19+
20+
#[inline(always)]
21+
unsafe fn load(ptr: *const f32) -> Self {
22+
F32x16(unsafe { _mm512_loadu_ps(ptr) })
23+
}
24+
25+
#[inline(always)]
26+
unsafe fn store(self, ptr: *mut f32) {
27+
unsafe { _mm512_storeu_ps(ptr, self.0) };
28+
}
29+
30+
#[inline(always)]
31+
fn splat(v: f32) -> Self {
32+
F32x16(unsafe { _mm512_set1_ps(v) })
33+
}
34+
35+
#[inline(always)]
36+
fn add(self, rhs: Self) -> Self {
37+
F32x16(unsafe { _mm512_add_ps(self.0, rhs.0) })
38+
}
39+
40+
#[inline(always)]
41+
fn sub(self, rhs: Self) -> Self {
42+
F32x16(unsafe { _mm512_sub_ps(self.0, rhs.0) })
43+
}
44+
45+
#[inline(always)]
46+
fn mul(self, rhs: Self) -> Self {
47+
F32x16(unsafe { _mm512_mul_ps(self.0, rhs.0) })
48+
}
49+
50+
#[inline(always)]
51+
fn div(self, rhs: Self) -> Self {
52+
F32x16(unsafe { _mm512_div_ps(self.0, rhs.0) })
53+
}
54+
55+
#[inline(always)]
56+
fn fma(self, b: Self, c: Self) -> Self {
57+
F32x16(unsafe { _mm512_fmadd_ps(self.0, b.0, c.0) })
58+
}
59+
60+
#[inline(always)]
61+
fn max(self, rhs: Self) -> Self {
62+
F32x16(unsafe { _mm512_max_ps(self.0, rhs.0) })
63+
}
64+
65+
#[inline(always)]
66+
fn min(self, rhs: Self) -> Self {
67+
F32x16(unsafe { _mm512_min_ps(self.0, rhs.0) })
68+
}
69+
70+
#[inline(always)]
71+
fn abs(self) -> Self {
72+
let mask = unsafe { _mm512_castsi512_ps(_mm512_set1_epi32(0x7FFF_FFFF_u32 as i32)) };
73+
F32x16(unsafe { _mm512_and_ps(self.0, mask) })
74+
}
75+
76+
#[inline(always)]
77+
fn neg(self) -> Self {
78+
let zero = unsafe { _mm512_setzero_ps() };
79+
F32x16(unsafe { _mm512_sub_ps(zero, self.0) })
80+
}
81+
82+
#[inline(always)]
83+
fn cmp_ge(self, rhs: Self) -> Self {
84+
// AVX-512 compares produce a 16-bit mask register, not a vector -
85+
// broadcast it back into a full lane-wise all-1s/all-0s vector so
86+
// this matches every other backend's `SimdFloat::cmp_ge` contract.
87+
let mask = unsafe { _mm512_cmp_ps_mask(self.0, rhs.0, _CMP_GE_OQ) };
88+
let all_ones = unsafe { _mm512_castsi512_ps(_mm512_set1_epi32(-1)) };
89+
F32x16(unsafe { _mm512_maskz_mov_ps(mask, all_ones) })
90+
}
91+
92+
#[inline(always)]
93+
fn blend(mask: Self, a: Self, b: Self) -> Self {
94+
// Inverse of `cmp_ge`: collapse the lane-wise vector mask back into
95+
// a mask register for `_mm512_mask_blend_ps`.
96+
let k = unsafe { _mm512_movepi32_mask(_mm512_castps_si512(mask.0)) };
97+
F32x16(unsafe { _mm512_mask_blend_ps(k, b.0, a.0) })
98+
}
99+
100+
#[inline(always)]
101+
fn first(self) -> f32 {
102+
unsafe { _mm_cvtss_f32(_mm512_castps512_ps128(self.0)) }
103+
}
104+
}

src/simd/generic/dispatch.rs

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,41 @@
11
// Runtime SIMD dispatch — selects the best available backend.
22
//
3-
// Currently returns the compile-time selected backend.
4-
// Platform-specific backends use `is_x86_feature_detected!` etc.
5-
// at initialization time, then store the result in a static.
3+
// Considers every backend compiled in: unconditionally under the `simd`
4+
// feature (this crate's own runtime-detected `x86_64`/`aarch64` layer,
5+
// which must work correctly on whatever CPU the binary actually ends up
6+
// running on), or under the matching `sim-avx2`/`sim-avx512`/`sim-neon`
7+
// opt-in feature (`crate::sim`'s compile-time-selected layer, which
8+
// assumes the build targets hardware known to support it - `sim`'s own
9+
// callers don't invoke this function to make that choice, they just
10+
// monomorphize against one backend directly, but `simd`'s callers do).
611

7-
/// Query which SIMD backend is available at runtime.
12+
/// Query which SIMD backend is available at runtime, among those compiled in.
813
pub fn detect_backend() -> SimdBackend {
914
#[cfg(target_arch = "x86_64")]
1015
{
11-
#[cfg(feature = "sim-avx512")]
16+
#[cfg(any(feature = "simd", feature = "sim-avx512"))]
1217
if is_x86_feature_detected!("avx512f") {
1318
return SimdBackend::Avx512;
1419
}
1520

16-
#[cfg(feature = "sim-avx2")]
21+
#[cfg(any(feature = "simd", feature = "sim-avx2"))]
1722
if is_x86_feature_detected!("avx2") && is_x86_feature_detected!("fma") {
1823
return SimdBackend::Avx2;
1924
}
25+
26+
// SSE2 is guaranteed present on every x86_64 target, so no runtime
27+
// check is needed - it's the floor beneath AVX2/AVX-512 for the
28+
// `simd` feature's portable, runtime-detected layer specifically
29+
// (`sim` has no equivalent opt-in feature for it).
30+
#[cfg(feature = "simd")]
31+
return SimdBackend::Sse2;
2032
}
2133

2234
// NEON is always available on aarch64
23-
#[cfg(all(target_arch = "aarch64", feature = "sim-neon"))]
35+
#[cfg(all(target_arch = "aarch64", any(feature = "simd", feature = "sim-neon")))]
2436
return SimdBackend::Neon;
2537

26-
#[cfg(not(all(target_arch = "aarch64", feature = "sim-neon")))]
38+
#[allow(unreachable_code)]
2739
SimdBackend::Scalar
2840
}
2941

@@ -32,6 +44,8 @@ pub fn detect_backend() -> SimdBackend {
3244
pub enum SimdBackend {
3345
/// No hardware SIMD; one lane at a time.
3446
Scalar,
47+
/// x86_64 baseline SSE2 (4 `f32` lanes).
48+
Sse2,
3549
/// x86_64 AVX2 (8 `f32` lanes).
3650
Avx2,
3751
/// x86_64 AVX-512 (16 `f32` lanes).
@@ -45,9 +59,21 @@ impl SimdBackend {
4559
pub fn width(self) -> usize {
4660
match self {
4761
SimdBackend::Scalar => 1,
62+
SimdBackend::Sse2 => 4,
4863
SimdBackend::Avx2 => 8,
4964
SimdBackend::Avx512 => 16,
5065
SimdBackend::Neon => 4,
5166
}
5267
}
68+
69+
/// Short identifying name (`"scalar"`, `"sse2"`, `"avx2"`, `"avx512"`, `"neon"`).
70+
pub fn as_str(self) -> &'static str {
71+
match self {
72+
SimdBackend::Scalar => "scalar",
73+
SimdBackend::Sse2 => "sse2",
74+
SimdBackend::Avx2 => "avx2",
75+
SimdBackend::Avx512 => "avx512",
76+
SimdBackend::Neon => "neon",
77+
}
78+
}
5379
}

src/simd/generic/mod.rs

Lines changed: 51 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,30 @@
11
//! SIMD abstraction layer and fast math functions.
22
//!
33
//! All numerical computation in this module is written generically over the
4-
//! [`SimdFloat`] trait. The default [`ScalarFloat`] backend (WIDTH=1) works
5-
//! everywhere; platform backends are activated via feature flags:
4+
//! [`crate::simd::generic::SimdFloat`] trait. The default
5+
//! [`crate::simd::generic::ScalarFloat`] backend (WIDTH=1) works everywhere;
6+
//! platform backends are activated by [`crate::sim`]'s compile-time-selected
7+
//! opt-in features, or unconditionally by [`crate::simd`]'s own
8+
//! runtime-detected `simd` feature (see
9+
//! [`crate::simd::generic::dispatch::detect_backend`]):
610
//!
7-
//! | Feature | Type | WIDTH | Platform |
8-
//! |---------------|-----------|-------|-----------|
9-
//! | (none) | `ScalarFloat` | 1 | all |
10-
//! | `sim-avx2` | `F32x8` | 8 | x86_64 |
11-
//! | `sim-avx512` | `F32x16` | 16 | x86_64 |
12-
//! | `sim-neon` | `F32x4` | 4 | aarch64 |
11+
//! | Feature | Type | WIDTH | Platform |
12+
//! |---------------|------------|-------|-----------|
13+
//! | (none) | `ScalarFloat` | 1 | all |
14+
//! | `simd` | `F32x4Sse` | 4 | x86_64 (baseline floor)|
15+
//! | `sim-avx2` | `F32x8` | 8 | x86_64 |
16+
//! | `sim-avx512` | `F32x16` | 16 | x86_64 |
17+
//! | `sim-neon` | `F32x4` | 4 | aarch64 |
1318
//!
1419
//! # Fast Math
1520
//!
1621
//! The following functions are provided with < 1e-5 relative error:
1722
//!
18-
//! - [`simd_exp`] -- exponential via range reduction + degree-5 polynomial
19-
//! - [`simd_log`] -- natural log via atanh series expansion
20-
//! - [`simd_log1pexp`] -- numerically stable softplus
21-
//! - [`simd_sigmoid`] -- logistic sigmoid without overflow
22-
//! - [`simd_recip`], [`simd_rsqrt`] -- Newton-refined reciprocal/inverse sqrt
23+
//! - [`crate::simd::generic::simd_exp`] -- exponential via range reduction + degree-5 polynomial
24+
//! - [`crate::simd::generic::simd_log`] -- natural log via atanh series expansion
25+
//! - [`crate::simd::generic::simd_log1pexp`] -- numerically stable softplus
26+
//! - [`crate::simd::generic::simd_sigmoid`] -- logistic sigmoid without overflow
27+
//! - [`crate::simd::generic::simd_recip`], [`crate::simd::generic::simd_rsqrt`] -- Newton-refined reciprocal/inverse sqrt
2328
2429
// `exp`'s Cody-Waite range-reduction constants are intentionally given to
2530
// more decimal digits than `f32` can represent exactly (for readability
@@ -36,7 +41,7 @@ pub mod reciprocal;
3641
pub mod scalar;
3742
/// SIMD logistic sigmoid (`simd_sigmoid`).
3843
pub mod sigmoid;
39-
/// The [`SimdFloat`] abstraction trait itself.
44+
/// The [`crate::simd::generic::SimdFloat`] abstraction trait itself.
4045
pub mod traits;
4146

4247
/// AVX2 `F32x8` backend, `x86_64` only. Compiled whenever either
@@ -46,29 +51,35 @@ pub mod traits;
4651
#[cfg(all(target_arch = "x86_64", any(feature = "simd", feature = "sim-avx2")))]
4752
pub mod avx2;
4853

49-
/// AVX-512 `F32x16` backend (`sim-avx512` feature, `x86_64` only).
50-
#[cfg(all(target_arch = "x86_64", feature = "sim-avx512"))]
54+
/// AVX-512 `F32x16` backend, `x86_64` only. Compiled whenever either
55+
/// [`crate::sim`]'s compile-time-selected `sim-avx512` backend or
56+
/// [`crate::simd`]'s runtime-dispatched `simd` feature needs it, mirroring
57+
/// [`crate::simd::generic::avx2`]'s dual use.
58+
#[cfg(all(target_arch = "x86_64", any(feature = "simd", feature = "sim-avx512")))]
5159
pub mod avx512;
5260

5361
/// ARM NEON `F32x4` backend, `aarch64` only. Compiled whenever either
5462
/// [`crate::sim`]'s compile-time-selected `sim-neon` backend or
5563
/// [`crate::simd`]'s runtime-dispatched `simd` feature needs it, mirroring
56-
/// [`avx2`]'s dual use.
64+
/// [`crate::simd::generic::avx2`]'s dual use.
5765
#[cfg(all(target_arch = "aarch64", any(feature = "simd", feature = "sim-neon")))]
5866
pub mod neon;
5967

60-
/// Baseline SSE2 `F32x4Sse` backend, `x86_64` only - see [`sse2`]'s module
61-
/// docs for why it needs no opt-in feature of its own.
68+
/// Baseline SSE2 `F32x4Sse` backend, `x86_64` only - see
69+
/// [`crate::simd::generic::sse2`]'s module docs for why it needs no opt-in
70+
/// feature of its own.
6271
#[cfg(target_arch = "x86_64")]
6372
pub mod sse2;
6473

6574
/// Width-agnostic `dot`/`mul_elementwise`/`mix_scalar` shared by every
66-
/// [`SimdFloat`] backend - see [`ops`]'s module docs.
75+
/// [`crate::simd::generic::SimdFloat`] backend - see
76+
/// [`crate::simd::generic::ops`]'s module docs.
6777
pub mod ops;
6878

69-
/// Runtime backend selection ([`dispatch::detect_backend`]).
79+
/// Runtime backend selection ([`crate::simd::generic::dispatch::detect_backend`]).
7080
pub mod dispatch;
7181

82+
pub use dispatch::SimdBackend;
7283
pub use scalar::ScalarFloat;
7384
pub use traits::SimdFloat;
7485

@@ -78,23 +89,36 @@ pub use reciprocal::{simd_recip, simd_rsqrt};
7889
pub use sigmoid::simd_sigmoid;
7990

8091
/// Select the best available SIMD backend at compile time.
81-
/// Returns a string identifying the active backend.
92+
///
93+
/// This is [`crate::sim`]'s own compile-time choice (which `sim-*` feature
94+
/// was enabled), distinct from
95+
/// [`crate::simd::generic::dispatch::detect_backend`]'s runtime CPU check -
96+
/// see that function's docs for why the two differ. Returns a string
97+
/// identifying the active backend, from the same vocabulary as
98+
/// [`SimdBackend::as_str`].
8299
pub fn active_backend() -> &'static str {
83100
#[cfg(all(target_arch = "x86_64", feature = "sim-avx512"))]
84101
{
85-
return "avx512";
102+
return SimdBackend::Avx512.as_str();
86103
}
87104

88-
#[cfg(all(target_arch = "x86_64", feature = "sim-avx2"))]
105+
// `not(sim-avx512)` keeps this mutually exclusive with the branch above
106+
// so `--all-features` (which enables every `sim-*` feature at once)
107+
// doesn't produce two unconditional `return`s in a row.
108+
#[cfg(all(
109+
target_arch = "x86_64",
110+
feature = "sim-avx2",
111+
not(feature = "sim-avx512")
112+
))]
89113
{
90-
return "avx2";
114+
return SimdBackend::Avx2.as_str();
91115
}
92116

93117
#[cfg(all(target_arch = "aarch64", feature = "sim-neon"))]
94118
{
95-
return "neon";
119+
return SimdBackend::Neon.as_str();
96120
}
97121

98122
#[allow(unreachable_code)]
99-
"scalar"
123+
SimdBackend::Scalar.as_str()
100124
}

src/simd/generic/sse2.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
//!
33
//! SSE2 is guaranteed present on every `x86_64` target (it's part of the
44
//! baseline ABI), so this needs no runtime feature detection and no opt-in
5-
//! Cargo feature - it's the always-available floor beneath [`super::avx2`].
5+
//! Cargo feature - it's the always-available floor beneath
6+
//! [`crate::simd::generic::avx2`].
67
78
use core::arch::x86_64::*;
89

0 commit comments

Comments
 (0)