Skip to main content

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`, `wasm128_relaxed`, `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_relaxed",
1273        suffix: "wasm128_relaxed",
1274        token_path: "archmage::Wasm128RelaxedToken",
1275        as_method: "as_wasm128_relaxed",
1276        target_arch: Some("wasm32"),
1277        cargo_feature: None,
1278        priority: 21,
1279    },
1280    TierDescriptor {
1281        name: "wasm128",
1282        suffix: "wasm128",
1283        token_path: "archmage::Wasm128Token",
1284        as_method: "as_wasm128",
1285        target_arch: Some("wasm32"),
1286        cargo_feature: None,
1287        priority: 20,
1288    },
1289    // Scalar (always last)
1290    TierDescriptor {
1291        name: "scalar",
1292        suffix: "scalar",
1293        token_path: "archmage::ScalarToken",
1294        as_method: "as_scalar",
1295        target_arch: None,
1296        cargo_feature: None,
1297        priority: 0,
1298    },
1299];
1300
1301/// Default tiers (backwards-compatible with pre-explicit behavior).
1302const DEFAULT_TIER_NAMES: &[&str] = &["v4", "v3", "neon", "wasm128", "scalar"];
1303
1304/// Look up a tier by name, returning an error on unknown names.
1305fn find_tier(name: &str) -> Option<&'static TierDescriptor> {
1306    ALL_TIERS.iter().find(|t| t.name == name)
1307}
1308
1309/// Resolve tier names to descriptors, sorted by dispatch priority (highest first).
1310/// Always appends "scalar" if not already present.
1311fn resolve_tiers(
1312    tier_names: &[String],
1313    error_span: proc_macro2::Span,
1314) -> syn::Result<Vec<&'static TierDescriptor>> {
1315    let mut tiers = Vec::new();
1316    for name in tier_names {
1317        match find_tier(name) {
1318            Some(tier) => tiers.push(tier),
1319            None => {
1320                let known: Vec<&str> = ALL_TIERS.iter().map(|t| t.name).collect();
1321                return Err(syn::Error::new(
1322                    error_span,
1323                    format!("unknown tier `{}`. Known tiers: {}", name, known.join(", ")),
1324                ));
1325            }
1326        }
1327    }
1328
1329    // Always include scalar fallback
1330    if !tiers.iter().any(|t| t.name == "scalar") {
1331        tiers.push(find_tier("scalar").unwrap());
1332    }
1333
1334    // Sort by priority (highest first) for correct dispatch order
1335    tiers.sort_by(|a, b| b.priority.cmp(&a.priority));
1336
1337    Ok(tiers)
1338}
1339
1340// =============================================================================
1341// incant! macro - dispatch to platform-specific variants
1342// =============================================================================
1343
1344/// Input for the incant! macro
1345struct IncantInput {
1346    /// Function path to call (e.g. `func` or `module::func`)
1347    func_path: syn::Path,
1348    /// Arguments to pass
1349    args: Vec<syn::Expr>,
1350    /// Optional token variable for passthrough mode
1351    with_token: Option<syn::Expr>,
1352    /// Optional explicit tier list (None = default tiers)
1353    tiers: Option<(Vec<String>, proc_macro2::Span)>,
1354}
1355
1356/// Create a suffixed version of a function path.
1357/// e.g. `module::func` + `"v3"` → `module::func_v3`
1358fn suffix_path(path: &syn::Path, suffix: &str) -> syn::Path {
1359    let mut suffixed = path.clone();
1360    if let Some(last) = suffixed.segments.last_mut() {
1361        last.ident = format_ident!("{}_{}", last.ident, suffix);
1362    }
1363    suffixed
1364}
1365
1366impl Parse for IncantInput {
1367    fn parse(input: ParseStream) -> syn::Result<Self> {
1368        // Parse: function_path(arg1, arg2, ...) [with token_expr] [, [tier1, tier2, ...]]
1369        let func_path: syn::Path = input.parse()?;
1370
1371        // Parse parenthesized arguments
1372        let content;
1373        syn::parenthesized!(content in input);
1374        let args = content
1375            .parse_terminated(syn::Expr::parse, Token![,])?
1376            .into_iter()
1377            .collect();
1378
1379        // Check for optional "with token"
1380        let with_token = if input.peek(Ident) {
1381            let kw: Ident = input.parse()?;
1382            if kw != "with" {
1383                return Err(syn::Error::new_spanned(kw, "expected `with` keyword"));
1384            }
1385            Some(input.parse()?)
1386        } else {
1387            None
1388        };
1389
1390        // Check for optional tier list: , [tier1, tier2, ...]
1391        let tiers = if input.peek(Token![,]) {
1392            let _: Token![,] = input.parse()?;
1393            let bracket_content;
1394            let bracket = syn::bracketed!(bracket_content in input);
1395            let tier_idents = bracket_content.parse_terminated(Ident::parse, Token![,])?;
1396            let tier_names: Vec<String> = tier_idents.iter().map(|i| i.to_string()).collect();
1397            Some((tier_names, bracket.span.join()))
1398        } else {
1399            None
1400        };
1401
1402        Ok(IncantInput {
1403            func_path,
1404            args,
1405            with_token,
1406            tiers,
1407        })
1408    }
1409}
1410
1411/// Dispatch to platform-specific SIMD variants.
1412///
1413/// # Entry Point Mode (no token yet)
1414///
1415/// Summons tokens and dispatches to the best available variant:
1416///
1417/// ```rust,ignore
1418/// pub fn public_api(data: &[f32]) -> f32 {
1419///     incant!(dot(data))
1420/// }
1421/// ```
1422///
1423/// Expands to runtime feature detection + dispatch to `dot_v3`, `dot_v4`,
1424/// `dot_neon`, `dot_wasm128`, or `dot_scalar`.
1425///
1426/// # Explicit Tiers
1427///
1428/// Specify which tiers to dispatch to:
1429///
1430/// ```rust,ignore
1431/// // Only dispatch to v1, v3, neon, and scalar
1432/// pub fn api(data: &[f32]) -> f32 {
1433///     incant!(process(data), [v1, v3, neon])
1434/// }
1435/// ```
1436///
1437/// `scalar` is always included implicitly. Unknown tier names cause a
1438/// compile error. Tiers are automatically sorted into correct dispatch
1439/// order (highest priority first).
1440///
1441/// Known tiers: `v1`, `v2`, `v3`, `v4`, `v4x`, `neon`, `neon_aes`,
1442/// `neon_sha3`, `neon_crc`, `wasm128`, `wasm128_relaxed`, `scalar`.
1443///
1444/// # Passthrough Mode (already have token)
1445///
1446/// Uses compile-time dispatch via `IntoConcreteToken`:
1447///
1448/// ```rust,ignore
1449/// #[arcane]
1450/// fn outer(token: X64V3Token, data: &[f32]) -> f32 {
1451///     incant!(inner(data) with token)
1452/// }
1453/// ```
1454///
1455/// Also supports explicit tiers:
1456///
1457/// ```rust,ignore
1458/// fn inner<T: IntoConcreteToken>(token: T, data: &[f32]) -> f32 {
1459///     incant!(process(data) with token, [v3, neon])
1460/// }
1461/// ```
1462///
1463/// The compiler monomorphizes the dispatch, eliminating non-matching branches.
1464///
1465/// # Variant Naming
1466///
1467/// Functions must have suffixed variants matching the selected tiers:
1468/// - `_v1` for `X64V1Token`
1469/// - `_v2` for `X64V2Token`
1470/// - `_v3` for `X64V3Token`
1471/// - `_v4` for `X64V4Token` (requires `avx512` feature)
1472/// - `_v4x` for `X64V4xToken` (requires `avx512` feature)
1473/// - `_neon` for `NeonToken`
1474/// - `_neon_aes` for `NeonAesToken`
1475/// - `_neon_sha3` for `NeonSha3Token`
1476/// - `_neon_crc` for `NeonCrcToken`
1477/// - `_wasm128` for `Wasm128Token`
1478/// - `_scalar` for `ScalarToken`
1479#[proc_macro]
1480pub fn incant(input: TokenStream) -> TokenStream {
1481    let input = parse_macro_input!(input as IncantInput);
1482    incant_impl(input)
1483}
1484
1485/// Legacy alias for [`incant!`].
1486#[proc_macro]
1487pub fn simd_route(input: TokenStream) -> TokenStream {
1488    let input = parse_macro_input!(input as IncantInput);
1489    incant_impl(input)
1490}
1491
1492/// Descriptive alias for [`incant!`].
1493///
1494/// Dispatches to architecture-specific function variants at runtime.
1495/// Looks for suffixed functions (`_v3`, `_v4`, `_neon`, `_wasm128`, `_scalar`)
1496/// and calls the best one the CPU supports.
1497///
1498/// See [`incant!`] for full documentation and examples.
1499#[proc_macro]
1500pub fn dispatch_variant(input: TokenStream) -> TokenStream {
1501    let input = parse_macro_input!(input as IncantInput);
1502    incant_impl(input)
1503}
1504
1505fn incant_impl(input: IncantInput) -> TokenStream {
1506    let func_path = &input.func_path;
1507    let args = &input.args;
1508
1509    // Resolve tiers
1510    let tier_names: Vec<String> = match &input.tiers {
1511        Some((names, _)) => names.clone(),
1512        None => DEFAULT_TIER_NAMES.iter().map(|s| s.to_string()).collect(),
1513    };
1514    let last_segment_span = func_path
1515        .segments
1516        .last()
1517        .map(|s| s.ident.span())
1518        .unwrap_or_else(proc_macro2::Span::call_site);
1519    let error_span = input
1520        .tiers
1521        .as_ref()
1522        .map(|(_, span)| *span)
1523        .unwrap_or(last_segment_span);
1524
1525    let tiers = match resolve_tiers(&tier_names, error_span) {
1526        Ok(t) => t,
1527        Err(e) => return e.to_compile_error().into(),
1528    };
1529
1530    // Group tiers by architecture for cfg-guarded blocks
1531    // Within each arch, tiers are already sorted by priority (highest first)
1532    if let Some(token_expr) = &input.with_token {
1533        gen_incant_passthrough(func_path, args, token_expr, &tiers)
1534    } else {
1535        gen_incant_entry(func_path, args, &tiers)
1536    }
1537}
1538
1539/// Generate incant! passthrough mode (already have a token).
1540fn gen_incant_passthrough(
1541    func_path: &syn::Path,
1542    args: &[syn::Expr],
1543    token_expr: &syn::Expr,
1544    tiers: &[&TierDescriptor],
1545) -> TokenStream {
1546    let mut dispatch_arms = Vec::new();
1547
1548    // Group non-scalar tiers by (target_arch, cargo_feature) for nested cfg blocks
1549    let mut arch_groups: Vec<(Option<&str>, Option<&str>, Vec<&TierDescriptor>)> = Vec::new();
1550    for tier in tiers {
1551        if tier.name == "scalar" {
1552            continue; // Handle scalar separately at the end
1553        }
1554        let key = (tier.target_arch, tier.cargo_feature);
1555        if let Some(group) = arch_groups.iter_mut().find(|(a, f, _)| (*a, *f) == key) {
1556            group.2.push(tier);
1557        } else {
1558            arch_groups.push((tier.target_arch, tier.cargo_feature, vec![tier]));
1559        }
1560    }
1561
1562    for (target_arch, cargo_feature, group_tiers) in &arch_groups {
1563        let mut tier_checks = Vec::new();
1564        for tier in group_tiers {
1565            let fn_suffixed = suffix_path(func_path, tier.suffix);
1566            let as_method = format_ident!("{}", tier.as_method);
1567            tier_checks.push(quote! {
1568                if let Some(__t) = __incant_token.#as_method() {
1569                    break '__incant #fn_suffixed(__t, #(#args),*);
1570                }
1571            });
1572        }
1573
1574        let inner = quote! { #(#tier_checks)* };
1575
1576        let guarded = match (target_arch, cargo_feature) {
1577            (Some(arch), Some(feat)) => quote! {
1578                #[cfg(target_arch = #arch)]
1579                {
1580                    #[cfg(feature = #feat)]
1581                    { #inner }
1582                }
1583            },
1584            (Some(arch), None) => quote! {
1585                #[cfg(target_arch = #arch)]
1586                { #inner }
1587            },
1588            (None, Some(feat)) => quote! {
1589                #[cfg(feature = #feat)]
1590                { #inner }
1591            },
1592            (None, None) => inner,
1593        };
1594
1595        dispatch_arms.push(guarded);
1596    }
1597
1598    // Scalar fallback (always last)
1599    let fn_scalar = suffix_path(func_path, "scalar");
1600    let scalar_arm = if tiers.iter().any(|t| t.name == "scalar") {
1601        quote! {
1602            if let Some(__t) = __incant_token.as_scalar() {
1603                break '__incant #fn_scalar(__t, #(#args),*);
1604            }
1605            unreachable!("Token did not match any known variant")
1606        }
1607    } else {
1608        quote! { unreachable!("Token did not match any known variant") }
1609    };
1610
1611    let expanded = quote! {
1612        '__incant: {
1613            use archmage::IntoConcreteToken;
1614            let __incant_token = #token_expr;
1615            #(#dispatch_arms)*
1616            #scalar_arm
1617        }
1618    };
1619    expanded.into()
1620}
1621
1622/// Generate incant! entry point mode (summon tokens).
1623fn gen_incant_entry(
1624    func_path: &syn::Path,
1625    args: &[syn::Expr],
1626    tiers: &[&TierDescriptor],
1627) -> TokenStream {
1628    let mut dispatch_arms = Vec::new();
1629
1630    // Group non-scalar tiers by target_arch for cfg blocks.
1631    // Within each arch group, further split by cargo_feature.
1632    let mut arch_groups: Vec<(Option<&str>, Vec<&TierDescriptor>)> = Vec::new();
1633    for tier in tiers {
1634        if tier.name == "scalar" {
1635            continue;
1636        }
1637        if let Some(group) = arch_groups.iter_mut().find(|(a, _)| *a == tier.target_arch) {
1638            group.1.push(tier);
1639        } else {
1640            arch_groups.push((tier.target_arch, vec![tier]));
1641        }
1642    }
1643
1644    for (target_arch, group_tiers) in &arch_groups {
1645        let mut tier_checks = Vec::new();
1646        for tier in group_tiers {
1647            let fn_suffixed = suffix_path(func_path, tier.suffix);
1648            let token_path: syn::Path = syn::parse_str(tier.token_path).unwrap();
1649
1650            let check = quote! {
1651                if let Some(__t) = #token_path::summon() {
1652                    break '__incant #fn_suffixed(__t, #(#args),*);
1653                }
1654            };
1655
1656            if let Some(feat) = tier.cargo_feature {
1657                tier_checks.push(quote! {
1658                    #[cfg(feature = #feat)]
1659                    { #check }
1660                });
1661            } else {
1662                tier_checks.push(check);
1663            }
1664        }
1665
1666        let inner = quote! { #(#tier_checks)* };
1667
1668        if let Some(arch) = target_arch {
1669            dispatch_arms.push(quote! {
1670                #[cfg(target_arch = #arch)]
1671                { #inner }
1672            });
1673        } else {
1674            dispatch_arms.push(inner);
1675        }
1676    }
1677
1678    // Scalar fallback
1679    let fn_scalar = suffix_path(func_path, "scalar");
1680
1681    let expanded = quote! {
1682        '__incant: {
1683            use archmage::SimdToken;
1684            #(#dispatch_arms)*
1685            #fn_scalar(archmage::ScalarToken, #(#args),*)
1686        }
1687    };
1688    expanded.into()
1689}
1690
1691// =============================================================================
1692// Unit tests for token/trait recognition maps
1693// =============================================================================
1694
1695#[cfg(test)]
1696mod tests {
1697    use super::*;
1698
1699    use super::generated::{ALL_CONCRETE_TOKENS, ALL_TRAIT_NAMES};
1700
1701    #[test]
1702    fn every_concrete_token_is_in_token_to_features() {
1703        for &name in ALL_CONCRETE_TOKENS {
1704            assert!(
1705                token_to_features(name).is_some(),
1706                "Token `{}` exists in runtime crate but is NOT recognized by \
1707                 token_to_features() in the proc macro. Add it!",
1708                name
1709            );
1710        }
1711    }
1712
1713    #[test]
1714    fn every_trait_is_in_trait_to_features() {
1715        for &name in ALL_TRAIT_NAMES {
1716            assert!(
1717                trait_to_features(name).is_some(),
1718                "Trait `{}` exists in runtime crate but is NOT recognized by \
1719                 trait_to_features() in the proc macro. Add it!",
1720                name
1721            );
1722        }
1723    }
1724
1725    #[test]
1726    fn token_aliases_map_to_same_features() {
1727        // Desktop64 = X64V3Token
1728        assert_eq!(
1729            token_to_features("Desktop64"),
1730            token_to_features("X64V3Token"),
1731            "Desktop64 and X64V3Token should map to identical features"
1732        );
1733
1734        // Server64 = X64V4Token = Avx512Token
1735        assert_eq!(
1736            token_to_features("Server64"),
1737            token_to_features("X64V4Token"),
1738            "Server64 and X64V4Token should map to identical features"
1739        );
1740        assert_eq!(
1741            token_to_features("X64V4Token"),
1742            token_to_features("Avx512Token"),
1743            "X64V4Token and Avx512Token should map to identical features"
1744        );
1745
1746        // Arm64 = NeonToken
1747        assert_eq!(
1748            token_to_features("Arm64"),
1749            token_to_features("NeonToken"),
1750            "Arm64 and NeonToken should map to identical features"
1751        );
1752    }
1753
1754    #[test]
1755    fn trait_to_features_includes_tokens_as_bounds() {
1756        // Tier tokens should also work as trait bounds
1757        // (for `impl X64V3Token` patterns, even though Rust won't allow it,
1758        // the macro processes AST before type checking)
1759        let tier_tokens = [
1760            "X64V2Token",
1761            "X64CryptoToken",
1762            "X64V3Token",
1763            "Desktop64",
1764            "Avx2FmaToken",
1765            "X64V4Token",
1766            "Avx512Token",
1767            "Server64",
1768            "X64V4xToken",
1769            "Avx512Fp16Token",
1770            "NeonToken",
1771            "Arm64",
1772            "NeonAesToken",
1773            "NeonSha3Token",
1774            "NeonCrcToken",
1775            "Arm64V2Token",
1776            "Arm64V3Token",
1777        ];
1778
1779        for &name in &tier_tokens {
1780            assert!(
1781                trait_to_features(name).is_some(),
1782                "Tier token `{}` should also be recognized in trait_to_features() \
1783                 for use as a generic bound. Add it!",
1784                name
1785            );
1786        }
1787    }
1788
1789    #[test]
1790    fn trait_features_are_cumulative() {
1791        // HasX64V4 should include all HasX64V2 features plus more
1792        let v2_features = trait_to_features("HasX64V2").unwrap();
1793        let v4_features = trait_to_features("HasX64V4").unwrap();
1794
1795        for &f in v2_features {
1796            assert!(
1797                v4_features.contains(&f),
1798                "HasX64V4 should include v2 feature `{}` but doesn't",
1799                f
1800            );
1801        }
1802
1803        // v4 should have more features than v2
1804        assert!(
1805            v4_features.len() > v2_features.len(),
1806            "HasX64V4 should have more features than HasX64V2"
1807        );
1808    }
1809
1810    #[test]
1811    fn x64v3_trait_features_include_v2() {
1812        // X64V3Token as trait bound should include v2 features
1813        let v2 = trait_to_features("HasX64V2").unwrap();
1814        let v3 = trait_to_features("X64V3Token").unwrap();
1815
1816        for &f in v2 {
1817            assert!(
1818                v3.contains(&f),
1819                "X64V3Token trait features should include v2 feature `{}` but don't",
1820                f
1821            );
1822        }
1823    }
1824
1825    #[test]
1826    fn has_neon_aes_includes_neon() {
1827        let neon = trait_to_features("HasNeon").unwrap();
1828        let neon_aes = trait_to_features("HasNeonAes").unwrap();
1829
1830        for &f in neon {
1831            assert!(
1832                neon_aes.contains(&f),
1833                "HasNeonAes should include NEON feature `{}`",
1834                f
1835            );
1836        }
1837    }
1838
1839    #[test]
1840    fn no_removed_traits_are_recognized() {
1841        // These traits were removed in 0.3.0 and should NOT be recognized
1842        let removed = [
1843            "HasSse",
1844            "HasSse2",
1845            "HasSse41",
1846            "HasSse42",
1847            "HasAvx",
1848            "HasAvx2",
1849            "HasFma",
1850            "HasAvx512f",
1851            "HasAvx512bw",
1852            "HasAvx512vl",
1853            "HasAvx512vbmi2",
1854            "HasSve",
1855            "HasSve2",
1856        ];
1857
1858        for &name in &removed {
1859            assert!(
1860                trait_to_features(name).is_none(),
1861                "Removed trait `{}` should NOT be in trait_to_features(). \
1862                 It was removed in 0.3.0 — users should migrate to tier traits.",
1863                name
1864            );
1865        }
1866    }
1867
1868    #[test]
1869    fn no_nonexistent_tokens_are_recognized() {
1870        // These tokens don't exist and should NOT be recognized
1871        let fake = [
1872            "SveToken",
1873            "Sve2Token",
1874            "Avx512VnniToken",
1875            "X64V4ModernToken",
1876            "NeonFp16Token",
1877        ];
1878
1879        for &name in &fake {
1880            assert!(
1881                token_to_features(name).is_none(),
1882                "Non-existent token `{}` should NOT be in token_to_features()",
1883                name
1884            );
1885        }
1886    }
1887
1888    #[test]
1889    fn featureless_traits_are_not_in_registries() {
1890        // SimdToken and IntoConcreteToken should NOT be in any feature registry
1891        // because they don't map to CPU features
1892        for &name in FEATURELESS_TRAIT_NAMES {
1893            assert!(
1894                token_to_features(name).is_none(),
1895                "`{}` should NOT be in token_to_features() — it has no CPU features",
1896                name
1897            );
1898            assert!(
1899                trait_to_features(name).is_none(),
1900                "`{}` should NOT be in trait_to_features() — it has no CPU features",
1901                name
1902            );
1903        }
1904    }
1905
1906    #[test]
1907    fn find_featureless_trait_detects_simdtoken() {
1908        let names = vec!["SimdToken".to_string()];
1909        assert_eq!(find_featureless_trait(&names), Some("SimdToken"));
1910
1911        let names = vec!["IntoConcreteToken".to_string()];
1912        assert_eq!(find_featureless_trait(&names), Some("IntoConcreteToken"));
1913
1914        // Feature-bearing traits should NOT be detected
1915        let names = vec!["HasX64V2".to_string()];
1916        assert_eq!(find_featureless_trait(&names), None);
1917
1918        let names = vec!["HasNeon".to_string()];
1919        assert_eq!(find_featureless_trait(&names), None);
1920
1921        // Mixed: if SimdToken is among real traits, still detected
1922        let names = vec!["SimdToken".to_string(), "HasX64V2".to_string()];
1923        assert_eq!(find_featureless_trait(&names), Some("SimdToken"));
1924    }
1925
1926    #[test]
1927    fn arm64_v2_v3_traits_are_cumulative() {
1928        let v2_features = trait_to_features("HasArm64V2").unwrap();
1929        let v3_features = trait_to_features("HasArm64V3").unwrap();
1930
1931        for &f in v2_features {
1932            assert!(
1933                v3_features.contains(&f),
1934                "HasArm64V3 should include v2 feature `{}` but doesn't",
1935                f
1936            );
1937        }
1938
1939        assert!(
1940            v3_features.len() > v2_features.len(),
1941            "HasArm64V3 should have more features than HasArm64V2"
1942        );
1943    }
1944}