zvidlib_core/simd.rs
1//! The process-wide SIMD instruction-set override shared by every codec kernel.
2//!
3//! zvidlib's codecs dispatch their hot loops to runtime-detected vector kernels
4//! in many independent places, each caching its own CPU feature probe. This
5//! module is the single switch every one of them consults first; see
6//! `zvidlib::simd` for the user-facing description and the per-site report.
7
8use core::sync::atomic::{AtomicU8, Ordering};
9
10#[doc(hidden)]
11pub mod vector;
12
13/// `0` means "no override"; every other value is a [`SimdIsa::code`].
14static OVERRIDE: AtomicU8 = AtomicU8::new(0);
15
16/// Forces every SIMD-dispatched kernel in the crate onto `isa`, or restores
17/// per-site automatic detection with `None`.
18///
19/// The override reaches every dispatch family: the AV1 transform and in-loop
20/// filter kernels, AV1 motion compensation (through the default level
21/// `crate::av1_mc::McContext::new` picks up), AV1 intra prediction, the
22/// Vorbis encoder's analysis kernels, the VP8 encoder, reconstruction and
23/// decoder kernels, every VP9 decoder kernel, every HEVC engine kernel, the
24/// AV1, VP8 and VP9 output color conversion, the Vorbis decoder's synthesis
25/// kernels, and the VP9 encoder's kernels. [`SimdIsa::Scalar`] therefore
26/// genuinely reaches the scalar code path rather than merely the widest
27/// scalar-ish one.
28///
29/// An instruction set this host cannot execute is clamped to
30/// [`SimdIsa::Scalar`] rather than silently ignored, so a caller that asks for
31/// AVX2 on an aarch64 machine gets a defined, reproducible arm instead of the
32/// host's best vector kernels.
33///
34/// This is safe to call at any time and from any thread: the kernels agree
35/// bit-for-bit, so a switch that lands between two blocks of the same frame
36/// still produces the same output.
37pub fn set_override(isa: Option<SimdIsa>) {
38 let code = match isa {
39 Some(isa) if available().contains(&isa) => isa.code(),
40 Some(_) => SimdIsa::Scalar.code(),
41 None => 0,
42 };
43 OVERRIDE.store(code, Ordering::Relaxed);
44}
45
46/// The instruction set currently in force: the [`set_override`] value when one
47/// is set, otherwise [`detected`].
48#[must_use]
49pub fn active() -> SimdIsa {
50 override_isa().unwrap_or_else(detected)
51}
52
53/// The widest instruction set this host supports, ignoring any
54/// [`set_override`].
55#[must_use]
56pub fn detected() -> SimdIsa {
57 detected_isa()
58}
59
60/// Every instruction set this host can execute, always including
61/// [`SimdIsa::Scalar`].
62///
63/// Benchmarks iterate this to build one measurement arm per available
64/// instruction set, so scalar and vector timings sit side by side.
65#[must_use]
66pub fn available() -> Vec<SimdIsa> {
67 available_isas()
68}
69
70/// An instruction set the SIMD kernels can run on.
71#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
72pub enum SimdIsa {
73 /// The portable scalar reference implementation.
74 Scalar,
75 /// x86_64 SSE4.1 (128-bit, four 32-bit lanes).
76 Sse41,
77 /// x86_64 AVX2 (256-bit, eight 32-bit lanes).
78 Avx2,
79 /// aarch64 NEON (128-bit, four 32-bit lanes).
80 Neon,
81}
82
83impl SimdIsa {
84 /// A short stable name, useful in benchmark and diagnostic output.
85 #[must_use]
86 pub fn name(self) -> &'static str {
87 match self {
88 SimdIsa::Scalar => "scalar",
89 SimdIsa::Sse41 => "sse4.1",
90 SimdIsa::Avx2 => "avx2",
91 SimdIsa::Neon => "neon",
92 }
93 }
94
95 #[doc(hidden)]
96 pub fn code(self) -> u8 {
97 match self {
98 SimdIsa::Scalar => 1,
99 SimdIsa::Sse41 => 2,
100 SimdIsa::Avx2 => 3,
101 SimdIsa::Neon => 4,
102 }
103 }
104
105 #[doc(hidden)]
106 pub fn from_code(code: u8) -> Option<Self> {
107 match code {
108 1 => Some(SimdIsa::Scalar),
109 2 => Some(SimdIsa::Sse41),
110 3 => Some(SimdIsa::Avx2),
111 4 => Some(SimdIsa::Neon),
112 _ => None,
113 }
114 }
115}
116
117/// Number of samples a single vector operation covers on `isa`, or `0` when
118/// `isa` has no vector path and callers should stay scalar.
119#[must_use]
120pub fn lanes(isa: SimdIsa) -> usize {
121 match isa {
122 SimdIsa::Scalar => 0,
123 SimdIsa::Sse41 | SimdIsa::Neon => 4,
124 SimdIsa::Avx2 => 8,
125 }
126}
127
128/// The best instruction set this CPU supports, ignoring any [`set_override`].
129#[must_use]
130pub fn detected_isa() -> SimdIsa {
131 #[cfg(target_arch = "x86_64")]
132 {
133 if std::is_x86_feature_detected!("avx2") {
134 return SimdIsa::Avx2;
135 }
136 if std::is_x86_feature_detected!("sse4.1") {
137 return SimdIsa::Sse41;
138 }
139 SimdIsa::Scalar
140 }
141 #[cfg(target_arch = "aarch64")]
142 {
143 // NEON is mandatory in the aarch64 base architecture, so no runtime
144 // probe is needed (and `is_aarch64_feature_detected!` is still
145 // unstable).
146 SimdIsa::Neon
147 }
148 #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
149 {
150 SimdIsa::Scalar
151 }
152}
153
154/// Every instruction set that can be exercised on this host, always including
155/// [`SimdIsa::Scalar`]. Used by the bit-exactness tests and the benchmark.
156#[must_use]
157pub fn available_isas() -> Vec<SimdIsa> {
158 #[cfg_attr(
159 not(any(target_arch = "x86_64", target_arch = "aarch64")),
160 allow(unused_mut)
161 )]
162 let mut isas = vec![SimdIsa::Scalar];
163 #[cfg(target_arch = "x86_64")]
164 {
165 if std::is_x86_feature_detected!("sse4.1") {
166 isas.push(SimdIsa::Sse41);
167 }
168 if std::is_x86_feature_detected!("avx2") {
169 isas.push(SimdIsa::Avx2);
170 }
171 }
172 #[cfg(target_arch = "aarch64")]
173 {
174 isas.push(SimdIsa::Neon);
175 }
176 isas
177}
178
179/// The active override, or `None` when detection is in charge.
180///
181/// Every dispatch site in the crate consults this *before* its own cached
182/// probe, which is what lets the override win over a `OnceLock` that has
183/// already resolved.
184#[doc(hidden)]
185#[inline]
186#[must_use]
187pub fn override_isa() -> Option<SimdIsa> {
188 SimdIsa::from_code(OVERRIDE.load(Ordering::Relaxed))
189}
190
191/// Serializes every test that pins the process-wide override.
192///
193/// The override is one global now, so tests that used to pin four independent
194/// switches (in `av1_simd`, the HEVC in-loop filter dispatcher, and here) can
195/// no longer each hold their own mutex — they would swap the instruction set
196/// out from under each other. They all take this one instead.
197#[doc(hidden)]
198pub fn test_lock() -> std::sync::MutexGuard<'static, ()> {
199 static LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
200 LOCK.lock().unwrap_or_else(|poisoned| poisoned.into_inner())
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206
207 use super::test_lock as lock;
208
209 #[test]
210 fn available_always_includes_scalar_and_contains_the_detected_set() {
211 let isas = available();
212 assert!(isas.contains(&SimdIsa::Scalar));
213 assert!(isas.contains(&detected()));
214 }
215
216 #[test]
217 fn override_takes_precedence_over_detection_and_clears() {
218 let _guard = lock();
219 set_override(Some(SimdIsa::Scalar));
220 assert_eq!(active(), SimdIsa::Scalar);
221 set_override(None);
222 assert_eq!(active(), detected());
223 }
224
225 #[test]
226 fn every_available_instruction_set_can_be_pinned() {
227 let _guard = lock();
228 for isa in available() {
229 set_override(Some(isa));
230 assert_eq!(active(), isa, "{}", isa.name());
231 }
232 set_override(None);
233 }
234
235 #[test]
236 fn an_unsupported_instruction_set_clamps_to_scalar() {
237 let _guard = lock();
238 let unsupported = [
239 SimdIsa::Scalar,
240 SimdIsa::Sse41,
241 SimdIsa::Avx2,
242 SimdIsa::Neon,
243 ]
244 .into_iter()
245 .find(|isa| !available().contains(isa));
246 if let Some(isa) = unsupported {
247 set_override(Some(isa));
248 assert_eq!(active(), SimdIsa::Scalar, "{}", isa.name());
249 }
250 set_override(None);
251 }
252}