1use crate::runtime::{Backend, OperationKind};
4
5use super::contracts::BackendFault;
6
7#[cfg(all(feature = "simd", target_has_atomic = "ptr"))]
8use core::sync::atomic::{AtomicUsize, Ordering};
9
10#[cfg(feature = "simd")]
11mod kat;
12
13#[cfg(all(feature = "simd", target_has_atomic = "ptr"))]
14const NEVER_RUN: usize = 0;
15#[cfg(any(test, all(feature = "simd", target_has_atomic = "ptr")))]
16const TESTING: usize = 1;
17#[cfg(any(test, all(feature = "simd", target_has_atomic = "ptr")))]
18const HEALTHY: usize = 2;
19#[cfg(any(test, all(feature = "simd", target_has_atomic = "ptr")))]
20const QUARANTINED: usize = 3;
21
22#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
24#[non_exhaustive]
25pub enum BackendHealthState {
26 NeverRun,
28 Testing,
30 Healthy,
32 Quarantined,
34}
35
36impl BackendHealthState {
37 #[must_use]
39 pub const fn as_str(self) -> &'static str {
40 match self {
41 Self::NeverRun => "never-run",
42 Self::Testing => "testing",
43 Self::Healthy => "healthy",
44 Self::Quarantined => "quarantined",
45 }
46 }
47}
48
49#[derive(Clone, Copy, Debug, Eq, PartialEq)]
51pub struct BackendHealthSnapshot {
52 pub operation: OperationKind,
54 pub backend: Backend,
56 pub state: BackendHealthState,
58 pub generation: usize,
60 pub fault: Option<BackendFault>,
62}
63
64#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
66pub struct BackendInitializationReport {
67 pub tested: usize,
69 pub healthy: usize,
71 pub quarantined: usize,
73 pub unavailable: usize,
75}
76
77impl BackendInitializationReport {
78 fn record(&mut self, result: InitializationResult) {
79 match result {
80 InitializationResult::Healthy => {
81 self.tested += 1;
82 self.healthy += 1;
83 }
84 InitializationResult::Quarantined => {
85 self.tested += 1;
86 self.quarantined += 1;
87 }
88 InitializationResult::Unavailable => self.unavailable += 1,
89 }
90 }
91}
92
93#[derive(Clone, Copy)]
94enum InitializationResult {
95 Healthy,
96 Quarantined,
97 Unavailable,
98}
99
100#[cfg(all(feature = "simd", target_has_atomic = "ptr"))]
101struct HealthCell {
102 state: AtomicUsize,
103 generation: AtomicUsize,
104 fault: AtomicUsize,
105 #[cfg(all(feature = "std", unix))]
106 process_id: AtomicUsize,
107}
108
109#[cfg(all(feature = "simd", target_has_atomic = "ptr"))]
110struct KatTransitionGuard<'a> {
111 cell: &'a HealthCell,
112 armed: bool,
113}
114
115#[cfg(all(feature = "simd", target_has_atomic = "ptr"))]
116impl<'a> KatTransitionGuard<'a> {
117 const fn new(cell: &'a HealthCell) -> Self {
118 Self { cell, armed: true }
119 }
120
121 fn complete(mut self, passed: bool) -> bool {
122 if passed {
123 self.cell.state.store(HEALTHY, Ordering::Release);
124 bump_generation(&self.cell.generation);
125 } else {
126 self.cell.quarantine(BackendFault::SelfTestFailed);
127 }
128 self.armed = false;
129 passed
130 }
131}
132
133#[cfg(all(feature = "simd", target_has_atomic = "ptr"))]
134impl Drop for KatTransitionGuard<'_> {
135 fn drop(&mut self) {
136 if self.armed {
137 self.cell.quarantine(BackendFault::SelfTestFailed);
138 }
139 }
140}
141
142#[cfg(all(feature = "simd", target_has_atomic = "ptr"))]
143impl HealthCell {
144 const fn new() -> Self {
145 Self {
146 state: AtomicUsize::new(NEVER_RUN),
147 generation: AtomicUsize::new(1),
148 fault: AtomicUsize::new(0),
149 #[cfg(all(feature = "std", unix))]
150 process_id: AtomicUsize::new(0),
151 }
152 }
153
154 fn snapshot(&self, operation: OperationKind, backend: Backend) -> BackendHealthSnapshot {
155 self.refresh_after_fork();
156 BackendHealthSnapshot {
157 operation,
158 backend,
159 state: decode_state(self.state.load(Ordering::Acquire)),
160 generation: self.generation.load(Ordering::Acquire),
161 fault: decode_fault(self.fault.load(Ordering::Acquire)),
162 }
163 }
164
165 fn ensure(&self, operation: OperationKind, backend: Backend) -> bool {
166 self.ensure_with(|| kat::run(operation, backend))
167 }
168
169 fn ensure_with(&self, run: impl FnOnce() -> bool) -> bool {
170 self.refresh_after_fork();
171 let mut run = Some(run);
172 loop {
173 match self.state.load(Ordering::Acquire) {
174 HEALTHY => return true,
175 QUARANTINED | TESTING => return false,
176 NEVER_RUN => {
177 if self
178 .state
179 .compare_exchange(NEVER_RUN, TESTING, Ordering::AcqRel, Ordering::Acquire)
180 .is_err()
181 {
182 continue;
183 }
184 bump_generation(&self.generation);
185 self.remember_process();
186 let Some(initializer) = run.take() else {
187 self.quarantine(BackendFault::ImpossibleState);
188 return false;
189 };
190 let transition = KatTransitionGuard::new(self);
191 let passed = run_catching_panics(initializer);
192 return transition.complete(passed);
193 }
194 _ => {
195 self.quarantine(BackendFault::ImpossibleState);
196 return false;
197 }
198 }
199 }
200 }
201
202 fn quarantine(&self, fault: BackendFault) {
203 self.fault.store(encode_fault(fault), Ordering::Release);
204 if self.state.swap(QUARANTINED, Ordering::AcqRel) != QUARANTINED {
205 bump_generation(&self.generation);
206 }
207 }
208
209 #[cfg(all(feature = "std", unix))]
210 fn remember_process(&self) {
211 self.process_id
212 .store(std::process::id() as usize, Ordering::Release);
213 }
214
215 #[cfg(not(all(feature = "std", unix)))]
216 const fn remember_process(&self) {
217 let _ = self;
218 }
219
220 #[cfg(all(feature = "std", unix))]
221 fn refresh_after_fork(&self) {
222 let current = std::process::id() as usize;
223 let recorded = self.process_id.load(Ordering::Acquire);
224 if recorded != 0
225 && recorded != current
226 && self
227 .state
228 .compare_exchange(TESTING, NEVER_RUN, Ordering::AcqRel, Ordering::Acquire)
229 .is_ok()
230 {
231 self.process_id.store(current, Ordering::Release);
232 bump_generation(&self.generation);
233 }
234 }
235
236 #[cfg(not(all(feature = "std", unix)))]
237 const fn refresh_after_fork(&self) {
238 let _ = self;
239 }
240}
241
242#[cfg(all(feature = "simd", target_has_atomic = "ptr"))]
243fn bump_generation(generation: &AtomicUsize) {
244 let mut current = generation.load(Ordering::Relaxed);
245 while current != usize::MAX {
246 match generation.compare_exchange_weak(
247 current,
248 current + 1,
249 Ordering::AcqRel,
250 Ordering::Relaxed,
251 ) {
252 Ok(_) => return,
253 Err(observed) => current = observed,
254 }
255 }
256}
257
258#[cfg(any(test, all(feature = "simd", target_has_atomic = "ptr")))]
259const fn decode_state(state: usize) -> BackendHealthState {
260 match state {
261 TESTING => BackendHealthState::Testing,
262 HEALTHY => BackendHealthState::Healthy,
263 QUARANTINED => BackendHealthState::Quarantined,
264 _ => BackendHealthState::NeverRun,
265 }
266}
267
268#[cfg(any(test, all(feature = "simd", target_has_atomic = "ptr")))]
269const fn encode_fault(fault: BackendFault) -> usize {
270 match fault {
271 BackendFault::SelfTestFailed => 1,
272 BackendFault::OutputMismatch => 2,
273 BackendFault::ImpossibleState => 3,
274 BackendFault::ScalarRetryFailed => 4,
275 }
276}
277
278#[cfg(any(test, all(feature = "simd", target_has_atomic = "ptr")))]
279const fn decode_fault(fault: usize) -> Option<BackendFault> {
280 match fault {
281 1 => Some(BackendFault::SelfTestFailed),
282 2 => Some(BackendFault::OutputMismatch),
283 3 => Some(BackendFault::ImpossibleState),
284 4 => Some(BackendFault::ScalarRetryFailed),
285 _ => None,
286 }
287}
288
289#[cfg(all(feature = "simd", target_has_atomic = "ptr"))]
290fn run_catching_panics(run: impl FnOnce() -> bool) -> bool {
291 #[cfg(feature = "std")]
292 {
293 std::panic::catch_unwind(std::panic::AssertUnwindSafe(run)).unwrap_or(false)
294 }
295 #[cfg(not(feature = "std"))]
296 {
297 run()
298 }
299}
300
301macro_rules! health_cells {
302 ($($name:ident),+ $(,)?) => {
303 $(
304 #[cfg(all(feature = "simd", target_has_atomic = "ptr"))]
305 static $name: HealthCell = HealthCell::new();
306 )+
307 };
308}
309
310health_cells!(
311 ENCODE_AVX512,
312 DECODE_AVX512,
313 ENCODE_AVX2,
314 DECODE_AVX2,
315 ENCODE_SSSE3,
316 DECODE_SSSE3,
317 ENCODE_NEON,
318 DECODE_NEON,
319 ENCODE_WASM,
320 DECODE_WASM,
321 ENCODE_RVV,
322 DECODE_RVV,
323);
324
325#[cfg(all(feature = "simd", target_has_atomic = "ptr"))]
326fn cell(operation: OperationKind, backend: Backend) -> Option<&'static HealthCell> {
327 match (operation, backend) {
328 (OperationKind::Encode, Backend::Avx512Vbmi) => Some(&ENCODE_AVX512),
329 (OperationKind::StrictDecode, Backend::Avx512Vbmi) => Some(&DECODE_AVX512),
330 (OperationKind::Encode, Backend::Avx2) => Some(&ENCODE_AVX2),
331 (OperationKind::StrictDecode, Backend::Avx2) => Some(&DECODE_AVX2),
332 (OperationKind::Encode, Backend::Ssse3Sse41) => Some(&ENCODE_SSSE3),
333 (OperationKind::StrictDecode, Backend::Ssse3Sse41) => Some(&DECODE_SSSE3),
334 (OperationKind::Encode, Backend::Neon) => Some(&ENCODE_NEON),
335 (OperationKind::StrictDecode, Backend::Neon) => Some(&DECODE_NEON),
336 (OperationKind::Encode, Backend::WasmSimd128) => Some(&ENCODE_WASM),
337 (OperationKind::StrictDecode, Backend::WasmSimd128) => Some(&DECODE_WASM),
338 (OperationKind::Encode, Backend::Rvv) => Some(&ENCODE_RVV),
339 (OperationKind::StrictDecode, Backend::Rvv) => Some(&DECODE_RVV),
340 _ => None,
341 }
342}
343
344pub(crate) fn admit(operation: OperationKind, backend: Backend) -> bool {
345 if backend == Backend::Scalar {
346 return true;
347 }
348 #[cfg(all(feature = "simd", target_has_atomic = "ptr"))]
349 if kat::available(backend)
350 && let Some(cell) = cell(operation, backend)
351 {
352 return cell.ensure(operation, backend);
353 }
354 let _ = operation;
355 false
356}
357
358#[cfg(all(feature = "simd", target_has_atomic = "ptr"))]
363pub(crate) fn admit_deployment_attested(operation: OperationKind, backend: Backend) -> bool {
364 cell(operation, backend).is_some_and(|cell| cell.ensure(operation, backend))
365}
366
367#[cfg(all(feature = "simd", not(target_has_atomic = "ptr")))]
368pub(crate) const fn admit_deployment_attested(
369 _operation: OperationKind,
370 _backend: Backend,
371) -> bool {
372 false
373}
374
375#[cfg(feature = "checked-backend")]
376pub(crate) fn quarantine(operation: OperationKind, backend: Backend, fault: BackendFault) {
377 #[cfg(all(feature = "simd", target_has_atomic = "ptr"))]
378 if let Some(cell) = cell(operation, backend) {
379 cell.quarantine(fault);
380 }
381 let _ = (operation, backend, fault);
382}
383
384#[cfg(feature = "checked-backend")]
385pub(crate) fn direct_encode<A: crate::Alphabet, const PAD: bool>(
386 backend: Backend,
387 input: &[u8],
388 output: &mut [u8],
389) -> Option<usize> {
390 kat::direct_encode::<A, PAD>(backend, input, output)
391}
392
393#[cfg(feature = "checked-backend")]
394pub(crate) fn direct_decode<A: crate::Alphabet, const PAD: bool>(
395 backend: Backend,
396 input: &[u8],
397 output: &mut [u8],
398) -> Option<usize> {
399 kat::direct_decode::<A, PAD>(backend, input, output)
400}
401
402pub(crate) fn snapshot(operation: OperationKind, backend: Backend) -> BackendHealthSnapshot {
403 #[cfg(all(feature = "simd", target_has_atomic = "ptr"))]
404 if let Some(cell) = cell(operation, backend) {
405 return cell.snapshot(operation, backend);
406 }
407 BackendHealthSnapshot {
408 operation,
409 backend,
410 state: if backend == Backend::Scalar {
411 BackendHealthState::Healthy
412 } else {
413 BackendHealthState::NeverRun
414 },
415 generation: 1,
416 fault: None,
417 }
418}
419
420#[must_use]
422pub fn initialize_backends() -> BackendInitializationReport {
423 let mut report = BackendInitializationReport::default();
424 for backend in candidate_backends() {
425 for operation in [OperationKind::Encode, OperationKind::StrictDecode] {
426 let result = if !backend_available(*backend) {
427 InitializationResult::Unavailable
428 } else if admit(operation, *backend) {
429 InitializationResult::Healthy
430 } else if snapshot(operation, *backend).state == BackendHealthState::Quarantined {
431 InitializationResult::Quarantined
432 } else {
433 InitializationResult::Unavailable
434 };
435 report.record(result);
436 }
437 }
438 report
439}
440
441fn backend_available(backend: Backend) -> bool {
442 #[cfg(feature = "simd")]
443 {
444 kat::available(backend)
445 }
446 #[cfg(not(feature = "simd"))]
447 {
448 let _ = backend;
449 false
450 }
451}
452
453const fn candidate_backends() -> &'static [Backend] {
454 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
455 return &[Backend::Avx512Vbmi, Backend::Avx2, Backend::Ssse3Sse41];
456 #[cfg(all(target_arch = "aarch64", target_endian = "little"))]
457 return &[Backend::Neon];
458 #[cfg(target_arch = "wasm32")]
459 return &[Backend::WasmSimd128];
460 #[cfg(all(feature = "std", target_arch = "riscv64", target_os = "linux"))]
461 return &[Backend::Rvv];
462 #[allow(unreachable_code)]
463 &[]
464}
465
466#[cfg(test)]
467mod tests;