1use 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#[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
59fn 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#[derive(Default)]
89struct ArcaneArgs {
90 inline_always: bool,
93 self_type: Option<Type>,
97 stub: bool,
100 nested: bool,
104 import_intrinsics: bool,
106 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 if input.peek(Token![,]) {
136 let _: Token![,] = input.parse()?;
137 }
138 }
139
140 if args.self_type.is_some() {
142 args.nested = true;
143 }
144
145 Ok(args)
146 }
147}
148
149mod 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
157enum TokenTypeInfo {
159 Concrete(String),
161 ImplTrait(Vec<String>),
163 Generic(String),
165}
166
167fn extract_token_type_info(ty: &Type) -> Option<TokenTypeInfo> {
169 match ty {
170 Type::Path(type_path) => {
171 type_path.path.segments.last().map(|seg| {
173 let name = seg.ident.to_string();
174 if token_to_features(&name).is_some() {
176 TokenTypeInfo::Concrete(name)
177 } else {
178 TokenTypeInfo::Generic(name)
180 }
181 })
182 }
183 Type::Reference(type_ref) => {
184 extract_token_type_info(&type_ref.elem)
186 }
187 Type::ImplTrait(impl_trait) => {
188 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
200fn 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
220fn find_generic_bounds(sig: &Signature, type_name: &str) -> Option<Vec<String>> {
222 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 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
253fn 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
274const FEATURELESS_TRAIT_NAMES: &[&str] = &["SimdToken", "IntoConcreteToken"];
278
279fn 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
292fn 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 let as_vec = vec![type_name.clone()];
309 if let Some(name) = find_featureless_trait(&as_vec) {
310 return Some(name);
311 }
312 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
326struct TokenParamInfo {
328 ident: Ident,
330 features: Vec<&'static str>,
332 target_arch: Option<&'static str>,
334 token_type_name: Option<String>,
336 magetypes_namespace: Option<&'static str>,
338}
339
340fn 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
351fn 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
361fn find_token_param(sig: &Signature) -> Option<TokenParamInfo> {
363 for arg in &sig.inputs {
364 match arg {
365 FnArg::Receiver(_) => {
366 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 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 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
425enum SelfReceiver {
427 Owned,
429 Ref,
431 RefMut,
433}
434
435fn 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 }
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
469fn arcane_impl(mut input_fn: LightFn, macro_name: &str, args: ArcaneArgs) -> TokenStream {
471 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 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 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 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 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 let target_feature_attrs: Vec<Attribute> = features
554 .iter()
555 .map(|feature| parse_quote!(#[target_feature(enable = #feature)]))
556 .collect();
557
558 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 let inline_attr: Attribute = if args.inline_always {
578 parse_quote!(#[inline(always)])
579 } else {
580 parse_quote!(#[inline])
581 };
582
583 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
617fn 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 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 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 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
709fn 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 let has_self_receiver = inputs
753 .first()
754 .map(|arg| matches!(arg, FnArg::Receiver(_)))
755 .unwrap_or(false);
756
757 let sibling_sig_inputs = inputs;
761
762 let sibling_call = if has_self_receiver {
764 let other_args: Vec<proc_macro2::TokenStream> = inputs
766 .iter()
767 .skip(1) .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 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 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, })
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 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 let wrapper_fn = quote! {
833 #[cfg(target_arch = #arch)]
834 #(#attrs)*
835 #vis #sig {
836 unsafe { #sibling_call }
841 }
842 };
843
844 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 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 unsafe { #sibling_call }
889 }
890 };
891
892 quote! {
893 #sibling_fn
894 #wrapper_fn
895 }
896 };
897
898 expanded.into()
899}
900
901fn 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 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 let inner_params: Vec<proc_macro2::TokenStream> = inputs
942 .iter()
943 .map(|arg| match arg {
944 FnArg::Receiver(_) => {
945 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 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)), })
977 .collect();
978
979 let inner_fn_name = format_ident!("__simd_inner_{}", fn_name);
980
981 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 #[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 #[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 unsafe { #inner_fn_name(#(#inner_args),*) }
1043 }
1044
1045 #stub
1046 }
1047 } else {
1048 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 unsafe { #inner_fn_name(#(#inner_args),*) }
1060 }
1061 }
1062 };
1063
1064 expanded.into()
1065}
1066
1067#[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#[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#[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#[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#[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#[derive(Default)]
1359struct RiteArgs {
1360 stub: bool,
1363 import_intrinsics: bool,
1365 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
1400fn rite_impl(mut input_fn: LightFn, args: RiteArgs) -> TokenStream {
1402 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 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 let target_feature_attrs: Vec<Attribute> = features
1439 .iter()
1440 .map(|feature| parse_quote!(#[target_feature(enable = #feature)]))
1441 .collect();
1442
1443 let inline_attr: Attribute = parse_quote!(#[inline]);
1445
1446 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 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 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 quote!(#input_fn).into()
1502 }
1503}
1504
1505#[proc_macro_attribute]
1564pub fn magetypes(attr: TokenStream, item: TokenStream) -> TokenStream {
1565 let input_fn = parse_macro_input!(item as LightFn);
1566
1567 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 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 let fn_str = input_fn.to_token_stream().to_string();
1599
1600 let mut variants = Vec::new();
1601
1602 for tier in tiers {
1603 let suffixed_name = format!("{}_{}", fn_name, tier.suffix);
1605
1606 let mut variant_str = fn_str.clone();
1608
1609 variant_str = variant_str.replacen(&fn_name.to_string(), &suffixed_name, 1);
1611
1612 variant_str = variant_str.replace("Token", tier.token_path);
1614
1615 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 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! {} }
1645 };
1646
1647 variants.push(if tier.name != "scalar" {
1648 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 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
1676struct TierDescriptor {
1686 name: &'static str,
1688 suffix: &'static str,
1690 token_path: &'static str,
1692 as_method: &'static str,
1694 target_arch: Option<&'static str>,
1696 cargo_feature: Option<&'static str>,
1698 priority: u32,
1700}
1701
1702const ALL_TIERS: &[TierDescriptor] = &[
1704 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 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 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 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
1854const DEFAULT_TIER_NAMES: &[&str] = &["v4", "v3", "neon", "wasm128", "scalar"];
1856
1857fn find_tier(name: &str) -> Option<&'static TierDescriptor> {
1859 ALL_TIERS.iter().find(|t| t.name == name)
1860}
1861
1862fn 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 if !tiers.iter().any(|t| t.name == "scalar") {
1884 tiers.push(find_tier("scalar").unwrap());
1885 }
1886
1887 tiers.sort_by(|a, b| b.priority.cmp(&a.priority));
1889
1890 Ok(tiers)
1891}
1892
1893struct IncantInput {
1899 func_path: syn::Path,
1901 args: Vec<syn::Expr>,
1903 with_token: Option<syn::Expr>,
1905 tiers: Option<(Vec<String>, proc_macro2::Span)>,
1907}
1908
1909fn 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 let func_path: syn::Path = input.parse()?;
1923
1924 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 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 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#[proc_macro]
2033pub fn incant(input: TokenStream) -> TokenStream {
2034 let input = parse_macro_input!(input as IncantInput);
2035 incant_impl(input)
2036}
2037
2038#[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#[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 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 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
2092fn 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 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; }
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 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
2175fn 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 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 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
2244struct AutoversionArgs {
2250 self_type: Option<Type>,
2252 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 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
2286struct SimdTokenParamInfo {
2288 index: usize,
2290 #[allow(dead_code)]
2292 ident: Ident,
2293}
2294
2295fn 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
2317fn autoversion_impl(mut input_fn: LightFn, args: AutoversionArgs) -> TokenStream {
2322 let has_self = input_fn
2324 .sig
2325 .inputs
2326 .first()
2327 .is_some_and(|arg| matches!(arg, FnArg::Receiver(_)));
2328
2329 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 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 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 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 let fn_attrs: Vec<Attribute> = input_fn.attrs.drain(..).collect();
2383
2384 let mut variants = Vec::new();
2394
2395 for tier in &tiers {
2396 let mut variant_fn = input_fn.clone();
2397
2398 variant_fn.sig.ident = format_ident!("{}_{}", fn_name, tier.suffix);
2400
2401 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 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 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 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 let mut dispatcher_inputs: Vec<FnArg> = input_fn.sig.inputs.iter().cloned().collect();
2450 dispatcher_inputs.remove(token_param.index);
2451
2452 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 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 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 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 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#[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#[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 assert_eq!(
2855 token_to_features("Desktop64"),
2856 token_to_features("X64V3Token"),
2857 "Desktop64 and X64V3Token should map to identical features"
2858 );
2859
2860 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 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 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 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 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 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 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 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 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 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 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 #[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 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 #[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 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 #[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 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 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 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 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 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 f.sig.ident = format_ident!("{}_{}", fn_name, tier.suffix);
3371
3372 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 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 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 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, );
3589 assert_eq!(f.sig.ident, "method_scalar");
3590
3591 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 #[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 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 let scalar = find_tier("scalar").unwrap();
3722 assert!(neon.priority > scalar.priority);
3723 assert!(v2.priority > scalar.priority);
3724 }
3725
3726 #[test]
3731 fn dispatcher_param_removal_free_fn() {
3732 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 assert_eq!(dispatcher_inputs.len(), 2);
3743
3744 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 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 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 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 #[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}