archmage_macros/lib.rs
1//! Proc-macros for archmage SIMD capability tokens.
2//!
3//! Provides `#[arcane]` attribute (with `#[arcane]` alias) to make raw intrinsics
4//! safe via token proof.
5
6use proc_macro::TokenStream;
7use quote::{ToTokens, format_ident, quote};
8use syn::{
9 Attribute, FnArg, GenericParam, Ident, ItemFn, PatType, ReturnType, Signature, Token, Type,
10 TypeParamBound,
11 fold::Fold,
12 parse::{Parse, ParseStream},
13 parse_macro_input, parse_quote,
14};
15
16/// A Fold implementation that replaces `Self` with a concrete type.
17struct ReplaceSelf<'a> {
18 replacement: &'a Type,
19}
20
21impl Fold for ReplaceSelf<'_> {
22 fn fold_type(&mut self, ty: Type) -> Type {
23 match ty {
24 Type::Path(ref type_path) if type_path.qself.is_none() => {
25 // Check if it's just `Self`
26 if type_path.path.is_ident("Self") {
27 return self.replacement.clone();
28 }
29 // Otherwise continue folding
30 syn::fold::fold_type(self, ty)
31 }
32 _ => syn::fold::fold_type(self, ty),
33 }
34 }
35}
36
37/// Arguments to the `#[arcane]` macro.
38#[derive(Default)]
39struct ArcaneArgs {
40 /// Use `#[inline(always)]` instead of `#[inline]` for the inner function.
41 /// Requires nightly Rust with `#![feature(target_feature_inline_always)]`.
42 inline_always: bool,
43 /// The concrete type to use for `self` receiver.
44 /// When specified, `self`/`&self`/`&mut self` is transformed to `_self: Type`/`&Type`/`&mut Type`.
45 self_type: Option<Type>,
46}
47
48impl Parse for ArcaneArgs {
49 fn parse(input: ParseStream) -> syn::Result<Self> {
50 let mut args = ArcaneArgs::default();
51
52 while !input.is_empty() {
53 let ident: Ident = input.parse()?;
54 match ident.to_string().as_str() {
55 "inline_always" => args.inline_always = true,
56 "_self" => {
57 let _: Token![=] = input.parse()?;
58 args.self_type = Some(input.parse()?);
59 }
60 other => {
61 return Err(syn::Error::new(
62 ident.span(),
63 format!("unknown arcane argument: `{}`", other),
64 ));
65 }
66 }
67 // Consume optional comma
68 if input.peek(Token![,]) {
69 let _: Token![,] = input.parse()?;
70 }
71 }
72
73 Ok(args)
74 }
75}
76
77// Token-to-features and trait-to-features mappings are generated from
78// token-registry.toml by xtask. Regenerate with: cargo run -p xtask -- generate
79mod generated;
80use generated::{token_to_arch, token_to_features, trait_to_features};
81
82/// Result of extracting token info from a type.
83enum TokenTypeInfo {
84 /// Concrete token type (e.g., `Avx2Token`)
85 Concrete(String),
86 /// impl Trait with the trait names (e.g., `impl HasX64V2`)
87 ImplTrait(Vec<String>),
88 /// Generic type parameter name (e.g., `T`)
89 Generic(String),
90}
91
92/// Extract token type information from a type.
93fn extract_token_type_info(ty: &Type) -> Option<TokenTypeInfo> {
94 match ty {
95 Type::Path(type_path) => {
96 // Get the last segment of the path (e.g., "Avx2Token" from "archmage::Avx2Token")
97 type_path.path.segments.last().map(|seg| {
98 let name = seg.ident.to_string();
99 // Check if it's a known concrete token type
100 if token_to_features(&name).is_some() {
101 TokenTypeInfo::Concrete(name)
102 } else {
103 // Might be a generic type parameter like `T`
104 TokenTypeInfo::Generic(name)
105 }
106 })
107 }
108 Type::Reference(type_ref) => {
109 // Handle &Token or &mut Token
110 extract_token_type_info(&type_ref.elem)
111 }
112 Type::ImplTrait(impl_trait) => {
113 // Handle `impl HasX64V2` or `impl HasX64V2 + HasNeon`
114 let traits: Vec<String> = extract_trait_names_from_bounds(&impl_trait.bounds);
115 if traits.is_empty() {
116 None
117 } else {
118 Some(TokenTypeInfo::ImplTrait(traits))
119 }
120 }
121 _ => None,
122 }
123}
124
125/// Extract trait names from type param bounds.
126fn extract_trait_names_from_bounds(
127 bounds: &syn::punctuated::Punctuated<TypeParamBound, Token![+]>,
128) -> Vec<String> {
129 bounds
130 .iter()
131 .filter_map(|bound| {
132 if let TypeParamBound::Trait(trait_bound) = bound {
133 trait_bound
134 .path
135 .segments
136 .last()
137 .map(|seg| seg.ident.to_string())
138 } else {
139 None
140 }
141 })
142 .collect()
143}
144
145/// Look up a generic type parameter in the function's generics.
146fn find_generic_bounds(sig: &Signature, type_name: &str) -> Option<Vec<String>> {
147 // Check inline bounds first (e.g., `fn foo<T: HasX64V2>(token: T)`)
148 for param in &sig.generics.params {
149 if let GenericParam::Type(type_param) = param
150 && type_param.ident == type_name
151 {
152 let traits = extract_trait_names_from_bounds(&type_param.bounds);
153 if !traits.is_empty() {
154 return Some(traits);
155 }
156 }
157 }
158
159 // Check where clause (e.g., `fn foo<T>(token: T) where T: HasX64V2`)
160 if let Some(where_clause) = &sig.generics.where_clause {
161 for predicate in &where_clause.predicates {
162 if let syn::WherePredicate::Type(pred_type) = predicate
163 && let Type::Path(type_path) = &pred_type.bounded_ty
164 && let Some(seg) = type_path.path.segments.last()
165 && seg.ident == type_name
166 {
167 let traits = extract_trait_names_from_bounds(&pred_type.bounds);
168 if !traits.is_empty() {
169 return Some(traits);
170 }
171 }
172 }
173 }
174
175 None
176}
177
178/// Convert trait names to features, collecting all features from all traits.
179fn traits_to_features(trait_names: &[String]) -> Option<Vec<&'static str>> {
180 let mut all_features = Vec::new();
181
182 for trait_name in trait_names {
183 if let Some(features) = trait_to_features(trait_name) {
184 for &feature in features {
185 if !all_features.contains(&feature) {
186 all_features.push(feature);
187 }
188 }
189 }
190 }
191
192 if all_features.is_empty() {
193 None
194 } else {
195 Some(all_features)
196 }
197}
198
199/// Trait names that don't map to any CPU features. These are valid in the type
200/// system but cannot be used as token bounds in `#[arcane]`/`#[rite]` because
201/// the macros need concrete features to generate `#[target_feature]` attributes.
202const FEATURELESS_TRAIT_NAMES: &[&str] = &["SimdToken", "IntoConcreteToken"];
203
204/// Check if any trait names are featureless (no CPU feature mapping).
205/// Returns the first featureless trait name found.
206fn find_featureless_trait(trait_names: &[String]) -> Option<&'static str> {
207 for name in trait_names {
208 for &featureless in FEATURELESS_TRAIT_NAMES {
209 if name == featureless {
210 return Some(featureless);
211 }
212 }
213 }
214 None
215}
216
217/// Diagnose why `find_token_param` failed. Returns the name of a featureless
218/// trait if the signature has a parameter bounded by one (e.g., `SimdToken`).
219fn diagnose_featureless_token(sig: &Signature) -> Option<&'static str> {
220 for arg in &sig.inputs {
221 if let FnArg::Typed(PatType { ty, .. }) = arg
222 && let Some(info) = extract_token_type_info(ty)
223 {
224 match &info {
225 TokenTypeInfo::ImplTrait(names) => {
226 if let Some(name) = find_featureless_trait(names) {
227 return Some(name);
228 }
229 }
230 TokenTypeInfo::Generic(type_name) => {
231 // Check if the type name itself is a featureless trait
232 // (e.g., `token: SimdToken` used as a bare path)
233 let as_vec = vec![type_name.clone()];
234 if let Some(name) = find_featureless_trait(&as_vec) {
235 return Some(name);
236 }
237 // Check generic bounds (e.g., `T: SimdToken`)
238 if let Some(bounds) = find_generic_bounds(sig, type_name)
239 && let Some(name) = find_featureless_trait(&bounds)
240 {
241 return Some(name);
242 }
243 }
244 TokenTypeInfo::Concrete(_) => {}
245 }
246 }
247 }
248 None
249}
250
251/// Result of finding a token parameter in a function signature.
252struct TokenParamInfo {
253 /// The parameter identifier (e.g., `token`)
254 ident: Ident,
255 /// Target features to enable (e.g., `["avx2", "fma"]`)
256 features: Vec<&'static str>,
257 /// Target architecture (Some for concrete tokens, None for traits/generics)
258 target_arch: Option<&'static str>,
259 /// Concrete token type name (Some for concrete tokens, None for traits/generics)
260 token_type_name: Option<String>,
261}
262
263/// Find the first token parameter in a function signature.
264fn find_token_param(sig: &Signature) -> Option<TokenParamInfo> {
265 for arg in &sig.inputs {
266 match arg {
267 FnArg::Receiver(_) => {
268 // Self receivers (self, &self, &mut self) are not yet supported.
269 // The macro creates an inner function, and Rust's inner functions
270 // cannot have `self` parameters. Supporting this would require
271 // AST rewriting to replace `self` with a regular parameter.
272 // See the module docs for the workaround.
273 continue;
274 }
275 FnArg::Typed(PatType { pat, ty, .. }) => {
276 if let Some(info) = extract_token_type_info(ty) {
277 let (features, arch, token_name) = match info {
278 TokenTypeInfo::Concrete(ref name) => {
279 let features = token_to_features(name).map(|f| f.to_vec());
280 let arch = token_to_arch(name);
281 (features, arch, Some(name.clone()))
282 }
283 TokenTypeInfo::ImplTrait(trait_names) => {
284 (traits_to_features(&trait_names), None, None)
285 }
286 TokenTypeInfo::Generic(type_name) => {
287 // Look up the generic parameter's bounds
288 let features = find_generic_bounds(sig, &type_name)
289 .and_then(|traits| traits_to_features(&traits));
290 (features, None, None)
291 }
292 };
293
294 if let Some(features) = features {
295 // Extract parameter name (or synthesize one for wildcard `_`)
296 let ident = match pat.as_ref() {
297 syn::Pat::Ident(pat_ident) => Some(pat_ident.ident.clone()),
298 syn::Pat::Wild(w) => {
299 Some(Ident::new("__archmage_token", w.underscore_token.span))
300 }
301 _ => None,
302 };
303 if let Some(ident) = ident {
304 return Some(TokenParamInfo {
305 ident,
306 features,
307 target_arch: arch,
308 token_type_name: token_name,
309 });
310 }
311 }
312 }
313 }
314 }
315 }
316 None
317}
318
319/// Represents the kind of self receiver and the transformed parameter.
320enum SelfReceiver {
321 /// `self` (by value/move)
322 Owned,
323 /// `&self` (shared reference)
324 Ref,
325 /// `&mut self` (mutable reference)
326 RefMut,
327}
328
329/// Shared implementation for arcane/arcane macros.
330fn arcane_impl(mut input_fn: ItemFn, macro_name: &str, args: ArcaneArgs) -> TokenStream {
331 // Check for self receiver
332 let has_self_receiver = input_fn
333 .sig
334 .inputs
335 .first()
336 .map(|arg| matches!(arg, FnArg::Receiver(_)))
337 .unwrap_or(false);
338
339 // If there's a self receiver, we need _self = Type
340 if has_self_receiver && args.self_type.is_none() {
341 let msg = format!(
342 "{} with self receiver requires `_self = Type` argument.\n\
343 Example: #[{}(_self = MyType)]\n\
344 Use `_self` (not `self`) in the function body to refer to self.",
345 macro_name, macro_name
346 );
347 return syn::Error::new_spanned(&input_fn.sig, msg)
348 .to_compile_error()
349 .into();
350 }
351
352 // Find the token parameter, its features, target arch, and token type name
353 let TokenParamInfo {
354 ident: _token_ident,
355 features,
356 target_arch,
357 token_type_name,
358 } = match find_token_param(&input_fn.sig) {
359 Some(result) => result,
360 None => {
361 // Check for specific misuse: featureless traits like SimdToken
362 if let Some(trait_name) = diagnose_featureless_token(&input_fn.sig) {
363 let msg = format!(
364 "`{trait_name}` cannot be used as a token bound in #[{macro_name}] \
365 because it doesn't specify any CPU features.\n\
366 \n\
367 #[{macro_name}] needs concrete features to generate #[target_feature]. \
368 Use a concrete token or a feature trait:\n\
369 \n\
370 Concrete tokens: X64V3Token, Desktop64, NeonToken, Arm64V2Token, ...\n\
371 Feature traits: impl HasX64V2, impl HasNeon, impl HasArm64V3, ..."
372 );
373 return syn::Error::new_spanned(&input_fn.sig, msg)
374 .to_compile_error()
375 .into();
376 }
377 let msg = format!(
378 "{} requires a token parameter. Supported forms:\n\
379 - Concrete: `token: X64V3Token`\n\
380 - impl Trait: `token: impl HasX64V2`\n\
381 - Generic: `fn foo<T: HasX64V2>(token: T, ...)`\n\
382 - With self: `#[{}(_self = Type)] fn method(&self, token: impl HasNeon, ...)`",
383 macro_name, macro_name
384 );
385 return syn::Error::new_spanned(&input_fn.sig, msg)
386 .to_compile_error()
387 .into();
388 }
389 };
390
391 // Build target_feature attributes
392 let target_feature_attrs: Vec<Attribute> = features
393 .iter()
394 .map(|feature| parse_quote!(#[target_feature(enable = #feature)]))
395 .collect();
396
397 // Rename wildcard patterns (`_: Type`) to named params so the inner call works
398 let mut wild_rename_counter = 0u32;
399 for arg in &mut input_fn.sig.inputs {
400 if let FnArg::Typed(pat_type) = arg
401 && matches!(pat_type.pat.as_ref(), syn::Pat::Wild(_))
402 {
403 let ident = format_ident!("__archmage_wild_{}", wild_rename_counter);
404 wild_rename_counter += 1;
405 *pat_type.pat = syn::Pat::Ident(syn::PatIdent {
406 attrs: vec![],
407 by_ref: None,
408 mutability: None,
409 ident,
410 subpat: None,
411 });
412 }
413 }
414
415 // Extract function components
416 let vis = &input_fn.vis;
417 let sig = &input_fn.sig;
418 let fn_name = &sig.ident;
419 let generics = &sig.generics;
420 let where_clause = &generics.where_clause;
421 let inputs = &sig.inputs;
422 let output = &sig.output;
423 let body = &input_fn.block;
424 let attrs = &input_fn.attrs;
425
426 // Determine self receiver type if present
427 let self_receiver_kind: Option<SelfReceiver> = inputs.first().and_then(|arg| match arg {
428 FnArg::Receiver(receiver) => {
429 if receiver.reference.is_none() {
430 Some(SelfReceiver::Owned)
431 } else if receiver.mutability.is_some() {
432 Some(SelfReceiver::RefMut)
433 } else {
434 Some(SelfReceiver::Ref)
435 }
436 }
437 _ => None,
438 });
439
440 // Build inner function parameters, transforming self if needed
441 let inner_params: Vec<proc_macro2::TokenStream> = inputs
442 .iter()
443 .map(|arg| match arg {
444 FnArg::Receiver(_) => {
445 // Transform self receiver to _self parameter
446 let self_ty = args.self_type.as_ref().unwrap();
447 match self_receiver_kind.as_ref().unwrap() {
448 SelfReceiver::Owned => quote!(_self: #self_ty),
449 SelfReceiver::Ref => quote!(_self: &#self_ty),
450 SelfReceiver::RefMut => quote!(_self: &mut #self_ty),
451 }
452 }
453 FnArg::Typed(pat_type) => quote!(#pat_type),
454 })
455 .collect();
456
457 // Build inner function call arguments
458 let inner_args: Vec<proc_macro2::TokenStream> = inputs
459 .iter()
460 .filter_map(|arg| match arg {
461 FnArg::Typed(pat_type) => {
462 if let syn::Pat::Ident(pat_ident) = pat_type.pat.as_ref() {
463 let ident = &pat_ident.ident;
464 Some(quote!(#ident))
465 } else {
466 None
467 }
468 }
469 FnArg::Receiver(_) => Some(quote!(self)), // Pass self to inner as _self
470 })
471 .collect();
472
473 let inner_fn_name = format_ident!("__simd_inner_{}", fn_name);
474
475 // Choose inline attribute based on args
476 // Note: #[inline(always)] + #[target_feature] requires nightly with
477 // #![feature(target_feature_inline_always)]
478 let inline_attr: Attribute = if args.inline_always {
479 parse_quote!(#[inline(always)])
480 } else {
481 parse_quote!(#[inline])
482 };
483
484 // Transform output and body to replace Self with concrete type if needed
485 let (inner_output, inner_body): (ReturnType, syn::Block) =
486 if let Some(ref self_ty) = args.self_type {
487 let mut replacer = ReplaceSelf {
488 replacement: self_ty,
489 };
490 let transformed_output = replacer.fold_return_type(output.clone());
491 let transformed_body = replacer.fold_block((**body).clone());
492 (transformed_output, transformed_body)
493 } else {
494 (output.clone(), (**body).clone())
495 };
496
497 // Generate the expanded function
498 // If we know the target arch (concrete token), generate cfg-gated real impl + stub
499 let token_type_str = token_type_name.as_deref().unwrap_or("UnknownToken");
500 let expanded = if let Some(arch) = target_arch {
501 quote! {
502 // Real implementation for the correct architecture
503 #[cfg(target_arch = #arch)]
504 #(#attrs)*
505 #vis #sig {
506 #(#target_feature_attrs)*
507 #inline_attr
508 fn #inner_fn_name #generics (#(#inner_params),*) #inner_output #where_clause
509 #inner_body
510
511 // SAFETY: The token parameter proves the required CPU features are available.
512 // Calling a #[target_feature] function from a non-matching context requires
513 // unsafe because the CPU may not support those instructions. The token's
514 // existence proves summon() succeeded, so the features are available.
515 unsafe { #inner_fn_name(#(#inner_args),*) }
516 }
517
518 // Stub for other architectures - the token cannot be obtained, so this is unreachable
519 #[cfg(not(target_arch = #arch))]
520 #(#attrs)*
521 #vis #sig {
522 // This token type cannot be summoned on this architecture.
523 // If you're seeing this at runtime, there's a bug in dispatch logic
524 // or forge_token_dangerously() was used incorrectly.
525 let _ = (#(#inner_args),*); // suppress unused warnings
526 unreachable!(
527 "BUG: {}() was called but requires {} (target_arch = \"{}\"). \
528 {}::summon() returns None on this architecture, so this function \
529 is unreachable in safe code. If you used forge_token_dangerously(), \
530 that is the bug.",
531 stringify!(#fn_name),
532 #token_type_str,
533 #arch,
534 #token_type_str,
535 )
536 }
537 }
538 } else {
539 // No specific arch (trait bounds or generic) - generate without cfg guards
540 quote! {
541 #(#attrs)*
542 #vis #sig {
543 #(#target_feature_attrs)*
544 #inline_attr
545 fn #inner_fn_name #generics (#(#inner_params),*) #inner_output #where_clause
546 #inner_body
547
548 // SAFETY: Calling a #[target_feature] function from a non-matching context
549 // requires unsafe. The token proves the required CPU features are available.
550 unsafe { #inner_fn_name(#(#inner_args),*) }
551 }
552 }
553 };
554
555 expanded.into()
556}
557
558/// Mark a function as an arcane SIMD function.
559///
560/// This macro enables safe use of SIMD intrinsics by generating an inner function
561/// with the appropriate `#[target_feature(enable = "...")]` attributes based on
562/// the token parameter type. The outer function calls the inner function unsafely,
563/// which is justified because the token parameter proves the features are available.
564///
565/// **The token is passed through to the inner function**, so you can call other
566/// token-taking functions from inside `#[arcane]`.
567///
568/// # Token Parameter Forms
569///
570/// The macro supports four forms of token parameters:
571///
572/// ## Concrete Token Types
573///
574/// ```ignore
575/// #[arcane]
576/// fn process(token: Avx2Token, data: &[f32; 8]) -> [f32; 8] {
577/// // AVX2 intrinsics safe here
578/// }
579/// ```
580///
581/// ## impl Trait Bounds
582///
583/// ```ignore
584/// #[arcane]
585/// fn process(token: impl HasX64V2, data: &[f32; 8]) -> [f32; 8] {
586/// // Accepts any token with x86-64-v2 features (SSE4.2+)
587/// }
588/// ```
589///
590/// ## Generic Type Parameters
591///
592/// ```ignore
593/// #[arcane]
594/// fn process<T: HasX64V2>(token: T, data: &[f32; 8]) -> [f32; 8] {
595/// // Generic over any v2-capable token
596/// }
597///
598/// // Also works with where clauses:
599/// #[arcane]
600/// fn process<T>(token: T, data: &[f32; 8]) -> [f32; 8]
601/// where
602/// T: HasX64V2
603/// {
604/// // ...
605/// }
606/// ```
607///
608/// ## Methods with Self Receivers
609///
610/// Methods with `self`, `&self`, `&mut self` receivers are supported via the
611/// `_self = Type` argument. Use `_self` in the function body instead of `self`:
612///
613/// ```ignore
614/// use archmage::{X64V3Token, arcane};
615/// use wide::f32x8;
616///
617/// trait SimdOps {
618/// fn double(&self, token: X64V3Token) -> Self;
619/// fn square(self, token: X64V3Token) -> Self;
620/// fn scale(&mut self, token: X64V3Token, factor: f32);
621/// }
622///
623/// impl SimdOps for f32x8 {
624/// #[arcane(_self = f32x8)]
625/// fn double(&self, _token: X64V3Token) -> Self {
626/// // Use _self instead of self in the body
627/// *_self + *_self
628/// }
629///
630/// #[arcane(_self = f32x8)]
631/// fn square(self, _token: X64V3Token) -> Self {
632/// _self * _self
633/// }
634///
635/// #[arcane(_self = f32x8)]
636/// fn scale(&mut self, _token: X64V3Token, factor: f32) {
637/// *_self = *_self * f32x8::splat(factor);
638/// }
639/// }
640/// ```
641///
642/// **Why `_self`?** The macro generates an inner function where `self` becomes
643/// a regular parameter named `_self`. Using `_self` in your code reminds you
644/// that you're not using the normal `self` keyword.
645///
646/// **All receiver types are supported:**
647/// - `self` (by value/move) → `_self: Type`
648/// - `&self` (shared reference) → `_self: &Type`
649/// - `&mut self` (mutable reference) → `_self: &mut Type`
650///
651/// # Multiple Trait Bounds
652///
653/// When using `impl Trait` or generic bounds with multiple traits,
654/// all required features are enabled:
655///
656/// ```ignore
657/// #[arcane]
658/// fn fma_kernel(token: impl HasX64V2 + HasNeon, data: &[f32; 8]) -> [f32; 8] {
659/// // Cross-platform: SSE4.2 on x86, NEON on ARM
660/// }
661/// ```
662///
663/// # Expansion
664///
665/// The macro expands to approximately:
666///
667/// ```ignore
668/// fn process(token: Avx2Token, data: &[f32; 8]) -> [f32; 8] {
669/// #[target_feature(enable = "avx2")]
670/// #[inline]
671/// fn __simd_inner_process(token: Avx2Token, data: &[f32; 8]) -> [f32; 8] {
672/// let v = unsafe { _mm256_loadu_ps(data.as_ptr()) };
673/// let doubled = _mm256_add_ps(v, v);
674/// let mut out = [0.0f32; 8];
675/// unsafe { _mm256_storeu_ps(out.as_mut_ptr(), doubled) };
676/// out
677/// }
678/// // SAFETY: Calling #[target_feature] fn from non-matching context.
679/// // Token proves the required features are available.
680/// unsafe { __simd_inner_process(token, data) }
681/// }
682/// ```
683///
684/// # Profile Tokens
685///
686/// Profile tokens automatically enable all required features:
687///
688/// ```ignore
689/// #[arcane]
690/// fn kernel(token: X64V3Token, data: &mut [f32]) {
691/// // AVX2 + FMA + BMI1 + BMI2 intrinsics all safe here!
692/// }
693/// ```
694///
695/// # Supported Tokens
696///
697/// - **x86_64 tiers**: `X64V2Token`, `X64V3Token` / `Desktop64` / `Avx2FmaToken`,
698/// `X64V4Token` / `Avx512Token` / `Server64`, `X64V4xToken`, `Avx512Fp16Token`
699/// - **ARM**: `NeonToken` / `Arm64`, `Arm64V2Token`, `Arm64V3Token`,
700/// `NeonAesToken`, `NeonSha3Token`, `NeonCrcToken`
701/// - **WASM**: `Wasm128Token`
702///
703/// # Supported Trait Bounds
704///
705/// - **x86_64 tiers**: `HasX64V2`, `HasX64V4`
706/// - **ARM**: `HasNeon`, `HasNeonAes`, `HasNeonSha3`, `HasArm64V2`, `HasArm64V3`
707///
708/// **Preferred:** Use concrete tokens (`X64V3Token`, `Desktop64`, `NeonToken`) directly.
709/// Concrete token types also work as trait bounds (e.g., `impl X64V3Token`).
710///
711/// **Not supported:** `SimdToken` and `IntoConcreteToken` cannot be used as token
712/// bounds because they don't map to any CPU features. The macro needs concrete
713/// features to generate `#[target_feature]` attributes.
714///
715/// # Options
716///
717/// ## `inline_always`
718///
719/// Use `#[inline(always)]` instead of `#[inline]` for the inner function.
720/// This can improve performance by ensuring aggressive inlining, but requires
721/// nightly Rust with `#![feature(target_feature_inline_always)]` enabled in
722/// the crate using the macro.
723///
724/// ```ignore
725/// #![feature(target_feature_inline_always)]
726///
727/// #[arcane(inline_always)]
728/// fn fast_kernel(token: Avx2Token, data: &mut [f32]) {
729/// // Inner function will use #[inline(always)]
730/// }
731/// ```
732#[proc_macro_attribute]
733pub fn arcane(attr: TokenStream, item: TokenStream) -> TokenStream {
734 let args = parse_macro_input!(attr as ArcaneArgs);
735 let input_fn = parse_macro_input!(item as ItemFn);
736 arcane_impl(input_fn, "arcane", args)
737}
738
739/// Legacy alias for [`arcane`].
740///
741/// **Deprecated:** Use `#[arcane]` instead. This alias exists only for migration.
742#[proc_macro_attribute]
743#[doc(hidden)]
744pub fn simd_fn(attr: TokenStream, item: TokenStream) -> TokenStream {
745 let args = parse_macro_input!(attr as ArcaneArgs);
746 let input_fn = parse_macro_input!(item as ItemFn);
747 arcane_impl(input_fn, "simd_fn", args)
748}
749
750/// Descriptive alias for [`arcane`].
751///
752/// Generates a safe wrapper around a `#[target_feature]` inner function.
753/// The token type in your signature determines which CPU features are enabled.
754/// Creates an LLVM optimization boundary — use [`token_target_features`]
755/// (alias for [`rite`]) for inner helpers to avoid this.
756///
757/// Since Rust 1.85, value-based SIMD intrinsics are safe inside
758/// `#[target_feature]` functions. This macro generates the `#[target_feature]`
759/// wrapper so you never need to write `unsafe` for SIMD code.
760///
761/// See [`arcane`] for full documentation and examples.
762#[proc_macro_attribute]
763pub fn token_target_features_boundary(attr: TokenStream, item: TokenStream) -> TokenStream {
764 let args = parse_macro_input!(attr as ArcaneArgs);
765 let input_fn = parse_macro_input!(item as ItemFn);
766 arcane_impl(input_fn, "token_target_features_boundary", args)
767}
768
769// ============================================================================
770// Rite macro for inner SIMD functions (inlines into matching #[target_feature] callers)
771// ============================================================================
772
773/// Annotate inner SIMD helpers called from `#[arcane]` functions.
774///
775/// Unlike `#[arcane]`, which creates an inner `#[target_feature]` function behind
776/// a safe boundary, `#[rite]` adds `#[target_feature]` and `#[inline]` directly.
777/// LLVM inlines it into any caller with matching features — no boundary crossing.
778///
779/// # When to Use
780///
781/// Use `#[rite]` for helper functions that are **only** called from within
782/// `#[arcane]` functions with matching or superset token types:
783///
784/// ```ignore
785/// use archmage::{arcane, rite, X64V3Token};
786///
787/// #[arcane]
788/// fn outer(token: X64V3Token, data: &[f32; 8]) -> f32 {
789/// // helper inlines — same target features, no boundary
790/// helper(token, data) * 2.0
791/// }
792///
793/// #[rite]
794/// fn helper(token: X64V3Token, data: &[f32; 8]) -> f32 {
795/// // Just has #[target_feature(enable = "avx2,fma,...")]
796/// // Called from #[arcane] context, so features are guaranteed
797/// let v = f32x8::from_array(token, *data);
798/// v.reduce_add()
799/// }
800/// ```
801///
802/// # Safety
803///
804/// `#[rite]` functions can only be safely called from contexts where the
805/// required CPU features are enabled:
806/// - From within `#[arcane]` functions with matching/superset tokens
807/// - From within other `#[rite]` functions with matching/superset tokens
808/// - From code compiled with `-Ctarget-cpu` that enables the features
809///
810/// Calling from other contexts requires `unsafe` and the caller must ensure
811/// the CPU supports the required features.
812///
813/// # Comparison with #[arcane]
814///
815/// | Aspect | `#[arcane]` | `#[rite]` |
816/// |--------|-------------|-----------|
817/// | Creates wrapper | Yes | No |
818/// | Entry point | Yes | No |
819/// | Inlines into caller | No (barrier) | Yes |
820/// | Safe to call anywhere | Yes (with token) | Only from feature-enabled context |
821#[proc_macro_attribute]
822pub fn rite(attr: TokenStream, item: TokenStream) -> TokenStream {
823 // Parse optional arguments (currently just inline_always)
824 let args = parse_macro_input!(attr as RiteArgs);
825 let input_fn = parse_macro_input!(item as ItemFn);
826 rite_impl(input_fn, args)
827}
828
829/// Descriptive alias for [`rite`].
830///
831/// Applies `#[target_feature]` + `#[inline]` based on the token type in your
832/// function signature. No wrapper, no optimization boundary. Use for functions
833/// called from within `#[arcane]`/`#[token_target_features_boundary]` code.
834///
835/// Since Rust 1.85, calling a `#[target_feature]` function from another function
836/// with matching features is safe — no `unsafe` needed.
837///
838/// See [`rite`] for full documentation and examples.
839#[proc_macro_attribute]
840pub fn token_target_features(attr: TokenStream, item: TokenStream) -> TokenStream {
841 let args = parse_macro_input!(attr as RiteArgs);
842 let input_fn = parse_macro_input!(item as ItemFn);
843 rite_impl(input_fn, args)
844}
845
846/// Arguments for the `#[rite]` macro.
847///
848/// Currently empty - `#[inline(always)]` is not supported because
849/// `#[inline(always)]` + `#[target_feature]` requires nightly Rust.
850/// The regular `#[inline]` hint is sufficient when called from
851/// matching `#[target_feature]` contexts.
852#[derive(Default)]
853struct RiteArgs {
854 // No options currently - inline_always doesn't work on stable
855}
856
857impl Parse for RiteArgs {
858 fn parse(input: ParseStream) -> syn::Result<Self> {
859 if !input.is_empty() {
860 let ident: Ident = input.parse()?;
861 return Err(syn::Error::new(
862 ident.span(),
863 "#[rite] takes no arguments. Note: inline_always is not supported \
864 because #[inline(always)] + #[target_feature] requires nightly Rust.",
865 ));
866 }
867 Ok(RiteArgs::default())
868 }
869}
870
871/// Implementation for the `#[rite]` macro.
872fn rite_impl(mut input_fn: ItemFn, args: RiteArgs) -> TokenStream {
873 // Find the token parameter and its features
874 let TokenParamInfo {
875 features,
876 target_arch,
877 ..
878 } = match find_token_param(&input_fn.sig) {
879 Some(result) => result,
880 None => {
881 // Check for specific misuse: featureless traits like SimdToken
882 if let Some(trait_name) = diagnose_featureless_token(&input_fn.sig) {
883 let msg = format!(
884 "`{trait_name}` cannot be used as a token bound in #[rite] \
885 because it doesn't specify any CPU features.\n\
886 \n\
887 #[rite] needs concrete features to generate #[target_feature]. \
888 Use a concrete token or a feature trait:\n\
889 \n\
890 Concrete tokens: X64V3Token, Desktop64, NeonToken, Arm64V2Token, ...\n\
891 Feature traits: impl HasX64V2, impl HasNeon, impl HasArm64V3, ..."
892 );
893 return syn::Error::new_spanned(&input_fn.sig, msg)
894 .to_compile_error()
895 .into();
896 }
897 let msg = "rite requires a token parameter. Supported forms:\n\
898 - Concrete: `token: X64V3Token`\n\
899 - impl Trait: `token: impl HasX64V2`\n\
900 - Generic: `fn foo<T: HasX64V2>(token: T, ...)`";
901 return syn::Error::new_spanned(&input_fn.sig, msg)
902 .to_compile_error()
903 .into();
904 }
905 };
906
907 // Build target_feature attributes
908 let target_feature_attrs: Vec<Attribute> = features
909 .iter()
910 .map(|feature| parse_quote!(#[target_feature(enable = #feature)]))
911 .collect();
912
913 // Always use #[inline] - #[inline(always)] + #[target_feature] requires nightly
914 let _ = args; // RiteArgs is currently empty but kept for future extensibility
915 let inline_attr: Attribute = parse_quote!(#[inline]);
916
917 // Prepend attributes to the function
918 let mut new_attrs = target_feature_attrs;
919 new_attrs.push(inline_attr);
920 new_attrs.append(&mut input_fn.attrs);
921 input_fn.attrs = new_attrs;
922
923 // If we know the target arch, generate cfg-gated impl + stub
924 if let Some(arch) = target_arch {
925 let vis = &input_fn.vis;
926 let sig = &input_fn.sig;
927 let attrs = &input_fn.attrs;
928 let block = &input_fn.block;
929
930 quote! {
931 #[cfg(target_arch = #arch)]
932 #(#attrs)*
933 #vis #sig
934 #block
935
936 #[cfg(not(target_arch = #arch))]
937 #vis #sig {
938 unreachable!(concat!(
939 "This function requires ",
940 #arch,
941 " architecture"
942 ))
943 }
944 }
945 .into()
946 } else {
947 // No specific arch (trait bounds) - just emit the annotated function
948 quote!(#input_fn).into()
949 }
950}
951
952// =============================================================================
953// magetypes! macro - generate platform variants from generic function
954// =============================================================================
955
956/// Generate platform-specific variants from a function by replacing `Token`.
957///
958/// Use `Token` as a placeholder for the token type. The macro generates
959/// suffixed variants with `Token` replaced by the concrete token type, and
960/// each variant wrapped in the appropriate `#[cfg(target_arch = ...)]` guard.
961///
962/// # Default tiers
963///
964/// Without arguments, generates `_v3`, `_v4`, `_neon`, `_wasm128`, `_scalar`:
965///
966/// ```rust,ignore
967/// #[magetypes]
968/// fn process(token: Token, data: &[f32]) -> f32 {
969/// inner_simd_work(token, data)
970/// }
971/// ```
972///
973/// # Explicit tiers
974///
975/// Specify which tiers to generate:
976///
977/// ```rust,ignore
978/// #[magetypes(v1, v3, neon)]
979/// fn process(token: Token, data: &[f32]) -> f32 {
980/// inner_simd_work(token, data)
981/// }
982/// // Generates: process_v1, process_v3, process_neon, process_scalar
983/// ```
984///
985/// `scalar` is always included implicitly.
986///
987/// Known tiers: `v1`, `v2`, `v3`, `v4`, `v4x`, `neon`, `neon_aes`,
988/// `neon_sha3`, `neon_crc`, `wasm128`, `scalar`.
989///
990/// # What gets replaced
991///
992/// **Only `Token`** is replaced — with the concrete token type for each variant
993/// (e.g., `archmage::X64V3Token`, `archmage::ScalarToken`). SIMD types like
994/// `f32x8` and constants like `LANES` are **not** replaced by this macro.
995///
996/// # Usage with incant!
997///
998/// The generated variants work with `incant!` for dispatch:
999///
1000/// ```rust,ignore
1001/// pub fn process_api(data: &[f32]) -> f32 {
1002/// incant!(process(data))
1003/// }
1004///
1005/// // Or with matching explicit tiers:
1006/// pub fn process_api(data: &[f32]) -> f32 {
1007/// incant!(process(data), [v1, v3, neon])
1008/// }
1009/// ```
1010#[proc_macro_attribute]
1011pub fn magetypes(attr: TokenStream, item: TokenStream) -> TokenStream {
1012 let input_fn = parse_macro_input!(item as ItemFn);
1013
1014 // Parse optional tier list from attribute args
1015 let tier_names: Vec<String> = if attr.is_empty() {
1016 DEFAULT_TIER_NAMES.iter().map(|s| s.to_string()).collect()
1017 } else {
1018 let parser = |input: ParseStream| input.parse_terminated(Ident::parse, Token![,]);
1019 let idents = match syn::parse::Parser::parse(parser, attr) {
1020 Ok(p) => p,
1021 Err(e) => return e.to_compile_error().into(),
1022 };
1023 idents.iter().map(|i| i.to_string()).collect()
1024 };
1025
1026 let tiers = match resolve_tiers(&tier_names, input_fn.sig.ident.span()) {
1027 Ok(t) => t,
1028 Err(e) => return e.to_compile_error().into(),
1029 };
1030
1031 magetypes_impl(input_fn, &tiers)
1032}
1033
1034fn magetypes_impl(mut input_fn: ItemFn, tiers: &[&TierDescriptor]) -> TokenStream {
1035 // Strip user-provided #[arcane] / #[rite] to prevent double-wrapping
1036 // (magetypes auto-adds #[arcane] on non-scalar variants)
1037 input_fn
1038 .attrs
1039 .retain(|attr| !attr.path().is_ident("arcane") && !attr.path().is_ident("rite"));
1040
1041 let fn_name = &input_fn.sig.ident;
1042 let fn_attrs = &input_fn.attrs;
1043
1044 // Convert function to string for text substitution
1045 let fn_str = input_fn.to_token_stream().to_string();
1046
1047 let mut variants = Vec::new();
1048
1049 for tier in tiers {
1050 // Create suffixed function name
1051 let suffixed_name = format!("{}_{}", fn_name, tier.suffix);
1052
1053 // Do text substitution
1054 let mut variant_str = fn_str.clone();
1055
1056 // Replace function name
1057 variant_str = variant_str.replacen(&fn_name.to_string(), &suffixed_name, 1);
1058
1059 // Replace Token type with concrete token
1060 variant_str = variant_str.replace("Token", tier.token_path);
1061
1062 // Parse back to tokens
1063 let variant_tokens: proc_macro2::TokenStream = match variant_str.parse() {
1064 Ok(t) => t,
1065 Err(e) => {
1066 return syn::Error::new_spanned(
1067 &input_fn,
1068 format!(
1069 "Failed to parse generated variant `{}`: {}",
1070 suffixed_name, e
1071 ),
1072 )
1073 .to_compile_error()
1074 .into();
1075 }
1076 };
1077
1078 // Add cfg guards
1079 let cfg_guard = match (tier.target_arch, tier.cargo_feature) {
1080 (Some(arch), Some(feature)) => {
1081 quote! { #[cfg(all(target_arch = #arch, feature = #feature))] }
1082 }
1083 (Some(arch), None) => {
1084 quote! { #[cfg(target_arch = #arch)] }
1085 }
1086 (None, Some(feature)) => {
1087 quote! { #[cfg(feature = #feature)] }
1088 }
1089 (None, None) => {
1090 quote! {} // No guard needed (scalar)
1091 }
1092 };
1093
1094 variants.push(if tier.name != "scalar" {
1095 // Non-scalar variants get #[arcane] so target_feature is applied
1096 quote! {
1097 #cfg_guard
1098 #[archmage::arcane]
1099 #variant_tokens
1100 }
1101 } else {
1102 quote! {
1103 #cfg_guard
1104 #variant_tokens
1105 }
1106 });
1107 }
1108
1109 // Remove attributes from the list that should not be duplicated
1110 let filtered_attrs: Vec<_> = fn_attrs
1111 .iter()
1112 .filter(|a| !a.path().is_ident("magetypes"))
1113 .collect();
1114
1115 let output = quote! {
1116 #(#filtered_attrs)*
1117 #(#variants)*
1118 };
1119
1120 output.into()
1121}
1122
1123// =============================================================================
1124// incant! macro - dispatch to platform-specific variants
1125// =============================================================================
1126
1127// =============================================================================
1128// Tier descriptors for incant! and #[magetypes]
1129// =============================================================================
1130
1131/// Describes a dispatch tier for incant! and #[magetypes].
1132struct TierDescriptor {
1133 /// Tier name as written in user code (e.g., "v3", "neon")
1134 name: &'static str,
1135 /// Function suffix (e.g., "v3", "neon", "scalar")
1136 suffix: &'static str,
1137 /// Token type path (e.g., "archmage::X64V3Token")
1138 token_path: &'static str,
1139 /// IntoConcreteToken method name (e.g., "as_x64v3")
1140 as_method: &'static str,
1141 /// Target architecture for cfg guard (None = no guard)
1142 target_arch: Option<&'static str>,
1143 /// Required cargo feature (None = no feature guard)
1144 cargo_feature: Option<&'static str>,
1145 /// Dispatch priority (higher = tried first within same arch)
1146 priority: u32,
1147}
1148
1149/// All known tiers in dispatch-priority order (highest first within arch).
1150const ALL_TIERS: &[TierDescriptor] = &[
1151 // x86: highest to lowest
1152 TierDescriptor {
1153 name: "v4x",
1154 suffix: "v4x",
1155 token_path: "archmage::X64V4xToken",
1156 as_method: "as_x64v4x",
1157 target_arch: Some("x86_64"),
1158 cargo_feature: Some("avx512"),
1159 priority: 50,
1160 },
1161 TierDescriptor {
1162 name: "v4",
1163 suffix: "v4",
1164 token_path: "archmage::X64V4Token",
1165 as_method: "as_x64v4",
1166 target_arch: Some("x86_64"),
1167 cargo_feature: Some("avx512"),
1168 priority: 40,
1169 },
1170 TierDescriptor {
1171 name: "v3_crypto",
1172 suffix: "v3_crypto",
1173 token_path: "archmage::X64V3CryptoToken",
1174 as_method: "as_x64v3_crypto",
1175 target_arch: Some("x86_64"),
1176 cargo_feature: None,
1177 priority: 35,
1178 },
1179 TierDescriptor {
1180 name: "v3",
1181 suffix: "v3",
1182 token_path: "archmage::X64V3Token",
1183 as_method: "as_x64v3",
1184 target_arch: Some("x86_64"),
1185 cargo_feature: None,
1186 priority: 30,
1187 },
1188 TierDescriptor {
1189 name: "x64_crypto",
1190 suffix: "x64_crypto",
1191 token_path: "archmage::X64CryptoToken",
1192 as_method: "as_x64_crypto",
1193 target_arch: Some("x86_64"),
1194 cargo_feature: None,
1195 priority: 25,
1196 },
1197 TierDescriptor {
1198 name: "v2",
1199 suffix: "v2",
1200 token_path: "archmage::X64V2Token",
1201 as_method: "as_x64v2",
1202 target_arch: Some("x86_64"),
1203 cargo_feature: None,
1204 priority: 20,
1205 },
1206 TierDescriptor {
1207 name: "v1",
1208 suffix: "v1",
1209 token_path: "archmage::X64V1Token",
1210 as_method: "as_x64v1",
1211 target_arch: Some("x86_64"),
1212 cargo_feature: None,
1213 priority: 10,
1214 },
1215 // ARM: highest to lowest
1216 TierDescriptor {
1217 name: "arm_v3",
1218 suffix: "arm_v3",
1219 token_path: "archmage::Arm64V3Token",
1220 as_method: "as_arm_v3",
1221 target_arch: Some("aarch64"),
1222 cargo_feature: None,
1223 priority: 50,
1224 },
1225 TierDescriptor {
1226 name: "arm_v2",
1227 suffix: "arm_v2",
1228 token_path: "archmage::Arm64V2Token",
1229 as_method: "as_arm_v2",
1230 target_arch: Some("aarch64"),
1231 cargo_feature: None,
1232 priority: 40,
1233 },
1234 TierDescriptor {
1235 name: "neon_aes",
1236 suffix: "neon_aes",
1237 token_path: "archmage::NeonAesToken",
1238 as_method: "as_neon_aes",
1239 target_arch: Some("aarch64"),
1240 cargo_feature: None,
1241 priority: 30,
1242 },
1243 TierDescriptor {
1244 name: "neon_sha3",
1245 suffix: "neon_sha3",
1246 token_path: "archmage::NeonSha3Token",
1247 as_method: "as_neon_sha3",
1248 target_arch: Some("aarch64"),
1249 cargo_feature: None,
1250 priority: 30,
1251 },
1252 TierDescriptor {
1253 name: "neon_crc",
1254 suffix: "neon_crc",
1255 token_path: "archmage::NeonCrcToken",
1256 as_method: "as_neon_crc",
1257 target_arch: Some("aarch64"),
1258 cargo_feature: None,
1259 priority: 30,
1260 },
1261 TierDescriptor {
1262 name: "neon",
1263 suffix: "neon",
1264 token_path: "archmage::NeonToken",
1265 as_method: "as_neon",
1266 target_arch: Some("aarch64"),
1267 cargo_feature: None,
1268 priority: 20,
1269 },
1270 // WASM
1271 TierDescriptor {
1272 name: "wasm128",
1273 suffix: "wasm128",
1274 token_path: "archmage::Wasm128Token",
1275 as_method: "as_wasm128",
1276 target_arch: Some("wasm32"),
1277 cargo_feature: None,
1278 priority: 20,
1279 },
1280 // Scalar (always last)
1281 TierDescriptor {
1282 name: "scalar",
1283 suffix: "scalar",
1284 token_path: "archmage::ScalarToken",
1285 as_method: "as_scalar",
1286 target_arch: None,
1287 cargo_feature: None,
1288 priority: 0,
1289 },
1290];
1291
1292/// Default tiers (backwards-compatible with pre-explicit behavior).
1293const DEFAULT_TIER_NAMES: &[&str] = &["v4", "v3", "neon", "wasm128", "scalar"];
1294
1295/// Look up a tier by name, returning an error on unknown names.
1296fn find_tier(name: &str) -> Option<&'static TierDescriptor> {
1297 ALL_TIERS.iter().find(|t| t.name == name)
1298}
1299
1300/// Resolve tier names to descriptors, sorted by dispatch priority (highest first).
1301/// Always appends "scalar" if not already present.
1302fn resolve_tiers(
1303 tier_names: &[String],
1304 error_span: proc_macro2::Span,
1305) -> syn::Result<Vec<&'static TierDescriptor>> {
1306 let mut tiers = Vec::new();
1307 for name in tier_names {
1308 match find_tier(name) {
1309 Some(tier) => tiers.push(tier),
1310 None => {
1311 let known: Vec<&str> = ALL_TIERS.iter().map(|t| t.name).collect();
1312 return Err(syn::Error::new(
1313 error_span,
1314 format!("unknown tier `{}`. Known tiers: {}", name, known.join(", ")),
1315 ));
1316 }
1317 }
1318 }
1319
1320 // Always include scalar fallback
1321 if !tiers.iter().any(|t| t.name == "scalar") {
1322 tiers.push(find_tier("scalar").unwrap());
1323 }
1324
1325 // Sort by priority (highest first) for correct dispatch order
1326 tiers.sort_by(|a, b| b.priority.cmp(&a.priority));
1327
1328 Ok(tiers)
1329}
1330
1331// =============================================================================
1332// incant! macro - dispatch to platform-specific variants
1333// =============================================================================
1334
1335/// Input for the incant! macro
1336struct IncantInput {
1337 /// Function path to call (e.g. `func` or `module::func`)
1338 func_path: syn::Path,
1339 /// Arguments to pass
1340 args: Vec<syn::Expr>,
1341 /// Optional token variable for passthrough mode
1342 with_token: Option<syn::Expr>,
1343 /// Optional explicit tier list (None = default tiers)
1344 tiers: Option<(Vec<String>, proc_macro2::Span)>,
1345}
1346
1347/// Create a suffixed version of a function path.
1348/// e.g. `module::func` + `"v3"` → `module::func_v3`
1349fn suffix_path(path: &syn::Path, suffix: &str) -> syn::Path {
1350 let mut suffixed = path.clone();
1351 if let Some(last) = suffixed.segments.last_mut() {
1352 last.ident = format_ident!("{}_{}", last.ident, suffix);
1353 }
1354 suffixed
1355}
1356
1357impl Parse for IncantInput {
1358 fn parse(input: ParseStream) -> syn::Result<Self> {
1359 // Parse: function_path(arg1, arg2, ...) [with token_expr] [, [tier1, tier2, ...]]
1360 let func_path: syn::Path = input.parse()?;
1361
1362 // Parse parenthesized arguments
1363 let content;
1364 syn::parenthesized!(content in input);
1365 let args = content
1366 .parse_terminated(syn::Expr::parse, Token![,])?
1367 .into_iter()
1368 .collect();
1369
1370 // Check for optional "with token"
1371 let with_token = if input.peek(Ident) {
1372 let kw: Ident = input.parse()?;
1373 if kw != "with" {
1374 return Err(syn::Error::new_spanned(kw, "expected `with` keyword"));
1375 }
1376 Some(input.parse()?)
1377 } else {
1378 None
1379 };
1380
1381 // Check for optional tier list: , [tier1, tier2, ...]
1382 let tiers = if input.peek(Token![,]) {
1383 let _: Token![,] = input.parse()?;
1384 let bracket_content;
1385 let bracket = syn::bracketed!(bracket_content in input);
1386 let tier_idents = bracket_content.parse_terminated(Ident::parse, Token![,])?;
1387 let tier_names: Vec<String> = tier_idents.iter().map(|i| i.to_string()).collect();
1388 Some((tier_names, bracket.span.join()))
1389 } else {
1390 None
1391 };
1392
1393 Ok(IncantInput {
1394 func_path,
1395 args,
1396 with_token,
1397 tiers,
1398 })
1399 }
1400}
1401
1402/// Dispatch to platform-specific SIMD variants.
1403///
1404/// # Entry Point Mode (no token yet)
1405///
1406/// Summons tokens and dispatches to the best available variant:
1407///
1408/// ```rust,ignore
1409/// pub fn public_api(data: &[f32]) -> f32 {
1410/// incant!(dot(data))
1411/// }
1412/// ```
1413///
1414/// Expands to runtime feature detection + dispatch to `dot_v3`, `dot_v4`,
1415/// `dot_neon`, `dot_wasm128`, or `dot_scalar`.
1416///
1417/// # Explicit Tiers
1418///
1419/// Specify which tiers to dispatch to:
1420///
1421/// ```rust,ignore
1422/// // Only dispatch to v1, v3, neon, and scalar
1423/// pub fn api(data: &[f32]) -> f32 {
1424/// incant!(process(data), [v1, v3, neon])
1425/// }
1426/// ```
1427///
1428/// `scalar` is always included implicitly. Unknown tier names cause a
1429/// compile error. Tiers are automatically sorted into correct dispatch
1430/// order (highest priority first).
1431///
1432/// Known tiers: `v1`, `v2`, `v3`, `v4`, `v4x`, `neon`, `neon_aes`,
1433/// `neon_sha3`, `neon_crc`, `wasm128`, `scalar`.
1434///
1435/// # Passthrough Mode (already have token)
1436///
1437/// Uses compile-time dispatch via `IntoConcreteToken`:
1438///
1439/// ```rust,ignore
1440/// #[arcane]
1441/// fn outer(token: X64V3Token, data: &[f32]) -> f32 {
1442/// incant!(inner(data) with token)
1443/// }
1444/// ```
1445///
1446/// Also supports explicit tiers:
1447///
1448/// ```rust,ignore
1449/// fn inner<T: IntoConcreteToken>(token: T, data: &[f32]) -> f32 {
1450/// incant!(process(data) with token, [v3, neon])
1451/// }
1452/// ```
1453///
1454/// The compiler monomorphizes the dispatch, eliminating non-matching branches.
1455///
1456/// # Variant Naming
1457///
1458/// Functions must have suffixed variants matching the selected tiers:
1459/// - `_v1` for `X64V1Token`
1460/// - `_v2` for `X64V2Token`
1461/// - `_v3` for `X64V3Token`
1462/// - `_v4` for `X64V4Token` (requires `avx512` feature)
1463/// - `_v4x` for `X64V4xToken` (requires `avx512` feature)
1464/// - `_neon` for `NeonToken`
1465/// - `_neon_aes` for `NeonAesToken`
1466/// - `_neon_sha3` for `NeonSha3Token`
1467/// - `_neon_crc` for `NeonCrcToken`
1468/// - `_wasm128` for `Wasm128Token`
1469/// - `_scalar` for `ScalarToken`
1470#[proc_macro]
1471pub fn incant(input: TokenStream) -> TokenStream {
1472 let input = parse_macro_input!(input as IncantInput);
1473 incant_impl(input)
1474}
1475
1476/// Legacy alias for [`incant!`].
1477#[proc_macro]
1478pub fn simd_route(input: TokenStream) -> TokenStream {
1479 let input = parse_macro_input!(input as IncantInput);
1480 incant_impl(input)
1481}
1482
1483/// Descriptive alias for [`incant!`].
1484///
1485/// Dispatches to architecture-specific function variants at runtime.
1486/// Looks for suffixed functions (`_v3`, `_v4`, `_neon`, `_wasm128`, `_scalar`)
1487/// and calls the best one the CPU supports.
1488///
1489/// See [`incant!`] for full documentation and examples.
1490#[proc_macro]
1491pub fn dispatch_variant(input: TokenStream) -> TokenStream {
1492 let input = parse_macro_input!(input as IncantInput);
1493 incant_impl(input)
1494}
1495
1496fn incant_impl(input: IncantInput) -> TokenStream {
1497 let func_path = &input.func_path;
1498 let args = &input.args;
1499
1500 // Resolve tiers
1501 let tier_names: Vec<String> = match &input.tiers {
1502 Some((names, _)) => names.clone(),
1503 None => DEFAULT_TIER_NAMES.iter().map(|s| s.to_string()).collect(),
1504 };
1505 let last_segment_span = func_path
1506 .segments
1507 .last()
1508 .map(|s| s.ident.span())
1509 .unwrap_or_else(proc_macro2::Span::call_site);
1510 let error_span = input
1511 .tiers
1512 .as_ref()
1513 .map(|(_, span)| *span)
1514 .unwrap_or(last_segment_span);
1515
1516 let tiers = match resolve_tiers(&tier_names, error_span) {
1517 Ok(t) => t,
1518 Err(e) => return e.to_compile_error().into(),
1519 };
1520
1521 // Group tiers by architecture for cfg-guarded blocks
1522 // Within each arch, tiers are already sorted by priority (highest first)
1523 if let Some(token_expr) = &input.with_token {
1524 gen_incant_passthrough(func_path, args, token_expr, &tiers)
1525 } else {
1526 gen_incant_entry(func_path, args, &tiers)
1527 }
1528}
1529
1530/// Generate incant! passthrough mode (already have a token).
1531fn gen_incant_passthrough(
1532 func_path: &syn::Path,
1533 args: &[syn::Expr],
1534 token_expr: &syn::Expr,
1535 tiers: &[&TierDescriptor],
1536) -> TokenStream {
1537 let mut dispatch_arms = Vec::new();
1538
1539 // Group non-scalar tiers by (target_arch, cargo_feature) for nested cfg blocks
1540 let mut arch_groups: Vec<(Option<&str>, Option<&str>, Vec<&TierDescriptor>)> = Vec::new();
1541 for tier in tiers {
1542 if tier.name == "scalar" {
1543 continue; // Handle scalar separately at the end
1544 }
1545 let key = (tier.target_arch, tier.cargo_feature);
1546 if let Some(group) = arch_groups.iter_mut().find(|(a, f, _)| (*a, *f) == key) {
1547 group.2.push(tier);
1548 } else {
1549 arch_groups.push((tier.target_arch, tier.cargo_feature, vec![tier]));
1550 }
1551 }
1552
1553 for (target_arch, cargo_feature, group_tiers) in &arch_groups {
1554 let mut tier_checks = Vec::new();
1555 for tier in group_tiers {
1556 let fn_suffixed = suffix_path(func_path, tier.suffix);
1557 let as_method = format_ident!("{}", tier.as_method);
1558 tier_checks.push(quote! {
1559 if let Some(__t) = __incant_token.#as_method() {
1560 break '__incant #fn_suffixed(__t, #(#args),*);
1561 }
1562 });
1563 }
1564
1565 let inner = quote! { #(#tier_checks)* };
1566
1567 let guarded = match (target_arch, cargo_feature) {
1568 (Some(arch), Some(feat)) => quote! {
1569 #[cfg(target_arch = #arch)]
1570 {
1571 #[cfg(feature = #feat)]
1572 { #inner }
1573 }
1574 },
1575 (Some(arch), None) => quote! {
1576 #[cfg(target_arch = #arch)]
1577 { #inner }
1578 },
1579 (None, Some(feat)) => quote! {
1580 #[cfg(feature = #feat)]
1581 { #inner }
1582 },
1583 (None, None) => inner,
1584 };
1585
1586 dispatch_arms.push(guarded);
1587 }
1588
1589 // Scalar fallback (always last)
1590 let fn_scalar = suffix_path(func_path, "scalar");
1591 let scalar_arm = if tiers.iter().any(|t| t.name == "scalar") {
1592 quote! {
1593 if let Some(__t) = __incant_token.as_scalar() {
1594 break '__incant #fn_scalar(__t, #(#args),*);
1595 }
1596 unreachable!("Token did not match any known variant")
1597 }
1598 } else {
1599 quote! { unreachable!("Token did not match any known variant") }
1600 };
1601
1602 let expanded = quote! {
1603 '__incant: {
1604 use archmage::IntoConcreteToken;
1605 let __incant_token = #token_expr;
1606 #(#dispatch_arms)*
1607 #scalar_arm
1608 }
1609 };
1610 expanded.into()
1611}
1612
1613/// Generate incant! entry point mode (summon tokens).
1614fn gen_incant_entry(
1615 func_path: &syn::Path,
1616 args: &[syn::Expr],
1617 tiers: &[&TierDescriptor],
1618) -> TokenStream {
1619 let mut dispatch_arms = Vec::new();
1620
1621 // Group non-scalar tiers by target_arch for cfg blocks.
1622 // Within each arch group, further split by cargo_feature.
1623 let mut arch_groups: Vec<(Option<&str>, Vec<&TierDescriptor>)> = Vec::new();
1624 for tier in tiers {
1625 if tier.name == "scalar" {
1626 continue;
1627 }
1628 if let Some(group) = arch_groups.iter_mut().find(|(a, _)| *a == tier.target_arch) {
1629 group.1.push(tier);
1630 } else {
1631 arch_groups.push((tier.target_arch, vec![tier]));
1632 }
1633 }
1634
1635 for (target_arch, group_tiers) in &arch_groups {
1636 let mut tier_checks = Vec::new();
1637 for tier in group_tiers {
1638 let fn_suffixed = suffix_path(func_path, tier.suffix);
1639 let token_path: syn::Path = syn::parse_str(tier.token_path).unwrap();
1640
1641 let check = quote! {
1642 if let Some(__t) = #token_path::summon() {
1643 break '__incant #fn_suffixed(__t, #(#args),*);
1644 }
1645 };
1646
1647 if let Some(feat) = tier.cargo_feature {
1648 tier_checks.push(quote! {
1649 #[cfg(feature = #feat)]
1650 { #check }
1651 });
1652 } else {
1653 tier_checks.push(check);
1654 }
1655 }
1656
1657 let inner = quote! { #(#tier_checks)* };
1658
1659 if let Some(arch) = target_arch {
1660 dispatch_arms.push(quote! {
1661 #[cfg(target_arch = #arch)]
1662 { #inner }
1663 });
1664 } else {
1665 dispatch_arms.push(inner);
1666 }
1667 }
1668
1669 // Scalar fallback
1670 let fn_scalar = suffix_path(func_path, "scalar");
1671
1672 let expanded = quote! {
1673 '__incant: {
1674 use archmage::SimdToken;
1675 #(#dispatch_arms)*
1676 #fn_scalar(archmage::ScalarToken, #(#args),*)
1677 }
1678 };
1679 expanded.into()
1680}
1681
1682// =============================================================================
1683// Unit tests for token/trait recognition maps
1684// =============================================================================
1685
1686#[cfg(test)]
1687mod tests {
1688 use super::*;
1689
1690 use super::generated::{ALL_CONCRETE_TOKENS, ALL_TRAIT_NAMES};
1691
1692 #[test]
1693 fn every_concrete_token_is_in_token_to_features() {
1694 for &name in ALL_CONCRETE_TOKENS {
1695 assert!(
1696 token_to_features(name).is_some(),
1697 "Token `{}` exists in runtime crate but is NOT recognized by \
1698 token_to_features() in the proc macro. Add it!",
1699 name
1700 );
1701 }
1702 }
1703
1704 #[test]
1705 fn every_trait_is_in_trait_to_features() {
1706 for &name in ALL_TRAIT_NAMES {
1707 assert!(
1708 trait_to_features(name).is_some(),
1709 "Trait `{}` exists in runtime crate but is NOT recognized by \
1710 trait_to_features() in the proc macro. Add it!",
1711 name
1712 );
1713 }
1714 }
1715
1716 #[test]
1717 fn token_aliases_map_to_same_features() {
1718 // Desktop64 = X64V3Token
1719 assert_eq!(
1720 token_to_features("Desktop64"),
1721 token_to_features("X64V3Token"),
1722 "Desktop64 and X64V3Token should map to identical features"
1723 );
1724
1725 // Server64 = X64V4Token = Avx512Token
1726 assert_eq!(
1727 token_to_features("Server64"),
1728 token_to_features("X64V4Token"),
1729 "Server64 and X64V4Token should map to identical features"
1730 );
1731 assert_eq!(
1732 token_to_features("X64V4Token"),
1733 token_to_features("Avx512Token"),
1734 "X64V4Token and Avx512Token should map to identical features"
1735 );
1736
1737 // Arm64 = NeonToken
1738 assert_eq!(
1739 token_to_features("Arm64"),
1740 token_to_features("NeonToken"),
1741 "Arm64 and NeonToken should map to identical features"
1742 );
1743 }
1744
1745 #[test]
1746 fn trait_to_features_includes_tokens_as_bounds() {
1747 // Tier tokens should also work as trait bounds
1748 // (for `impl X64V3Token` patterns, even though Rust won't allow it,
1749 // the macro processes AST before type checking)
1750 let tier_tokens = [
1751 "X64V2Token",
1752 "X64CryptoToken",
1753 "X64V3Token",
1754 "Desktop64",
1755 "Avx2FmaToken",
1756 "X64V4Token",
1757 "Avx512Token",
1758 "Server64",
1759 "X64V4xToken",
1760 "Avx512Fp16Token",
1761 "NeonToken",
1762 "Arm64",
1763 "NeonAesToken",
1764 "NeonSha3Token",
1765 "NeonCrcToken",
1766 "Arm64V2Token",
1767 "Arm64V3Token",
1768 ];
1769
1770 for &name in &tier_tokens {
1771 assert!(
1772 trait_to_features(name).is_some(),
1773 "Tier token `{}` should also be recognized in trait_to_features() \
1774 for use as a generic bound. Add it!",
1775 name
1776 );
1777 }
1778 }
1779
1780 #[test]
1781 fn trait_features_are_cumulative() {
1782 // HasX64V4 should include all HasX64V2 features plus more
1783 let v2_features = trait_to_features("HasX64V2").unwrap();
1784 let v4_features = trait_to_features("HasX64V4").unwrap();
1785
1786 for &f in v2_features {
1787 assert!(
1788 v4_features.contains(&f),
1789 "HasX64V4 should include v2 feature `{}` but doesn't",
1790 f
1791 );
1792 }
1793
1794 // v4 should have more features than v2
1795 assert!(
1796 v4_features.len() > v2_features.len(),
1797 "HasX64V4 should have more features than HasX64V2"
1798 );
1799 }
1800
1801 #[test]
1802 fn x64v3_trait_features_include_v2() {
1803 // X64V3Token as trait bound should include v2 features
1804 let v2 = trait_to_features("HasX64V2").unwrap();
1805 let v3 = trait_to_features("X64V3Token").unwrap();
1806
1807 for &f in v2 {
1808 assert!(
1809 v3.contains(&f),
1810 "X64V3Token trait features should include v2 feature `{}` but don't",
1811 f
1812 );
1813 }
1814 }
1815
1816 #[test]
1817 fn has_neon_aes_includes_neon() {
1818 let neon = trait_to_features("HasNeon").unwrap();
1819 let neon_aes = trait_to_features("HasNeonAes").unwrap();
1820
1821 for &f in neon {
1822 assert!(
1823 neon_aes.contains(&f),
1824 "HasNeonAes should include NEON feature `{}`",
1825 f
1826 );
1827 }
1828 }
1829
1830 #[test]
1831 fn no_removed_traits_are_recognized() {
1832 // These traits were removed in 0.3.0 and should NOT be recognized
1833 let removed = [
1834 "HasSse",
1835 "HasSse2",
1836 "HasSse41",
1837 "HasSse42",
1838 "HasAvx",
1839 "HasAvx2",
1840 "HasFma",
1841 "HasAvx512f",
1842 "HasAvx512bw",
1843 "HasAvx512vl",
1844 "HasAvx512vbmi2",
1845 "HasSve",
1846 "HasSve2",
1847 ];
1848
1849 for &name in &removed {
1850 assert!(
1851 trait_to_features(name).is_none(),
1852 "Removed trait `{}` should NOT be in trait_to_features(). \
1853 It was removed in 0.3.0 — users should migrate to tier traits.",
1854 name
1855 );
1856 }
1857 }
1858
1859 #[test]
1860 fn no_nonexistent_tokens_are_recognized() {
1861 // These tokens don't exist and should NOT be recognized
1862 let fake = [
1863 "SveToken",
1864 "Sve2Token",
1865 "Avx512VnniToken",
1866 "X64V4ModernToken",
1867 "NeonFp16Token",
1868 ];
1869
1870 for &name in &fake {
1871 assert!(
1872 token_to_features(name).is_none(),
1873 "Non-existent token `{}` should NOT be in token_to_features()",
1874 name
1875 );
1876 }
1877 }
1878
1879 #[test]
1880 fn featureless_traits_are_not_in_registries() {
1881 // SimdToken and IntoConcreteToken should NOT be in any feature registry
1882 // because they don't map to CPU features
1883 for &name in FEATURELESS_TRAIT_NAMES {
1884 assert!(
1885 token_to_features(name).is_none(),
1886 "`{}` should NOT be in token_to_features() — it has no CPU features",
1887 name
1888 );
1889 assert!(
1890 trait_to_features(name).is_none(),
1891 "`{}` should NOT be in trait_to_features() — it has no CPU features",
1892 name
1893 );
1894 }
1895 }
1896
1897 #[test]
1898 fn find_featureless_trait_detects_simdtoken() {
1899 let names = vec!["SimdToken".to_string()];
1900 assert_eq!(find_featureless_trait(&names), Some("SimdToken"));
1901
1902 let names = vec!["IntoConcreteToken".to_string()];
1903 assert_eq!(find_featureless_trait(&names), Some("IntoConcreteToken"));
1904
1905 // Feature-bearing traits should NOT be detected
1906 let names = vec!["HasX64V2".to_string()];
1907 assert_eq!(find_featureless_trait(&names), None);
1908
1909 let names = vec!["HasNeon".to_string()];
1910 assert_eq!(find_featureless_trait(&names), None);
1911
1912 // Mixed: if SimdToken is among real traits, still detected
1913 let names = vec!["SimdToken".to_string(), "HasX64V2".to_string()];
1914 assert_eq!(find_featureless_trait(&names), Some("SimdToken"));
1915 }
1916
1917 #[test]
1918 fn arm64_v2_v3_traits_are_cumulative() {
1919 let v2_features = trait_to_features("HasArm64V2").unwrap();
1920 let v3_features = trait_to_features("HasArm64V3").unwrap();
1921
1922 for &f in v2_features {
1923 assert!(
1924 v3_features.contains(&f),
1925 "HasArm64V3 should include v2 feature `{}` but doesn't",
1926 f
1927 );
1928 }
1929
1930 assert!(
1931 v3_features.len() > v2_features.len(),
1932 "HasArm64V3 should have more features than HasArm64V2"
1933 );
1934 }
1935}