archmage/tokens/mod.rs
1//! SIMD capability tokens
2//!
3//! Tokens are zero-sized proof types that demonstrate a CPU feature is available.
4//! They should be obtained via `summon()` and passed through function calls.
5//!
6//! ## Token Availability
7//!
8//! Use `compiled_with()` to check compile-time availability:
9//! - `Some(true)` — Binary was compiled with these features enabled (use `summon().unwrap()`)
10//! - `Some(false)` — Wrong architecture (this token can never be available)
11//! - `None` — Might be available, call `summon()` to check at runtime
12//!
13//! ## Cross-Architecture Design
14//!
15//! All token types exist on all architectures for easier cross-platform code.
16//! On unsupported architectures, `summon()` returns `None` and `compiled_with()`
17//! returns `Some(false)`.
18//!
19//! ## Disabling Tokens
20//!
21//! For testing and benchmarking, tokens can be disabled process-wide:
22//!
23//! ```rust,ignore
24//! // Force scalar fallback for benchmarking
25//! X64V3Token::dangerously_disable_token_process_wide(true)?;
26//! // All summon() calls now return None (cascades to V4, Modern, Fp16)
27//! assert!(X64V3Token::summon().is_none());
28//! ```
29//!
30//! Disabling returns `Err(CompileTimeGuaranteedError)` when the features are
31//! compile-time enabled (e.g., via `-Ctarget-cpu=native`), since the compiler
32//! has already elided the runtime checks.
33
34mod sealed {
35 /// Sealed trait preventing external implementations of [`SimdToken`](super::SimdToken).
36 pub trait Sealed {}
37}
38
39pub use sealed::Sealed;
40
41/// Marker trait for SIMD capability tokens.
42///
43/// All tokens implement this trait, enabling generic code over different
44/// SIMD feature levels.
45///
46/// This trait is **sealed** — it cannot be implemented outside of archmage.
47/// Only tokens created by `summon()` or downcasting are valid.
48///
49/// # Token Lifecycle
50///
51/// 1. Optionally check `compiled_with()` to see if runtime check is needed
52/// 2. Call `summon()` at runtime to detect CPU support
53/// 3. Pass the token through to `#[arcane]` functions — don't forge new ones
54///
55/// # Example
56///
57/// ```rust,ignore
58/// use archmage::{X64V3Token, SimdToken};
59///
60/// fn process(data: &[f32]) -> f32 {
61/// if let Some(token) = X64V3Token::summon() {
62/// return process_avx2(token, data);
63/// }
64/// process_scalar(data)
65/// }
66/// ```
67pub trait SimdToken: sealed::Sealed + Copy + Clone + Send + Sync + 'static {
68 /// Human-readable name for diagnostics and error messages.
69 const NAME: &'static str;
70
71 /// Comma-delimited target features (e.g., `"sse,sse2,avx2,fma,bmi1,bmi2,f16c,lzcnt"`).
72 ///
73 /// All features are listed, including baseline features like SSE/SSE2 for
74 /// x86 tokens. For WASM, this is `"simd128"`.
75 ///
76 /// Empty for [`ScalarToken`].
77 const TARGET_FEATURES: &'static str;
78
79 /// RUSTFLAGS to enable these features at compile time.
80 ///
81 /// Example: `"-Ctarget-feature=+avx2,+fma,+bmi1,+bmi2,+f16c,+lzcnt"`
82 ///
83 /// Empty for [`ScalarToken`].
84 const ENABLE_TARGET_FEATURES: &'static str;
85
86 /// RUSTFLAGS to disable these features at compile time.
87 ///
88 /// Example: `"-Ctarget-feature=-avx2,-fma,-bmi1,-bmi2,-f16c,-lzcnt"`
89 ///
90 /// Useful in [`CompileTimeGuaranteedError`] messages to tell users how
91 /// to recompile so that runtime disable works.
92 ///
93 /// Empty for [`ScalarToken`].
94 const DISABLE_TARGET_FEATURES: &'static str;
95
96 /// Check if this binary was compiled with the required target features enabled.
97 ///
98 /// Returns:
99 /// - `Some(true)` — Features are compile-time enabled (e.g., `-C target-cpu=haswell`)
100 /// - `Some(false)` — Wrong architecture, token can never be available
101 /// - `None` — Might be available, call `summon()` to check at runtime
102 ///
103 /// When `compiled_with()` returns `Some(true)`, `summon().unwrap()` is safe and
104 /// the compiler will elide the runtime check entirely.
105 ///
106 /// # Example
107 ///
108 /// ```rust,ignore
109 /// match X64V3Token::compiled_with() {
110 /// Some(true) => { /* summon().unwrap() is safe, no runtime check */ }
111 /// Some(false) => { /* use fallback, this arch can't support it */ }
112 /// None => { /* call summon() to check at runtime */ }
113 /// }
114 /// ```
115 fn compiled_with() -> Option<bool>;
116
117 /// Deprecated alias for [`compiled_with()`](Self::compiled_with).
118 #[inline(always)]
119 #[deprecated(since = "0.6.0", note = "Use compiled_with() instead")]
120 fn guaranteed() -> Option<bool> {
121 Self::compiled_with()
122 }
123
124 /// Attempt to create a token with runtime feature detection.
125 ///
126 /// Returns `Some(token)` if the CPU supports this feature, `None` otherwise.
127 ///
128 /// # Example
129 ///
130 /// ```rust,ignore
131 /// if let Some(token) = X64V3Token::summon() {
132 /// // Use AVX2+FMA operations
133 /// } else {
134 /// // Fallback path
135 /// }
136 /// ```
137 fn summon() -> Option<Self>;
138
139 /// Attempt to create a token with runtime feature detection.
140 ///
141 /// This is an alias for [`summon()`](Self::summon).
142 #[inline(always)]
143 fn attempt() -> Option<Self> {
144 Self::summon()
145 }
146
147 /// Legacy alias for [`summon()`](Self::summon).
148 ///
149 /// **Deprecated:** Use `summon()` instead.
150 #[inline(always)]
151 #[doc(hidden)]
152 fn try_new() -> Option<Self> {
153 Self::summon()
154 }
155
156 /// Create a token without any checks.
157 ///
158 /// # Safety
159 ///
160 /// Caller must guarantee the CPU feature is available. Using a forged token
161 /// when the feature is unavailable causes undefined behavior.
162 ///
163 /// # Deprecated
164 ///
165 /// **Do not use in new code.** Pass tokens through from `summon()` instead.
166 /// If you're inside a `#[cfg(target_feature = "...")]` block where the
167 /// feature is compile-time guaranteed, use `summon().unwrap()`.
168 #[deprecated(
169 since = "0.5.0",
170 note = "Pass tokens through from summon() instead of forging"
171 )]
172 unsafe fn forge_token_dangerously() -> Self;
173}
174
175// All token types, traits, and aliases are generated from token-registry.toml.
176mod generated;
177pub use generated::*;
178
179/// Scalar fallback token — always available on all platforms.
180///
181/// Use this for fallback paths when no SIMD is available. `ScalarToken`
182/// provides type-level proof that "we've given up on SIMD" and allows
183/// consistent API shapes in dispatch code.
184///
185/// # Example
186///
187/// ```rust
188/// use archmage::{ScalarToken, SimdToken};
189///
190/// // Always succeeds
191/// let token = ScalarToken::summon().unwrap();
192///
193/// // Or use the shorthand
194/// let token = ScalarToken;
195/// ```
196#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
197pub struct ScalarToken;
198
199impl sealed::Sealed for ScalarToken {}
200
201impl SimdToken for ScalarToken {
202 const NAME: &'static str = "Scalar";
203 const TARGET_FEATURES: &'static str = "";
204 const ENABLE_TARGET_FEATURES: &'static str = "";
205 const DISABLE_TARGET_FEATURES: &'static str = "";
206
207 /// Always returns `Some(true)` — scalar fallback is always available.
208 #[inline(always)]
209 fn compiled_with() -> Option<bool> {
210 Some(true)
211 }
212
213 /// Always returns `Some(ScalarToken)`.
214 #[inline(always)]
215 fn summon() -> Option<Self> {
216 Some(Self)
217 }
218
219 #[allow(deprecated)]
220 #[inline(always)]
221 unsafe fn forge_token_dangerously() -> Self {
222 Self
223 }
224}
225
226impl ScalarToken {
227 /// Scalar tokens cannot be disabled (they are always available).
228 ///
229 /// Always returns `Err(CompileTimeGuaranteedError)` because `ScalarToken`
230 /// is unconditionally available — there is no runtime check to bypass.
231 pub fn dangerously_disable_token_process_wide(
232 _disabled: bool,
233 ) -> Result<(), CompileTimeGuaranteedError> {
234 Err(CompileTimeGuaranteedError {
235 token_name: Self::NAME,
236 target_features: Self::TARGET_FEATURES,
237 disable_flags: Self::DISABLE_TARGET_FEATURES,
238 })
239 }
240
241 /// Scalar tokens cannot be disabled (they are always available).
242 ///
243 /// Always returns `Err(CompileTimeGuaranteedError)`.
244 pub fn manually_disabled() -> Result<bool, CompileTimeGuaranteedError> {
245 Err(CompileTimeGuaranteedError {
246 token_name: Self::NAME,
247 target_features: Self::TARGET_FEATURES,
248 disable_flags: Self::DISABLE_TARGET_FEATURES,
249 })
250 }
251}
252
253/// Error returned when attempting to disable a token whose features are
254/// compile-time enabled.
255///
256/// When all required features are enabled via `-Ctarget-cpu` or
257/// `-Ctarget-feature`, the compiler has already elided the runtime
258/// detection — `summon()` unconditionally returns `Some`. Disabling
259/// the token would have no effect and silently produce incorrect behavior.
260///
261/// # Resolution
262///
263/// To use `dangerously_disable_token_process_wide`, compile without
264/// the target features enabled. For example:
265/// - Remove `-Ctarget-cpu=native` from `RUSTFLAGS`
266/// - Use the [`disable_flags`](Self::disable_flags) string to disable specific features
267#[derive(Debug, Clone)]
268pub struct CompileTimeGuaranteedError {
269 /// The token type that cannot be disabled.
270 pub token_name: &'static str,
271 /// Comma-delimited target features (e.g., `"avx2,fma,bmi1,bmi2,f16c,lzcnt"`).
272 /// Empty for ScalarToken.
273 pub target_features: &'static str,
274 /// RUSTFLAGS to disable these features (e.g., `"-Ctarget-feature=-avx2,-fma,..."`).
275 /// Empty for ScalarToken.
276 pub disable_flags: &'static str,
277}
278
279impl core::fmt::Display for CompileTimeGuaranteedError {
280 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
281 if self.target_features.is_empty() {
282 write!(f, "Cannot disable {} — always available.", self.token_name)
283 } else {
284 write!(
285 f,
286 "Cannot disable {} — features [{}] are compile-time enabled.\n\n\
287 To allow runtime disable, recompile with RUSTFLAGS:\n \
288 \"{}\"\n\n\
289 Or remove `-Ctarget-cpu` from RUSTFLAGS entirely.",
290 self.token_name, self.target_features, self.disable_flags
291 )
292 }
293 }
294}
295
296#[cfg(feature = "std")]
297impl std::error::Error for CompileTimeGuaranteedError {}
298
299/// Error returned when [`dangerously_disable_tokens_except_wasm`] fails to
300/// disable one or more tokens because their features are compile-time enabled.
301///
302/// Contains the individual [`CompileTimeGuaranteedError`] for each token that
303/// could not be disabled.
304#[derive(Debug, Clone)]
305pub struct DisableAllSimdError {
306 /// The individual errors for each token that could not be disabled.
307 pub errors: alloc::vec::Vec<CompileTimeGuaranteedError>,
308}
309
310impl core::fmt::Display for DisableAllSimdError {
311 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
312 write!(f, "Failed to disable {} token(s):", self.errors.len())?;
313 for err in &self.errors {
314 write!(f, "\n - {}", err.token_name)?;
315 }
316 Ok(())
317 }
318}
319
320#[cfg(feature = "std")]
321impl std::error::Error for DisableAllSimdError {}
322
323/// Disable all SIMD tokens process-wide (except WASM, which is always compile-time).
324///
325/// This is a convenience function that calls
326/// [`dangerously_disable_token_process_wide`](X64V2Token::dangerously_disable_token_process_wide)
327/// on each root token for the current architecture. Root tokens cascade to
328/// their descendants, so this disables everything:
329///
330/// - **x86/x86_64:** `X64V2Token` (cascades to V3, V4, Modern, Fp16)
331/// - **AArch64:** `NeonToken` (cascades to Aes, Sha3, Crc)
332///
333/// WASM's `Wasm128Token` is excluded because `simd128` is always a compile-time
334/// decision on `wasm32` — there is no runtime detection to bypass.
335///
336/// Pass `disabled = false` to re-enable all tokens.
337///
338/// # Errors
339///
340/// Returns [`DisableAllSimdError`] if any token's features are compile-time
341/// enabled (e.g., via `-Ctarget-cpu=native`). Tokens that *can* be disabled
342/// are still disabled; only the failures are collected.
343///
344/// # Example
345///
346/// ```rust,ignore
347/// use archmage::dangerously_disable_tokens_except_wasm;
348///
349/// // Force scalar fallbacks for benchmarking
350/// dangerously_disable_tokens_except_wasm(true)?;
351/// // ... run benchmarks ...
352/// dangerously_disable_tokens_except_wasm(false)?;
353/// ```
354pub fn dangerously_disable_tokens_except_wasm(disabled: bool) -> Result<(), DisableAllSimdError> {
355 let mut errors = alloc::vec::Vec::new();
356
357 // x86/x86_64: disable V2 (cascades to V3, V4, Modern, Fp16)
358 #[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
359 if let Err(e) = X64V2Token::dangerously_disable_token_process_wide(disabled) {
360 errors.push(e);
361 }
362
363 // AArch64: disable NEON (cascades to Aes, Sha3, Crc)
364 #[cfg(target_arch = "aarch64")]
365 if let Err(e) = NeonToken::dangerously_disable_token_process_wide(disabled) {
366 errors.push(e);
367 }
368
369 if errors.is_empty() {
370 Ok(())
371 } else {
372 Err(DisableAllSimdError { errors })
373 }
374}
375
376/// Trait for compile-time dispatch via monomorphization.
377///
378/// Each concrete token implements this trait, returning `Some(self)` for its
379/// own type and `None` for others. The compiler monomorphizes away all the
380/// `None` branches, leaving only the matching path.
381///
382/// # Example
383///
384/// ```rust
385/// use archmage::{IntoConcreteToken, SimdToken, ScalarToken};
386///
387/// fn dispatch<T: IntoConcreteToken>(token: T, data: &[f32]) -> f32 {
388/// // Compiler eliminates non-matching branches
389/// if let Some(_t) = token.as_scalar() {
390/// return data.iter().sum();
391/// }
392/// // ... other paths for x64v3, neon, etc.
393/// 0.0
394/// }
395///
396/// let result = dispatch(ScalarToken, &[1.0, 2.0, 3.0]);
397/// assert_eq!(result, 6.0);
398/// ```
399pub trait IntoConcreteToken: SimdToken + Sized {
400 /// Try to cast to X64V1Token.
401 #[inline(always)]
402 fn as_x64v1(self) -> Option<X64V1Token> {
403 None
404 }
405
406 /// Try to cast to X64V2Token.
407 #[inline(always)]
408 fn as_x64v2(self) -> Option<X64V2Token> {
409 None
410 }
411
412 /// Try to cast to X64CryptoToken.
413 #[inline(always)]
414 fn as_x64_crypto(self) -> Option<X64CryptoToken> {
415 None
416 }
417
418 /// Try to cast to X64V3Token.
419 #[inline(always)]
420 fn as_x64v3(self) -> Option<X64V3Token> {
421 None
422 }
423
424 /// Try to cast to X64V3CryptoToken.
425 #[inline(always)]
426 fn as_x64v3_crypto(self) -> Option<X64V3CryptoToken> {
427 None
428 }
429
430 /// Try to cast to X64V4Token.
431 #[inline(always)]
432 fn as_x64v4(self) -> Option<X64V4Token> {
433 None
434 }
435
436 /// Try to cast to X64V4xToken.
437 #[inline(always)]
438 fn as_x64v4x(self) -> Option<X64V4xToken> {
439 None
440 }
441
442 /// Try to cast to Avx512Fp16Token.
443 #[inline(always)]
444 fn as_avx512_fp16(self) -> Option<Avx512Fp16Token> {
445 None
446 }
447
448 /// Try to cast to NeonToken.
449 #[inline(always)]
450 fn as_neon(self) -> Option<NeonToken> {
451 None
452 }
453
454 /// Try to cast to NeonAesToken.
455 #[inline(always)]
456 fn as_neon_aes(self) -> Option<NeonAesToken> {
457 None
458 }
459
460 /// Try to cast to NeonSha3Token.
461 #[inline(always)]
462 fn as_neon_sha3(self) -> Option<NeonSha3Token> {
463 None
464 }
465
466 /// Try to cast to NeonCrcToken.
467 #[inline(always)]
468 fn as_neon_crc(self) -> Option<NeonCrcToken> {
469 None
470 }
471
472 /// Try to cast to Arm64V2Token.
473 #[inline(always)]
474 fn as_arm_v2(self) -> Option<Arm64V2Token> {
475 None
476 }
477
478 /// Try to cast to Arm64V3Token.
479 #[inline(always)]
480 fn as_arm_v3(self) -> Option<Arm64V3Token> {
481 None
482 }
483
484 /// Try to cast to Wasm128Token.
485 #[inline(always)]
486 fn as_wasm128(self) -> Option<Wasm128Token> {
487 None
488 }
489
490 /// Try to cast to ScalarToken.
491 #[inline(always)]
492 fn as_scalar(self) -> Option<ScalarToken> {
493 None
494 }
495}
496
497// Implement IntoConcreteToken for ScalarToken
498impl IntoConcreteToken for ScalarToken {
499 #[inline(always)]
500 fn as_scalar(self) -> Option<ScalarToken> {
501 Some(self)
502 }
503}
504
505// Implement IntoConcreteToken for X64V1Token
506impl IntoConcreteToken for X64V1Token {
507 #[inline(always)]
508 fn as_x64v1(self) -> Option<X64V1Token> {
509 Some(self)
510 }
511}
512
513// Implement IntoConcreteToken for X64V2Token
514impl IntoConcreteToken for X64V2Token {
515 #[inline(always)]
516 fn as_x64v2(self) -> Option<X64V2Token> {
517 Some(self)
518 }
519}
520
521// Implement IntoConcreteToken for X64CryptoToken
522impl IntoConcreteToken for X64CryptoToken {
523 #[inline(always)]
524 fn as_x64_crypto(self) -> Option<X64CryptoToken> {
525 Some(self)
526 }
527}
528
529// Implement IntoConcreteToken for X64V3Token
530impl IntoConcreteToken for X64V3Token {
531 #[inline(always)]
532 fn as_x64v3(self) -> Option<X64V3Token> {
533 Some(self)
534 }
535}
536
537// Implement IntoConcreteToken for X64V3CryptoToken
538impl IntoConcreteToken for X64V3CryptoToken {
539 #[inline(always)]
540 fn as_x64v3_crypto(self) -> Option<X64V3CryptoToken> {
541 Some(self)
542 }
543}
544
545// Implement IntoConcreteToken for X64V4Token
546impl IntoConcreteToken for X64V4Token {
547 #[inline(always)]
548 fn as_x64v4(self) -> Option<X64V4Token> {
549 Some(self)
550 }
551}
552
553// Implement IntoConcreteToken for X64V4xToken
554impl IntoConcreteToken for X64V4xToken {
555 #[inline(always)]
556 fn as_x64v4x(self) -> Option<X64V4xToken> {
557 Some(self)
558 }
559}
560
561// Implement IntoConcreteToken for Avx512Fp16Token
562impl IntoConcreteToken for Avx512Fp16Token {
563 #[inline(always)]
564 fn as_avx512_fp16(self) -> Option<Avx512Fp16Token> {
565 Some(self)
566 }
567}
568
569// Implement IntoConcreteToken for NeonToken
570impl IntoConcreteToken for NeonToken {
571 #[inline(always)]
572 fn as_neon(self) -> Option<NeonToken> {
573 Some(self)
574 }
575}
576
577// Implement IntoConcreteToken for NeonAesToken
578impl IntoConcreteToken for NeonAesToken {
579 #[inline(always)]
580 fn as_neon_aes(self) -> Option<NeonAesToken> {
581 Some(self)
582 }
583}
584
585// Implement IntoConcreteToken for NeonSha3Token
586impl IntoConcreteToken for NeonSha3Token {
587 #[inline(always)]
588 fn as_neon_sha3(self) -> Option<NeonSha3Token> {
589 Some(self)
590 }
591}
592
593// Implement IntoConcreteToken for NeonCrcToken
594impl IntoConcreteToken for NeonCrcToken {
595 #[inline(always)]
596 fn as_neon_crc(self) -> Option<NeonCrcToken> {
597 Some(self)
598 }
599}
600
601// Implement IntoConcreteToken for Arm64V2Token
602impl IntoConcreteToken for Arm64V2Token {
603 #[inline(always)]
604 fn as_arm_v2(self) -> Option<Arm64V2Token> {
605 Some(self)
606 }
607}
608
609// Implement IntoConcreteToken for Arm64V3Token
610impl IntoConcreteToken for Arm64V3Token {
611 #[inline(always)]
612 fn as_arm_v3(self) -> Option<Arm64V3Token> {
613 Some(self)
614 }
615}
616
617// Implement IntoConcreteToken for Wasm128Token
618impl IntoConcreteToken for Wasm128Token {
619 #[inline(always)]
620 fn as_wasm128(self) -> Option<Wasm128Token> {
621 Some(self)
622 }
623}