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, PatType, Signature, Token, Type, TypeParamBound,
10    parse::{Parse, ParseStream},
11    parse_macro_input, parse_quote, token,
12};
13
14/// A function parsed with the body left as an opaque TokenStream.
15///
16/// Only the signature is fully parsed into an AST — the body tokens are collected
17/// without building any AST nodes (no expressions, statements, or patterns parsed).
18/// This saves ~2ms per function invocation at 100 lines of code.
19#[derive(Clone)]
20struct LightFn {
21    attrs: Vec<Attribute>,
22    vis: syn::Visibility,
23    sig: Signature,
24    brace_token: token::Brace,
25    body: proc_macro2::TokenStream,
26}
27
28impl Parse for LightFn {
29    fn parse(input: ParseStream) -> syn::Result<Self> {
30        let attrs = input.call(Attribute::parse_outer)?;
31        let vis: syn::Visibility = input.parse()?;
32        let sig: Signature = input.parse()?;
33        let content;
34        let brace_token = syn::braced!(content in input);
35        let body: proc_macro2::TokenStream = content.parse()?;
36        Ok(LightFn {
37            attrs,
38            vis,
39            sig,
40            brace_token,
41            body,
42        })
43    }
44}
45
46impl ToTokens for LightFn {
47    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
48        for attr in &self.attrs {
49            attr.to_tokens(tokens);
50        }
51        self.vis.to_tokens(tokens);
52        self.sig.to_tokens(tokens);
53        self.brace_token.surround(tokens, |tokens| {
54            self.body.to_tokens(tokens);
55        });
56    }
57}
58
59/// Replace all `Self` identifier tokens with a concrete type in a token stream.
60///
61/// Recurses into groups (braces, parens, brackets). Used for `#[arcane(_self = Type)]`
62/// to replace `Self` in both the return type and body without needing to parse the body.
63fn replace_self_in_tokens(
64    tokens: proc_macro2::TokenStream,
65    replacement: &Type,
66) -> proc_macro2::TokenStream {
67    let mut result = proc_macro2::TokenStream::new();
68    for tt in tokens {
69        match tt {
70            proc_macro2::TokenTree::Ident(ref ident) if ident == "Self" => {
71                result.extend(replacement.to_token_stream());
72            }
73            proc_macro2::TokenTree::Group(group) => {
74                let new_stream = replace_self_in_tokens(group.stream(), replacement);
75                let mut new_group = proc_macro2::Group::new(group.delimiter(), new_stream);
76                new_group.set_span(group.span());
77                result.extend(std::iter::once(proc_macro2::TokenTree::Group(new_group)));
78            }
79            other => {
80                result.extend(std::iter::once(other));
81            }
82        }
83    }
84    result
85}
86
87/// Arguments to the `#[arcane]` macro.
88#[derive(Default)]
89struct ArcaneArgs {
90    /// Use `#[inline(always)]` instead of `#[inline]` for the inner function.
91    /// Requires nightly Rust with `#![feature(target_feature_inline_always)]`.
92    inline_always: bool,
93    /// The concrete type to use for `self` receiver.
94    /// When specified, `self`/`&self`/`&mut self` is transformed to `_self: Type`/`&Type`/`&mut Type`.
95    /// Implies `nested = true`.
96    self_type: Option<Type>,
97    /// Generate an `unreachable!()` stub on the wrong architecture.
98    /// Default is false (cfg-out: no function emitted on wrong arch).
99    stub: bool,
100    /// Use nested inner function instead of sibling function.
101    /// Implied by `_self = Type`. Required for associated functions in impl blocks
102    /// that have no `self` receiver (the macro can't distinguish them from free functions).
103    nested: bool,
104    /// Inject `use archmage::intrinsics::{arch}::*;` (includes safe memory ops).
105    import_intrinsics: bool,
106    /// Inject `use magetypes::simd::{ns}::*;`, `use magetypes::simd::generic::*;`,
107    /// and `use magetypes::simd::backends::*;`.
108    import_magetypes: bool,
109}
110
111impl Parse for ArcaneArgs {
112    fn parse(input: ParseStream) -> syn::Result<Self> {
113        let mut args = ArcaneArgs::default();
114
115        while !input.is_empty() {
116            let ident: Ident = input.parse()?;
117            match ident.to_string().as_str() {
118                "inline_always" => args.inline_always = true,
119                "stub" => args.stub = true,
120                "nested" => args.nested = true,
121                "import_intrinsics" => args.import_intrinsics = true,
122                "import_magetypes" => args.import_magetypes = true,
123                "_self" => {
124                    let _: Token![=] = input.parse()?;
125                    args.self_type = Some(input.parse()?);
126                }
127                other => {
128                    return Err(syn::Error::new(
129                        ident.span(),
130                        format!("unknown arcane argument: `{}`", other),
131                    ));
132                }
133            }
134            // Consume optional comma
135            if input.peek(Token![,]) {
136                let _: Token![,] = input.parse()?;
137            }
138        }
139
140        // _self = Type implies nested (inner fn needed for Self replacement)
141        if args.self_type.is_some() {
142            args.nested = true;
143        }
144
145        Ok(args)
146    }
147}
148
149// Token-to-features and trait-to-features mappings are generated from
150// token-registry.toml by xtask. Regenerate with: cargo run -p xtask -- generate
151mod generated;
152use generated::{
153    token_to_arch, token_to_features, token_to_magetypes_namespace, trait_to_arch,
154    trait_to_features, trait_to_magetypes_namespace,
155};
156
157/// Result of extracting token info from a type.
158enum TokenTypeInfo {
159    /// Concrete token type (e.g., `Avx2Token`)
160    Concrete(String),
161    /// impl Trait with the trait names (e.g., `impl HasX64V2`)
162    ImplTrait(Vec<String>),
163    /// Generic type parameter name (e.g., `T`)
164    Generic(String),
165}
166
167/// Extract token type information from a type.
168fn extract_token_type_info(ty: &Type) -> Option<TokenTypeInfo> {
169    match ty {
170        Type::Path(type_path) => {
171            // Get the last segment of the path (e.g., "Avx2Token" from "archmage::Avx2Token")
172            type_path.path.segments.last().map(|seg| {
173                let name = seg.ident.to_string();
174                // Check if it's a known concrete token type
175                if token_to_features(&name).is_some() {
176                    TokenTypeInfo::Concrete(name)
177                } else {
178                    // Might be a generic type parameter like `T`
179                    TokenTypeInfo::Generic(name)
180                }
181            })
182        }
183        Type::Reference(type_ref) => {
184            // Handle &Token or &mut Token
185            extract_token_type_info(&type_ref.elem)
186        }
187        Type::ImplTrait(impl_trait) => {
188            // Handle `impl HasX64V2` or `impl HasX64V2 + HasNeon`
189            let traits: Vec<String> = extract_trait_names_from_bounds(&impl_trait.bounds);
190            if traits.is_empty() {
191                None
192            } else {
193                Some(TokenTypeInfo::ImplTrait(traits))
194            }
195        }
196        _ => None,
197    }
198}
199
200/// Extract trait names from type param bounds.
201fn extract_trait_names_from_bounds(
202    bounds: &syn::punctuated::Punctuated<TypeParamBound, Token![+]>,
203) -> Vec<String> {
204    bounds
205        .iter()
206        .filter_map(|bound| {
207            if let TypeParamBound::Trait(trait_bound) = bound {
208                trait_bound
209                    .path
210                    .segments
211                    .last()
212                    .map(|seg| seg.ident.to_string())
213            } else {
214                None
215            }
216        })
217        .collect()
218}
219
220/// Look up a generic type parameter in the function's generics.
221fn find_generic_bounds(sig: &Signature, type_name: &str) -> Option<Vec<String>> {
222    // Check inline bounds first (e.g., `fn foo<T: HasX64V2>(token: T)`)
223    for param in &sig.generics.params {
224        if let GenericParam::Type(type_param) = param
225            && type_param.ident == type_name
226        {
227            let traits = extract_trait_names_from_bounds(&type_param.bounds);
228            if !traits.is_empty() {
229                return Some(traits);
230            }
231        }
232    }
233
234    // Check where clause (e.g., `fn foo<T>(token: T) where T: HasX64V2`)
235    if let Some(where_clause) = &sig.generics.where_clause {
236        for predicate in &where_clause.predicates {
237            if let syn::WherePredicate::Type(pred_type) = predicate
238                && let Type::Path(type_path) = &pred_type.bounded_ty
239                && let Some(seg) = type_path.path.segments.last()
240                && seg.ident == type_name
241            {
242                let traits = extract_trait_names_from_bounds(&pred_type.bounds);
243                if !traits.is_empty() {
244                    return Some(traits);
245                }
246            }
247        }
248    }
249
250    None
251}
252
253/// Convert trait names to features, collecting all features from all traits.
254fn traits_to_features(trait_names: &[String]) -> Option<Vec<&'static str>> {
255    let mut all_features = Vec::new();
256
257    for trait_name in trait_names {
258        if let Some(features) = trait_to_features(trait_name) {
259            for &feature in features {
260                if !all_features.contains(&feature) {
261                    all_features.push(feature);
262                }
263            }
264        }
265    }
266
267    if all_features.is_empty() {
268        None
269    } else {
270        Some(all_features)
271    }
272}
273
274/// Trait names that don't map to any CPU features. These are valid in the type
275/// system but cannot be used as token bounds in `#[arcane]`/`#[rite]` because
276/// the macros need concrete features to generate `#[target_feature]` attributes.
277const FEATURELESS_TRAIT_NAMES: &[&str] = &["SimdToken", "IntoConcreteToken"];
278
279/// Check if any trait names are featureless (no CPU feature mapping).
280/// Returns the first featureless trait name found.
281fn find_featureless_trait(trait_names: &[String]) -> Option<&'static str> {
282    for name in trait_names {
283        for &featureless in FEATURELESS_TRAIT_NAMES {
284            if name == featureless {
285                return Some(featureless);
286            }
287        }
288    }
289    None
290}
291
292/// Diagnose why `find_token_param` failed. Returns the name of a featureless
293/// trait if the signature has a parameter bounded by one (e.g., `SimdToken`).
294fn diagnose_featureless_token(sig: &Signature) -> Option<&'static str> {
295    for arg in &sig.inputs {
296        if let FnArg::Typed(PatType { ty, .. }) = arg
297            && let Some(info) = extract_token_type_info(ty)
298        {
299            match &info {
300                TokenTypeInfo::ImplTrait(names) => {
301                    if let Some(name) = find_featureless_trait(names) {
302                        return Some(name);
303                    }
304                }
305                TokenTypeInfo::Generic(type_name) => {
306                    // Check if the type name itself is a featureless trait
307                    // (e.g., `token: SimdToken` used as a bare path)
308                    let as_vec = vec![type_name.clone()];
309                    if let Some(name) = find_featureless_trait(&as_vec) {
310                        return Some(name);
311                    }
312                    // Check generic bounds (e.g., `T: SimdToken`)
313                    if let Some(bounds) = find_generic_bounds(sig, type_name)
314                        && let Some(name) = find_featureless_trait(&bounds)
315                    {
316                        return Some(name);
317                    }
318                }
319                TokenTypeInfo::Concrete(_) => {}
320            }
321        }
322    }
323    None
324}
325
326/// Result of finding a token parameter in a function signature.
327struct TokenParamInfo {
328    /// The parameter identifier (e.g., `token`)
329    ident: Ident,
330    /// Target features to enable (e.g., `["avx2", "fma"]`)
331    features: Vec<&'static str>,
332    /// Target architecture (Some for concrete tokens, None for traits/generics)
333    target_arch: Option<&'static str>,
334    /// Concrete token type name (Some for concrete tokens, None for traits/generics)
335    token_type_name: Option<String>,
336    /// Magetypes width namespace (e.g., "v3", "neon", "wasm128")
337    magetypes_namespace: Option<&'static str>,
338}
339
340/// Resolve magetypes namespace from a list of trait names.
341/// Returns the first matching namespace found.
342fn traits_to_magetypes_namespace(trait_names: &[String]) -> Option<&'static str> {
343    for name in trait_names {
344        if let Some(ns) = trait_to_magetypes_namespace(name) {
345            return Some(ns);
346        }
347    }
348    None
349}
350
351/// Given trait bound names, return the first matching target architecture.
352fn traits_to_arch(trait_names: &[String]) -> Option<&'static str> {
353    for name in trait_names {
354        if let Some(arch) = trait_to_arch(name) {
355            return Some(arch);
356        }
357    }
358    None
359}
360
361/// Find the first token parameter in a function signature.
362fn find_token_param(sig: &Signature) -> Option<TokenParamInfo> {
363    for arg in &sig.inputs {
364        match arg {
365            FnArg::Receiver(_) => {
366                // Self receivers (self, &self, &mut self) are not yet supported.
367                // The macro creates an inner function, and Rust's inner functions
368                // cannot have `self` parameters. Supporting this would require
369                // AST rewriting to replace `self` with a regular parameter.
370                // See the module docs for the workaround.
371                continue;
372            }
373            FnArg::Typed(PatType { pat, ty, .. }) => {
374                if let Some(info) = extract_token_type_info(ty) {
375                    let (features, arch, token_name, mage_ns) = match info {
376                        TokenTypeInfo::Concrete(ref name) => {
377                            let features = token_to_features(name).map(|f| f.to_vec());
378                            let arch = token_to_arch(name);
379                            let ns = token_to_magetypes_namespace(name);
380                            (features, arch, Some(name.clone()), ns)
381                        }
382                        TokenTypeInfo::ImplTrait(ref trait_names) => {
383                            let ns = traits_to_magetypes_namespace(trait_names);
384                            let arch = traits_to_arch(trait_names);
385                            (traits_to_features(trait_names), arch, None, ns)
386                        }
387                        TokenTypeInfo::Generic(type_name) => {
388                            // Look up the generic parameter's bounds
389                            let bounds = find_generic_bounds(sig, &type_name);
390                            let features = bounds.as_ref().and_then(|t| traits_to_features(t));
391                            let ns = bounds
392                                .as_ref()
393                                .and_then(|t| traits_to_magetypes_namespace(t));
394                            let arch = bounds.as_ref().and_then(|t| traits_to_arch(t));
395                            (features, arch, None, ns)
396                        }
397                    };
398
399                    if let Some(features) = features {
400                        // Extract parameter name (or synthesize one for wildcard `_`)
401                        let ident = match pat.as_ref() {
402                            syn::Pat::Ident(pat_ident) => Some(pat_ident.ident.clone()),
403                            syn::Pat::Wild(w) => {
404                                Some(Ident::new("__archmage_token", w.underscore_token.span))
405                            }
406                            _ => None,
407                        };
408                        if let Some(ident) = ident {
409                            return Some(TokenParamInfo {
410                                ident,
411                                features,
412                                target_arch: arch,
413                                token_type_name: token_name,
414                                magetypes_namespace: mage_ns,
415                            });
416                        }
417                    }
418                }
419            }
420        }
421    }
422    None
423}
424
425/// Represents the kind of self receiver and the transformed parameter.
426enum SelfReceiver {
427    /// `self` (by value/move)
428    Owned,
429    /// `&self` (shared reference)
430    Ref,
431    /// `&mut self` (mutable reference)
432    RefMut,
433}
434
435/// Generate import statements to prepend to a function body.
436///
437/// Returns a `TokenStream` of `use` statements based on the import flags,
438/// target architecture, and magetypes namespace.
439fn generate_imports(
440    target_arch: Option<&str>,
441    magetypes_namespace: Option<&str>,
442    import_intrinsics: bool,
443    import_magetypes: bool,
444) -> proc_macro2::TokenStream {
445    let mut imports = proc_macro2::TokenStream::new();
446
447    if import_intrinsics && let Some(arch) = target_arch {
448        let arch_ident = format_ident!("{}", arch);
449        imports.extend(quote! {
450            #[allow(unused_imports)]
451            use archmage::intrinsics::#arch_ident::*;
452        });
453        // ScalarToken or unknown arch: import_intrinsics is a no-op
454    }
455
456    if import_magetypes && let Some(ns) = magetypes_namespace {
457        let ns_ident = format_ident!("{}", ns);
458        imports.extend(quote! {
459            #[allow(unused_imports)]
460            use magetypes::simd::#ns_ident::*;
461            #[allow(unused_imports)]
462            use magetypes::simd::backends::*;
463        });
464    }
465
466    imports
467}
468
469/// Shared implementation for arcane/arcane macros.
470fn arcane_impl(mut input_fn: LightFn, macro_name: &str, args: ArcaneArgs) -> TokenStream {
471    // Check for self receiver
472    let has_self_receiver = input_fn
473        .sig
474        .inputs
475        .first()
476        .map(|arg| matches!(arg, FnArg::Receiver(_)))
477        .unwrap_or(false);
478
479    // Nested mode is required when _self = Type is used (for Self replacement in nested fn).
480    // In sibling mode, self/Self work naturally since both fns live in the same impl scope.
481    // However, if there's a self receiver in nested mode, we still need _self = Type.
482    if has_self_receiver && args.nested && args.self_type.is_none() {
483        let msg = format!(
484            "{} with self receiver in nested mode requires `_self = Type` argument.\n\
485             Example: #[{}(nested, _self = MyType)]\n\
486             Use `_self` (not `self`) in the function body to refer to self.\n\
487             \n\
488             Alternatively, remove `nested` to use sibling expansion (default), \
489             which handles self/Self naturally.",
490            macro_name, macro_name
491        );
492        return syn::Error::new_spanned(&input_fn.sig, msg)
493            .to_compile_error()
494            .into();
495    }
496
497    // Find the token parameter, its features, target arch, and token type name
498    let TokenParamInfo {
499        ident: _token_ident,
500        features,
501        target_arch,
502        token_type_name,
503        magetypes_namespace,
504    } = match find_token_param(&input_fn.sig) {
505        Some(result) => result,
506        None => {
507            // Check for specific misuse: featureless traits like SimdToken
508            if let Some(trait_name) = diagnose_featureless_token(&input_fn.sig) {
509                let msg = format!(
510                    "`{trait_name}` cannot be used as a token bound in #[{macro_name}] \
511                     because it doesn't specify any CPU features.\n\
512                     \n\
513                     #[{macro_name}] needs concrete features to generate #[target_feature]. \
514                     Use a concrete token or a feature trait:\n\
515                     \n\
516                     Concrete tokens: X64V3Token, Desktop64, NeonToken, Arm64V2Token, ...\n\
517                     Feature traits:  impl HasX64V2, impl HasNeon, impl HasArm64V3, ..."
518                );
519                return syn::Error::new_spanned(&input_fn.sig, msg)
520                    .to_compile_error()
521                    .into();
522            }
523            let msg = format!(
524                "{} requires a token parameter. Supported forms:\n\
525                 - Concrete: `token: X64V3Token`\n\
526                 - impl Trait: `token: impl HasX64V2`\n\
527                 - Generic: `fn foo<T: HasX64V2>(token: T, ...)`\n\
528                 - With self: `#[{}(_self = Type)] fn method(&self, token: impl HasNeon, ...)`",
529                macro_name, macro_name
530            );
531            return syn::Error::new_spanned(&input_fn.sig, msg)
532                .to_compile_error()
533                .into();
534        }
535    };
536
537    // Prepend import statements to body if requested
538    let body_imports = generate_imports(
539        target_arch,
540        magetypes_namespace,
541        args.import_intrinsics,
542        args.import_magetypes,
543    );
544    if !body_imports.is_empty() {
545        let original_body = &input_fn.body;
546        input_fn.body = quote! {
547            #body_imports
548            #original_body
549        };
550    }
551
552    // Build target_feature attributes
553    let target_feature_attrs: Vec<Attribute> = features
554        .iter()
555        .map(|feature| parse_quote!(#[target_feature(enable = #feature)]))
556        .collect();
557
558    // Rename wildcard patterns (`_: Type`) to named params so the inner/sibling call works
559    let mut wild_rename_counter = 0u32;
560    for arg in &mut input_fn.sig.inputs {
561        if let FnArg::Typed(pat_type) = arg
562            && matches!(pat_type.pat.as_ref(), syn::Pat::Wild(_))
563        {
564            let ident = format_ident!("__archmage_wild_{}", wild_rename_counter);
565            wild_rename_counter += 1;
566            *pat_type.pat = syn::Pat::Ident(syn::PatIdent {
567                attrs: vec![],
568                by_ref: None,
569                mutability: None,
570                ident,
571                subpat: None,
572            });
573        }
574    }
575
576    // Choose inline attribute based on args
577    let inline_attr: Attribute = if args.inline_always {
578        parse_quote!(#[inline(always)])
579    } else {
580        parse_quote!(#[inline])
581    };
582
583    // On wasm32, #[target_feature(enable = "simd128")] functions are safe (Rust 1.54+).
584    // The wasm validation model guarantees unsupported instructions trap deterministically,
585    // so there's no UB from feature mismatch. Skip the unsafe wrapper entirely.
586    if target_arch == Some("wasm32") {
587        return arcane_impl_wasm_safe(
588            input_fn,
589            &args,
590            token_type_name,
591            target_feature_attrs,
592            inline_attr,
593        );
594    }
595
596    if args.nested {
597        arcane_impl_nested(
598            input_fn,
599            &args,
600            target_arch,
601            token_type_name,
602            target_feature_attrs,
603            inline_attr,
604        )
605    } else {
606        arcane_impl_sibling(
607            input_fn,
608            &args,
609            target_arch,
610            token_type_name,
611            target_feature_attrs,
612            inline_attr,
613        )
614    }
615}
616
617/// WASM-safe expansion: emits rite-style output (no unsafe wrapper).
618///
619/// On wasm32, `#[target_feature(enable = "simd128")]` is safe — the wasm validation
620/// model traps deterministically on unsupported instructions, so there's no UB.
621/// We emit the function directly with `#[target_feature]` + `#[inline]`, like `#[rite]`.
622///
623/// If `_self = Type` is set, we inject `let _self = self;` at the top of the body
624/// (the function stays in impl scope, so `Self` resolves naturally — no replacement needed).
625fn arcane_impl_wasm_safe(
626    input_fn: LightFn,
627    args: &ArcaneArgs,
628    token_type_name: Option<String>,
629    target_feature_attrs: Vec<Attribute>,
630    inline_attr: Attribute,
631) -> TokenStream {
632    let vis = &input_fn.vis;
633    let sig = &input_fn.sig;
634    let fn_name = &sig.ident;
635    let attrs = &input_fn.attrs;
636
637    let token_type_str = token_type_name.as_deref().unwrap_or("UnknownToken");
638
639    // If _self = Type is set, inject `let _self = self;` at top of body so user code
640    // referencing `_self` works. The function remains in impl scope, so `Self` resolves
641    // naturally — no Self replacement needed (unlike nested mode's inner fn).
642    let body = if args.self_type.is_some() {
643        let original_body = &input_fn.body;
644        quote! {
645            let _self = self;
646            #original_body
647        }
648    } else {
649        input_fn.body.clone()
650    };
651
652    // Prepend target_feature + inline attrs
653    let mut new_attrs = target_feature_attrs;
654    new_attrs.push(inline_attr);
655    for attr in attrs {
656        new_attrs.push(attr.clone());
657    }
658
659    let stub = if args.stub {
660        // Build stub args for suppressing unused-variable warnings
661        let stub_args: Vec<proc_macro2::TokenStream> = sig
662            .inputs
663            .iter()
664            .filter_map(|arg| match arg {
665                FnArg::Typed(pat_type) => {
666                    if let syn::Pat::Ident(pat_ident) = pat_type.pat.as_ref() {
667                        let ident = &pat_ident.ident;
668                        Some(quote!(#ident))
669                    } else {
670                        None
671                    }
672                }
673                FnArg::Receiver(_) => None,
674            })
675            .collect();
676
677        quote! {
678            #[cfg(not(target_arch = "wasm32"))]
679            #vis #sig {
680                let _ = (#(#stub_args),*);
681                unreachable!(
682                    "BUG: {}() was called but requires {} (target_arch = \"wasm32\"). \
683                     {}::summon() returns None on this architecture, so this function \
684                     is unreachable in safe code. If you used forge_token_dangerously(), \
685                     that is the bug.",
686                    stringify!(#fn_name),
687                    #token_type_str,
688                    #token_type_str,
689                )
690            }
691        }
692    } else {
693        quote! {}
694    };
695
696    let expanded = quote! {
697        #[cfg(target_arch = "wasm32")]
698        #(#new_attrs)*
699        #vis #sig {
700            #body
701        }
702
703        #stub
704    };
705
706    expanded.into()
707}
708
709/// Sibling expansion (default): generates two functions at the same scope level.
710///
711/// ```ignore
712/// // #[arcane] fn process(token: X64V3Token, data: &[f32; 8]) -> [f32; 8] { body }
713/// // expands to:
714/// #[cfg(target_arch = "x86_64")]
715/// #[doc(hidden)]
716/// #[target_feature(enable = "avx2,fma,...")]
717/// #[inline]
718/// fn __arcane_process(token: X64V3Token, data: &[f32; 8]) -> [f32; 8] { body }
719///
720/// #[cfg(target_arch = "x86_64")]
721/// fn process(token: X64V3Token, data: &[f32; 8]) -> [f32; 8] {
722///     unsafe { __arcane_process(token, data) }
723/// }
724/// ```
725///
726/// The sibling function is safe (Rust 2024 edition allows safe `#[target_feature]`
727/// functions). Only the call from the wrapper needs `unsafe` because the wrapper
728/// lacks matching target features. Compatible with `#![forbid(unsafe_code)]`.
729///
730/// Self/self work naturally since both functions live in the same impl scope.
731fn arcane_impl_sibling(
732    input_fn: LightFn,
733    args: &ArcaneArgs,
734    target_arch: Option<&str>,
735    token_type_name: Option<String>,
736    target_feature_attrs: Vec<Attribute>,
737    inline_attr: Attribute,
738) -> TokenStream {
739    let vis = &input_fn.vis;
740    let sig = &input_fn.sig;
741    let fn_name = &sig.ident;
742    let generics = &sig.generics;
743    let where_clause = &generics.where_clause;
744    let inputs = &sig.inputs;
745    let output = &sig.output;
746    let body = &input_fn.body;
747    let attrs = &input_fn.attrs;
748
749    let sibling_name = format_ident!("__arcane_{}", fn_name);
750
751    // Detect self receiver
752    let has_self_receiver = inputs
753        .first()
754        .map(|arg| matches!(arg, FnArg::Receiver(_)))
755        .unwrap_or(false);
756
757    // Build sibling signature: same as original but with sibling name, #[doc(hidden)]
758    // NOT unsafe — Rust 2024 edition allows safe #[target_feature] functions.
759    // Only the call from non-matching context (the wrapper) needs unsafe.
760    let sibling_sig_inputs = inputs;
761
762    // Build the call from wrapper to sibling
763    let sibling_call = if has_self_receiver {
764        // Method: self.__arcane_fn(other_args...)
765        let other_args: Vec<proc_macro2::TokenStream> = inputs
766            .iter()
767            .skip(1) // skip self receiver
768            .filter_map(|arg| {
769                if let FnArg::Typed(pat_type) = arg
770                    && let syn::Pat::Ident(pat_ident) = pat_type.pat.as_ref()
771                {
772                    let ident = &pat_ident.ident;
773                    Some(quote!(#ident))
774                } else {
775                    None
776                }
777            })
778            .collect();
779        quote! { self.#sibling_name(#(#other_args),*) }
780    } else {
781        // Free function: __arcane_fn(all_args...)
782        let all_args: Vec<proc_macro2::TokenStream> = inputs
783            .iter()
784            .filter_map(|arg| {
785                if let FnArg::Typed(pat_type) = arg
786                    && let syn::Pat::Ident(pat_ident) = pat_type.pat.as_ref()
787                {
788                    let ident = &pat_ident.ident;
789                    Some(quote!(#ident))
790                } else {
791                    None
792                }
793            })
794            .collect();
795        quote! { #sibling_name(#(#all_args),*) }
796    };
797
798    // Build stub args for suppressing unused warnings
799    let stub_args: Vec<proc_macro2::TokenStream> = inputs
800        .iter()
801        .filter_map(|arg| match arg {
802            FnArg::Typed(pat_type) => {
803                if let syn::Pat::Ident(pat_ident) = pat_type.pat.as_ref() {
804                    let ident = &pat_ident.ident;
805                    Some(quote!(#ident))
806                } else {
807                    None
808                }
809            }
810            FnArg::Receiver(_) => None, // self doesn't need _ = suppression
811        })
812        .collect();
813
814    let token_type_str = token_type_name.as_deref().unwrap_or("UnknownToken");
815
816    let expanded = if let Some(arch) = target_arch {
817        // Sibling function: #[doc(hidden)] #[target_feature] pub(?) fn __arcane_fn(...)
818        // Safe declaration — Rust 2024 allows safe #[target_feature] functions.
819        let sibling_fn = quote! {
820            #[cfg(target_arch = #arch)]
821            #[doc(hidden)]
822            #(#target_feature_attrs)*
823            #inline_attr
824            #vis fn #sibling_name #generics (#sibling_sig_inputs) #output #where_clause {
825                #body
826            }
827        };
828
829        // Wrapper function: fn original_name(...) { unsafe { sibling_call } }
830        // The unsafe block is needed because the sibling has #[target_feature] and
831        // the wrapper doesn't — calling across this boundary requires unsafe.
832        let wrapper_fn = quote! {
833            #[cfg(target_arch = #arch)]
834            #(#attrs)*
835            #vis #sig {
836                // SAFETY: The token parameter proves the required CPU features are available.
837                // Calling a #[target_feature] function from a non-matching context requires
838                // unsafe because the CPU may not support those instructions. The token's
839                // existence proves summon() succeeded, so the features are available.
840                unsafe { #sibling_call }
841            }
842        };
843
844        // Optional stub for other architectures
845        let stub = if args.stub {
846            quote! {
847                #[cfg(not(target_arch = #arch))]
848                #(#attrs)*
849                #vis #sig {
850                    let _ = (#(#stub_args),*);
851                    unreachable!(
852                        "BUG: {}() was called but requires {} (target_arch = \"{}\"). \
853                         {}::summon() returns None on this architecture, so this function \
854                         is unreachable in safe code. If you used forge_token_dangerously(), \
855                         that is the bug.",
856                        stringify!(#fn_name),
857                        #token_type_str,
858                        #arch,
859                        #token_type_str,
860                    )
861                }
862            }
863        } else {
864            quote! {}
865        };
866
867        quote! {
868            #sibling_fn
869            #wrapper_fn
870            #stub
871        }
872    } else {
873        // No specific arch (trait bounds or generic) - no cfg guards, no stub needed.
874        // Still use sibling pattern for consistency.
875        let sibling_fn = quote! {
876            #[doc(hidden)]
877            #(#target_feature_attrs)*
878            #inline_attr
879            #vis fn #sibling_name #generics (#sibling_sig_inputs) #output #where_clause {
880                #body
881            }
882        };
883
884        let wrapper_fn = quote! {
885            #(#attrs)*
886            #vis #sig {
887                // SAFETY: The token proves the required CPU features are available.
888                unsafe { #sibling_call }
889            }
890        };
891
892        quote! {
893            #sibling_fn
894            #wrapper_fn
895        }
896    };
897
898    expanded.into()
899}
900
901/// Nested inner function expansion (opt-in via `nested` or `_self = Type`).
902///
903/// This is the original approach: generates a nested inner function inside the
904/// original function. Required when `_self = Type` is used because Self must be
905/// replaced in the nested function (where it's not in scope).
906fn arcane_impl_nested(
907    input_fn: LightFn,
908    args: &ArcaneArgs,
909    target_arch: Option<&str>,
910    token_type_name: Option<String>,
911    target_feature_attrs: Vec<Attribute>,
912    inline_attr: Attribute,
913) -> TokenStream {
914    let vis = &input_fn.vis;
915    let sig = &input_fn.sig;
916    let fn_name = &sig.ident;
917    let generics = &sig.generics;
918    let where_clause = &generics.where_clause;
919    let inputs = &sig.inputs;
920    let output = &sig.output;
921    let body = &input_fn.body;
922    let attrs = &input_fn.attrs;
923
924    // Determine self receiver type if present
925    let self_receiver_kind: Option<SelfReceiver> = inputs.first().and_then(|arg| match arg {
926        FnArg::Receiver(receiver) => {
927            if receiver.reference.is_none() {
928                Some(SelfReceiver::Owned)
929            } else if receiver.mutability.is_some() {
930                Some(SelfReceiver::RefMut)
931            } else {
932                Some(SelfReceiver::Ref)
933            }
934        }
935        _ => None,
936    });
937
938    // Build inner function parameters, transforming self if needed.
939    // Also replace Self in non-self parameter types when _self = Type is set,
940    // since the inner function is a nested fn where Self from the impl is not in scope.
941    let inner_params: Vec<proc_macro2::TokenStream> = inputs
942        .iter()
943        .map(|arg| match arg {
944            FnArg::Receiver(_) => {
945                // Transform self receiver to _self parameter
946                let self_ty = args.self_type.as_ref().unwrap();
947                match self_receiver_kind.as_ref().unwrap() {
948                    SelfReceiver::Owned => quote!(_self: #self_ty),
949                    SelfReceiver::Ref => quote!(_self: &#self_ty),
950                    SelfReceiver::RefMut => quote!(_self: &mut #self_ty),
951                }
952            }
953            FnArg::Typed(pat_type) => {
954                if let Some(ref self_ty) = args.self_type {
955                    replace_self_in_tokens(quote!(#pat_type), self_ty)
956                } else {
957                    quote!(#pat_type)
958                }
959            }
960        })
961        .collect();
962
963    // Build inner function call arguments
964    let inner_args: Vec<proc_macro2::TokenStream> = inputs
965        .iter()
966        .filter_map(|arg| match arg {
967            FnArg::Typed(pat_type) => {
968                if let syn::Pat::Ident(pat_ident) = pat_type.pat.as_ref() {
969                    let ident = &pat_ident.ident;
970                    Some(quote!(#ident))
971                } else {
972                    None
973                }
974            }
975            FnArg::Receiver(_) => Some(quote!(self)), // Pass self to inner as _self
976        })
977        .collect();
978
979    let inner_fn_name = format_ident!("__simd_inner_{}", fn_name);
980
981    // Transform output, body, and where clause to replace Self with concrete type if needed.
982    let (inner_output, inner_body, inner_where_clause): (
983        proc_macro2::TokenStream,
984        proc_macro2::TokenStream,
985        proc_macro2::TokenStream,
986    ) = if let Some(ref self_ty) = args.self_type {
987        let transformed_output = replace_self_in_tokens(output.to_token_stream(), self_ty);
988        let transformed_body = replace_self_in_tokens(body.clone(), self_ty);
989        let transformed_where = where_clause
990            .as_ref()
991            .map(|wc| replace_self_in_tokens(wc.to_token_stream(), self_ty))
992            .unwrap_or_default();
993        (transformed_output, transformed_body, transformed_where)
994    } else {
995        (
996            output.to_token_stream(),
997            body.clone(),
998            where_clause
999                .as_ref()
1000                .map(|wc| wc.to_token_stream())
1001                .unwrap_or_default(),
1002        )
1003    };
1004
1005    let token_type_str = token_type_name.as_deref().unwrap_or("UnknownToken");
1006    let expanded = if let Some(arch) = target_arch {
1007        let stub = if args.stub {
1008            quote! {
1009                // Stub for other architectures - the token cannot be obtained
1010                #[cfg(not(target_arch = #arch))]
1011                #(#attrs)*
1012                #vis #sig {
1013                    let _ = (#(#inner_args),*);
1014                    unreachable!(
1015                        "BUG: {}() was called but requires {} (target_arch = \"{}\"). \
1016                         {}::summon() returns None on this architecture, so this function \
1017                         is unreachable in safe code. If you used forge_token_dangerously(), \
1018                         that is the bug.",
1019                        stringify!(#fn_name),
1020                        #token_type_str,
1021                        #arch,
1022                        #token_type_str,
1023                    )
1024                }
1025            }
1026        } else {
1027            quote! {}
1028        };
1029
1030        quote! {
1031            // Real implementation for the correct architecture
1032            #[cfg(target_arch = #arch)]
1033            #(#attrs)*
1034            #vis #sig {
1035                #(#target_feature_attrs)*
1036                #inline_attr
1037                fn #inner_fn_name #generics (#(#inner_params),*) #inner_output #inner_where_clause {
1038                    #inner_body
1039                }
1040
1041                // SAFETY: The token parameter proves the required CPU features are available.
1042                unsafe { #inner_fn_name(#(#inner_args),*) }
1043            }
1044
1045            #stub
1046        }
1047    } else {
1048        // No specific arch (trait bounds or generic) - generate without cfg guards
1049        quote! {
1050            #(#attrs)*
1051            #vis #sig {
1052                #(#target_feature_attrs)*
1053                #inline_attr
1054                fn #inner_fn_name #generics (#(#inner_params),*) #inner_output #inner_where_clause {
1055                    #inner_body
1056                }
1057
1058                // SAFETY: The token proves the required CPU features are available.
1059                unsafe { #inner_fn_name(#(#inner_args),*) }
1060            }
1061        }
1062    };
1063
1064    expanded.into()
1065}
1066
1067/// Mark a function as an arcane SIMD function.
1068///
1069/// This macro generates a safe wrapper around a `#[target_feature]` function.
1070/// The token parameter type determines which CPU features are enabled.
1071///
1072/// # Expansion Modes
1073///
1074/// ## Sibling (default)
1075///
1076/// Generates two functions at the same scope: a safe `#[target_feature]` sibling
1077/// and a safe wrapper. `self`/`Self` work naturally since both functions share scope.
1078/// Compatible with `#![forbid(unsafe_code)]`.
1079///
1080/// ```ignore
1081/// #[arcane]
1082/// fn process(token: X64V3Token, data: &[f32; 8]) -> [f32; 8] { /* body */ }
1083/// // Expands to (x86_64 only):
1084/// #[cfg(target_arch = "x86_64")]
1085/// #[doc(hidden)]
1086/// #[target_feature(enable = "avx2,fma,...")]
1087/// fn __arcane_process(token: X64V3Token, data: &[f32; 8]) -> [f32; 8] { /* body */ }
1088///
1089/// #[cfg(target_arch = "x86_64")]
1090/// fn process(token: X64V3Token, data: &[f32; 8]) -> [f32; 8] {
1091///     unsafe { __arcane_process(token, data) }
1092/// }
1093/// ```
1094///
1095/// Methods work naturally:
1096///
1097/// ```ignore
1098/// impl MyType {
1099///     #[arcane]
1100///     fn compute(&self, token: X64V3Token) -> f32 {
1101///         self.data.iter().sum()  // self/Self just work!
1102///     }
1103/// }
1104/// ```
1105///
1106/// ## Nested (`nested` or `_self = Type`)
1107///
1108/// Generates a nested inner function inside the original. Required for trait impls
1109/// (where sibling functions would fail) and when `_self = Type` is used.
1110///
1111/// ```ignore
1112/// impl SimdOps for MyType {
1113///     #[arcane(_self = MyType)]
1114///     fn compute(&self, token: X64V3Token) -> Self {
1115///         // Use _self instead of self, Self replaced with MyType
1116///         _self.data.iter().sum()
1117///     }
1118/// }
1119/// ```
1120///
1121/// # Cross-Architecture Behavior
1122///
1123/// **Default (cfg-out):** On the wrong architecture, the function is not emitted
1124/// at all — no stub, no dead code. Code that references it must be cfg-gated.
1125///
1126/// **With `stub`:** Generates an `unreachable!()` stub on wrong architectures.
1127/// Use when cross-arch dispatch references the function without cfg guards.
1128///
1129/// ```ignore
1130/// #[arcane(stub)]  // generates stub on wrong arch
1131/// fn process_neon(token: NeonToken, data: &[f32]) -> f32 { ... }
1132/// ```
1133///
1134/// `incant!` is unaffected — it already cfg-gates dispatch calls by architecture.
1135///
1136/// # Token Parameter Forms
1137///
1138/// ```ignore
1139/// // Concrete token
1140/// #[arcane]
1141/// fn process(token: X64V3Token, data: &[f32; 8]) -> [f32; 8] { ... }
1142///
1143/// // impl Trait bound
1144/// #[arcane]
1145/// fn process(token: impl HasX64V2, data: &[f32; 8]) -> [f32; 8] { ... }
1146///
1147/// // Generic with inline or where-clause bounds
1148/// #[arcane]
1149/// fn process<T: HasX64V2>(token: T, data: &[f32; 8]) -> [f32; 8] { ... }
1150///
1151/// // Wildcard
1152/// #[arcane]
1153/// fn process(_: X64V3Token, data: &[f32; 8]) -> [f32; 8] { ... }
1154/// ```
1155///
1156/// # Options
1157///
1158/// | Option | Effect |
1159/// |--------|--------|
1160/// | `stub` | Generate `unreachable!()` stub on wrong architecture |
1161/// | `nested` | Use nested inner function instead of sibling |
1162/// | `_self = Type` | Implies `nested`, transforms self receiver, replaces Self |
1163/// | `inline_always` | Use `#[inline(always)]` (requires nightly) |
1164/// | `import_intrinsics` | Auto-import `archmage::intrinsics::{arch}::*` (includes safe memory ops) |
1165/// | `import_magetypes` | Auto-import `magetypes::simd::{ns}::*` and `magetypes::simd::backends::*` |
1166///
1167/// ## Auto-Imports
1168///
1169/// `import_intrinsics` and `import_magetypes` inject `use` statements into the
1170/// function body, eliminating boilerplate. The macro derives the architecture and
1171/// namespace from the token type:
1172///
1173/// ```ignore
1174/// // Without auto-imports — lots of boilerplate:
1175/// use std::arch::x86_64::*;
1176/// use magetypes::simd::v3::*;
1177///
1178/// #[arcane]
1179/// fn process(token: X64V3Token, data: &[f32; 8]) -> f32 {
1180///     let v = f32x8::load(token, data);
1181///     let zero = _mm256_setzero_ps();
1182///     // ...
1183/// }
1184///
1185/// // With auto-imports — clean:
1186/// #[arcane(import_intrinsics, import_magetypes)]
1187/// fn process(token: X64V3Token, data: &[f32; 8]) -> f32 {
1188///     let v = f32x8::load(token, data);
1189///     let zero = _mm256_setzero_ps();
1190///     // ...
1191/// }
1192/// ```
1193///
1194/// The namespace mapping is token-driven:
1195///
1196/// | Token | `import_intrinsics` | `import_magetypes` |
1197/// |-------|--------------------|--------------------|
1198/// | `X64V1..V3Token` | `archmage::intrinsics::x86_64::*` | `magetypes::simd::v3::*` |
1199/// | `X64V4Token` | `archmage::intrinsics::x86_64::*` | `magetypes::simd::v4::*` |
1200/// | `X64V4xToken` | `archmage::intrinsics::x86_64::*` | `magetypes::simd::v4x::*` |
1201/// | `NeonToken` / ARM | `archmage::intrinsics::aarch64::*` | `magetypes::simd::neon::*` |
1202/// | `Wasm128Token` | `archmage::intrinsics::wasm32::*` | `magetypes::simd::wasm128::*` |
1203///
1204/// Works with concrete tokens, `impl Trait` bounds, and generic parameters.
1205///
1206/// # Supported Tokens
1207///
1208/// - **x86_64**: `X64V2Token`, `X64V3Token`/`Desktop64`, `X64V4Token`/`Avx512Token`/`Server64`,
1209///   `X64V4xToken`, `Avx512Fp16Token`, `X64CryptoToken`, `X64V3CryptoToken`
1210/// - **ARM**: `NeonToken`/`Arm64`, `Arm64V2Token`, `Arm64V3Token`,
1211///   `NeonAesToken`, `NeonSha3Token`, `NeonCrcToken`
1212/// - **WASM**: `Wasm128Token`
1213///
1214/// # Supported Trait Bounds
1215///
1216/// `HasX64V2`, `HasX64V4`, `HasNeon`, `HasNeonAes`, `HasNeonSha3`, `HasArm64V2`, `HasArm64V3`
1217///
1218/// ```ignore
1219/// #![feature(target_feature_inline_always)]
1220///
1221/// #[arcane(inline_always)]
1222/// fn fast_kernel(token: Avx2Token, data: &mut [f32]) {
1223///     // Inner function will use #[inline(always)]
1224/// }
1225/// ```
1226#[proc_macro_attribute]
1227pub fn arcane(attr: TokenStream, item: TokenStream) -> TokenStream {
1228    let args = parse_macro_input!(attr as ArcaneArgs);
1229    let input_fn = parse_macro_input!(item as LightFn);
1230    arcane_impl(input_fn, "arcane", args)
1231}
1232
1233/// Legacy alias for [`arcane`].
1234///
1235/// **Deprecated:** Use `#[arcane]` instead. This alias exists only for migration.
1236#[proc_macro_attribute]
1237#[doc(hidden)]
1238pub fn simd_fn(attr: TokenStream, item: TokenStream) -> TokenStream {
1239    let args = parse_macro_input!(attr as ArcaneArgs);
1240    let input_fn = parse_macro_input!(item as LightFn);
1241    arcane_impl(input_fn, "simd_fn", args)
1242}
1243
1244/// Descriptive alias for [`arcane`].
1245///
1246/// Generates a safe wrapper around a `#[target_feature]` inner function.
1247/// The token type in your signature determines which CPU features are enabled.
1248/// Creates an LLVM optimization boundary — use [`token_target_features`]
1249/// (alias for [`rite`]) for inner helpers to avoid this.
1250///
1251/// Since Rust 1.85, value-based SIMD intrinsics are safe inside
1252/// `#[target_feature]` functions. This macro generates the `#[target_feature]`
1253/// wrapper so you never need to write `unsafe` for SIMD code.
1254///
1255/// See [`arcane`] for full documentation and examples.
1256#[proc_macro_attribute]
1257pub fn token_target_features_boundary(attr: TokenStream, item: TokenStream) -> TokenStream {
1258    let args = parse_macro_input!(attr as ArcaneArgs);
1259    let input_fn = parse_macro_input!(item as LightFn);
1260    arcane_impl(input_fn, "token_target_features_boundary", args)
1261}
1262
1263// ============================================================================
1264// Rite macro for inner SIMD functions (inlines into matching #[target_feature] callers)
1265// ============================================================================
1266
1267/// Annotate inner SIMD helpers called from `#[arcane]` functions.
1268///
1269/// Unlike `#[arcane]`, which creates an inner `#[target_feature]` function behind
1270/// a safe boundary, `#[rite]` adds `#[target_feature]` and `#[inline]` directly.
1271/// LLVM inlines it into any caller with matching features — no boundary crossing.
1272///
1273/// # When to Use
1274///
1275/// Use `#[rite]` for helper functions that are **only** called from within
1276/// `#[arcane]` functions with matching or superset token types:
1277///
1278/// ```ignore
1279/// use archmage::{arcane, rite, X64V3Token};
1280///
1281/// #[arcane]
1282/// fn outer(token: X64V3Token, data: &[f32; 8]) -> f32 {
1283///     // helper inlines — same target features, no boundary
1284///     helper(token, data) * 2.0
1285/// }
1286///
1287/// #[rite]
1288/// fn helper(token: X64V3Token, data: &[f32; 8]) -> f32 {
1289///     // Just has #[target_feature(enable = "avx2,fma,...")]
1290///     // Called from #[arcane] context, so features are guaranteed
1291///     let v = f32x8::from_array(token, *data);
1292///     v.reduce_add()
1293/// }
1294/// ```
1295///
1296/// # Safety
1297///
1298/// `#[rite]` functions can only be safely called from contexts where the
1299/// required CPU features are enabled:
1300/// - From within `#[arcane]` functions with matching/superset tokens
1301/// - From within other `#[rite]` functions with matching/superset tokens
1302/// - From code compiled with `-Ctarget-cpu` that enables the features
1303///
1304/// Calling from other contexts requires `unsafe` and the caller must ensure
1305/// the CPU supports the required features.
1306///
1307/// # Cross-Architecture Behavior
1308///
1309/// Like `#[arcane]`, defaults to cfg-out (no function on wrong arch).
1310/// Use `#[rite(stub)]` to generate an unreachable stub instead.
1311///
1312/// # Options
1313///
1314/// | Option | Effect |
1315/// |--------|--------|
1316/// | `stub` | Generate `unreachable!()` stub on wrong architecture |
1317/// | `import_intrinsics` | Auto-import `archmage::intrinsics::{arch}::*` (includes safe memory ops) |
1318/// | `import_magetypes` | Auto-import `magetypes::simd::{ns}::*` and `magetypes::simd::backends::*` |
1319///
1320/// See `#[arcane]` docs for the full namespace mapping table.
1321///
1322/// # Comparison with #[arcane]
1323///
1324/// | Aspect | `#[arcane]` | `#[rite]` |
1325/// |--------|-------------|-----------|
1326/// | Creates wrapper | Yes | No |
1327/// | Entry point | Yes | No |
1328/// | Inlines into caller | No (barrier) | Yes |
1329/// | Safe to call anywhere | Yes (with token) | Only from feature-enabled context |
1330/// | `stub` param | Yes | Yes |
1331/// | `import_intrinsics` | Yes | Yes |
1332/// | `import_magetypes` | Yes | Yes |
1333#[proc_macro_attribute]
1334pub fn rite(attr: TokenStream, item: TokenStream) -> TokenStream {
1335    let args = parse_macro_input!(attr as RiteArgs);
1336    let input_fn = parse_macro_input!(item as LightFn);
1337    rite_impl(input_fn, args)
1338}
1339
1340/// Descriptive alias for [`rite`].
1341///
1342/// Applies `#[target_feature]` + `#[inline]` based on the token type in your
1343/// function signature. No wrapper, no optimization boundary. Use for functions
1344/// called from within `#[arcane]`/`#[token_target_features_boundary]` code.
1345///
1346/// Since Rust 1.85, calling a `#[target_feature]` function from another function
1347/// with matching features is safe — no `unsafe` needed.
1348///
1349/// See [`rite`] for full documentation and examples.
1350#[proc_macro_attribute]
1351pub fn token_target_features(attr: TokenStream, item: TokenStream) -> TokenStream {
1352    let args = parse_macro_input!(attr as RiteArgs);
1353    let input_fn = parse_macro_input!(item as LightFn);
1354    rite_impl(input_fn, args)
1355}
1356
1357/// Arguments for the `#[rite]` macro.
1358#[derive(Default)]
1359struct RiteArgs {
1360    /// Generate an `unreachable!()` stub on the wrong architecture.
1361    /// Default is false (cfg-out: no function emitted on wrong arch).
1362    stub: bool,
1363    /// Inject `use archmage::intrinsics::{arch}::*;` (includes safe memory ops).
1364    import_intrinsics: bool,
1365    /// Inject `use magetypes::simd::{ns}::*;`, `use magetypes::simd::generic::*;`,
1366    /// and `use magetypes::simd::backends::*;`.
1367    import_magetypes: bool,
1368}
1369
1370impl Parse for RiteArgs {
1371    fn parse(input: ParseStream) -> syn::Result<Self> {
1372        let mut args = RiteArgs::default();
1373
1374        while !input.is_empty() {
1375            let ident: Ident = input.parse()?;
1376            match ident.to_string().as_str() {
1377                "stub" => args.stub = true,
1378                "import_intrinsics" => args.import_intrinsics = true,
1379                "import_magetypes" => args.import_magetypes = true,
1380                other => {
1381                    return Err(syn::Error::new(
1382                        ident.span(),
1383                        format!(
1384                            "unknown rite argument: `{}`. Supported: `stub`, \
1385                             `import_intrinsics`, `import_magetypes`.",
1386                            other
1387                        ),
1388                    ));
1389                }
1390            }
1391            if input.peek(Token![,]) {
1392                let _: Token![,] = input.parse()?;
1393            }
1394        }
1395
1396        Ok(args)
1397    }
1398}
1399
1400/// Implementation for the `#[rite]` macro.
1401fn rite_impl(mut input_fn: LightFn, args: RiteArgs) -> TokenStream {
1402    // Find the token parameter and its features
1403    let TokenParamInfo {
1404        features,
1405        target_arch,
1406        magetypes_namespace,
1407        ..
1408    } = match find_token_param(&input_fn.sig) {
1409        Some(result) => result,
1410        None => {
1411            // Check for specific misuse: featureless traits like SimdToken
1412            if let Some(trait_name) = diagnose_featureless_token(&input_fn.sig) {
1413                let msg = format!(
1414                    "`{trait_name}` cannot be used as a token bound in #[rite] \
1415                     because it doesn't specify any CPU features.\n\
1416                     \n\
1417                     #[rite] needs concrete features to generate #[target_feature]. \
1418                     Use a concrete token or a feature trait:\n\
1419                     \n\
1420                     Concrete tokens: X64V3Token, Desktop64, NeonToken, Arm64V2Token, ...\n\
1421                     Feature traits:  impl HasX64V2, impl HasNeon, impl HasArm64V3, ..."
1422                );
1423                return syn::Error::new_spanned(&input_fn.sig, msg)
1424                    .to_compile_error()
1425                    .into();
1426            }
1427            let msg = "rite requires a token parameter. Supported forms:\n\
1428                 - Concrete: `token: X64V3Token`\n\
1429                 - impl Trait: `token: impl HasX64V2`\n\
1430                 - Generic: `fn foo<T: HasX64V2>(token: T, ...)`";
1431            return syn::Error::new_spanned(&input_fn.sig, msg)
1432                .to_compile_error()
1433                .into();
1434        }
1435    };
1436
1437    // Build target_feature attributes
1438    let target_feature_attrs: Vec<Attribute> = features
1439        .iter()
1440        .map(|feature| parse_quote!(#[target_feature(enable = #feature)]))
1441        .collect();
1442
1443    // Always use #[inline] - #[inline(always)] + #[target_feature] requires nightly
1444    let inline_attr: Attribute = parse_quote!(#[inline]);
1445
1446    // Prepend attributes to the function
1447    let mut new_attrs = target_feature_attrs;
1448    new_attrs.push(inline_attr);
1449    new_attrs.append(&mut input_fn.attrs);
1450    input_fn.attrs = new_attrs;
1451
1452    // Prepend import statements to body if requested
1453    let body_imports = generate_imports(
1454        target_arch,
1455        magetypes_namespace,
1456        args.import_intrinsics,
1457        args.import_magetypes,
1458    );
1459    if !body_imports.is_empty() {
1460        let original_body = &input_fn.body;
1461        input_fn.body = quote! {
1462            #body_imports
1463            #original_body
1464        };
1465    }
1466
1467    // If we know the target arch, generate cfg-gated impl (+ optional stub)
1468    if let Some(arch) = target_arch {
1469        let vis = &input_fn.vis;
1470        let sig = &input_fn.sig;
1471        let attrs = &input_fn.attrs;
1472        let body = &input_fn.body;
1473
1474        let stub = if args.stub {
1475            quote! {
1476                #[cfg(not(target_arch = #arch))]
1477                #vis #sig {
1478                    unreachable!(concat!(
1479                        "This function requires ",
1480                        #arch,
1481                        " architecture"
1482                    ))
1483                }
1484            }
1485        } else {
1486            quote! {}
1487        };
1488
1489        quote! {
1490            #[cfg(target_arch = #arch)]
1491            #(#attrs)*
1492            #vis #sig {
1493                #body
1494            }
1495
1496            #stub
1497        }
1498        .into()
1499    } else {
1500        // No specific arch (trait bounds) - just emit the annotated function
1501        quote!(#input_fn).into()
1502    }
1503}
1504
1505// =============================================================================
1506// magetypes! macro - generate platform variants from generic function
1507// =============================================================================
1508
1509/// Generate platform-specific variants from a function by replacing `Token`.
1510///
1511/// Use `Token` as a placeholder for the token type. The macro generates
1512/// suffixed variants with `Token` replaced by the concrete token type, and
1513/// each variant wrapped in the appropriate `#[cfg(target_arch = ...)]` guard.
1514///
1515/// # Default tiers
1516///
1517/// Without arguments, generates `_v3`, `_v4`, `_neon`, `_wasm128`, `_scalar`:
1518///
1519/// ```rust,ignore
1520/// #[magetypes]
1521/// fn process(token: Token, data: &[f32]) -> f32 {
1522///     inner_simd_work(token, data)
1523/// }
1524/// ```
1525///
1526/// # Explicit tiers
1527///
1528/// Specify which tiers to generate:
1529///
1530/// ```rust,ignore
1531/// #[magetypes(v1, v3, neon)]
1532/// fn process(token: Token, data: &[f32]) -> f32 {
1533///     inner_simd_work(token, data)
1534/// }
1535/// // Generates: process_v1, process_v3, process_neon, process_scalar
1536/// ```
1537///
1538/// `scalar` is always included implicitly.
1539///
1540/// Known tiers: `v1`, `v2`, `v3`, `v4`, `v4x`, `neon`, `neon_aes`,
1541/// `neon_sha3`, `neon_crc`, `wasm128`, `wasm128_relaxed`, `scalar`.
1542///
1543/// # What gets replaced
1544///
1545/// **Only `Token`** is replaced — with the concrete token type for each variant
1546/// (e.g., `archmage::X64V3Token`, `archmage::ScalarToken`). SIMD types like
1547/// `f32x8` and constants like `LANES` are **not** replaced by this macro.
1548///
1549/// # Usage with incant!
1550///
1551/// The generated variants work with `incant!` for dispatch:
1552///
1553/// ```rust,ignore
1554/// pub fn process_api(data: &[f32]) -> f32 {
1555///     incant!(process(data))
1556/// }
1557///
1558/// // Or with matching explicit tiers:
1559/// pub fn process_api(data: &[f32]) -> f32 {
1560///     incant!(process(data), [v1, v3, neon])
1561/// }
1562/// ```
1563#[proc_macro_attribute]
1564pub fn magetypes(attr: TokenStream, item: TokenStream) -> TokenStream {
1565    let input_fn = parse_macro_input!(item as LightFn);
1566
1567    // Parse optional tier list from attribute args
1568    let tier_names: Vec<String> = if attr.is_empty() {
1569        DEFAULT_TIER_NAMES.iter().map(|s| s.to_string()).collect()
1570    } else {
1571        let parser = |input: ParseStream| input.parse_terminated(Ident::parse, Token![,]);
1572        let idents = match syn::parse::Parser::parse(parser, attr) {
1573            Ok(p) => p,
1574            Err(e) => return e.to_compile_error().into(),
1575        };
1576        idents.iter().map(|i| i.to_string()).collect()
1577    };
1578
1579    let tiers = match resolve_tiers(&tier_names, input_fn.sig.ident.span()) {
1580        Ok(t) => t,
1581        Err(e) => return e.to_compile_error().into(),
1582    };
1583
1584    magetypes_impl(input_fn, &tiers)
1585}
1586
1587fn magetypes_impl(mut input_fn: LightFn, tiers: &[&TierDescriptor]) -> TokenStream {
1588    // Strip user-provided #[arcane] / #[rite] to prevent double-wrapping
1589    // (magetypes auto-adds #[arcane] on non-scalar variants)
1590    input_fn
1591        .attrs
1592        .retain(|attr| !attr.path().is_ident("arcane") && !attr.path().is_ident("rite"));
1593
1594    let fn_name = &input_fn.sig.ident;
1595    let fn_attrs = &input_fn.attrs;
1596
1597    // Convert function to string for text substitution
1598    let fn_str = input_fn.to_token_stream().to_string();
1599
1600    let mut variants = Vec::new();
1601
1602    for tier in tiers {
1603        // Create suffixed function name
1604        let suffixed_name = format!("{}_{}", fn_name, tier.suffix);
1605
1606        // Do text substitution
1607        let mut variant_str = fn_str.clone();
1608
1609        // Replace function name
1610        variant_str = variant_str.replacen(&fn_name.to_string(), &suffixed_name, 1);
1611
1612        // Replace Token type with concrete token
1613        variant_str = variant_str.replace("Token", tier.token_path);
1614
1615        // Parse back to tokens
1616        let variant_tokens: proc_macro2::TokenStream = match variant_str.parse() {
1617            Ok(t) => t,
1618            Err(e) => {
1619                return syn::Error::new_spanned(
1620                    &input_fn,
1621                    format!(
1622                        "Failed to parse generated variant `{}`: {}",
1623                        suffixed_name, e
1624                    ),
1625                )
1626                .to_compile_error()
1627                .into();
1628            }
1629        };
1630
1631        // Add cfg guards
1632        let cfg_guard = match (tier.target_arch, tier.cargo_feature) {
1633            (Some(arch), Some(feature)) => {
1634                quote! { #[cfg(all(target_arch = #arch, feature = #feature))] }
1635            }
1636            (Some(arch), None) => {
1637                quote! { #[cfg(target_arch = #arch)] }
1638            }
1639            (None, Some(feature)) => {
1640                quote! { #[cfg(feature = #feature)] }
1641            }
1642            (None, None) => {
1643                quote! {} // No guard needed (scalar)
1644            }
1645        };
1646
1647        variants.push(if tier.name != "scalar" {
1648            // Non-scalar variants get #[arcane] so target_feature is applied
1649            quote! {
1650                #cfg_guard
1651                #[archmage::arcane]
1652                #variant_tokens
1653            }
1654        } else {
1655            quote! {
1656                #cfg_guard
1657                #variant_tokens
1658            }
1659        });
1660    }
1661
1662    // Remove attributes from the list that should not be duplicated
1663    let filtered_attrs: Vec<_> = fn_attrs
1664        .iter()
1665        .filter(|a| !a.path().is_ident("magetypes"))
1666        .collect();
1667
1668    let output = quote! {
1669        #(#filtered_attrs)*
1670        #(#variants)*
1671    };
1672
1673    output.into()
1674}
1675
1676// =============================================================================
1677// incant! macro - dispatch to platform-specific variants
1678// =============================================================================
1679
1680// =============================================================================
1681// Tier descriptors for incant! and #[magetypes]
1682// =============================================================================
1683
1684/// Describes a dispatch tier for incant! and #[magetypes].
1685struct TierDescriptor {
1686    /// Tier name as written in user code (e.g., "v3", "neon")
1687    name: &'static str,
1688    /// Function suffix (e.g., "v3", "neon", "scalar")
1689    suffix: &'static str,
1690    /// Token type path (e.g., "archmage::X64V3Token")
1691    token_path: &'static str,
1692    /// IntoConcreteToken method name (e.g., "as_x64v3")
1693    as_method: &'static str,
1694    /// Target architecture for cfg guard (None = no guard)
1695    target_arch: Option<&'static str>,
1696    /// Required cargo feature (None = no feature guard)
1697    cargo_feature: Option<&'static str>,
1698    /// Dispatch priority (higher = tried first within same arch)
1699    priority: u32,
1700}
1701
1702/// All known tiers in dispatch-priority order (highest first within arch).
1703const ALL_TIERS: &[TierDescriptor] = &[
1704    // x86: highest to lowest
1705    TierDescriptor {
1706        name: "v4x",
1707        suffix: "v4x",
1708        token_path: "archmage::X64V4xToken",
1709        as_method: "as_x64v4x",
1710        target_arch: Some("x86_64"),
1711        cargo_feature: Some("avx512"),
1712        priority: 50,
1713    },
1714    TierDescriptor {
1715        name: "v4",
1716        suffix: "v4",
1717        token_path: "archmage::X64V4Token",
1718        as_method: "as_x64v4",
1719        target_arch: Some("x86_64"),
1720        cargo_feature: Some("avx512"),
1721        priority: 40,
1722    },
1723    TierDescriptor {
1724        name: "v3_crypto",
1725        suffix: "v3_crypto",
1726        token_path: "archmage::X64V3CryptoToken",
1727        as_method: "as_x64v3_crypto",
1728        target_arch: Some("x86_64"),
1729        cargo_feature: None,
1730        priority: 35,
1731    },
1732    TierDescriptor {
1733        name: "v3",
1734        suffix: "v3",
1735        token_path: "archmage::X64V3Token",
1736        as_method: "as_x64v3",
1737        target_arch: Some("x86_64"),
1738        cargo_feature: None,
1739        priority: 30,
1740    },
1741    TierDescriptor {
1742        name: "x64_crypto",
1743        suffix: "x64_crypto",
1744        token_path: "archmage::X64CryptoToken",
1745        as_method: "as_x64_crypto",
1746        target_arch: Some("x86_64"),
1747        cargo_feature: None,
1748        priority: 25,
1749    },
1750    TierDescriptor {
1751        name: "v2",
1752        suffix: "v2",
1753        token_path: "archmage::X64V2Token",
1754        as_method: "as_x64v2",
1755        target_arch: Some("x86_64"),
1756        cargo_feature: None,
1757        priority: 20,
1758    },
1759    TierDescriptor {
1760        name: "v1",
1761        suffix: "v1",
1762        token_path: "archmage::X64V1Token",
1763        as_method: "as_x64v1",
1764        target_arch: Some("x86_64"),
1765        cargo_feature: None,
1766        priority: 10,
1767    },
1768    // ARM: highest to lowest
1769    TierDescriptor {
1770        name: "arm_v3",
1771        suffix: "arm_v3",
1772        token_path: "archmage::Arm64V3Token",
1773        as_method: "as_arm_v3",
1774        target_arch: Some("aarch64"),
1775        cargo_feature: None,
1776        priority: 50,
1777    },
1778    TierDescriptor {
1779        name: "arm_v2",
1780        suffix: "arm_v2",
1781        token_path: "archmage::Arm64V2Token",
1782        as_method: "as_arm_v2",
1783        target_arch: Some("aarch64"),
1784        cargo_feature: None,
1785        priority: 40,
1786    },
1787    TierDescriptor {
1788        name: "neon_aes",
1789        suffix: "neon_aes",
1790        token_path: "archmage::NeonAesToken",
1791        as_method: "as_neon_aes",
1792        target_arch: Some("aarch64"),
1793        cargo_feature: None,
1794        priority: 30,
1795    },
1796    TierDescriptor {
1797        name: "neon_sha3",
1798        suffix: "neon_sha3",
1799        token_path: "archmage::NeonSha3Token",
1800        as_method: "as_neon_sha3",
1801        target_arch: Some("aarch64"),
1802        cargo_feature: None,
1803        priority: 30,
1804    },
1805    TierDescriptor {
1806        name: "neon_crc",
1807        suffix: "neon_crc",
1808        token_path: "archmage::NeonCrcToken",
1809        as_method: "as_neon_crc",
1810        target_arch: Some("aarch64"),
1811        cargo_feature: None,
1812        priority: 30,
1813    },
1814    TierDescriptor {
1815        name: "neon",
1816        suffix: "neon",
1817        token_path: "archmage::NeonToken",
1818        as_method: "as_neon",
1819        target_arch: Some("aarch64"),
1820        cargo_feature: None,
1821        priority: 20,
1822    },
1823    // WASM
1824    TierDescriptor {
1825        name: "wasm128_relaxed",
1826        suffix: "wasm128_relaxed",
1827        token_path: "archmage::Wasm128RelaxedToken",
1828        as_method: "as_wasm128_relaxed",
1829        target_arch: Some("wasm32"),
1830        cargo_feature: None,
1831        priority: 21,
1832    },
1833    TierDescriptor {
1834        name: "wasm128",
1835        suffix: "wasm128",
1836        token_path: "archmage::Wasm128Token",
1837        as_method: "as_wasm128",
1838        target_arch: Some("wasm32"),
1839        cargo_feature: None,
1840        priority: 20,
1841    },
1842    // Scalar (always last)
1843    TierDescriptor {
1844        name: "scalar",
1845        suffix: "scalar",
1846        token_path: "archmage::ScalarToken",
1847        as_method: "as_scalar",
1848        target_arch: None,
1849        cargo_feature: None,
1850        priority: 0,
1851    },
1852];
1853
1854/// Default tiers (backwards-compatible with pre-explicit behavior).
1855const DEFAULT_TIER_NAMES: &[&str] = &["v4", "v3", "neon", "wasm128", "scalar"];
1856
1857/// Look up a tier by name, returning an error on unknown names.
1858fn find_tier(name: &str) -> Option<&'static TierDescriptor> {
1859    ALL_TIERS.iter().find(|t| t.name == name)
1860}
1861
1862/// Resolve tier names to descriptors, sorted by dispatch priority (highest first).
1863/// Always appends "scalar" if not already present.
1864fn resolve_tiers(
1865    tier_names: &[String],
1866    error_span: proc_macro2::Span,
1867) -> syn::Result<Vec<&'static TierDescriptor>> {
1868    let mut tiers = Vec::new();
1869    for name in tier_names {
1870        match find_tier(name) {
1871            Some(tier) => tiers.push(tier),
1872            None => {
1873                let known: Vec<&str> = ALL_TIERS.iter().map(|t| t.name).collect();
1874                return Err(syn::Error::new(
1875                    error_span,
1876                    format!("unknown tier `{}`. Known tiers: {}", name, known.join(", ")),
1877                ));
1878            }
1879        }
1880    }
1881
1882    // Always include scalar fallback
1883    if !tiers.iter().any(|t| t.name == "scalar") {
1884        tiers.push(find_tier("scalar").unwrap());
1885    }
1886
1887    // Sort by priority (highest first) for correct dispatch order
1888    tiers.sort_by(|a, b| b.priority.cmp(&a.priority));
1889
1890    Ok(tiers)
1891}
1892
1893// =============================================================================
1894// incant! macro - dispatch to platform-specific variants
1895// =============================================================================
1896
1897/// Input for the incant! macro
1898struct IncantInput {
1899    /// Function path to call (e.g. `func` or `module::func`)
1900    func_path: syn::Path,
1901    /// Arguments to pass
1902    args: Vec<syn::Expr>,
1903    /// Optional token variable for passthrough mode
1904    with_token: Option<syn::Expr>,
1905    /// Optional explicit tier list (None = default tiers)
1906    tiers: Option<(Vec<String>, proc_macro2::Span)>,
1907}
1908
1909/// Create a suffixed version of a function path.
1910/// e.g. `module::func` + `"v3"` → `module::func_v3`
1911fn suffix_path(path: &syn::Path, suffix: &str) -> syn::Path {
1912    let mut suffixed = path.clone();
1913    if let Some(last) = suffixed.segments.last_mut() {
1914        last.ident = format_ident!("{}_{}", last.ident, suffix);
1915    }
1916    suffixed
1917}
1918
1919impl Parse for IncantInput {
1920    fn parse(input: ParseStream) -> syn::Result<Self> {
1921        // Parse: function_path(arg1, arg2, ...) [with token_expr] [, [tier1, tier2, ...]]
1922        let func_path: syn::Path = input.parse()?;
1923
1924        // Parse parenthesized arguments
1925        let content;
1926        syn::parenthesized!(content in input);
1927        let args = content
1928            .parse_terminated(syn::Expr::parse, Token![,])?
1929            .into_iter()
1930            .collect();
1931
1932        // Check for optional "with token"
1933        let with_token = if input.peek(Ident) {
1934            let kw: Ident = input.parse()?;
1935            if kw != "with" {
1936                return Err(syn::Error::new_spanned(kw, "expected `with` keyword"));
1937            }
1938            Some(input.parse()?)
1939        } else {
1940            None
1941        };
1942
1943        // Check for optional tier list: , [tier1, tier2, ...]
1944        let tiers = if input.peek(Token![,]) {
1945            let _: Token![,] = input.parse()?;
1946            let bracket_content;
1947            let bracket = syn::bracketed!(bracket_content in input);
1948            let tier_idents = bracket_content.parse_terminated(Ident::parse, Token![,])?;
1949            let tier_names: Vec<String> = tier_idents.iter().map(|i| i.to_string()).collect();
1950            Some((tier_names, bracket.span.join()))
1951        } else {
1952            None
1953        };
1954
1955        Ok(IncantInput {
1956            func_path,
1957            args,
1958            with_token,
1959            tiers,
1960        })
1961    }
1962}
1963
1964/// Dispatch to platform-specific SIMD variants.
1965///
1966/// # Entry Point Mode (no token yet)
1967///
1968/// Summons tokens and dispatches to the best available variant:
1969///
1970/// ```rust,ignore
1971/// pub fn public_api(data: &[f32]) -> f32 {
1972///     incant!(dot(data))
1973/// }
1974/// ```
1975///
1976/// Expands to runtime feature detection + dispatch to `dot_v3`, `dot_v4`,
1977/// `dot_neon`, `dot_wasm128`, or `dot_scalar`.
1978///
1979/// # Explicit Tiers
1980///
1981/// Specify which tiers to dispatch to:
1982///
1983/// ```rust,ignore
1984/// // Only dispatch to v1, v3, neon, and scalar
1985/// pub fn api(data: &[f32]) -> f32 {
1986///     incant!(process(data), [v1, v3, neon])
1987/// }
1988/// ```
1989///
1990/// `scalar` is always included implicitly. Unknown tier names cause a
1991/// compile error. Tiers are automatically sorted into correct dispatch
1992/// order (highest priority first).
1993///
1994/// Known tiers: `v1`, `v2`, `v3`, `v4`, `v4x`, `neon`, `neon_aes`,
1995/// `neon_sha3`, `neon_crc`, `wasm128`, `wasm128_relaxed`, `scalar`.
1996///
1997/// # Passthrough Mode (already have token)
1998///
1999/// Uses compile-time dispatch via `IntoConcreteToken`:
2000///
2001/// ```rust,ignore
2002/// #[arcane]
2003/// fn outer(token: X64V3Token, data: &[f32]) -> f32 {
2004///     incant!(inner(data) with token)
2005/// }
2006/// ```
2007///
2008/// Also supports explicit tiers:
2009///
2010/// ```rust,ignore
2011/// fn inner<T: IntoConcreteToken>(token: T, data: &[f32]) -> f32 {
2012///     incant!(process(data) with token, [v3, neon])
2013/// }
2014/// ```
2015///
2016/// The compiler monomorphizes the dispatch, eliminating non-matching branches.
2017///
2018/// # Variant Naming
2019///
2020/// Functions must have suffixed variants matching the selected tiers:
2021/// - `_v1` for `X64V1Token`
2022/// - `_v2` for `X64V2Token`
2023/// - `_v3` for `X64V3Token`
2024/// - `_v4` for `X64V4Token` (requires `avx512` feature)
2025/// - `_v4x` for `X64V4xToken` (requires `avx512` feature)
2026/// - `_neon` for `NeonToken`
2027/// - `_neon_aes` for `NeonAesToken`
2028/// - `_neon_sha3` for `NeonSha3Token`
2029/// - `_neon_crc` for `NeonCrcToken`
2030/// - `_wasm128` for `Wasm128Token`
2031/// - `_scalar` for `ScalarToken`
2032#[proc_macro]
2033pub fn incant(input: TokenStream) -> TokenStream {
2034    let input = parse_macro_input!(input as IncantInput);
2035    incant_impl(input)
2036}
2037
2038/// Legacy alias for [`incant!`].
2039#[proc_macro]
2040pub fn simd_route(input: TokenStream) -> TokenStream {
2041    let input = parse_macro_input!(input as IncantInput);
2042    incant_impl(input)
2043}
2044
2045/// Descriptive alias for [`incant!`].
2046///
2047/// Dispatches to architecture-specific function variants at runtime.
2048/// Looks for suffixed functions (`_v3`, `_v4`, `_neon`, `_wasm128`, `_scalar`)
2049/// and calls the best one the CPU supports.
2050///
2051/// See [`incant!`] for full documentation and examples.
2052#[proc_macro]
2053pub fn dispatch_variant(input: TokenStream) -> TokenStream {
2054    let input = parse_macro_input!(input as IncantInput);
2055    incant_impl(input)
2056}
2057
2058fn incant_impl(input: IncantInput) -> TokenStream {
2059    let func_path = &input.func_path;
2060    let args = &input.args;
2061
2062    // Resolve tiers
2063    let tier_names: Vec<String> = match &input.tiers {
2064        Some((names, _)) => names.clone(),
2065        None => DEFAULT_TIER_NAMES.iter().map(|s| s.to_string()).collect(),
2066    };
2067    let last_segment_span = func_path
2068        .segments
2069        .last()
2070        .map(|s| s.ident.span())
2071        .unwrap_or_else(proc_macro2::Span::call_site);
2072    let error_span = input
2073        .tiers
2074        .as_ref()
2075        .map(|(_, span)| *span)
2076        .unwrap_or(last_segment_span);
2077
2078    let tiers = match resolve_tiers(&tier_names, error_span) {
2079        Ok(t) => t,
2080        Err(e) => return e.to_compile_error().into(),
2081    };
2082
2083    // Group tiers by architecture for cfg-guarded blocks
2084    // Within each arch, tiers are already sorted by priority (highest first)
2085    if let Some(token_expr) = &input.with_token {
2086        gen_incant_passthrough(func_path, args, token_expr, &tiers)
2087    } else {
2088        gen_incant_entry(func_path, args, &tiers)
2089    }
2090}
2091
2092/// Generate incant! passthrough mode (already have a token).
2093fn gen_incant_passthrough(
2094    func_path: &syn::Path,
2095    args: &[syn::Expr],
2096    token_expr: &syn::Expr,
2097    tiers: &[&TierDescriptor],
2098) -> TokenStream {
2099    let mut dispatch_arms = Vec::new();
2100
2101    // Group non-scalar tiers by (target_arch, cargo_feature) for nested cfg blocks
2102    let mut arch_groups: Vec<(Option<&str>, Option<&str>, Vec<&TierDescriptor>)> = Vec::new();
2103    for tier in tiers {
2104        if tier.name == "scalar" {
2105            continue; // Handle scalar separately at the end
2106        }
2107        let key = (tier.target_arch, tier.cargo_feature);
2108        if let Some(group) = arch_groups.iter_mut().find(|(a, f, _)| (*a, *f) == key) {
2109            group.2.push(tier);
2110        } else {
2111            arch_groups.push((tier.target_arch, tier.cargo_feature, vec![tier]));
2112        }
2113    }
2114
2115    for (target_arch, cargo_feature, group_tiers) in &arch_groups {
2116        let mut tier_checks = Vec::new();
2117        for tier in group_tiers {
2118            let fn_suffixed = suffix_path(func_path, tier.suffix);
2119            let as_method = format_ident!("{}", tier.as_method);
2120            tier_checks.push(quote! {
2121                if let Some(__t) = __incant_token.#as_method() {
2122                    break '__incant #fn_suffixed(__t, #(#args),*);
2123                }
2124            });
2125        }
2126
2127        let inner = quote! { #(#tier_checks)* };
2128
2129        let guarded = match (target_arch, cargo_feature) {
2130            (Some(arch), Some(feat)) => quote! {
2131                #[cfg(target_arch = #arch)]
2132                {
2133                    #[cfg(feature = #feat)]
2134                    { #inner }
2135                }
2136            },
2137            (Some(arch), None) => quote! {
2138                #[cfg(target_arch = #arch)]
2139                { #inner }
2140            },
2141            (None, Some(feat)) => quote! {
2142                #[cfg(feature = #feat)]
2143                { #inner }
2144            },
2145            (None, None) => inner,
2146        };
2147
2148        dispatch_arms.push(guarded);
2149    }
2150
2151    // Scalar fallback (always last)
2152    let fn_scalar = suffix_path(func_path, "scalar");
2153    let scalar_arm = if tiers.iter().any(|t| t.name == "scalar") {
2154        quote! {
2155            if let Some(__t) = __incant_token.as_scalar() {
2156                break '__incant #fn_scalar(__t, #(#args),*);
2157            }
2158            unreachable!("Token did not match any known variant")
2159        }
2160    } else {
2161        quote! { unreachable!("Token did not match any known variant") }
2162    };
2163
2164    let expanded = quote! {
2165        '__incant: {
2166            use archmage::IntoConcreteToken;
2167            let __incant_token = #token_expr;
2168            #(#dispatch_arms)*
2169            #scalar_arm
2170        }
2171    };
2172    expanded.into()
2173}
2174
2175/// Generate incant! entry point mode (summon tokens).
2176fn gen_incant_entry(
2177    func_path: &syn::Path,
2178    args: &[syn::Expr],
2179    tiers: &[&TierDescriptor],
2180) -> TokenStream {
2181    let mut dispatch_arms = Vec::new();
2182
2183    // Group non-scalar tiers by target_arch for cfg blocks.
2184    // Within each arch group, further split by cargo_feature.
2185    let mut arch_groups: Vec<(Option<&str>, Vec<&TierDescriptor>)> = Vec::new();
2186    for tier in tiers {
2187        if tier.name == "scalar" {
2188            continue;
2189        }
2190        if let Some(group) = arch_groups.iter_mut().find(|(a, _)| *a == tier.target_arch) {
2191            group.1.push(tier);
2192        } else {
2193            arch_groups.push((tier.target_arch, vec![tier]));
2194        }
2195    }
2196
2197    for (target_arch, group_tiers) in &arch_groups {
2198        let mut tier_checks = Vec::new();
2199        for tier in group_tiers {
2200            let fn_suffixed = suffix_path(func_path, tier.suffix);
2201            let token_path: syn::Path = syn::parse_str(tier.token_path).unwrap();
2202
2203            let check = quote! {
2204                if let Some(__t) = #token_path::summon() {
2205                    break '__incant #fn_suffixed(__t, #(#args),*);
2206                }
2207            };
2208
2209            if let Some(feat) = tier.cargo_feature {
2210                tier_checks.push(quote! {
2211                    #[cfg(feature = #feat)]
2212                    { #check }
2213                });
2214            } else {
2215                tier_checks.push(check);
2216            }
2217        }
2218
2219        let inner = quote! { #(#tier_checks)* };
2220
2221        if let Some(arch) = target_arch {
2222            dispatch_arms.push(quote! {
2223                #[cfg(target_arch = #arch)]
2224                { #inner }
2225            });
2226        } else {
2227            dispatch_arms.push(inner);
2228        }
2229    }
2230
2231    // Scalar fallback
2232    let fn_scalar = suffix_path(func_path, "scalar");
2233
2234    let expanded = quote! {
2235        '__incant: {
2236            use archmage::SimdToken;
2237            #(#dispatch_arms)*
2238            #fn_scalar(archmage::ScalarToken, #(#args),*)
2239        }
2240    };
2241    expanded.into()
2242}
2243
2244// =============================================================================
2245// autoversion - combined variant generation + dispatch
2246// =============================================================================
2247
2248/// Arguments to the `#[autoversion]` macro.
2249struct AutoversionArgs {
2250    /// The concrete type to use for `self` receiver (inherent methods only).
2251    self_type: Option<Type>,
2252    /// Explicit tier names (None = default tiers).
2253    tiers: Option<Vec<String>>,
2254}
2255
2256impl Parse for AutoversionArgs {
2257    fn parse(input: ParseStream) -> syn::Result<Self> {
2258        let mut self_type = None;
2259        let mut tier_names = Vec::new();
2260
2261        while !input.is_empty() {
2262            let ident: Ident = input.parse()?;
2263            if ident == "_self" {
2264                let _: Token![=] = input.parse()?;
2265                self_type = Some(input.parse()?);
2266            } else {
2267                // Treat as tier name — validated later by resolve_tiers
2268                tier_names.push(ident.to_string());
2269            }
2270            if input.peek(Token![,]) {
2271                let _: Token![,] = input.parse()?;
2272            }
2273        }
2274
2275        Ok(AutoversionArgs {
2276            self_type,
2277            tiers: if tier_names.is_empty() {
2278                None
2279            } else {
2280                Some(tier_names)
2281            },
2282        })
2283    }
2284}
2285
2286/// Information about the `SimdToken` parameter found in a function signature.
2287struct SimdTokenParamInfo {
2288    /// Index of the parameter in `sig.inputs`
2289    index: usize,
2290    /// The parameter identifier
2291    #[allow(dead_code)]
2292    ident: Ident,
2293}
2294
2295/// Find the `SimdToken` parameter in a function signature.
2296///
2297/// Searches all typed parameters for one whose type path ends in `SimdToken`.
2298/// Returns the parameter index and identifier, or `None` if not found.
2299fn find_simd_token_param(sig: &Signature) -> Option<SimdTokenParamInfo> {
2300    for (i, arg) in sig.inputs.iter().enumerate() {
2301        if let FnArg::Typed(PatType { pat, ty, .. }) = arg
2302            && let Type::Path(type_path) = ty.as_ref()
2303            && let Some(seg) = type_path.path.segments.last()
2304            && seg.ident == "SimdToken"
2305        {
2306            let ident = match pat.as_ref() {
2307                syn::Pat::Ident(pi) => pi.ident.clone(),
2308                syn::Pat::Wild(w) => Ident::new("__autoversion_token", w.underscore_token.span),
2309                _ => continue,
2310            };
2311            return Some(SimdTokenParamInfo { index: i, ident });
2312        }
2313    }
2314    None
2315}
2316
2317/// Core implementation for `#[autoversion]`.
2318///
2319/// Generates suffixed SIMD variants (like `#[magetypes]`) and a runtime
2320/// dispatcher function (like `incant!`) from a single annotated function.
2321fn autoversion_impl(mut input_fn: LightFn, args: AutoversionArgs) -> TokenStream {
2322    // Check for self receiver
2323    let has_self = input_fn
2324        .sig
2325        .inputs
2326        .first()
2327        .is_some_and(|arg| matches!(arg, FnArg::Receiver(_)));
2328
2329    // Self receiver requires _self = Type
2330    if has_self && args.self_type.is_none() {
2331        return syn::Error::new_spanned(
2332            &input_fn.sig,
2333            "autoversion with self receiver requires `_self = Type` argument.\n\
2334             Example: #[autoversion(_self = MyType)]\n\
2335             Use `_self` (not `self`) in the function body to refer to self.\n\n\
2336             Trait methods are not supported. Use the delegation pattern:\n\
2337             impl Trait for Type {\n    \
2338                 fn method(&self, data: &[f32]) -> f32 {\n        \
2339                     self.method_impl(data) // delegate to autoversioned inherent method\n    \
2340                 }\n\
2341             }",
2342        )
2343        .to_compile_error()
2344        .into();
2345    }
2346
2347    // Find SimdToken parameter
2348    let token_param = match find_simd_token_param(&input_fn.sig) {
2349        Some(p) => p,
2350        None => {
2351            return syn::Error::new_spanned(
2352                &input_fn.sig,
2353                "autoversion requires a `SimdToken` parameter.\n\
2354                 Example: fn process(token: SimdToken, data: &[f32]) -> f32 { ... }\n\n\
2355                 SimdToken is the dispatch placeholder — autoversion replaces it \
2356                 with concrete token types and generates a runtime dispatcher.",
2357            )
2358            .to_compile_error()
2359            .into();
2360        }
2361    };
2362
2363    // Resolve tiers
2364    let tier_names: Vec<String> = match &args.tiers {
2365        Some(names) => names.clone(),
2366        None => DEFAULT_TIER_NAMES.iter().map(|s| s.to_string()).collect(),
2367    };
2368    let tiers = match resolve_tiers(&tier_names, input_fn.sig.ident.span()) {
2369        Ok(t) => t,
2370        Err(e) => return e.to_compile_error().into(),
2371    };
2372
2373    // Strip #[arcane] / #[rite] to prevent double-wrapping
2374    input_fn
2375        .attrs
2376        .retain(|attr| !attr.path().is_ident("arcane") && !attr.path().is_ident("rite"));
2377
2378    let fn_name = &input_fn.sig.ident;
2379    let vis = input_fn.vis.clone();
2380
2381    // Move attrs to dispatcher only; variants get no user attrs
2382    let fn_attrs: Vec<Attribute> = input_fn.attrs.drain(..).collect();
2383
2384    // =========================================================================
2385    // Generate suffixed variants
2386    // =========================================================================
2387    //
2388    // AST manipulation only — we clone the parsed LightFn and swap the token
2389    // param's type annotation. No serialize/reparse round-trip. The body is
2390    // never touched unless _self = Type requires a `let _self = self;`
2391    // preamble on the scalar variant.
2392
2393    let mut variants = Vec::new();
2394
2395    for tier in &tiers {
2396        let mut variant_fn = input_fn.clone();
2397
2398        // Rename: process → process_v3
2399        variant_fn.sig.ident = format_ident!("{}_{}", fn_name, tier.suffix);
2400
2401        // Replace SimdToken param type with concrete token type
2402        let concrete_type: Type = syn::parse_str(tier.token_path).unwrap();
2403        if let FnArg::Typed(pt) = &mut variant_fn.sig.inputs[token_param.index] {
2404            *pt.ty = concrete_type;
2405        }
2406
2407        // Scalar with self: inject `let _self = self;` preamble so body's
2408        // _self references resolve (non-scalar variants get this from #[arcane])
2409        if tier.name == "scalar" && has_self {
2410            let original_body = variant_fn.body.clone();
2411            variant_fn.body = quote!(let _self = self; #original_body);
2412        }
2413
2414        // cfg guard
2415        let cfg_guard = match (tier.target_arch, tier.cargo_feature) {
2416            (Some(arch), Some(feature)) => {
2417                quote! { #[cfg(all(target_arch = #arch, feature = #feature))] }
2418            }
2419            (Some(arch), None) => quote! { #[cfg(target_arch = #arch)] },
2420            (None, Some(feature)) => quote! { #[cfg(feature = #feature)] },
2421            (None, None) => quote! {},
2422        };
2423
2424        if tier.name != "scalar" {
2425            // Non-scalar: add #[arcane] (with _self if needed)
2426            let arcane_attr = if let Some(ref self_type) = args.self_type {
2427                quote! { #[archmage::arcane(_self = #self_type)] }
2428            } else {
2429                quote! { #[archmage::arcane] }
2430            };
2431            variants.push(quote! {
2432                #cfg_guard
2433                #arcane_attr
2434                #variant_fn
2435            });
2436        } else {
2437            variants.push(quote! {
2438                #cfg_guard
2439                #variant_fn
2440            });
2441        }
2442    }
2443
2444    // =========================================================================
2445    // Generate dispatcher (adapted from gen_incant_entry)
2446    // =========================================================================
2447
2448    // Build dispatcher inputs: original params minus SimdToken
2449    let mut dispatcher_inputs: Vec<FnArg> = input_fn.sig.inputs.iter().cloned().collect();
2450    dispatcher_inputs.remove(token_param.index);
2451
2452    // Rename wildcard params so we can pass them as arguments
2453    let mut wild_counter = 0u32;
2454    for arg in &mut dispatcher_inputs {
2455        if let FnArg::Typed(pat_type) = arg
2456            && matches!(pat_type.pat.as_ref(), syn::Pat::Wild(_))
2457        {
2458            let ident = format_ident!("__autoversion_wild_{}", wild_counter);
2459            wild_counter += 1;
2460            *pat_type.pat = syn::Pat::Ident(syn::PatIdent {
2461                attrs: vec![],
2462                by_ref: None,
2463                mutability: None,
2464                ident,
2465                subpat: None,
2466            });
2467        }
2468    }
2469
2470    // Collect argument idents for dispatch calls (exclude self receiver)
2471    let dispatch_args: Vec<Ident> = dispatcher_inputs
2472        .iter()
2473        .filter_map(|arg| {
2474            if let FnArg::Typed(PatType { pat, .. }) = arg
2475                && let syn::Pat::Ident(pi) = pat.as_ref()
2476            {
2477                return Some(pi.ident.clone());
2478            }
2479            None
2480        })
2481        .collect();
2482
2483    // Group non-scalar tiers by target_arch for cfg blocks
2484    let mut arch_groups: Vec<(Option<&str>, Vec<&&TierDescriptor>)> = Vec::new();
2485    for tier in &tiers {
2486        if tier.name == "scalar" {
2487            continue;
2488        }
2489        if let Some(group) = arch_groups.iter_mut().find(|(a, _)| *a == tier.target_arch) {
2490            group.1.push(tier);
2491        } else {
2492            arch_groups.push((tier.target_arch, vec![tier]));
2493        }
2494    }
2495
2496    let mut dispatch_arms = Vec::new();
2497    for (target_arch, group_tiers) in &arch_groups {
2498        let mut tier_checks = Vec::new();
2499        for tier in group_tiers {
2500            let suffixed = format_ident!("{}_{}", fn_name, tier.suffix);
2501            let token_path: syn::Path = syn::parse_str(tier.token_path).unwrap();
2502
2503            let call = if has_self {
2504                quote! { self.#suffixed(__t, #(#dispatch_args),*) }
2505            } else {
2506                quote! { #suffixed(__t, #(#dispatch_args),*) }
2507            };
2508
2509            let check = quote! {
2510                if let Some(__t) = #token_path::summon() {
2511                    break '__dispatch #call;
2512                }
2513            };
2514
2515            if let Some(feat) = tier.cargo_feature {
2516                tier_checks.push(quote! {
2517                    #[cfg(feature = #feat)]
2518                    { #check }
2519                });
2520            } else {
2521                tier_checks.push(check);
2522            }
2523        }
2524
2525        let inner = quote! { #(#tier_checks)* };
2526
2527        if let Some(arch) = target_arch {
2528            dispatch_arms.push(quote! {
2529                #[cfg(target_arch = #arch)]
2530                { #inner }
2531            });
2532        } else {
2533            dispatch_arms.push(inner);
2534        }
2535    }
2536
2537    // Scalar fallback (always available, no summon needed)
2538    let scalar_name = format_ident!("{}_scalar", fn_name);
2539    let scalar_call = if has_self {
2540        quote! { self.#scalar_name(archmage::ScalarToken, #(#dispatch_args),*) }
2541    } else {
2542        quote! { #scalar_name(archmage::ScalarToken, #(#dispatch_args),*) }
2543    };
2544
2545    // Build dispatcher function
2546    let dispatcher_inputs_punct: syn::punctuated::Punctuated<FnArg, Token![,]> =
2547        dispatcher_inputs.into_iter().collect();
2548    let output = &input_fn.sig.output;
2549    let generics = &input_fn.sig.generics;
2550    let where_clause = &generics.where_clause;
2551
2552    let dispatcher = quote! {
2553        #(#fn_attrs)*
2554        #vis fn #fn_name #generics (#dispatcher_inputs_punct) #output #where_clause {
2555            '__dispatch: {
2556                use archmage::SimdToken;
2557                #(#dispatch_arms)*
2558                #scalar_call
2559            }
2560        }
2561    };
2562
2563    let expanded = quote! {
2564        #dispatcher
2565        #(#variants)*
2566    };
2567
2568    expanded.into()
2569}
2570
2571/// Let the compiler auto-vectorize scalar code for each architecture.
2572///
2573/// Write a plain scalar function with a `SimdToken` placeholder parameter.
2574/// `#[autoversion]` generates architecture-specific copies — each compiled
2575/// with different `#[target_feature]` flags via `#[arcane]` — plus a runtime
2576/// dispatcher that calls the best one the CPU supports.
2577///
2578/// You don't touch intrinsics, don't import SIMD types, don't think about
2579/// lane widths. The compiler's auto-vectorizer does the work; you give it
2580/// permission via `#[target_feature]`, which `#[autoversion]` handles.
2581///
2582/// # The simple win
2583///
2584/// ```rust,ignore
2585/// use archmage::SimdToken;
2586///
2587/// #[autoversion]
2588/// fn sum_of_squares(_token: SimdToken, data: &[f32]) -> f32 {
2589///     let mut sum = 0.0f32;
2590///     for &x in data {
2591///         sum += x * x;
2592///     }
2593///     sum
2594/// }
2595///
2596/// // Call directly — no token, no unsafe:
2597/// let result = sum_of_squares(&my_data);
2598/// ```
2599///
2600/// The `_token` parameter is never used in the body. It exists so the macro
2601/// knows where to substitute concrete token types. Each generated variant
2602/// gets `#[arcane]` → `#[target_feature(enable = "avx2,fma,...")]`, which
2603/// unlocks the compiler's auto-vectorizer for that feature set.
2604///
2605/// On x86-64 with the `_v3` variant (AVX2+FMA), that loop compiles to
2606/// `vfmadd231ps` — fused multiply-add on 8 floats per cycle. On aarch64
2607/// with NEON, you get `fmla`. The `_scalar` fallback compiles without any
2608/// SIMD target features, as a safety net for unknown hardware.
2609///
2610/// # Chunks + remainder
2611///
2612/// The classic data-processing pattern works naturally:
2613///
2614/// ```rust,ignore
2615/// #[autoversion]
2616/// fn normalize(_token: SimdToken, data: &mut [f32], scale: f32) {
2617///     // Compiler auto-vectorizes this — no manual SIMD needed.
2618///     // On v3, this becomes vdivps + vmulps on 8 floats at a time.
2619///     for x in data.iter_mut() {
2620///         *x = (*x - 128.0) * scale;
2621///     }
2622/// }
2623/// ```
2624///
2625/// If you want explicit control over chunk boundaries (e.g., for
2626/// accumulator patterns), that works too:
2627///
2628/// ```rust,ignore
2629/// #[autoversion]
2630/// fn dot_product(_token: SimdToken, a: &[f32], b: &[f32]) -> f32 {
2631///     let n = a.len().min(b.len());
2632///     let mut sum = 0.0f32;
2633///     for i in 0..n {
2634///         sum += a[i] * b[i];
2635///     }
2636///     sum
2637/// }
2638/// ```
2639///
2640/// The compiler decides the chunk size based on the target features of each
2641/// variant (8 floats for AVX2, 4 for NEON, 1 for scalar).
2642///
2643/// # What gets generated
2644///
2645/// With default tiers, `#[autoversion] fn process(_t: SimdToken, data: &[f32]) -> f32`
2646/// expands to:
2647///
2648/// - `process_v4(token: X64V4Token, ...)` — AVX-512 (behind `#[cfg(feature = "avx512")]`)
2649/// - `process_v3(token: X64V3Token, ...)` — AVX2+FMA
2650/// - `process_neon(token: NeonToken, ...)` — aarch64 NEON
2651/// - `process_wasm128(token: Wasm128Token, ...)` — WASM SIMD
2652/// - `process_scalar(token: ScalarToken, ...)` — no SIMD, always available
2653/// - `process(data: &[f32]) -> f32` — **dispatcher** (SimdToken param removed)
2654///
2655/// Each non-scalar variant is wrapped in `#[arcane]` (for `#[target_feature]`)
2656/// and `#[cfg(target_arch = ...)]`. The dispatcher does runtime CPU feature
2657/// detection via `Token::summon()` and calls the best match. When compiled
2658/// with `-C target-cpu=native`, the detection is elided by the compiler.
2659///
2660/// The suffixed variants are real, visible sibling functions. You can call
2661/// them individually, and they work with `incant!` for manual dispatch.
2662///
2663/// # SimdToken replacement
2664///
2665/// `#[autoversion]` replaces the `SimdToken` type annotation in the function
2666/// signature with the concrete token type for each variant (e.g.,
2667/// `archmage::X64V3Token`). Only the parameter's type changes — the function
2668/// body is never reparsed, which keeps compile times low.
2669///
2670/// The token variable (whatever you named it — `token`, `_token`, `_t`)
2671/// keeps working in the body because its type comes from the signature.
2672/// So `f32x8::from_array(token, ...)` works — `token` is now an `X64V3Token`
2673/// which satisfies the same trait bounds as `SimdToken`.
2674///
2675/// `#[magetypes]` takes a different approach: it replaces the text `Token`
2676/// everywhere in the function — signature and body — via string substitution.
2677/// Use `#[magetypes]` when you need body-level type substitution (e.g.,
2678/// `Token`-dependent constants or type aliases that differ per variant).
2679/// Use `#[autoversion]` when you want compiler auto-vectorization of scalar
2680/// code with zero boilerplate.
2681///
2682/// # Benchmarking
2683///
2684/// Measure the speedup with a side-by-side comparison. The generated
2685/// `_scalar` variant serves as the baseline; the dispatcher picks the
2686/// best available:
2687///
2688/// ```rust,ignore
2689/// use criterion::{Criterion, black_box, criterion_group, criterion_main};
2690/// use archmage::SimdToken;
2691///
2692/// #[autoversion]
2693/// fn sum_squares(_token: SimdToken, data: &[f32]) -> f32 {
2694///     data.iter().map(|&x| x * x).fold(0.0f32, |a, b| a + b)
2695/// }
2696///
2697/// fn bench(c: &mut Criterion) {
2698///     let data: Vec<f32> = (0..4096).map(|i| i as f32 * 0.01).collect();
2699///     let mut group = c.benchmark_group("sum_squares");
2700///
2701///     // Dispatched — picks best available at runtime
2702///     group.bench_function("dispatched", |b| {
2703///         b.iter(|| sum_squares(black_box(&data)))
2704///     });
2705///
2706///     // Scalar baseline — no target_feature, no auto-vectorization
2707///     group.bench_function("scalar", |b| {
2708///         b.iter(|| sum_squares_scalar(archmage::ScalarToken, black_box(&data)))
2709///     });
2710///
2711///     // Specific tier (useful for isolating which tier wins)
2712///     #[cfg(target_arch = "x86_64")]
2713///     if let Some(t) = archmage::X64V3Token::summon() {
2714///         group.bench_function("v3_avx2_fma", |b| {
2715///             b.iter(|| sum_squares_v3(t, black_box(&data)));
2716///         });
2717///     }
2718///
2719///     group.finish();
2720/// }
2721///
2722/// criterion_group!(benches, bench);
2723/// criterion_main!(benches);
2724/// ```
2725///
2726/// For a tight numeric loop on x86-64, the `_v3` variant (AVX2+FMA)
2727/// typically runs 4-8x faster than `_scalar` because `#[target_feature]`
2728/// unlocks auto-vectorization that the baseline build can't use.
2729///
2730/// # Explicit tiers
2731///
2732/// ```rust,ignore
2733/// #[autoversion(v3, v4, v4x, neon, arm_v2, wasm128)]
2734/// fn process(_token: SimdToken, data: &[f32]) -> f32 {
2735///     // ...
2736/// }
2737/// ```
2738///
2739/// `scalar` is always included implicitly.
2740///
2741/// Default tiers (when no list given): `v4`, `v3`, `neon`, `wasm128`, `scalar`.
2742///
2743/// Known tiers: `v1`, `v2`, `v3`, `v3_crypto`, `v4`, `v4x`, `neon`,
2744/// `neon_aes`, `neon_sha3`, `neon_crc`, `arm_v2`, `arm_v3`, `wasm128`,
2745/// `wasm128_relaxed`, `x64_crypto`, `scalar`.
2746///
2747/// # Methods with self receivers
2748///
2749/// For inherent methods, pass `_self = Type` and use `_self` instead of
2750/// `self` in the body:
2751///
2752/// ```rust,ignore
2753/// impl ImageBuffer {
2754///     #[autoversion(_self = ImageBuffer)]
2755///     fn normalize(&mut self, token: SimdToken, gamma: f32) {
2756///         for pixel in &mut _self.data {
2757///             *pixel = (*pixel / 255.0).powf(gamma);
2758///         }
2759///     }
2760/// }
2761///
2762/// // Call normally — no token:
2763/// buffer.normalize(2.2);
2764/// ```
2765///
2766/// All receiver types work: `self`, `&self`, `&mut self`. Non-scalar variants
2767/// get `#[arcane(_self = Type)]` which handles the inner-function transform.
2768/// The scalar variant gets `let _self = self;` so the body's `_self`
2769/// references compile without `#[arcane]`.
2770///
2771/// # Trait methods
2772///
2773/// Trait methods can't use `#[autoversion]` directly because proc macro
2774/// attributes on trait impl items can't expand to multiple sibling functions.
2775/// Use the delegation pattern:
2776///
2777/// ```rust,ignore
2778/// trait Processor {
2779///     fn process(&self, data: &[f32]) -> f32;
2780/// }
2781///
2782/// impl Processor for MyType {
2783///     fn process(&self, data: &[f32]) -> f32 {
2784///         self.process_impl(data) // delegate to autoversioned method
2785///     }
2786/// }
2787///
2788/// impl MyType {
2789///     #[autoversion(_self = MyType)]
2790///     fn process_impl(&self, token: SimdToken, data: &[f32]) -> f32 {
2791///         _self.weights.iter().zip(data).map(|(w, d)| w * d).sum()
2792///     }
2793/// }
2794/// ```
2795///
2796/// # Comparison with `#[magetypes]` + `incant!`
2797///
2798/// | | `#[autoversion]` | `#[magetypes]` + `incant!` |
2799/// |---|---|---|
2800/// | Placeholder | `SimdToken` | `Token` |
2801/// | Generates variants | Yes | Yes (magetypes) |
2802/// | Generates dispatcher | Yes | No (you write `incant!`) |
2803/// | Best for | Scalar auto-vectorization | Explicit SIMD with typed vectors |
2804/// | Lines of code | 1 attribute | 2+ (magetypes + incant + arcane) |
2805///
2806/// Use `#[autoversion]` for scalar loops you want auto-vectorized. Use
2807/// `#[magetypes]` + `incant!` when you need `f32x8`, `u8x32`, and
2808/// hand-tuned SIMD code per architecture
2809#[proc_macro_attribute]
2810pub fn autoversion(attr: TokenStream, item: TokenStream) -> TokenStream {
2811    let args = parse_macro_input!(attr as AutoversionArgs);
2812    let input_fn = parse_macro_input!(item as LightFn);
2813    autoversion_impl(input_fn, args)
2814}
2815
2816// =============================================================================
2817// Unit tests for token/trait recognition maps
2818// =============================================================================
2819
2820#[cfg(test)]
2821mod tests {
2822    use super::*;
2823
2824    use super::generated::{ALL_CONCRETE_TOKENS, ALL_TRAIT_NAMES};
2825    use syn::{ItemFn, ReturnType};
2826
2827    #[test]
2828    fn every_concrete_token_is_in_token_to_features() {
2829        for &name in ALL_CONCRETE_TOKENS {
2830            assert!(
2831                token_to_features(name).is_some(),
2832                "Token `{}` exists in runtime crate but is NOT recognized by \
2833                 token_to_features() in the proc macro. Add it!",
2834                name
2835            );
2836        }
2837    }
2838
2839    #[test]
2840    fn every_trait_is_in_trait_to_features() {
2841        for &name in ALL_TRAIT_NAMES {
2842            assert!(
2843                trait_to_features(name).is_some(),
2844                "Trait `{}` exists in runtime crate but is NOT recognized by \
2845                 trait_to_features() in the proc macro. Add it!",
2846                name
2847            );
2848        }
2849    }
2850
2851    #[test]
2852    fn token_aliases_map_to_same_features() {
2853        // Desktop64 = X64V3Token
2854        assert_eq!(
2855            token_to_features("Desktop64"),
2856            token_to_features("X64V3Token"),
2857            "Desktop64 and X64V3Token should map to identical features"
2858        );
2859
2860        // Server64 = X64V4Token = Avx512Token
2861        assert_eq!(
2862            token_to_features("Server64"),
2863            token_to_features("X64V4Token"),
2864            "Server64 and X64V4Token should map to identical features"
2865        );
2866        assert_eq!(
2867            token_to_features("X64V4Token"),
2868            token_to_features("Avx512Token"),
2869            "X64V4Token and Avx512Token should map to identical features"
2870        );
2871
2872        // Arm64 = NeonToken
2873        assert_eq!(
2874            token_to_features("Arm64"),
2875            token_to_features("NeonToken"),
2876            "Arm64 and NeonToken should map to identical features"
2877        );
2878    }
2879
2880    #[test]
2881    fn trait_to_features_includes_tokens_as_bounds() {
2882        // Tier tokens should also work as trait bounds
2883        // (for `impl X64V3Token` patterns, even though Rust won't allow it,
2884        // the macro processes AST before type checking)
2885        let tier_tokens = [
2886            "X64V2Token",
2887            "X64CryptoToken",
2888            "X64V3Token",
2889            "Desktop64",
2890            "Avx2FmaToken",
2891            "X64V4Token",
2892            "Avx512Token",
2893            "Server64",
2894            "X64V4xToken",
2895            "Avx512Fp16Token",
2896            "NeonToken",
2897            "Arm64",
2898            "NeonAesToken",
2899            "NeonSha3Token",
2900            "NeonCrcToken",
2901            "Arm64V2Token",
2902            "Arm64V3Token",
2903        ];
2904
2905        for &name in &tier_tokens {
2906            assert!(
2907                trait_to_features(name).is_some(),
2908                "Tier token `{}` should also be recognized in trait_to_features() \
2909                 for use as a generic bound. Add it!",
2910                name
2911            );
2912        }
2913    }
2914
2915    #[test]
2916    fn trait_features_are_cumulative() {
2917        // HasX64V4 should include all HasX64V2 features plus more
2918        let v2_features = trait_to_features("HasX64V2").unwrap();
2919        let v4_features = trait_to_features("HasX64V4").unwrap();
2920
2921        for &f in v2_features {
2922            assert!(
2923                v4_features.contains(&f),
2924                "HasX64V4 should include v2 feature `{}` but doesn't",
2925                f
2926            );
2927        }
2928
2929        // v4 should have more features than v2
2930        assert!(
2931            v4_features.len() > v2_features.len(),
2932            "HasX64V4 should have more features than HasX64V2"
2933        );
2934    }
2935
2936    #[test]
2937    fn x64v3_trait_features_include_v2() {
2938        // X64V3Token as trait bound should include v2 features
2939        let v2 = trait_to_features("HasX64V2").unwrap();
2940        let v3 = trait_to_features("X64V3Token").unwrap();
2941
2942        for &f in v2 {
2943            assert!(
2944                v3.contains(&f),
2945                "X64V3Token trait features should include v2 feature `{}` but don't",
2946                f
2947            );
2948        }
2949    }
2950
2951    #[test]
2952    fn has_neon_aes_includes_neon() {
2953        let neon = trait_to_features("HasNeon").unwrap();
2954        let neon_aes = trait_to_features("HasNeonAes").unwrap();
2955
2956        for &f in neon {
2957            assert!(
2958                neon_aes.contains(&f),
2959                "HasNeonAes should include NEON feature `{}`",
2960                f
2961            );
2962        }
2963    }
2964
2965    #[test]
2966    fn no_removed_traits_are_recognized() {
2967        // These traits were removed in 0.3.0 and should NOT be recognized
2968        let removed = [
2969            "HasSse",
2970            "HasSse2",
2971            "HasSse41",
2972            "HasSse42",
2973            "HasAvx",
2974            "HasAvx2",
2975            "HasFma",
2976            "HasAvx512f",
2977            "HasAvx512bw",
2978            "HasAvx512vl",
2979            "HasAvx512vbmi2",
2980            "HasSve",
2981            "HasSve2",
2982        ];
2983
2984        for &name in &removed {
2985            assert!(
2986                trait_to_features(name).is_none(),
2987                "Removed trait `{}` should NOT be in trait_to_features(). \
2988                 It was removed in 0.3.0 — users should migrate to tier traits.",
2989                name
2990            );
2991        }
2992    }
2993
2994    #[test]
2995    fn no_nonexistent_tokens_are_recognized() {
2996        // These tokens don't exist and should NOT be recognized
2997        let fake = [
2998            "SveToken",
2999            "Sve2Token",
3000            "Avx512VnniToken",
3001            "X64V4ModernToken",
3002            "NeonFp16Token",
3003        ];
3004
3005        for &name in &fake {
3006            assert!(
3007                token_to_features(name).is_none(),
3008                "Non-existent token `{}` should NOT be in token_to_features()",
3009                name
3010            );
3011        }
3012    }
3013
3014    #[test]
3015    fn featureless_traits_are_not_in_registries() {
3016        // SimdToken and IntoConcreteToken should NOT be in any feature registry
3017        // because they don't map to CPU features
3018        for &name in FEATURELESS_TRAIT_NAMES {
3019            assert!(
3020                token_to_features(name).is_none(),
3021                "`{}` should NOT be in token_to_features() — it has no CPU features",
3022                name
3023            );
3024            assert!(
3025                trait_to_features(name).is_none(),
3026                "`{}` should NOT be in trait_to_features() — it has no CPU features",
3027                name
3028            );
3029        }
3030    }
3031
3032    #[test]
3033    fn find_featureless_trait_detects_simdtoken() {
3034        let names = vec!["SimdToken".to_string()];
3035        assert_eq!(find_featureless_trait(&names), Some("SimdToken"));
3036
3037        let names = vec!["IntoConcreteToken".to_string()];
3038        assert_eq!(find_featureless_trait(&names), Some("IntoConcreteToken"));
3039
3040        // Feature-bearing traits should NOT be detected
3041        let names = vec!["HasX64V2".to_string()];
3042        assert_eq!(find_featureless_trait(&names), None);
3043
3044        let names = vec!["HasNeon".to_string()];
3045        assert_eq!(find_featureless_trait(&names), None);
3046
3047        // Mixed: if SimdToken is among real traits, still detected
3048        let names = vec!["SimdToken".to_string(), "HasX64V2".to_string()];
3049        assert_eq!(find_featureless_trait(&names), Some("SimdToken"));
3050    }
3051
3052    #[test]
3053    fn arm64_v2_v3_traits_are_cumulative() {
3054        let v2_features = trait_to_features("HasArm64V2").unwrap();
3055        let v3_features = trait_to_features("HasArm64V3").unwrap();
3056
3057        for &f in v2_features {
3058            assert!(
3059                v3_features.contains(&f),
3060                "HasArm64V3 should include v2 feature `{}` but doesn't",
3061                f
3062            );
3063        }
3064
3065        assert!(
3066            v3_features.len() > v2_features.len(),
3067            "HasArm64V3 should have more features than HasArm64V2"
3068        );
3069    }
3070
3071    // =========================================================================
3072    // autoversion — argument parsing
3073    // =========================================================================
3074
3075    #[test]
3076    fn autoversion_args_empty() {
3077        let args: AutoversionArgs = syn::parse_str("").unwrap();
3078        assert!(args.self_type.is_none());
3079        assert!(args.tiers.is_none());
3080    }
3081
3082    #[test]
3083    fn autoversion_args_single_tier() {
3084        let args: AutoversionArgs = syn::parse_str("v3").unwrap();
3085        assert!(args.self_type.is_none());
3086        assert_eq!(args.tiers.as_ref().unwrap(), &["v3"]);
3087    }
3088
3089    #[test]
3090    fn autoversion_args_tiers_only() {
3091        let args: AutoversionArgs = syn::parse_str("v3, v4, neon").unwrap();
3092        assert!(args.self_type.is_none());
3093        let tiers = args.tiers.unwrap();
3094        assert_eq!(tiers, vec!["v3", "v4", "neon"]);
3095    }
3096
3097    #[test]
3098    fn autoversion_args_many_tiers() {
3099        let args: AutoversionArgs =
3100            syn::parse_str("v1, v2, v3, v4, v4x, neon, arm_v2, wasm128").unwrap();
3101        assert_eq!(
3102            args.tiers.unwrap(),
3103            vec!["v1", "v2", "v3", "v4", "v4x", "neon", "arm_v2", "wasm128"]
3104        );
3105    }
3106
3107    #[test]
3108    fn autoversion_args_trailing_comma() {
3109        let args: AutoversionArgs = syn::parse_str("v3, v4,").unwrap();
3110        assert_eq!(args.tiers.as_ref().unwrap(), &["v3", "v4"]);
3111    }
3112
3113    #[test]
3114    fn autoversion_args_self_only() {
3115        let args: AutoversionArgs = syn::parse_str("_self = MyType").unwrap();
3116        assert!(args.self_type.is_some());
3117        assert!(args.tiers.is_none());
3118    }
3119
3120    #[test]
3121    fn autoversion_args_self_and_tiers() {
3122        let args: AutoversionArgs = syn::parse_str("_self = MyType, v3, neon").unwrap();
3123        assert!(args.self_type.is_some());
3124        let tiers = args.tiers.unwrap();
3125        assert_eq!(tiers, vec!["v3", "neon"]);
3126    }
3127
3128    #[test]
3129    fn autoversion_args_tiers_then_self() {
3130        // _self can appear after tier names
3131        let args: AutoversionArgs = syn::parse_str("v3, neon, _self = MyType").unwrap();
3132        assert!(args.self_type.is_some());
3133        let tiers = args.tiers.unwrap();
3134        assert_eq!(tiers, vec!["v3", "neon"]);
3135    }
3136
3137    #[test]
3138    fn autoversion_args_self_with_path_type() {
3139        let args: AutoversionArgs = syn::parse_str("_self = crate::MyType").unwrap();
3140        assert!(args.self_type.is_some());
3141        assert!(args.tiers.is_none());
3142    }
3143
3144    #[test]
3145    fn autoversion_args_self_with_generic_type() {
3146        let args: AutoversionArgs = syn::parse_str("_self = Vec<u8>").unwrap();
3147        assert!(args.self_type.is_some());
3148        let ty_str = args.self_type.unwrap().to_token_stream().to_string();
3149        assert!(ty_str.contains("Vec"), "Expected Vec<u8>, got: {}", ty_str);
3150    }
3151
3152    #[test]
3153    fn autoversion_args_self_trailing_comma() {
3154        let args: AutoversionArgs = syn::parse_str("_self = MyType,").unwrap();
3155        assert!(args.self_type.is_some());
3156        assert!(args.tiers.is_none());
3157    }
3158
3159    // =========================================================================
3160    // autoversion — find_simd_token_param
3161    // =========================================================================
3162
3163    #[test]
3164    fn find_simd_token_param_first_position() {
3165        let f: ItemFn =
3166            syn::parse_str("fn process(token: SimdToken, data: &[f32]) -> f32 {}").unwrap();
3167        let param = find_simd_token_param(&f.sig).unwrap();
3168        assert_eq!(param.index, 0);
3169        assert_eq!(param.ident, "token");
3170    }
3171
3172    #[test]
3173    fn find_simd_token_param_second_position() {
3174        let f: ItemFn =
3175            syn::parse_str("fn process(data: &[f32], token: SimdToken) -> f32 {}").unwrap();
3176        let param = find_simd_token_param(&f.sig).unwrap();
3177        assert_eq!(param.index, 1);
3178        assert_eq!(param.ident, "token");
3179    }
3180
3181    #[test]
3182    fn find_simd_token_param_underscore_prefix() {
3183        let f: ItemFn =
3184            syn::parse_str("fn process(_token: SimdToken, data: &[f32]) -> f32 {}").unwrap();
3185        let param = find_simd_token_param(&f.sig).unwrap();
3186        assert_eq!(param.index, 0);
3187        assert_eq!(param.ident, "_token");
3188    }
3189
3190    #[test]
3191    fn find_simd_token_param_wildcard() {
3192        let f: ItemFn = syn::parse_str("fn process(_: SimdToken, data: &[f32]) -> f32 {}").unwrap();
3193        let param = find_simd_token_param(&f.sig).unwrap();
3194        assert_eq!(param.index, 0);
3195        assert_eq!(param.ident, "__autoversion_token");
3196    }
3197
3198    #[test]
3199    fn find_simd_token_param_not_found() {
3200        let f: ItemFn = syn::parse_str("fn process(data: &[f32]) -> f32 {}").unwrap();
3201        assert!(find_simd_token_param(&f.sig).is_none());
3202    }
3203
3204    #[test]
3205    fn find_simd_token_param_no_params() {
3206        let f: ItemFn = syn::parse_str("fn process() {}").unwrap();
3207        assert!(find_simd_token_param(&f.sig).is_none());
3208    }
3209
3210    #[test]
3211    fn find_simd_token_param_concrete_token_not_matched() {
3212        // autoversion looks specifically for SimdToken, not concrete tokens
3213        let f: ItemFn =
3214            syn::parse_str("fn process(token: X64V3Token, data: &[f32]) -> f32 {}").unwrap();
3215        assert!(find_simd_token_param(&f.sig).is_none());
3216    }
3217
3218    #[test]
3219    fn find_simd_token_param_scalar_token_not_matched() {
3220        let f: ItemFn =
3221            syn::parse_str("fn process(token: ScalarToken, data: &[f32]) -> f32 {}").unwrap();
3222        assert!(find_simd_token_param(&f.sig).is_none());
3223    }
3224
3225    #[test]
3226    fn find_simd_token_param_among_many() {
3227        let f: ItemFn = syn::parse_str(
3228            "fn process(a: i32, b: f64, token: SimdToken, c: &str, d: bool) -> f32 {}",
3229        )
3230        .unwrap();
3231        let param = find_simd_token_param(&f.sig).unwrap();
3232        assert_eq!(param.index, 2);
3233        assert_eq!(param.ident, "token");
3234    }
3235
3236    #[test]
3237    fn find_simd_token_param_with_generics() {
3238        let f: ItemFn =
3239            syn::parse_str("fn process<T: Clone>(token: SimdToken, data: &[T]) -> T {}").unwrap();
3240        let param = find_simd_token_param(&f.sig).unwrap();
3241        assert_eq!(param.index, 0);
3242        assert_eq!(param.ident, "token");
3243    }
3244
3245    #[test]
3246    fn find_simd_token_param_with_where_clause() {
3247        let f: ItemFn = syn::parse_str(
3248            "fn process<T>(token: SimdToken, data: &[T]) -> T where T: Copy + Default {}",
3249        )
3250        .unwrap();
3251        let param = find_simd_token_param(&f.sig).unwrap();
3252        assert_eq!(param.index, 0);
3253    }
3254
3255    #[test]
3256    fn find_simd_token_param_with_lifetime() {
3257        let f: ItemFn =
3258            syn::parse_str("fn process<'a>(token: SimdToken, data: &'a [f32]) -> &'a f32 {}")
3259                .unwrap();
3260        let param = find_simd_token_param(&f.sig).unwrap();
3261        assert_eq!(param.index, 0);
3262    }
3263
3264    // =========================================================================
3265    // autoversion — tier resolution
3266    // =========================================================================
3267
3268    #[test]
3269    fn autoversion_default_tiers_all_resolve() {
3270        let names: Vec<String> = DEFAULT_TIER_NAMES.iter().map(|s| s.to_string()).collect();
3271        let tiers = resolve_tiers(&names, proc_macro2::Span::call_site()).unwrap();
3272        assert!(!tiers.is_empty());
3273        // scalar should be present
3274        assert!(tiers.iter().any(|t| t.name == "scalar"));
3275    }
3276
3277    #[test]
3278    fn autoversion_scalar_always_appended() {
3279        let names = vec!["v3".to_string(), "neon".to_string()];
3280        let tiers = resolve_tiers(&names, proc_macro2::Span::call_site()).unwrap();
3281        assert!(
3282            tiers.iter().any(|t| t.name == "scalar"),
3283            "scalar must be auto-appended"
3284        );
3285    }
3286
3287    #[test]
3288    fn autoversion_scalar_not_duplicated() {
3289        let names = vec!["v3".to_string(), "scalar".to_string()];
3290        let tiers = resolve_tiers(&names, proc_macro2::Span::call_site()).unwrap();
3291        let scalar_count = tiers.iter().filter(|t| t.name == "scalar").count();
3292        assert_eq!(scalar_count, 1, "scalar must not be duplicated");
3293    }
3294
3295    #[test]
3296    fn autoversion_tiers_sorted_by_priority() {
3297        let names = vec!["neon".to_string(), "v4".to_string(), "v3".to_string()];
3298        let tiers = resolve_tiers(&names, proc_macro2::Span::call_site()).unwrap();
3299        // v4 (priority 40) > v3 (30) > neon (20) > scalar (0)
3300        let priorities: Vec<u32> = tiers.iter().map(|t| t.priority).collect();
3301        for window in priorities.windows(2) {
3302            assert!(
3303                window[0] >= window[1],
3304                "Tiers not sorted by priority: {:?}",
3305                priorities
3306            );
3307        }
3308    }
3309
3310    #[test]
3311    fn autoversion_unknown_tier_errors() {
3312        let names = vec!["v3".to_string(), "avx9000".to_string()];
3313        let result = resolve_tiers(&names, proc_macro2::Span::call_site());
3314        match result {
3315            Ok(_) => panic!("Expected error for unknown tier 'avx9000'"),
3316            Err(e) => {
3317                let err_msg = e.to_string();
3318                assert!(
3319                    err_msg.contains("avx9000"),
3320                    "Error should mention unknown tier: {}",
3321                    err_msg
3322                );
3323            }
3324        }
3325    }
3326
3327    #[test]
3328    fn autoversion_all_known_tiers_resolve() {
3329        // Every tier in ALL_TIERS should be findable
3330        for tier in ALL_TIERS {
3331            assert!(
3332                find_tier(tier.name).is_some(),
3333                "Tier '{}' should be findable by name",
3334                tier.name
3335            );
3336        }
3337    }
3338
3339    #[test]
3340    fn autoversion_default_tier_list_is_sensible() {
3341        // Defaults should cover x86, ARM, WASM, and scalar
3342        let names: Vec<String> = DEFAULT_TIER_NAMES.iter().map(|s| s.to_string()).collect();
3343        let tiers = resolve_tiers(&names, proc_macro2::Span::call_site()).unwrap();
3344
3345        let has_x86 = tiers.iter().any(|t| t.target_arch == Some("x86_64"));
3346        let has_arm = tiers.iter().any(|t| t.target_arch == Some("aarch64"));
3347        let has_wasm = tiers.iter().any(|t| t.target_arch == Some("wasm32"));
3348        let has_scalar = tiers.iter().any(|t| t.name == "scalar");
3349
3350        assert!(has_x86, "Default tiers should include an x86_64 tier");
3351        assert!(has_arm, "Default tiers should include an aarch64 tier");
3352        assert!(has_wasm, "Default tiers should include a wasm32 tier");
3353        assert!(has_scalar, "Default tiers should include scalar");
3354    }
3355
3356    // =========================================================================
3357    // autoversion — variant replacement (AST manipulation)
3358    // =========================================================================
3359
3360    /// Mirrors what `autoversion_impl` does for a single variant: parse an
3361    /// ItemFn (for test convenience), rename it, swap the SimdToken param
3362    /// type, optionally inject the `_self` preamble for scalar+self.
3363    fn do_variant_replacement(func: &str, tier_name: &str, has_self: bool) -> ItemFn {
3364        let mut f: ItemFn = syn::parse_str(func).unwrap();
3365        let fn_name = f.sig.ident.to_string();
3366
3367        let tier = find_tier(tier_name).unwrap();
3368
3369        // Rename
3370        f.sig.ident = format_ident!("{}_{}", fn_name, tier.suffix);
3371
3372        // Find and replace SimdToken param type
3373        let token_idx = find_simd_token_param(&f.sig)
3374            .unwrap_or_else(|| panic!("No SimdToken param in: {}", func))
3375            .index;
3376        let concrete_type: Type = syn::parse_str(tier.token_path).unwrap();
3377        if let FnArg::Typed(pt) = &mut f.sig.inputs[token_idx] {
3378            *pt.ty = concrete_type;
3379        }
3380
3381        // Scalar + self: inject preamble
3382        if tier_name == "scalar" && has_self {
3383            let preamble: syn::Stmt = syn::parse_quote!(let _self = self;);
3384            f.block.stmts.insert(0, preamble);
3385        }
3386
3387        f
3388    }
3389
3390    #[test]
3391    fn variant_replacement_v3_renames_function() {
3392        let f = do_variant_replacement(
3393            "fn process(token: SimdToken, data: &[f32]) -> f32 { 0.0 }",
3394            "v3",
3395            false,
3396        );
3397        assert_eq!(f.sig.ident, "process_v3");
3398    }
3399
3400    #[test]
3401    fn variant_replacement_v3_replaces_token_type() {
3402        let f = do_variant_replacement(
3403            "fn process(token: SimdToken, data: &[f32]) -> f32 { 0.0 }",
3404            "v3",
3405            false,
3406        );
3407        let first_param_ty = match &f.sig.inputs[0] {
3408            FnArg::Typed(pt) => pt.ty.to_token_stream().to_string(),
3409            _ => panic!("Expected typed param"),
3410        };
3411        assert!(
3412            first_param_ty.contains("X64V3Token"),
3413            "Expected X64V3Token, got: {}",
3414            first_param_ty
3415        );
3416    }
3417
3418    #[test]
3419    fn variant_replacement_neon_produces_valid_fn() {
3420        let f = do_variant_replacement(
3421            "fn compute(token: SimdToken, data: &[f32]) -> f32 { 0.0 }",
3422            "neon",
3423            false,
3424        );
3425        assert_eq!(f.sig.ident, "compute_neon");
3426        let first_param_ty = match &f.sig.inputs[0] {
3427            FnArg::Typed(pt) => pt.ty.to_token_stream().to_string(),
3428            _ => panic!("Expected typed param"),
3429        };
3430        assert!(
3431            first_param_ty.contains("NeonToken"),
3432            "Expected NeonToken, got: {}",
3433            first_param_ty
3434        );
3435    }
3436
3437    #[test]
3438    fn variant_replacement_wasm128_produces_valid_fn() {
3439        let f = do_variant_replacement(
3440            "fn compute(_t: SimdToken, data: &[f32]) -> f32 { 0.0 }",
3441            "wasm128",
3442            false,
3443        );
3444        assert_eq!(f.sig.ident, "compute_wasm128");
3445    }
3446
3447    #[test]
3448    fn variant_replacement_scalar_produces_valid_fn() {
3449        let f = do_variant_replacement(
3450            "fn compute(token: SimdToken, data: &[f32]) -> f32 { 0.0 }",
3451            "scalar",
3452            false,
3453        );
3454        assert_eq!(f.sig.ident, "compute_scalar");
3455        let first_param_ty = match &f.sig.inputs[0] {
3456            FnArg::Typed(pt) => pt.ty.to_token_stream().to_string(),
3457            _ => panic!("Expected typed param"),
3458        };
3459        assert!(
3460            first_param_ty.contains("ScalarToken"),
3461            "Expected ScalarToken, got: {}",
3462            first_param_ty
3463        );
3464    }
3465
3466    #[test]
3467    fn variant_replacement_v4_produces_valid_fn() {
3468        let f = do_variant_replacement(
3469            "fn transform(token: SimdToken, data: &mut [f32]) { }",
3470            "v4",
3471            false,
3472        );
3473        assert_eq!(f.sig.ident, "transform_v4");
3474        let first_param_ty = match &f.sig.inputs[0] {
3475            FnArg::Typed(pt) => pt.ty.to_token_stream().to_string(),
3476            _ => panic!("Expected typed param"),
3477        };
3478        assert!(
3479            first_param_ty.contains("X64V4Token"),
3480            "Expected X64V4Token, got: {}",
3481            first_param_ty
3482        );
3483    }
3484
3485    #[test]
3486    fn variant_replacement_v4x_produces_valid_fn() {
3487        let f = do_variant_replacement(
3488            "fn transform(token: SimdToken, data: &mut [f32]) { }",
3489            "v4x",
3490            false,
3491        );
3492        assert_eq!(f.sig.ident, "transform_v4x");
3493    }
3494
3495    #[test]
3496    fn variant_replacement_arm_v2_produces_valid_fn() {
3497        let f = do_variant_replacement(
3498            "fn transform(token: SimdToken, data: &mut [f32]) { }",
3499            "arm_v2",
3500            false,
3501        );
3502        assert_eq!(f.sig.ident, "transform_arm_v2");
3503    }
3504
3505    #[test]
3506    fn variant_replacement_preserves_generics() {
3507        let f = do_variant_replacement(
3508            "fn process<T: Copy + Default>(token: SimdToken, data: &[T]) -> T { T::default() }",
3509            "v3",
3510            false,
3511        );
3512        assert_eq!(f.sig.ident, "process_v3");
3513        // Generic params should still be present
3514        assert!(
3515            !f.sig.generics.params.is_empty(),
3516            "Generics should be preserved"
3517        );
3518    }
3519
3520    #[test]
3521    fn variant_replacement_preserves_where_clause() {
3522        let f = do_variant_replacement(
3523            "fn process<T>(token: SimdToken, data: &[T]) -> T where T: Copy + Default { T::default() }",
3524            "v3",
3525            false,
3526        );
3527        assert!(
3528            f.sig.generics.where_clause.is_some(),
3529            "Where clause should be preserved"
3530        );
3531    }
3532
3533    #[test]
3534    fn variant_replacement_preserves_return_type() {
3535        let f = do_variant_replacement(
3536            "fn process(token: SimdToken, data: &[f32]) -> Vec<f32> { vec![] }",
3537            "neon",
3538            false,
3539        );
3540        let ret = f.sig.output.to_token_stream().to_string();
3541        assert!(
3542            ret.contains("Vec"),
3543            "Return type should be preserved, got: {}",
3544            ret
3545        );
3546    }
3547
3548    #[test]
3549    fn variant_replacement_preserves_multiple_params() {
3550        let f = do_variant_replacement(
3551            "fn process(token: SimdToken, a: &[f32], b: &[f32], scale: f32) -> f32 { 0.0 }",
3552            "v3",
3553            false,
3554        );
3555        // SimdToken → X64V3Token, plus the 3 other params
3556        assert_eq!(f.sig.inputs.len(), 4);
3557    }
3558
3559    #[test]
3560    fn variant_replacement_preserves_no_return_type() {
3561        let f = do_variant_replacement(
3562            "fn transform(token: SimdToken, data: &mut [f32]) { }",
3563            "v3",
3564            false,
3565        );
3566        assert!(
3567            matches!(f.sig.output, ReturnType::Default),
3568            "No return type should remain as Default"
3569        );
3570    }
3571
3572    #[test]
3573    fn variant_replacement_preserves_lifetime_params() {
3574        let f = do_variant_replacement(
3575            "fn process<'a>(token: SimdToken, data: &'a [f32]) -> &'a [f32] { data }",
3576            "v3",
3577            false,
3578        );
3579        assert!(!f.sig.generics.params.is_empty());
3580    }
3581
3582    #[test]
3583    fn variant_replacement_scalar_self_injects_preamble() {
3584        let f = do_variant_replacement(
3585            "fn method(token: SimdToken, data: &[f32]) -> f32 { 0.0 }",
3586            "scalar",
3587            true, // has_self
3588        );
3589        assert_eq!(f.sig.ident, "method_scalar");
3590
3591        // First statement should be `let _self = self;`
3592        let body_str = f.block.to_token_stream().to_string();
3593        assert!(
3594            body_str.contains("let _self = self"),
3595            "Scalar+self variant should have _self preamble, got: {}",
3596            body_str
3597        );
3598    }
3599
3600    #[test]
3601    fn variant_replacement_all_default_tiers_produce_valid_fns() {
3602        let names: Vec<String> = DEFAULT_TIER_NAMES.iter().map(|s| s.to_string()).collect();
3603        let tiers = resolve_tiers(&names, proc_macro2::Span::call_site()).unwrap();
3604
3605        for tier in &tiers {
3606            let f = do_variant_replacement(
3607                "fn process(token: SimdToken, data: &[f32]) -> f32 { 0.0 }",
3608                tier.name,
3609                false,
3610            );
3611            let expected_name = format!("process_{}", tier.suffix);
3612            assert_eq!(
3613                f.sig.ident.to_string(),
3614                expected_name,
3615                "Tier '{}' should produce function '{}'",
3616                tier.name,
3617                expected_name
3618            );
3619        }
3620    }
3621
3622    #[test]
3623    fn variant_replacement_all_known_tiers_produce_valid_fns() {
3624        for tier in ALL_TIERS {
3625            let f = do_variant_replacement(
3626                "fn compute(token: SimdToken, data: &[f32]) -> f32 { 0.0 }",
3627                tier.name,
3628                false,
3629            );
3630            let expected_name = format!("compute_{}", tier.suffix);
3631            assert_eq!(
3632                f.sig.ident.to_string(),
3633                expected_name,
3634                "Tier '{}' should produce function '{}'",
3635                tier.name,
3636                expected_name
3637            );
3638        }
3639    }
3640
3641    #[test]
3642    fn variant_replacement_no_simdtoken_remains() {
3643        for tier in ALL_TIERS {
3644            let f = do_variant_replacement(
3645                "fn compute(token: SimdToken, data: &[f32]) -> f32 { 0.0 }",
3646                tier.name,
3647                false,
3648            );
3649            let full_str = f.to_token_stream().to_string();
3650            assert!(
3651                !full_str.contains("SimdToken"),
3652                "Tier '{}' variant still contains 'SimdToken': {}",
3653                tier.name,
3654                full_str
3655            );
3656        }
3657    }
3658
3659    // =========================================================================
3660    // autoversion — cfg guard and tier descriptor properties
3661    // =========================================================================
3662
3663    #[test]
3664    fn tier_v3_targets_x86_64() {
3665        let tier = find_tier("v3").unwrap();
3666        assert_eq!(tier.target_arch, Some("x86_64"));
3667        assert_eq!(tier.cargo_feature, None);
3668    }
3669
3670    #[test]
3671    fn tier_v4_requires_avx512_feature() {
3672        let tier = find_tier("v4").unwrap();
3673        assert_eq!(tier.target_arch, Some("x86_64"));
3674        assert_eq!(tier.cargo_feature, Some("avx512"));
3675    }
3676
3677    #[test]
3678    fn tier_v4x_requires_avx512_feature() {
3679        let tier = find_tier("v4x").unwrap();
3680        assert_eq!(tier.cargo_feature, Some("avx512"));
3681    }
3682
3683    #[test]
3684    fn tier_neon_targets_aarch64() {
3685        let tier = find_tier("neon").unwrap();
3686        assert_eq!(tier.target_arch, Some("aarch64"));
3687        assert_eq!(tier.cargo_feature, None);
3688    }
3689
3690    #[test]
3691    fn tier_wasm128_targets_wasm32() {
3692        let tier = find_tier("wasm128").unwrap();
3693        assert_eq!(tier.target_arch, Some("wasm32"));
3694        assert_eq!(tier.cargo_feature, None);
3695    }
3696
3697    #[test]
3698    fn tier_scalar_has_no_guards() {
3699        let tier = find_tier("scalar").unwrap();
3700        assert_eq!(tier.target_arch, None);
3701        assert_eq!(tier.cargo_feature, None);
3702        assert_eq!(tier.priority, 0);
3703    }
3704
3705    #[test]
3706    fn tier_priorities_are_consistent() {
3707        // Higher-capability tiers within the same arch should have higher priority
3708        let v2 = find_tier("v2").unwrap();
3709        let v3 = find_tier("v3").unwrap();
3710        let v4 = find_tier("v4").unwrap();
3711        assert!(v4.priority > v3.priority);
3712        assert!(v3.priority > v2.priority);
3713
3714        let neon = find_tier("neon").unwrap();
3715        let arm_v2 = find_tier("arm_v2").unwrap();
3716        let arm_v3 = find_tier("arm_v3").unwrap();
3717        assert!(arm_v3.priority > arm_v2.priority);
3718        assert!(arm_v2.priority > neon.priority);
3719
3720        // scalar is lowest
3721        let scalar = find_tier("scalar").unwrap();
3722        assert!(neon.priority > scalar.priority);
3723        assert!(v2.priority > scalar.priority);
3724    }
3725
3726    // =========================================================================
3727    // autoversion — dispatcher structure
3728    // =========================================================================
3729
3730    #[test]
3731    fn dispatcher_param_removal_free_fn() {
3732        // Simulate what autoversion_impl does: remove the SimdToken param
3733        let f: ItemFn =
3734            syn::parse_str("fn process(token: SimdToken, data: &[f32], scale: f32) -> f32 { 0.0 }")
3735                .unwrap();
3736
3737        let token_param = find_simd_token_param(&f.sig).unwrap();
3738        let mut dispatcher_inputs: Vec<FnArg> = f.sig.inputs.iter().cloned().collect();
3739        dispatcher_inputs.remove(token_param.index);
3740
3741        // Should have 2 params remaining: data, scale
3742        assert_eq!(dispatcher_inputs.len(), 2);
3743
3744        // Neither should be SimdToken
3745        for arg in &dispatcher_inputs {
3746            if let FnArg::Typed(pt) = arg {
3747                let ty_str = pt.ty.to_token_stream().to_string();
3748                assert!(
3749                    !ty_str.contains("SimdToken"),
3750                    "SimdToken should be removed from dispatcher, found: {}",
3751                    ty_str
3752                );
3753            }
3754        }
3755    }
3756
3757    #[test]
3758    fn dispatcher_param_removal_token_only() {
3759        let f: ItemFn = syn::parse_str("fn process(token: SimdToken) -> f32 { 0.0 }").unwrap();
3760
3761        let token_param = find_simd_token_param(&f.sig).unwrap();
3762        let mut dispatcher_inputs: Vec<FnArg> = f.sig.inputs.iter().cloned().collect();
3763        dispatcher_inputs.remove(token_param.index);
3764
3765        // No params left — dispatcher takes no arguments
3766        assert_eq!(dispatcher_inputs.len(), 0);
3767    }
3768
3769    #[test]
3770    fn dispatcher_param_removal_token_last() {
3771        let f: ItemFn =
3772            syn::parse_str("fn process(data: &[f32], scale: f32, token: SimdToken) -> f32 { 0.0 }")
3773                .unwrap();
3774
3775        let token_param = find_simd_token_param(&f.sig).unwrap();
3776        assert_eq!(token_param.index, 2);
3777
3778        let mut dispatcher_inputs: Vec<FnArg> = f.sig.inputs.iter().cloned().collect();
3779        dispatcher_inputs.remove(token_param.index);
3780
3781        assert_eq!(dispatcher_inputs.len(), 2);
3782    }
3783
3784    #[test]
3785    fn dispatcher_dispatch_args_extraction() {
3786        // Test that we correctly extract idents for the dispatch call
3787        let f: ItemFn =
3788            syn::parse_str("fn process(data: &[f32], scale: f32) -> f32 { 0.0 }").unwrap();
3789
3790        let dispatch_args: Vec<String> = f
3791            .sig
3792            .inputs
3793            .iter()
3794            .filter_map(|arg| {
3795                if let FnArg::Typed(PatType { pat, .. }) = arg {
3796                    if let syn::Pat::Ident(pi) = pat.as_ref() {
3797                        return Some(pi.ident.to_string());
3798                    }
3799                }
3800                None
3801            })
3802            .collect();
3803
3804        assert_eq!(dispatch_args, vec!["data", "scale"]);
3805    }
3806
3807    #[test]
3808    fn dispatcher_wildcard_params_get_renamed() {
3809        let f: ItemFn = syn::parse_str("fn process(_: &[f32], _: f32) -> f32 { 0.0 }").unwrap();
3810
3811        let mut dispatcher_inputs: Vec<FnArg> = f.sig.inputs.iter().cloned().collect();
3812
3813        let mut wild_counter = 0u32;
3814        for arg in &mut dispatcher_inputs {
3815            if let FnArg::Typed(pat_type) = arg {
3816                if matches!(pat_type.pat.as_ref(), syn::Pat::Wild(_)) {
3817                    let ident = format_ident!("__autoversion_wild_{}", wild_counter);
3818                    wild_counter += 1;
3819                    *pat_type.pat = syn::Pat::Ident(syn::PatIdent {
3820                        attrs: vec![],
3821                        by_ref: None,
3822                        mutability: None,
3823                        ident,
3824                        subpat: None,
3825                    });
3826                }
3827            }
3828        }
3829
3830        // Both wildcards should be renamed
3831        assert_eq!(wild_counter, 2);
3832
3833        let names: Vec<String> = dispatcher_inputs
3834            .iter()
3835            .filter_map(|arg| {
3836                if let FnArg::Typed(PatType { pat, .. }) = arg {
3837                    if let syn::Pat::Ident(pi) = pat.as_ref() {
3838                        return Some(pi.ident.to_string());
3839                    }
3840                }
3841                None
3842            })
3843            .collect();
3844
3845        assert_eq!(names, vec!["__autoversion_wild_0", "__autoversion_wild_1"]);
3846    }
3847
3848    // =========================================================================
3849    // autoversion — suffix_path (reused in dispatch)
3850    // =========================================================================
3851
3852    #[test]
3853    fn suffix_path_simple() {
3854        let path: syn::Path = syn::parse_str("process").unwrap();
3855        let suffixed = suffix_path(&path, "v3");
3856        assert_eq!(suffixed.to_token_stream().to_string(), "process_v3");
3857    }
3858
3859    #[test]
3860    fn suffix_path_qualified() {
3861        let path: syn::Path = syn::parse_str("module::process").unwrap();
3862        let suffixed = suffix_path(&path, "neon");
3863        let s = suffixed.to_token_stream().to_string();
3864        assert!(
3865            s.contains("process_neon"),
3866            "Expected process_neon, got: {}",
3867            s
3868        );
3869    }
3870}