1use proc_macro::TokenStream;
7use quote::{ToTokens, format_ident, quote, quote_spanned};
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 filter_inline_attrs(attrs: &[Attribute]) -> Vec<&Attribute> {
64 attrs
65 .iter()
66 .filter(|attr| !attr.path().is_ident("inline"))
67 .collect()
68}
69
70fn is_lint_attr(attr: &Attribute) -> bool {
76 let path = attr.path();
77 path.is_ident("allow")
78 || path.is_ident("expect")
79 || path.is_ident("deny")
80 || path.is_ident("warn")
81 || path.is_ident("forbid")
82}
83
84fn filter_lint_attrs(attrs: &[Attribute]) -> Vec<&Attribute> {
90 attrs.iter().filter(|attr| is_lint_attr(attr)).collect()
91}
92
93fn build_turbofish(generics: &syn::Generics) -> proc_macro2::TokenStream {
102 let params: Vec<proc_macro2::TokenStream> = generics
103 .params
104 .iter()
105 .filter_map(|param| match param {
106 GenericParam::Type(tp) => {
107 let ident = &tp.ident;
108 Some(quote! { #ident })
109 }
110 GenericParam::Const(cp) => {
111 let ident = &cp.ident;
112 Some(quote! { #ident })
113 }
114 GenericParam::Lifetime(_) => None,
115 })
116 .collect();
117 if params.is_empty() {
118 quote! {}
119 } else {
120 quote! { ::<#(#params),*> }
121 }
122}
123
124fn replace_self_in_tokens(
129 tokens: proc_macro2::TokenStream,
130 replacement: &Type,
131) -> proc_macro2::TokenStream {
132 let mut result = proc_macro2::TokenStream::new();
133 for tt in tokens {
134 match tt {
135 proc_macro2::TokenTree::Ident(ref ident) if ident == "Self" => {
136 result.extend(replacement.to_token_stream());
137 }
138 proc_macro2::TokenTree::Group(group) => {
139 let new_stream = replace_self_in_tokens(group.stream(), replacement);
140 let mut new_group = proc_macro2::Group::new(group.delimiter(), new_stream);
141 new_group.set_span(group.span());
142 result.extend(std::iter::once(proc_macro2::TokenTree::Group(new_group)));
143 }
144 other => {
145 result.extend(std::iter::once(other));
146 }
147 }
148 }
149 result
150}
151
152#[derive(Default)]
154struct ArcaneArgs {
155 inline_always: bool,
158 self_type: Option<Type>,
162 stub: bool,
165 nested: bool,
169 import_intrinsics: bool,
171 import_magetypes: bool,
174}
175
176impl Parse for ArcaneArgs {
177 fn parse(input: ParseStream) -> syn::Result<Self> {
178 let mut args = ArcaneArgs::default();
179
180 while !input.is_empty() {
181 let ident: Ident = input.parse()?;
182 match ident.to_string().as_str() {
183 "inline_always" => args.inline_always = true,
184 "stub" => args.stub = true,
185 "nested" => args.nested = true,
186 "import_intrinsics" => args.import_intrinsics = true,
187 "import_magetypes" => args.import_magetypes = true,
188 "_self" => {
189 let _: Token![=] = input.parse()?;
190 args.self_type = Some(input.parse()?);
191 }
192 other => {
193 return Err(syn::Error::new(
194 ident.span(),
195 format!("unknown arcane argument: `{}`", other),
196 ));
197 }
198 }
199 if input.peek(Token![,]) {
201 let _: Token![,] = input.parse()?;
202 }
203 }
204
205 if args.self_type.is_some() {
207 args.nested = true;
208 }
209
210 Ok(args)
211 }
212}
213
214mod generated;
217use generated::{
218 canonical_token_to_tier_suffix, tier_to_canonical_token, token_to_arch, token_to_features,
219 token_to_magetypes_namespace, trait_to_arch, trait_to_features, trait_to_magetypes_namespace,
220};
221
222enum TokenTypeInfo {
224 Concrete(String),
226 ImplTrait(Vec<String>),
228 Generic(String),
230}
231
232fn extract_token_type_info(ty: &Type) -> Option<TokenTypeInfo> {
234 match ty {
235 Type::Path(type_path) => {
236 type_path.path.segments.last().map(|seg| {
238 let name = seg.ident.to_string();
239 if token_to_features(&name).is_some() {
241 TokenTypeInfo::Concrete(name)
242 } else {
243 TokenTypeInfo::Generic(name)
245 }
246 })
247 }
248 Type::Reference(type_ref) => {
249 extract_token_type_info(&type_ref.elem)
251 }
252 Type::ImplTrait(impl_trait) => {
253 let traits: Vec<String> = extract_trait_names_from_bounds(&impl_trait.bounds);
255 if traits.is_empty() {
256 None
257 } else {
258 Some(TokenTypeInfo::ImplTrait(traits))
259 }
260 }
261 _ => None,
262 }
263}
264
265fn extract_trait_names_from_bounds(
267 bounds: &syn::punctuated::Punctuated<TypeParamBound, Token![+]>,
268) -> Vec<String> {
269 bounds
270 .iter()
271 .filter_map(|bound| {
272 if let TypeParamBound::Trait(trait_bound) = bound {
273 trait_bound
274 .path
275 .segments
276 .last()
277 .map(|seg| seg.ident.to_string())
278 } else {
279 None
280 }
281 })
282 .collect()
283}
284
285fn find_generic_bounds(sig: &Signature, type_name: &str) -> Option<Vec<String>> {
287 for param in &sig.generics.params {
289 if let GenericParam::Type(type_param) = param
290 && type_param.ident == type_name
291 {
292 let traits = extract_trait_names_from_bounds(&type_param.bounds);
293 if !traits.is_empty() {
294 return Some(traits);
295 }
296 }
297 }
298
299 if let Some(where_clause) = &sig.generics.where_clause {
301 for predicate in &where_clause.predicates {
302 if let syn::WherePredicate::Type(pred_type) = predicate
303 && let Type::Path(type_path) = &pred_type.bounded_ty
304 && let Some(seg) = type_path.path.segments.last()
305 && seg.ident == type_name
306 {
307 let traits = extract_trait_names_from_bounds(&pred_type.bounds);
308 if !traits.is_empty() {
309 return Some(traits);
310 }
311 }
312 }
313 }
314
315 None
316}
317
318fn traits_to_features(trait_names: &[String]) -> Option<Vec<&'static str>> {
320 let mut all_features = Vec::new();
321
322 for trait_name in trait_names {
323 if let Some(features) = trait_to_features(trait_name) {
324 for &feature in features {
325 if !all_features.contains(&feature) {
326 all_features.push(feature);
327 }
328 }
329 }
330 }
331
332 if all_features.is_empty() {
333 None
334 } else {
335 Some(all_features)
336 }
337}
338
339const FEATURELESS_TRAIT_NAMES: &[&str] = &["SimdToken", "IntoConcreteToken"];
343
344fn find_featureless_trait(trait_names: &[String]) -> Option<&'static str> {
347 for name in trait_names {
348 for &featureless in FEATURELESS_TRAIT_NAMES {
349 if name == featureless {
350 return Some(featureless);
351 }
352 }
353 }
354 None
355}
356
357fn diagnose_featureless_token(sig: &Signature) -> Option<&'static str> {
360 for arg in &sig.inputs {
361 if let FnArg::Typed(PatType { ty, .. }) = arg
362 && let Some(info) = extract_token_type_info(ty)
363 {
364 match &info {
365 TokenTypeInfo::ImplTrait(names) => {
366 if let Some(name) = find_featureless_trait(names) {
367 return Some(name);
368 }
369 }
370 TokenTypeInfo::Generic(type_name) => {
371 let as_vec = vec![type_name.clone()];
374 if let Some(name) = find_featureless_trait(&as_vec) {
375 return Some(name);
376 }
377 if let Some(bounds) = find_generic_bounds(sig, type_name)
379 && let Some(name) = find_featureless_trait(&bounds)
380 {
381 return Some(name);
382 }
383 }
384 TokenTypeInfo::Concrete(_) => {}
385 }
386 }
387 }
388 None
389}
390
391struct TokenParamInfo {
393 ident: Ident,
395 features: Vec<&'static str>,
397 target_arch: Option<&'static str>,
399 token_type_name: Option<String>,
401 magetypes_namespace: Option<&'static str>,
403}
404
405fn traits_to_magetypes_namespace(trait_names: &[String]) -> Option<&'static str> {
408 for name in trait_names {
409 if let Some(ns) = trait_to_magetypes_namespace(name) {
410 return Some(ns);
411 }
412 }
413 None
414}
415
416fn traits_to_arch(trait_names: &[String]) -> Option<&'static str> {
418 for name in trait_names {
419 if let Some(arch) = trait_to_arch(name) {
420 return Some(arch);
421 }
422 }
423 None
424}
425
426fn find_token_param(sig: &Signature) -> Option<TokenParamInfo> {
428 for arg in &sig.inputs {
429 match arg {
430 FnArg::Receiver(_) => {
431 continue;
437 }
438 FnArg::Typed(PatType { pat, ty, .. }) => {
439 if let Some(info) = extract_token_type_info(ty) {
440 let (features, arch, token_name, mage_ns) = match info {
441 TokenTypeInfo::Concrete(ref name) => {
442 let features = token_to_features(name).map(|f| f.to_vec());
443 let arch = token_to_arch(name);
444 let ns = token_to_magetypes_namespace(name);
445 (features, arch, Some(name.clone()), ns)
446 }
447 TokenTypeInfo::ImplTrait(ref trait_names) => {
448 let ns = traits_to_magetypes_namespace(trait_names);
449 let arch = traits_to_arch(trait_names);
450 (traits_to_features(trait_names), arch, None, ns)
451 }
452 TokenTypeInfo::Generic(type_name) => {
453 let bounds = find_generic_bounds(sig, &type_name);
455 let features = bounds.as_ref().and_then(|t| traits_to_features(t));
456 let ns = bounds
457 .as_ref()
458 .and_then(|t| traits_to_magetypes_namespace(t));
459 let arch = bounds.as_ref().and_then(|t| traits_to_arch(t));
460 (features, arch, None, ns)
461 }
462 };
463
464 if let Some(features) = features {
465 let ident = match pat.as_ref() {
467 syn::Pat::Ident(pat_ident) => Some(pat_ident.ident.clone()),
468 syn::Pat::Wild(w) => {
469 Some(Ident::new("__archmage_token", w.underscore_token.span))
470 }
471 _ => None,
472 };
473 if let Some(ident) = ident {
474 return Some(TokenParamInfo {
475 ident,
476 features,
477 target_arch: arch,
478 token_type_name: token_name,
479 magetypes_namespace: mage_ns,
480 });
481 }
482 }
483 }
484 }
485 }
486 }
487 None
488}
489
490enum SelfReceiver {
492 Owned,
494 Ref,
496 RefMut,
498}
499
500fn generate_imports(
505 target_arch: Option<&str>,
506 magetypes_namespace: Option<&str>,
507 import_intrinsics: bool,
508 import_magetypes: bool,
509) -> proc_macro2::TokenStream {
510 let mut imports = proc_macro2::TokenStream::new();
511
512 if import_intrinsics && let Some(arch) = target_arch {
513 let arch_ident = format_ident!("{}", arch);
514 imports.extend(quote! {
515 #[allow(unused_imports)]
516 use archmage::intrinsics::#arch_ident::*;
517 });
518 }
520
521 if import_magetypes && let Some(ns) = magetypes_namespace {
522 let ns_ident = format_ident!("{}", ns);
523 imports.extend(quote! {
524 #[allow(unused_imports)]
525 use magetypes::simd::#ns_ident::*;
526 #[allow(unused_imports)]
527 use magetypes::simd::backends::*;
528 });
529 }
530
531 imports
532}
533
534fn arcane_impl(mut input_fn: LightFn, macro_name: &str, args: ArcaneArgs) -> TokenStream {
536 let has_self_receiver = input_fn
538 .sig
539 .inputs
540 .first()
541 .map(|arg| matches!(arg, FnArg::Receiver(_)))
542 .unwrap_or(false);
543
544 if has_self_receiver && args.nested && args.self_type.is_none() {
548 let msg = format!(
549 "{} with self receiver in nested mode requires `_self = Type` argument.\n\
550 Example: #[{}(nested, _self = MyType)]\n\
551 Use `_self` (not `self`) in the function body to refer to self.\n\
552 \n\
553 Alternatively, remove `nested` to use sibling expansion (default), \
554 which handles self/Self naturally.",
555 macro_name, macro_name
556 );
557 return syn::Error::new_spanned(&input_fn.sig, msg)
558 .to_compile_error()
559 .into();
560 }
561
562 let TokenParamInfo {
564 ident: _token_ident,
565 features,
566 target_arch,
567 token_type_name,
568 magetypes_namespace,
569 } = match find_token_param(&input_fn.sig) {
570 Some(result) => result,
571 None => {
572 if let Some(trait_name) = diagnose_featureless_token(&input_fn.sig) {
574 let msg = format!(
575 "`{trait_name}` cannot be used as a token bound in #[{macro_name}] \
576 because it doesn't specify any CPU features.\n\
577 \n\
578 #[{macro_name}] needs concrete features to generate #[target_feature]. \
579 Use a concrete token or a feature trait:\n\
580 \n\
581 Concrete tokens: X64V3Token, Desktop64, NeonToken, Arm64V2Token, ...\n\
582 Feature traits: impl HasX64V2, impl HasNeon, impl HasArm64V3, ..."
583 );
584 return syn::Error::new_spanned(&input_fn.sig, msg)
585 .to_compile_error()
586 .into();
587 }
588 let msg = format!(
589 "{} requires a token parameter. Supported forms:\n\
590 - Concrete: `token: X64V3Token`\n\
591 - impl Trait: `token: impl HasX64V2`\n\
592 - Generic: `fn foo<T: HasX64V2>(token: T, ...)`\n\
593 - With self: `#[{}(_self = Type)] fn method(&self, token: impl HasNeon, ...)`",
594 macro_name, macro_name
595 );
596 return syn::Error::new_spanned(&input_fn.sig, msg)
597 .to_compile_error()
598 .into();
599 }
600 };
601
602 #[cfg(not(feature = "avx512"))]
610 if args.import_intrinsics && features.iter().any(|f| f.starts_with("avx512")) {
611 let token_desc = token_type_name.as_deref().unwrap_or("an AVX-512 token");
612 let msg = format!(
613 "Using {token_desc} with `import_intrinsics` requires the `avx512` feature.\n\
614 \n\
615 Add to your Cargo.toml:\n\
616 \x20 archmage = {{ version = \"...\", features = [\"avx512\"] }}\n\
617 \n\
618 Without it, 512-bit safe memory ops (_mm512_loadu_ps etc.) are not available.\n\
619 If you only need value intrinsics (no memory ops), remove `import_intrinsics`."
620 );
621 return syn::Error::new_spanned(&input_fn.sig, msg)
622 .to_compile_error()
623 .into();
624 }
625
626 let body_imports = generate_imports(
628 target_arch,
629 magetypes_namespace,
630 args.import_intrinsics,
631 args.import_magetypes,
632 );
633 if !body_imports.is_empty() {
634 let original_body = &input_fn.body;
635 input_fn.body = quote! {
636 #body_imports
637 #original_body
638 };
639 }
640
641 let target_feature_attrs: Vec<Attribute> = features
643 .iter()
644 .map(|feature| parse_quote!(#[target_feature(enable = #feature)]))
645 .collect();
646
647 let mut wild_rename_counter = 0u32;
649 for arg in &mut input_fn.sig.inputs {
650 if let FnArg::Typed(pat_type) = arg
651 && matches!(pat_type.pat.as_ref(), syn::Pat::Wild(_))
652 {
653 let ident = format_ident!("__archmage_wild_{}", wild_rename_counter);
654 wild_rename_counter += 1;
655 *pat_type.pat = syn::Pat::Ident(syn::PatIdent {
656 attrs: vec![],
657 by_ref: None,
658 mutability: None,
659 ident,
660 subpat: None,
661 });
662 }
663 }
664
665 let inline_attr: Attribute = if args.inline_always {
667 parse_quote!(#[inline(always)])
668 } else {
669 parse_quote!(#[inline])
670 };
671
672 if target_arch == Some("wasm32") {
676 return arcane_impl_wasm_safe(
677 input_fn,
678 &args,
679 token_type_name,
680 target_feature_attrs,
681 inline_attr,
682 );
683 }
684
685 if args.nested {
686 arcane_impl_nested(
687 input_fn,
688 &args,
689 target_arch,
690 token_type_name,
691 target_feature_attrs,
692 inline_attr,
693 )
694 } else {
695 arcane_impl_sibling(
696 input_fn,
697 &args,
698 target_arch,
699 token_type_name,
700 target_feature_attrs,
701 inline_attr,
702 )
703 }
704}
705
706fn arcane_impl_wasm_safe(
715 input_fn: LightFn,
716 args: &ArcaneArgs,
717 token_type_name: Option<String>,
718 target_feature_attrs: Vec<Attribute>,
719 inline_attr: Attribute,
720) -> TokenStream {
721 let vis = &input_fn.vis;
722 let sig = &input_fn.sig;
723 let fn_name = &sig.ident;
724 let attrs = &input_fn.attrs;
725
726 let token_type_str = token_type_name.as_deref().unwrap_or("UnknownToken");
727
728 let body = if args.self_type.is_some() {
732 let original_body = &input_fn.body;
733 quote! {
734 let _self = self;
735 #original_body
736 }
737 } else {
738 input_fn.body.clone()
739 };
740
741 let mut new_attrs = target_feature_attrs;
743 new_attrs.push(inline_attr);
744 for attr in filter_inline_attrs(attrs) {
745 new_attrs.push(attr.clone());
746 }
747
748 let stub = if args.stub {
749 let stub_args: Vec<proc_macro2::TokenStream> = sig
751 .inputs
752 .iter()
753 .filter_map(|arg| match arg {
754 FnArg::Typed(pat_type) => {
755 if let syn::Pat::Ident(pat_ident) = pat_type.pat.as_ref() {
756 let ident = &pat_ident.ident;
757 Some(quote!(#ident))
758 } else {
759 None
760 }
761 }
762 FnArg::Receiver(_) => None,
763 })
764 .collect();
765
766 quote! {
767 #[cfg(not(target_arch = "wasm32"))]
768 #vis #sig {
769 let _ = (#(#stub_args),*);
770 unreachable!(
771 "BUG: {}() was called but requires {} (target_arch = \"wasm32\"). \
772 {}::summon() returns None on this architecture, so this function \
773 is unreachable in safe code. If you used forge_token_dangerously(), \
774 that is the bug.",
775 stringify!(#fn_name),
776 #token_type_str,
777 #token_type_str,
778 )
779 }
780 }
781 } else {
782 quote! {}
783 };
784
785 let expanded = quote! {
786 #[cfg(target_arch = "wasm32")]
787 #(#new_attrs)*
788 #vis #sig {
789 #body
790 }
791
792 #stub
793 };
794
795 expanded.into()
796}
797
798fn arcane_impl_sibling(
821 input_fn: LightFn,
822 args: &ArcaneArgs,
823 target_arch: Option<&str>,
824 token_type_name: Option<String>,
825 target_feature_attrs: Vec<Attribute>,
826 inline_attr: Attribute,
827) -> TokenStream {
828 let vis = &input_fn.vis;
829 let sig = &input_fn.sig;
830 let fn_name = &sig.ident;
831 let generics = &sig.generics;
832 let where_clause = &generics.where_clause;
833 let inputs = &sig.inputs;
834 let output = &sig.output;
835 let body = &input_fn.body;
836 let attrs = filter_inline_attrs(&input_fn.attrs);
839 let lint_attrs = filter_lint_attrs(&input_fn.attrs);
842
843 let sibling_name = format_ident!("__arcane_{}", fn_name);
844
845 let has_self_receiver = inputs
847 .first()
848 .map(|arg| matches!(arg, FnArg::Receiver(_)))
849 .unwrap_or(false);
850
851 let sibling_sig_inputs = inputs;
855
856 let turbofish = build_turbofish(generics);
858
859 let sibling_call = if has_self_receiver {
861 let other_args: Vec<proc_macro2::TokenStream> = inputs
863 .iter()
864 .skip(1) .filter_map(|arg| {
866 if let FnArg::Typed(pat_type) = arg
867 && let syn::Pat::Ident(pat_ident) = pat_type.pat.as_ref()
868 {
869 let ident = &pat_ident.ident;
870 Some(quote!(#ident))
871 } else {
872 None
873 }
874 })
875 .collect();
876 quote! { self.#sibling_name #turbofish(#(#other_args),*) }
877 } else {
878 let all_args: Vec<proc_macro2::TokenStream> = inputs
880 .iter()
881 .filter_map(|arg| {
882 if let FnArg::Typed(pat_type) = arg
883 && let syn::Pat::Ident(pat_ident) = pat_type.pat.as_ref()
884 {
885 let ident = &pat_ident.ident;
886 Some(quote!(#ident))
887 } else {
888 None
889 }
890 })
891 .collect();
892 quote! { #sibling_name #turbofish(#(#all_args),*) }
893 };
894
895 let stub_args: Vec<proc_macro2::TokenStream> = inputs
897 .iter()
898 .filter_map(|arg| match arg {
899 FnArg::Typed(pat_type) => {
900 if let syn::Pat::Ident(pat_ident) = pat_type.pat.as_ref() {
901 let ident = &pat_ident.ident;
902 Some(quote!(#ident))
903 } else {
904 None
905 }
906 }
907 FnArg::Receiver(_) => None, })
909 .collect();
910
911 let token_type_str = token_type_name.as_deref().unwrap_or("UnknownToken");
912
913 let expanded = if let Some(arch) = target_arch {
914 let sibling_fn = quote! {
918 #[cfg(target_arch = #arch)]
919 #[doc(hidden)]
920 #(#lint_attrs)*
921 #(#target_feature_attrs)*
922 #inline_attr
923 fn #sibling_name #generics (#sibling_sig_inputs) #output #where_clause {
924 #body
925 }
926 };
927
928 let wrapper_fn = quote! {
932 #[cfg(target_arch = #arch)]
933 #(#attrs)*
934 #[inline(always)]
935 #vis #sig {
936 unsafe { #sibling_call }
941 }
942 };
943
944 let stub = if args.stub {
946 quote! {
947 #[cfg(not(target_arch = #arch))]
948 #(#attrs)*
949 #vis #sig {
950 let _ = (#(#stub_args),*);
951 unreachable!(
952 "BUG: {}() was called but requires {} (target_arch = \"{}\"). \
953 {}::summon() returns None on this architecture, so this function \
954 is unreachable in safe code. If you used forge_token_dangerously(), \
955 that is the bug.",
956 stringify!(#fn_name),
957 #token_type_str,
958 #arch,
959 #token_type_str,
960 )
961 }
962 }
963 } else {
964 quote! {}
965 };
966
967 quote! {
968 #sibling_fn
969 #wrapper_fn
970 #stub
971 }
972 } else {
973 let sibling_fn = quote! {
976 #[doc(hidden)]
977 #(#lint_attrs)*
978 #(#target_feature_attrs)*
979 #inline_attr
980 fn #sibling_name #generics (#sibling_sig_inputs) #output #where_clause {
981 #body
982 }
983 };
984
985 let wrapper_fn = quote! {
986 #(#attrs)*
987 #[inline(always)]
988 #vis #sig {
989 unsafe { #sibling_call }
991 }
992 };
993
994 quote! {
995 #sibling_fn
996 #wrapper_fn
997 }
998 };
999
1000 expanded.into()
1001}
1002
1003fn arcane_impl_nested(
1009 input_fn: LightFn,
1010 args: &ArcaneArgs,
1011 target_arch: Option<&str>,
1012 token_type_name: Option<String>,
1013 target_feature_attrs: Vec<Attribute>,
1014 inline_attr: Attribute,
1015) -> TokenStream {
1016 let vis = &input_fn.vis;
1017 let sig = &input_fn.sig;
1018 let fn_name = &sig.ident;
1019 let generics = &sig.generics;
1020 let where_clause = &generics.where_clause;
1021 let inputs = &sig.inputs;
1022 let output = &sig.output;
1023 let body = &input_fn.body;
1024 let attrs = filter_inline_attrs(&input_fn.attrs);
1026 let lint_attrs = filter_lint_attrs(&input_fn.attrs);
1028
1029 let self_receiver_kind: Option<SelfReceiver> = inputs.first().and_then(|arg| match arg {
1031 FnArg::Receiver(receiver) => {
1032 if receiver.reference.is_none() {
1033 Some(SelfReceiver::Owned)
1034 } else if receiver.mutability.is_some() {
1035 Some(SelfReceiver::RefMut)
1036 } else {
1037 Some(SelfReceiver::Ref)
1038 }
1039 }
1040 _ => None,
1041 });
1042
1043 let inner_params: Vec<proc_macro2::TokenStream> = inputs
1047 .iter()
1048 .map(|arg| match arg {
1049 FnArg::Receiver(_) => {
1050 let self_ty = args.self_type.as_ref().unwrap();
1052 match self_receiver_kind.as_ref().unwrap() {
1053 SelfReceiver::Owned => quote!(_self: #self_ty),
1054 SelfReceiver::Ref => quote!(_self: &#self_ty),
1055 SelfReceiver::RefMut => quote!(_self: &mut #self_ty),
1056 }
1057 }
1058 FnArg::Typed(pat_type) => {
1059 if let Some(ref self_ty) = args.self_type {
1060 replace_self_in_tokens(quote!(#pat_type), self_ty)
1061 } else {
1062 quote!(#pat_type)
1063 }
1064 }
1065 })
1066 .collect();
1067
1068 let inner_args: Vec<proc_macro2::TokenStream> = inputs
1070 .iter()
1071 .filter_map(|arg| match arg {
1072 FnArg::Typed(pat_type) => {
1073 if let syn::Pat::Ident(pat_ident) = pat_type.pat.as_ref() {
1074 let ident = &pat_ident.ident;
1075 Some(quote!(#ident))
1076 } else {
1077 None
1078 }
1079 }
1080 FnArg::Receiver(_) => Some(quote!(self)), })
1082 .collect();
1083
1084 let inner_fn_name = format_ident!("__simd_inner_{}", fn_name);
1085
1086 let turbofish = build_turbofish(generics);
1088
1089 let (inner_output, inner_body, inner_where_clause): (
1091 proc_macro2::TokenStream,
1092 proc_macro2::TokenStream,
1093 proc_macro2::TokenStream,
1094 ) = if let Some(ref self_ty) = args.self_type {
1095 let transformed_output = replace_self_in_tokens(output.to_token_stream(), self_ty);
1096 let transformed_body = replace_self_in_tokens(body.clone(), self_ty);
1097 let transformed_where = where_clause
1098 .as_ref()
1099 .map(|wc| replace_self_in_tokens(wc.to_token_stream(), self_ty))
1100 .unwrap_or_default();
1101 (transformed_output, transformed_body, transformed_where)
1102 } else {
1103 (
1104 output.to_token_stream(),
1105 body.clone(),
1106 where_clause
1107 .as_ref()
1108 .map(|wc| wc.to_token_stream())
1109 .unwrap_or_default(),
1110 )
1111 };
1112
1113 let token_type_str = token_type_name.as_deref().unwrap_or("UnknownToken");
1114 let expanded = if let Some(arch) = target_arch {
1115 let stub = if args.stub {
1116 quote! {
1117 #[cfg(not(target_arch = #arch))]
1119 #(#attrs)*
1120 #vis #sig {
1121 let _ = (#(#inner_args),*);
1122 unreachable!(
1123 "BUG: {}() was called but requires {} (target_arch = \"{}\"). \
1124 {}::summon() returns None on this architecture, so this function \
1125 is unreachable in safe code. If you used forge_token_dangerously(), \
1126 that is the bug.",
1127 stringify!(#fn_name),
1128 #token_type_str,
1129 #arch,
1130 #token_type_str,
1131 )
1132 }
1133 }
1134 } else {
1135 quote! {}
1136 };
1137
1138 quote! {
1139 #[cfg(target_arch = #arch)]
1141 #(#attrs)*
1142 #[inline(always)]
1143 #vis #sig {
1144 #(#target_feature_attrs)*
1145 #inline_attr
1146 #(#lint_attrs)*
1147 fn #inner_fn_name #generics (#(#inner_params),*) #inner_output #inner_where_clause {
1148 #inner_body
1149 }
1150
1151 unsafe { #inner_fn_name #turbofish(#(#inner_args),*) }
1153 }
1154
1155 #stub
1156 }
1157 } else {
1158 quote! {
1160 #(#attrs)*
1161 #[inline(always)]
1162 #vis #sig {
1163 #(#target_feature_attrs)*
1164 #inline_attr
1165 #(#lint_attrs)*
1166 fn #inner_fn_name #generics (#(#inner_params),*) #inner_output #inner_where_clause {
1167 #inner_body
1168 }
1169
1170 unsafe { #inner_fn_name #turbofish(#(#inner_args),*) }
1172 }
1173 }
1174 };
1175
1176 expanded.into()
1177}
1178
1179#[proc_macro_attribute]
1339pub fn arcane(attr: TokenStream, item: TokenStream) -> TokenStream {
1340 let args = parse_macro_input!(attr as ArcaneArgs);
1341 let input_fn = parse_macro_input!(item as LightFn);
1342 arcane_impl(input_fn, "arcane", args)
1343}
1344
1345#[proc_macro_attribute]
1349#[doc(hidden)]
1350pub fn simd_fn(attr: TokenStream, item: TokenStream) -> TokenStream {
1351 let args = parse_macro_input!(attr as ArcaneArgs);
1352 let input_fn = parse_macro_input!(item as LightFn);
1353 arcane_impl(input_fn, "simd_fn", args)
1354}
1355
1356#[proc_macro_attribute]
1369pub fn token_target_features_boundary(attr: TokenStream, item: TokenStream) -> TokenStream {
1370 let args = parse_macro_input!(attr as ArcaneArgs);
1371 let input_fn = parse_macro_input!(item as LightFn);
1372 arcane_impl(input_fn, "token_target_features_boundary", args)
1373}
1374
1375#[proc_macro_attribute]
1454pub fn rite(attr: TokenStream, item: TokenStream) -> TokenStream {
1455 let args = parse_macro_input!(attr as RiteArgs);
1456 let input_fn = parse_macro_input!(item as LightFn);
1457 rite_impl(input_fn, args)
1458}
1459
1460#[proc_macro_attribute]
1471pub fn token_target_features(attr: TokenStream, item: TokenStream) -> TokenStream {
1472 let args = parse_macro_input!(attr as RiteArgs);
1473 let input_fn = parse_macro_input!(item as LightFn);
1474 rite_impl(input_fn, args)
1475}
1476
1477#[derive(Default)]
1479struct RiteArgs {
1480 stub: bool,
1483 import_intrinsics: bool,
1485 import_magetypes: bool,
1488 tier_tokens: Vec<String>,
1493}
1494
1495impl Parse for RiteArgs {
1496 fn parse(input: ParseStream) -> syn::Result<Self> {
1497 let mut args = RiteArgs::default();
1498
1499 while !input.is_empty() {
1500 let ident: Ident = input.parse()?;
1501 match ident.to_string().as_str() {
1502 "stub" => args.stub = true,
1503 "import_intrinsics" => args.import_intrinsics = true,
1504 "import_magetypes" => args.import_magetypes = true,
1505 other => {
1506 if let Some(canonical) = tier_to_canonical_token(other) {
1507 args.tier_tokens.push(String::from(canonical));
1508 } else {
1509 return Err(syn::Error::new(
1510 ident.span(),
1511 format!(
1512 "unknown rite argument: `{}`. Supported: tier names \
1513 (v1, v2, v3, v4, neon, arm_v2, wasm128, ...), \
1514 `stub`, `import_intrinsics`, `import_magetypes`.",
1515 other
1516 ),
1517 ));
1518 }
1519 }
1520 }
1521 if input.peek(Token![,]) {
1522 let _: Token![,] = input.parse()?;
1523 }
1524 }
1525
1526 Ok(args)
1527 }
1528}
1529
1530fn rite_impl(input_fn: LightFn, args: RiteArgs) -> TokenStream {
1532 if args.tier_tokens.len() > 1 {
1534 return rite_multi_tier_impl(input_fn, &args);
1535 }
1536
1537 rite_single_impl(input_fn, args)
1539}
1540
1541fn rite_single_impl(mut input_fn: LightFn, args: RiteArgs) -> TokenStream {
1543 let TokenParamInfo {
1545 features,
1546 target_arch,
1547 token_type_name: _token_type_name,
1548 magetypes_namespace,
1549 ..
1550 } = if let Some(tier_token) = args.tier_tokens.first() {
1551 let features = token_to_features(tier_token)
1553 .expect("tier_to_canonical_token returned invalid token name")
1554 .to_vec();
1555 let target_arch = token_to_arch(tier_token);
1556 let magetypes_namespace = token_to_magetypes_namespace(tier_token);
1557 TokenParamInfo {
1558 ident: Ident::new("_", proc_macro2::Span::call_site()),
1559 features,
1560 target_arch,
1561 token_type_name: Some(tier_token.clone()),
1562 magetypes_namespace,
1563 }
1564 } else {
1565 match find_token_param(&input_fn.sig) {
1566 Some(result) => result,
1567 None => {
1568 if let Some(trait_name) = diagnose_featureless_token(&input_fn.sig) {
1570 let msg = format!(
1571 "`{trait_name}` cannot be used as a token bound in #[rite] \
1572 because it doesn't specify any CPU features.\n\
1573 \n\
1574 #[rite] needs concrete features to generate #[target_feature]. \
1575 Use a concrete token, a feature trait, or a tier name:\n\
1576 \n\
1577 Concrete tokens: X64V3Token, Desktop64, NeonToken, Arm64V2Token, ...\n\
1578 Feature traits: impl HasX64V2, impl HasNeon, impl HasArm64V3, ...\n\
1579 Tier names: #[rite(v3)], #[rite(neon)], #[rite(v4)], ..."
1580 );
1581 return syn::Error::new_spanned(&input_fn.sig, msg)
1582 .to_compile_error()
1583 .into();
1584 }
1585 let msg = "rite requires a token parameter or a tier name. Supported forms:\n\
1586 - Tier name: `#[rite(v3)]`, `#[rite(neon)]`\n\
1587 - Multi-tier: `#[rite(v3, v4, neon)]` (generates suffixed variants)\n\
1588 - Concrete: `token: X64V3Token`\n\
1589 - impl Trait: `token: impl HasX64V2`\n\
1590 - Generic: `fn foo<T: HasX64V2>(token: T, ...)`";
1591 return syn::Error::new_spanned(&input_fn.sig, msg)
1592 .to_compile_error()
1593 .into();
1594 }
1595 }
1596 };
1597
1598 #[cfg(not(feature = "avx512"))]
1601 if args.import_intrinsics && features.iter().any(|f| f.starts_with("avx512")) {
1602 let token_desc = _token_type_name.as_deref().unwrap_or("an AVX-512 token");
1603 let msg = format!(
1604 "Using {token_desc} with `import_intrinsics` requires the `avx512` feature.\n\
1605 \n\
1606 Add to your Cargo.toml:\n\
1607 \x20 archmage = {{ version = \"...\", features = [\"avx512\"] }}\n\
1608 \n\
1609 Without it, 512-bit safe memory ops (_mm512_loadu_ps etc.) are not available.\n\
1610 If you only need value intrinsics (no memory ops), remove `import_intrinsics`."
1611 );
1612 return syn::Error::new_spanned(&input_fn.sig, msg)
1613 .to_compile_error()
1614 .into();
1615 }
1616
1617 let target_feature_attrs: Vec<Attribute> = features
1619 .iter()
1620 .map(|feature| parse_quote!(#[target_feature(enable = #feature)]))
1621 .collect();
1622
1623 let inline_attr: Attribute = parse_quote!(#[inline]);
1625
1626 let mut new_attrs = target_feature_attrs;
1628 new_attrs.push(inline_attr);
1629 for attr in filter_inline_attrs(&input_fn.attrs) {
1630 new_attrs.push(attr.clone());
1631 }
1632 input_fn.attrs = new_attrs;
1633
1634 let body_imports = generate_imports(
1636 target_arch,
1637 magetypes_namespace,
1638 args.import_intrinsics,
1639 args.import_magetypes,
1640 );
1641 if !body_imports.is_empty() {
1642 let original_body = &input_fn.body;
1643 input_fn.body = quote! {
1644 #body_imports
1645 #original_body
1646 };
1647 }
1648
1649 if let Some(arch) = target_arch {
1651 let vis = &input_fn.vis;
1652 let sig = &input_fn.sig;
1653 let attrs = &input_fn.attrs;
1654 let body = &input_fn.body;
1655
1656 let stub = if args.stub {
1657 quote! {
1658 #[cfg(not(target_arch = #arch))]
1659 #vis #sig {
1660 unreachable!(concat!(
1661 "This function requires ",
1662 #arch,
1663 " architecture"
1664 ))
1665 }
1666 }
1667 } else {
1668 quote! {}
1669 };
1670
1671 quote! {
1672 #[cfg(target_arch = #arch)]
1673 #(#attrs)*
1674 #vis #sig {
1675 #body
1676 }
1677
1678 #stub
1679 }
1680 .into()
1681 } else {
1682 quote!(#input_fn).into()
1684 }
1685}
1686
1687fn rite_multi_tier_impl(input_fn: LightFn, args: &RiteArgs) -> TokenStream {
1696 let fn_name = &input_fn.sig.ident;
1697 let mut variants = proc_macro2::TokenStream::new();
1698
1699 for tier_token in &args.tier_tokens {
1700 let features = match token_to_features(tier_token) {
1701 Some(f) => f,
1702 None => {
1703 return syn::Error::new_spanned(
1704 &input_fn.sig,
1705 format!("unknown token `{tier_token}` in multi-tier #[rite]"),
1706 )
1707 .to_compile_error()
1708 .into();
1709 }
1710 };
1711 let target_arch = token_to_arch(tier_token);
1712 let magetypes_namespace = token_to_magetypes_namespace(tier_token);
1713
1714 #[cfg(not(feature = "avx512"))]
1716 if args.import_intrinsics && features.iter().any(|f| f.starts_with("avx512")) {
1717 let msg = format!(
1718 "Using {tier_token} with `import_intrinsics` requires the `avx512` feature.\n\
1719 \n\
1720 Add to your Cargo.toml:\n\
1721 \x20 archmage = {{ version = \"...\", features = [\"avx512\"] }}\n\
1722 \n\
1723 Without it, 512-bit safe memory ops (_mm512_loadu_ps etc.) are not available.\n\
1724 If you only need value intrinsics (no memory ops), remove `import_intrinsics`."
1725 );
1726 return syn::Error::new_spanned(&input_fn.sig, msg)
1727 .to_compile_error()
1728 .into();
1729 }
1730
1731 let suffix = canonical_token_to_tier_suffix(tier_token)
1732 .expect("canonical token must have a tier suffix");
1733
1734 let suffixed_ident = format_ident!("{}_{}", fn_name, suffix);
1736
1737 let mut variant_fn = input_fn.clone();
1739 variant_fn.sig.ident = suffixed_ident;
1740
1741 let target_feature_attrs: Vec<Attribute> = features
1743 .iter()
1744 .map(|feature| parse_quote!(#[target_feature(enable = #feature)]))
1745 .collect();
1746 let inline_attr: Attribute = parse_quote!(#[inline]);
1747
1748 let mut new_attrs = target_feature_attrs;
1749 new_attrs.push(inline_attr);
1750 for attr in filter_inline_attrs(&variant_fn.attrs) {
1751 new_attrs.push(attr.clone());
1752 }
1753 variant_fn.attrs = new_attrs;
1754
1755 let body_imports = generate_imports(
1757 target_arch,
1758 magetypes_namespace,
1759 args.import_intrinsics,
1760 args.import_magetypes,
1761 );
1762 if !body_imports.is_empty() {
1763 let original_body = &variant_fn.body;
1764 variant_fn.body = quote! {
1765 #body_imports
1766 #original_body
1767 };
1768 }
1769
1770 if let Some(arch) = target_arch {
1772 let vis = &variant_fn.vis;
1773 let sig = &variant_fn.sig;
1774 let attrs = &variant_fn.attrs;
1775 let body = &variant_fn.body;
1776
1777 variants.extend(quote! {
1778 #[cfg(target_arch = #arch)]
1779 #(#attrs)*
1780 #vis #sig {
1781 #body
1782 }
1783 });
1784
1785 if args.stub {
1786 variants.extend(quote! {
1787 #[cfg(not(target_arch = #arch))]
1788 #vis #sig {
1789 unreachable!(concat!(
1790 "This function requires ",
1791 #arch,
1792 " architecture"
1793 ))
1794 }
1795 });
1796 }
1797 } else {
1798 variants.extend(quote!(#variant_fn));
1800 }
1801 }
1802
1803 variants.into()
1804}
1805
1806#[proc_macro_attribute]
1865pub fn magetypes(attr: TokenStream, item: TokenStream) -> TokenStream {
1866 let input_fn = parse_macro_input!(item as LightFn);
1867
1868 let tier_names: Vec<String> = if attr.is_empty() {
1870 DEFAULT_TIER_NAMES.iter().map(|s| s.to_string()).collect()
1871 } else {
1872 let parser = |input: ParseStream| input.parse_terminated(Ident::parse, Token![,]);
1873 let idents = match syn::parse::Parser::parse(parser, attr) {
1874 Ok(p) => p,
1875 Err(e) => return e.to_compile_error().into(),
1876 };
1877 idents.iter().map(|i| i.to_string()).collect()
1878 };
1879
1880 let tiers = match resolve_tiers(&tier_names, input_fn.sig.ident.span()) {
1881 Ok(t) => t,
1882 Err(e) => return e.to_compile_error().into(),
1883 };
1884
1885 magetypes_impl(input_fn, &tiers)
1886}
1887
1888fn magetypes_impl(mut input_fn: LightFn, tiers: &[&TierDescriptor]) -> TokenStream {
1889 input_fn
1892 .attrs
1893 .retain(|attr| !attr.path().is_ident("arcane") && !attr.path().is_ident("rite"));
1894
1895 let fn_name = &input_fn.sig.ident;
1896 let fn_attrs = &input_fn.attrs;
1897
1898 let fn_str = input_fn.to_token_stream().to_string();
1900
1901 let mut variants = Vec::new();
1902
1903 for tier in tiers {
1904 let suffixed_name = format!("{}_{}", fn_name, tier.suffix);
1906
1907 let mut variant_str = fn_str.clone();
1909
1910 variant_str = variant_str.replacen(&fn_name.to_string(), &suffixed_name, 1);
1912
1913 variant_str = variant_str.replace("Token", tier.token_path);
1915
1916 let variant_tokens: proc_macro2::TokenStream = match variant_str.parse() {
1918 Ok(t) => t,
1919 Err(e) => {
1920 return syn::Error::new_spanned(
1921 &input_fn,
1922 format!(
1923 "Failed to parse generated variant `{}`: {}",
1924 suffixed_name, e
1925 ),
1926 )
1927 .to_compile_error()
1928 .into();
1929 }
1930 };
1931
1932 let cfg_guard = match tier.target_arch {
1934 Some(arch) => quote! { #[cfg(target_arch = #arch)] },
1935 None => quote! {},
1936 };
1937
1938 variants.push(if tier.name != "scalar" {
1939 quote! {
1941 #cfg_guard
1942 #[archmage::arcane]
1943 #variant_tokens
1944 }
1945 } else {
1946 quote! {
1947 #cfg_guard
1948 #variant_tokens
1949 }
1950 });
1951 }
1952
1953 let filtered_attrs: Vec<_> = fn_attrs
1955 .iter()
1956 .filter(|a| !a.path().is_ident("magetypes"))
1957 .collect();
1958
1959 let output = quote! {
1960 #(#filtered_attrs)*
1961 #(#variants)*
1962 };
1963
1964 output.into()
1965}
1966
1967struct TierDescriptor {
1977 name: &'static str,
1979 suffix: &'static str,
1981 token_path: &'static str,
1983 as_method: &'static str,
1985 target_arch: Option<&'static str>,
1987 priority: u32,
1989}
1990
1991const ALL_TIERS: &[TierDescriptor] = &[
1993 TierDescriptor {
1995 name: "v4x",
1996 suffix: "v4x",
1997 token_path: "archmage::X64V4xToken",
1998 as_method: "as_x64v4x",
1999 target_arch: Some("x86_64"),
2000
2001 priority: 50,
2002 },
2003 TierDescriptor {
2004 name: "v4",
2005 suffix: "v4",
2006 token_path: "archmage::X64V4Token",
2007 as_method: "as_x64v4",
2008 target_arch: Some("x86_64"),
2009
2010 priority: 40,
2011 },
2012 TierDescriptor {
2013 name: "v3_crypto",
2014 suffix: "v3_crypto",
2015 token_path: "archmage::X64V3CryptoToken",
2016 as_method: "as_x64v3_crypto",
2017 target_arch: Some("x86_64"),
2018
2019 priority: 35,
2020 },
2021 TierDescriptor {
2022 name: "v3",
2023 suffix: "v3",
2024 token_path: "archmage::X64V3Token",
2025 as_method: "as_x64v3",
2026 target_arch: Some("x86_64"),
2027
2028 priority: 30,
2029 },
2030 TierDescriptor {
2031 name: "x64_crypto",
2032 suffix: "x64_crypto",
2033 token_path: "archmage::X64CryptoToken",
2034 as_method: "as_x64_crypto",
2035 target_arch: Some("x86_64"),
2036
2037 priority: 25,
2038 },
2039 TierDescriptor {
2040 name: "v2",
2041 suffix: "v2",
2042 token_path: "archmage::X64V2Token",
2043 as_method: "as_x64v2",
2044 target_arch: Some("x86_64"),
2045
2046 priority: 20,
2047 },
2048 TierDescriptor {
2049 name: "v1",
2050 suffix: "v1",
2051 token_path: "archmage::X64V1Token",
2052 as_method: "as_x64v1",
2053 target_arch: Some("x86_64"),
2054
2055 priority: 10,
2056 },
2057 TierDescriptor {
2059 name: "arm_v3",
2060 suffix: "arm_v3",
2061 token_path: "archmage::Arm64V3Token",
2062 as_method: "as_arm_v3",
2063 target_arch: Some("aarch64"),
2064
2065 priority: 50,
2066 },
2067 TierDescriptor {
2068 name: "arm_v2",
2069 suffix: "arm_v2",
2070 token_path: "archmage::Arm64V2Token",
2071 as_method: "as_arm_v2",
2072 target_arch: Some("aarch64"),
2073
2074 priority: 40,
2075 },
2076 TierDescriptor {
2077 name: "neon_aes",
2078 suffix: "neon_aes",
2079 token_path: "archmage::NeonAesToken",
2080 as_method: "as_neon_aes",
2081 target_arch: Some("aarch64"),
2082
2083 priority: 30,
2084 },
2085 TierDescriptor {
2086 name: "neon_sha3",
2087 suffix: "neon_sha3",
2088 token_path: "archmage::NeonSha3Token",
2089 as_method: "as_neon_sha3",
2090 target_arch: Some("aarch64"),
2091
2092 priority: 30,
2093 },
2094 TierDescriptor {
2095 name: "neon_crc",
2096 suffix: "neon_crc",
2097 token_path: "archmage::NeonCrcToken",
2098 as_method: "as_neon_crc",
2099 target_arch: Some("aarch64"),
2100
2101 priority: 30,
2102 },
2103 TierDescriptor {
2104 name: "neon",
2105 suffix: "neon",
2106 token_path: "archmage::NeonToken",
2107 as_method: "as_neon",
2108 target_arch: Some("aarch64"),
2109
2110 priority: 20,
2111 },
2112 TierDescriptor {
2114 name: "wasm128_relaxed",
2115 suffix: "wasm128_relaxed",
2116 token_path: "archmage::Wasm128RelaxedToken",
2117 as_method: "as_wasm128_relaxed",
2118 target_arch: Some("wasm32"),
2119
2120 priority: 21,
2121 },
2122 TierDescriptor {
2123 name: "wasm128",
2124 suffix: "wasm128",
2125 token_path: "archmage::Wasm128Token",
2126 as_method: "as_wasm128",
2127 target_arch: Some("wasm32"),
2128
2129 priority: 20,
2130 },
2131 TierDescriptor {
2133 name: "scalar",
2134 suffix: "scalar",
2135 token_path: "archmage::ScalarToken",
2136 as_method: "as_scalar",
2137 target_arch: None,
2138
2139 priority: 0,
2140 },
2141];
2142
2143#[cfg(feature = "avx512")]
2149const DEFAULT_TIER_NAMES: &[&str] = &["v4", "v3", "neon", "wasm128", "scalar"];
2150#[cfg(not(feature = "avx512"))]
2151const DEFAULT_TIER_NAMES: &[&str] = &["v3", "neon", "wasm128", "scalar"];
2152
2153const AUTOVERSION_DEFAULT_TIER_NAMES: &[&str] = &["v4", "v3", "neon", "wasm128", "scalar"];
2157
2158const REQUIRE_EXPLICIT_SCALAR: bool = false;
2161
2162fn find_tier(name: &str) -> Option<&'static TierDescriptor> {
2164 ALL_TIERS.iter().find(|t| t.name == name)
2165}
2166
2167fn resolve_tiers(
2170 tier_names: &[String],
2171 error_span: proc_macro2::Span,
2172) -> syn::Result<Vec<&'static TierDescriptor>> {
2173 let mut tiers = Vec::new();
2174 for name in tier_names {
2175 match find_tier(name) {
2176 Some(tier) => tiers.push(tier),
2177 None => {
2178 let known: Vec<&str> = ALL_TIERS.iter().map(|t| t.name).collect();
2179 return Err(syn::Error::new(
2180 error_span,
2181 format!("unknown tier `{}`. Known tiers: {}", name, known.join(", ")),
2182 ));
2183 }
2184 }
2185 }
2186
2187 if !tiers.iter().any(|t| t.name == "scalar") {
2189 tiers.push(find_tier("scalar").unwrap());
2190 }
2191
2192 tiers.sort_by(|a, b| b.priority.cmp(&a.priority));
2194
2195 Ok(tiers)
2196}
2197
2198struct IncantInput {
2204 func_path: syn::Path,
2206 args: Vec<syn::Expr>,
2208 with_token: Option<syn::Expr>,
2210 tiers: Option<(Vec<String>, proc_macro2::Span)>,
2212}
2213
2214fn suffix_path(path: &syn::Path, suffix: &str) -> syn::Path {
2217 let mut suffixed = path.clone();
2218 if let Some(last) = suffixed.segments.last_mut() {
2219 last.ident = format_ident!("{}_{}", last.ident, suffix);
2220 }
2221 suffixed
2222}
2223
2224impl Parse for IncantInput {
2225 fn parse(input: ParseStream) -> syn::Result<Self> {
2226 let func_path: syn::Path = input.parse()?;
2228
2229 let content;
2231 syn::parenthesized!(content in input);
2232 let args = content
2233 .parse_terminated(syn::Expr::parse, Token![,])?
2234 .into_iter()
2235 .collect();
2236
2237 let with_token = if input.peek(Ident) {
2239 let kw: Ident = input.parse()?;
2240 if kw != "with" {
2241 return Err(syn::Error::new_spanned(kw, "expected `with` keyword"));
2242 }
2243 Some(input.parse()?)
2244 } else {
2245 None
2246 };
2247
2248 let tiers = if input.peek(Token![,]) {
2250 let _: Token![,] = input.parse()?;
2251 let bracket_content;
2252 let bracket = syn::bracketed!(bracket_content in input);
2253 let tier_idents = bracket_content.parse_terminated(Ident::parse, Token![,])?;
2254 let tier_names: Vec<String> = tier_idents.iter().map(|i| i.to_string()).collect();
2255 Some((tier_names, bracket.span.join()))
2256 } else {
2257 None
2258 };
2259
2260 Ok(IncantInput {
2261 func_path,
2262 args,
2263 with_token,
2264 tiers,
2265 })
2266 }
2267}
2268
2269#[proc_macro]
2341pub fn incant(input: TokenStream) -> TokenStream {
2342 let input = parse_macro_input!(input as IncantInput);
2343 incant_impl(input)
2344}
2345
2346#[proc_macro]
2348pub fn simd_route(input: TokenStream) -> TokenStream {
2349 let input = parse_macro_input!(input as IncantInput);
2350 incant_impl(input)
2351}
2352
2353#[proc_macro]
2361pub fn dispatch_variant(input: TokenStream) -> TokenStream {
2362 let input = parse_macro_input!(input as IncantInput);
2363 incant_impl(input)
2364}
2365
2366fn incant_impl(input: IncantInput) -> TokenStream {
2367 let func_path = &input.func_path;
2368 let args = &input.args;
2369
2370 let tier_names: Vec<String> = match &input.tiers {
2372 Some((names, _)) => names.clone(),
2373 None => DEFAULT_TIER_NAMES.iter().map(|s| s.to_string()).collect(),
2374 };
2375 let last_segment_span = func_path
2376 .segments
2377 .last()
2378 .map(|s| s.ident.span())
2379 .unwrap_or_else(proc_macro2::Span::call_site);
2380 let error_span = input
2381 .tiers
2382 .as_ref()
2383 .map(|(_, span)| *span)
2384 .unwrap_or(last_segment_span);
2385
2386 if REQUIRE_EXPLICIT_SCALAR
2391 && let Some((names, span)) = &input.tiers
2392 && !names.iter().any(|n| n == "scalar")
2393 {
2394 return syn::Error::new(
2395 *span,
2396 "explicit tier list must include `scalar`. \
2397 incant! always dispatches to fn_scalar() as the final fallback, \
2398 so `scalar` must appear in the tier list to acknowledge this. \
2399 Example: [v3, neon, scalar]",
2400 )
2401 .to_compile_error()
2402 .into();
2403 }
2404
2405 let tiers = match resolve_tiers(&tier_names, error_span) {
2406 Ok(t) => t,
2407 Err(e) => return e.to_compile_error().into(),
2408 };
2409
2410 if let Some(token_expr) = &input.with_token {
2413 gen_incant_passthrough(func_path, args, token_expr, &tiers)
2414 } else {
2415 gen_incant_entry(func_path, args, &tiers)
2416 }
2417}
2418
2419fn gen_incant_passthrough(
2421 func_path: &syn::Path,
2422 args: &[syn::Expr],
2423 token_expr: &syn::Expr,
2424 tiers: &[&TierDescriptor],
2425) -> TokenStream {
2426 let mut dispatch_arms = Vec::new();
2427
2428 let mut arch_groups: Vec<(Option<&str>, Vec<&TierDescriptor>)> = Vec::new();
2430 for tier in tiers {
2431 if tier.name == "scalar" {
2432 continue; }
2434 if let Some(group) = arch_groups.iter_mut().find(|(a, _)| *a == tier.target_arch) {
2435 group.1.push(tier);
2436 } else {
2437 arch_groups.push((tier.target_arch, vec![tier]));
2438 }
2439 }
2440
2441 for (target_arch, group_tiers) in &arch_groups {
2442 let mut tier_checks = Vec::new();
2443 for tier in group_tiers {
2444 let fn_suffixed = suffix_path(func_path, tier.suffix);
2445 let as_method = format_ident!("{}", tier.as_method);
2446 tier_checks.push(quote! {
2447 if let Some(__t) = __incant_token.#as_method() {
2448 break '__incant #fn_suffixed(__t, #(#args),*);
2449 }
2450 });
2451 }
2452
2453 let inner = quote! { #(#tier_checks)* };
2454
2455 if let Some(arch) = target_arch {
2456 dispatch_arms.push(quote! {
2457 #[cfg(target_arch = #arch)]
2458 { #inner }
2459 });
2460 } else {
2461 dispatch_arms.push(inner);
2462 }
2463 }
2464
2465 let fn_scalar = suffix_path(func_path, "scalar");
2467 let scalar_arm = if tiers.iter().any(|t| t.name == "scalar") {
2468 quote! {
2469 if let Some(__t) = __incant_token.as_scalar() {
2470 break '__incant #fn_scalar(__t, #(#args),*);
2471 }
2472 unreachable!("Token did not match any known variant")
2473 }
2474 } else {
2475 quote! { unreachable!("Token did not match any known variant") }
2476 };
2477
2478 let expanded = quote! {
2479 '__incant: {
2480 use archmage::IntoConcreteToken;
2481 let __incant_token = #token_expr;
2482 #(#dispatch_arms)*
2483 #scalar_arm
2484 }
2485 };
2486 expanded.into()
2487}
2488
2489fn gen_incant_entry(
2491 func_path: &syn::Path,
2492 args: &[syn::Expr],
2493 tiers: &[&TierDescriptor],
2494) -> TokenStream {
2495 let mut dispatch_arms = Vec::new();
2496
2497 let mut arch_groups: Vec<(Option<&str>, Vec<&TierDescriptor>)> = Vec::new();
2499 for tier in tiers {
2500 if tier.name == "scalar" {
2501 continue;
2502 }
2503 if let Some(group) = arch_groups.iter_mut().find(|(a, _)| *a == tier.target_arch) {
2504 group.1.push(tier);
2505 } else {
2506 arch_groups.push((tier.target_arch, vec![tier]));
2507 }
2508 }
2509
2510 for (target_arch, group_tiers) in &arch_groups {
2511 let mut tier_checks = Vec::new();
2512 for tier in group_tiers {
2513 let fn_suffixed = suffix_path(func_path, tier.suffix);
2514 let token_path: syn::Path = syn::parse_str(tier.token_path).unwrap();
2515
2516 tier_checks.push(quote! {
2517 if let Some(__t) = #token_path::summon() {
2518 break '__incant #fn_suffixed(__t, #(#args),*);
2519 }
2520 });
2521 }
2522
2523 let inner = quote! { #(#tier_checks)* };
2524
2525 if let Some(arch) = target_arch {
2526 dispatch_arms.push(quote! {
2527 #[cfg(target_arch = #arch)]
2528 { #inner }
2529 });
2530 } else {
2531 dispatch_arms.push(inner);
2532 }
2533 }
2534
2535 let fn_scalar = suffix_path(func_path, "scalar");
2537
2538 let expanded = quote! {
2539 '__incant: {
2540 use archmage::SimdToken;
2541 #(#dispatch_arms)*
2542 #fn_scalar(archmage::ScalarToken, #(#args),*)
2543 }
2544 };
2545 expanded.into()
2546}
2547
2548struct AutoversionArgs {
2554 self_type: Option<Type>,
2556 tiers: Option<Vec<String>>,
2558}
2559
2560impl Parse for AutoversionArgs {
2561 fn parse(input: ParseStream) -> syn::Result<Self> {
2562 let mut self_type = None;
2563 let mut tier_names = Vec::new();
2564
2565 while !input.is_empty() {
2566 let ident: Ident = input.parse()?;
2567 if ident == "_self" {
2568 let _: Token![=] = input.parse()?;
2569 self_type = Some(input.parse()?);
2570 } else {
2571 tier_names.push(ident.to_string());
2573 }
2574 if input.peek(Token![,]) {
2575 let _: Token![,] = input.parse()?;
2576 }
2577 }
2578
2579 Ok(AutoversionArgs {
2580 self_type,
2581 tiers: if tier_names.is_empty() {
2582 None
2583 } else {
2584 Some(tier_names)
2585 },
2586 })
2587 }
2588}
2589
2590struct SimdTokenParamInfo {
2592 index: usize,
2594 #[allow(dead_code)]
2596 ident: Ident,
2597}
2598
2599fn find_simd_token_param(sig: &Signature) -> Option<SimdTokenParamInfo> {
2604 for (i, arg) in sig.inputs.iter().enumerate() {
2605 if let FnArg::Typed(PatType { pat, ty, .. }) = arg
2606 && let Type::Path(type_path) = ty.as_ref()
2607 && let Some(seg) = type_path.path.segments.last()
2608 && seg.ident == "SimdToken"
2609 {
2610 let ident = match pat.as_ref() {
2611 syn::Pat::Ident(pi) => pi.ident.clone(),
2612 syn::Pat::Wild(w) => Ident::new("__autoversion_token", w.underscore_token.span),
2613 _ => continue,
2614 };
2615 return Some(SimdTokenParamInfo { index: i, ident });
2616 }
2617 }
2618 None
2619}
2620
2621fn autoversion_impl(mut input_fn: LightFn, args: AutoversionArgs) -> TokenStream {
2626 let has_self = input_fn
2628 .sig
2629 .inputs
2630 .first()
2631 .is_some_and(|arg| matches!(arg, FnArg::Receiver(_)));
2632
2633 let token_param = match find_simd_token_param(&input_fn.sig) {
2638 Some(p) => p,
2639 None => {
2640 return syn::Error::new_spanned(
2641 &input_fn.sig,
2642 "autoversion requires a `SimdToken` parameter.\n\
2643 Example: fn process(token: SimdToken, data: &[f32]) -> f32 { ... }\n\n\
2644 SimdToken is the dispatch placeholder — autoversion replaces it \
2645 with concrete token types and generates a runtime dispatcher.",
2646 )
2647 .to_compile_error()
2648 .into();
2649 }
2650 };
2651
2652 let tier_names: Vec<String> = match &args.tiers {
2655 Some(names) => names.clone(),
2656 None => AUTOVERSION_DEFAULT_TIER_NAMES
2657 .iter()
2658 .map(|s| s.to_string())
2659 .collect(),
2660 };
2661 let tiers = match resolve_tiers(&tier_names, input_fn.sig.ident.span()) {
2662 Ok(t) => t,
2663 Err(e) => return e.to_compile_error().into(),
2664 };
2665
2666 input_fn
2668 .attrs
2669 .retain(|attr| !attr.path().is_ident("arcane") && !attr.path().is_ident("rite"));
2670
2671 let fn_name = &input_fn.sig.ident;
2672 let vis = input_fn.vis.clone();
2673
2674 let fn_attrs: Vec<Attribute> = input_fn.attrs.drain(..).collect();
2676
2677 let mut variants = Vec::new();
2687
2688 for tier in &tiers {
2689 let mut variant_fn = input_fn.clone();
2690
2691 variant_fn.vis = syn::Visibility::Inherited;
2693
2694 variant_fn.sig.ident = format_ident!("{}_{}", fn_name, tier.suffix);
2696
2697 let concrete_type: Type = syn::parse_str(tier.token_path).unwrap();
2699 if let FnArg::Typed(pt) = &mut variant_fn.sig.inputs[token_param.index] {
2700 *pt.ty = concrete_type;
2701 }
2702
2703 if tier.name == "scalar" && has_self && args.self_type.is_some() {
2706 let original_body = variant_fn.body.clone();
2707 variant_fn.body = quote!(let _self = self; #original_body);
2708 }
2709
2710 let cfg_guard = match tier.target_arch {
2711 Some(arch) => quote! { #[cfg(target_arch = #arch)] },
2712 None => quote! {},
2713 };
2714
2715 if tier.name != "scalar" {
2720 let arcane_attr = if let Some(ref self_type) = args.self_type {
2721 quote! { #[archmage::arcane(_self = #self_type)] }
2722 } else {
2723 quote! { #[archmage::arcane] }
2724 };
2725 variants.push(quote! {
2726 #cfg_guard
2727 #[allow(dead_code)]
2728 #arcane_attr
2729 #variant_fn
2730 });
2731 } else {
2732 variants.push(quote! {
2733 #cfg_guard
2734 #[allow(dead_code)]
2735 #variant_fn
2736 });
2737 }
2738 }
2739
2740 let mut dispatcher_inputs: Vec<FnArg> = input_fn.sig.inputs.iter().cloned().collect();
2746 dispatcher_inputs.remove(token_param.index);
2747
2748 let mut wild_counter = 0u32;
2750 for arg in &mut dispatcher_inputs {
2751 if let FnArg::Typed(pat_type) = arg
2752 && matches!(pat_type.pat.as_ref(), syn::Pat::Wild(_))
2753 {
2754 let ident = format_ident!("__autoversion_wild_{}", wild_counter);
2755 wild_counter += 1;
2756 *pat_type.pat = syn::Pat::Ident(syn::PatIdent {
2757 attrs: vec![],
2758 by_ref: None,
2759 mutability: None,
2760 ident,
2761 subpat: None,
2762 });
2763 }
2764 }
2765
2766 let dispatch_args: Vec<Ident> = dispatcher_inputs
2768 .iter()
2769 .filter_map(|arg| {
2770 if let FnArg::Typed(PatType { pat, .. }) = arg
2771 && let syn::Pat::Ident(pi) = pat.as_ref()
2772 {
2773 return Some(pi.ident.clone());
2774 }
2775 None
2776 })
2777 .collect();
2778
2779 let turbofish = build_turbofish(&input_fn.sig.generics);
2781
2782 let mut arch_groups: Vec<(Option<&str>, Vec<&&TierDescriptor>)> = Vec::new();
2784 for tier in &tiers {
2785 if tier.name == "scalar" {
2786 continue;
2787 }
2788 if let Some(group) = arch_groups.iter_mut().find(|(a, _)| *a == tier.target_arch) {
2789 group.1.push(tier);
2790 } else {
2791 arch_groups.push((tier.target_arch, vec![tier]));
2792 }
2793 }
2794
2795 let mut dispatch_arms = Vec::new();
2796 for (target_arch, group_tiers) in &arch_groups {
2797 let mut tier_checks = Vec::new();
2798 for tier in group_tiers {
2799 let suffixed = format_ident!("{}_{}", fn_name, tier.suffix);
2800 let token_path: syn::Path = syn::parse_str(tier.token_path).unwrap();
2801
2802 let call = if has_self {
2803 quote! { self.#suffixed #turbofish(__t, #(#dispatch_args),*) }
2804 } else {
2805 quote! { #suffixed #turbofish(__t, #(#dispatch_args),*) }
2806 };
2807
2808 tier_checks.push(quote! {
2809 if let Some(__t) = #token_path::summon() {
2810 break '__dispatch #call;
2811 }
2812 });
2813 }
2814
2815 let inner = quote! { #(#tier_checks)* };
2816
2817 if let Some(arch) = target_arch {
2818 dispatch_arms.push(quote! {
2819 #[cfg(target_arch = #arch)]
2820 { #inner }
2821 });
2822 } else {
2823 dispatch_arms.push(inner);
2824 }
2825 }
2826
2827 let scalar_name = format_ident!("{}_scalar", fn_name);
2829 let scalar_call = if has_self {
2830 quote! { self.#scalar_name #turbofish(archmage::ScalarToken, #(#dispatch_args),*) }
2831 } else {
2832 quote! { #scalar_name #turbofish(archmage::ScalarToken, #(#dispatch_args),*) }
2833 };
2834
2835 let dispatcher_inputs_punct: syn::punctuated::Punctuated<FnArg, Token![,]> =
2837 dispatcher_inputs.into_iter().collect();
2838 let output = &input_fn.sig.output;
2839 let generics = &input_fn.sig.generics;
2840 let where_clause = &generics.where_clause;
2841
2842 let user_span = fn_name.span();
2845 let dispatcher = quote_spanned! { user_span =>
2846 #(#fn_attrs)*
2847 #vis fn #fn_name #generics (#dispatcher_inputs_punct) #output #where_clause {
2848 '__dispatch: {
2849 use archmage::SimdToken;
2850 #(#dispatch_arms)*
2851 #scalar_call
2852 }
2853 }
2854 };
2855
2856 let expanded = quote! {
2857 #dispatcher
2858 #(#variants)*
2859 };
2860
2861 expanded.into()
2862}
2863
2864#[proc_macro_attribute]
3104pub fn autoversion(attr: TokenStream, item: TokenStream) -> TokenStream {
3105 let args = parse_macro_input!(attr as AutoversionArgs);
3106 let input_fn = parse_macro_input!(item as LightFn);
3107 autoversion_impl(input_fn, args)
3108}
3109
3110#[cfg(test)]
3115mod tests {
3116 use super::*;
3117
3118 use super::generated::{ALL_CONCRETE_TOKENS, ALL_TRAIT_NAMES};
3119 use syn::{ItemFn, ReturnType};
3120
3121 #[test]
3122 fn every_concrete_token_is_in_token_to_features() {
3123 for &name in ALL_CONCRETE_TOKENS {
3124 assert!(
3125 token_to_features(name).is_some(),
3126 "Token `{}` exists in runtime crate but is NOT recognized by \
3127 token_to_features() in the proc macro. Add it!",
3128 name
3129 );
3130 }
3131 }
3132
3133 #[test]
3134 fn every_trait_is_in_trait_to_features() {
3135 for &name in ALL_TRAIT_NAMES {
3136 assert!(
3137 trait_to_features(name).is_some(),
3138 "Trait `{}` exists in runtime crate but is NOT recognized by \
3139 trait_to_features() in the proc macro. Add it!",
3140 name
3141 );
3142 }
3143 }
3144
3145 #[test]
3146 fn token_aliases_map_to_same_features() {
3147 assert_eq!(
3149 token_to_features("Desktop64"),
3150 token_to_features("X64V3Token"),
3151 "Desktop64 and X64V3Token should map to identical features"
3152 );
3153
3154 assert_eq!(
3156 token_to_features("Server64"),
3157 token_to_features("X64V4Token"),
3158 "Server64 and X64V4Token should map to identical features"
3159 );
3160 assert_eq!(
3161 token_to_features("X64V4Token"),
3162 token_to_features("Avx512Token"),
3163 "X64V4Token and Avx512Token should map to identical features"
3164 );
3165
3166 assert_eq!(
3168 token_to_features("Arm64"),
3169 token_to_features("NeonToken"),
3170 "Arm64 and NeonToken should map to identical features"
3171 );
3172 }
3173
3174 #[test]
3175 fn trait_to_features_includes_tokens_as_bounds() {
3176 let tier_tokens = [
3180 "X64V2Token",
3181 "X64CryptoToken",
3182 "X64V3Token",
3183 "Desktop64",
3184 "Avx2FmaToken",
3185 "X64V4Token",
3186 "Avx512Token",
3187 "Server64",
3188 "X64V4xToken",
3189 "Avx512Fp16Token",
3190 "NeonToken",
3191 "Arm64",
3192 "NeonAesToken",
3193 "NeonSha3Token",
3194 "NeonCrcToken",
3195 "Arm64V2Token",
3196 "Arm64V3Token",
3197 ];
3198
3199 for &name in &tier_tokens {
3200 assert!(
3201 trait_to_features(name).is_some(),
3202 "Tier token `{}` should also be recognized in trait_to_features() \
3203 for use as a generic bound. Add it!",
3204 name
3205 );
3206 }
3207 }
3208
3209 #[test]
3210 fn trait_features_are_cumulative() {
3211 let v2_features = trait_to_features("HasX64V2").unwrap();
3213 let v4_features = trait_to_features("HasX64V4").unwrap();
3214
3215 for &f in v2_features {
3216 assert!(
3217 v4_features.contains(&f),
3218 "HasX64V4 should include v2 feature `{}` but doesn't",
3219 f
3220 );
3221 }
3222
3223 assert!(
3225 v4_features.len() > v2_features.len(),
3226 "HasX64V4 should have more features than HasX64V2"
3227 );
3228 }
3229
3230 #[test]
3231 fn x64v3_trait_features_include_v2() {
3232 let v2 = trait_to_features("HasX64V2").unwrap();
3234 let v3 = trait_to_features("X64V3Token").unwrap();
3235
3236 for &f in v2 {
3237 assert!(
3238 v3.contains(&f),
3239 "X64V3Token trait features should include v2 feature `{}` but don't",
3240 f
3241 );
3242 }
3243 }
3244
3245 #[test]
3246 fn has_neon_aes_includes_neon() {
3247 let neon = trait_to_features("HasNeon").unwrap();
3248 let neon_aes = trait_to_features("HasNeonAes").unwrap();
3249
3250 for &f in neon {
3251 assert!(
3252 neon_aes.contains(&f),
3253 "HasNeonAes should include NEON feature `{}`",
3254 f
3255 );
3256 }
3257 }
3258
3259 #[test]
3260 fn no_removed_traits_are_recognized() {
3261 let removed = [
3263 "HasSse",
3264 "HasSse2",
3265 "HasSse41",
3266 "HasSse42",
3267 "HasAvx",
3268 "HasAvx2",
3269 "HasFma",
3270 "HasAvx512f",
3271 "HasAvx512bw",
3272 "HasAvx512vl",
3273 "HasAvx512vbmi2",
3274 "HasSve",
3275 "HasSve2",
3276 ];
3277
3278 for &name in &removed {
3279 assert!(
3280 trait_to_features(name).is_none(),
3281 "Removed trait `{}` should NOT be in trait_to_features(). \
3282 It was removed in 0.3.0 — users should migrate to tier traits.",
3283 name
3284 );
3285 }
3286 }
3287
3288 #[test]
3289 fn no_nonexistent_tokens_are_recognized() {
3290 let fake = [
3292 "SveToken",
3293 "Sve2Token",
3294 "Avx512VnniToken",
3295 "X64V4ModernToken",
3296 "NeonFp16Token",
3297 ];
3298
3299 for &name in &fake {
3300 assert!(
3301 token_to_features(name).is_none(),
3302 "Non-existent token `{}` should NOT be in token_to_features()",
3303 name
3304 );
3305 }
3306 }
3307
3308 #[test]
3309 fn featureless_traits_are_not_in_registries() {
3310 for &name in FEATURELESS_TRAIT_NAMES {
3313 assert!(
3314 token_to_features(name).is_none(),
3315 "`{}` should NOT be in token_to_features() — it has no CPU features",
3316 name
3317 );
3318 assert!(
3319 trait_to_features(name).is_none(),
3320 "`{}` should NOT be in trait_to_features() — it has no CPU features",
3321 name
3322 );
3323 }
3324 }
3325
3326 #[test]
3327 fn find_featureless_trait_detects_simdtoken() {
3328 let names = vec!["SimdToken".to_string()];
3329 assert_eq!(find_featureless_trait(&names), Some("SimdToken"));
3330
3331 let names = vec!["IntoConcreteToken".to_string()];
3332 assert_eq!(find_featureless_trait(&names), Some("IntoConcreteToken"));
3333
3334 let names = vec!["HasX64V2".to_string()];
3336 assert_eq!(find_featureless_trait(&names), None);
3337
3338 let names = vec!["HasNeon".to_string()];
3339 assert_eq!(find_featureless_trait(&names), None);
3340
3341 let names = vec!["SimdToken".to_string(), "HasX64V2".to_string()];
3343 assert_eq!(find_featureless_trait(&names), Some("SimdToken"));
3344 }
3345
3346 #[test]
3347 fn arm64_v2_v3_traits_are_cumulative() {
3348 let v2_features = trait_to_features("HasArm64V2").unwrap();
3349 let v3_features = trait_to_features("HasArm64V3").unwrap();
3350
3351 for &f in v2_features {
3352 assert!(
3353 v3_features.contains(&f),
3354 "HasArm64V3 should include v2 feature `{}` but doesn't",
3355 f
3356 );
3357 }
3358
3359 assert!(
3360 v3_features.len() > v2_features.len(),
3361 "HasArm64V3 should have more features than HasArm64V2"
3362 );
3363 }
3364
3365 #[test]
3370 fn autoversion_args_empty() {
3371 let args: AutoversionArgs = syn::parse_str("").unwrap();
3372 assert!(args.self_type.is_none());
3373 assert!(args.tiers.is_none());
3374 }
3375
3376 #[test]
3377 fn autoversion_args_single_tier() {
3378 let args: AutoversionArgs = syn::parse_str("v3").unwrap();
3379 assert!(args.self_type.is_none());
3380 assert_eq!(args.tiers.as_ref().unwrap(), &["v3"]);
3381 }
3382
3383 #[test]
3384 fn autoversion_args_tiers_only() {
3385 let args: AutoversionArgs = syn::parse_str("v3, v4, neon").unwrap();
3386 assert!(args.self_type.is_none());
3387 let tiers = args.tiers.unwrap();
3388 assert_eq!(tiers, vec!["v3", "v4", "neon"]);
3389 }
3390
3391 #[test]
3392 fn autoversion_args_many_tiers() {
3393 let args: AutoversionArgs =
3394 syn::parse_str("v1, v2, v3, v4, v4x, neon, arm_v2, wasm128").unwrap();
3395 assert_eq!(
3396 args.tiers.unwrap(),
3397 vec!["v1", "v2", "v3", "v4", "v4x", "neon", "arm_v2", "wasm128"]
3398 );
3399 }
3400
3401 #[test]
3402 fn autoversion_args_trailing_comma() {
3403 let args: AutoversionArgs = syn::parse_str("v3, v4,").unwrap();
3404 assert_eq!(args.tiers.as_ref().unwrap(), &["v3", "v4"]);
3405 }
3406
3407 #[test]
3408 fn autoversion_args_self_only() {
3409 let args: AutoversionArgs = syn::parse_str("_self = MyType").unwrap();
3410 assert!(args.self_type.is_some());
3411 assert!(args.tiers.is_none());
3412 }
3413
3414 #[test]
3415 fn autoversion_args_self_and_tiers() {
3416 let args: AutoversionArgs = syn::parse_str("_self = MyType, v3, neon").unwrap();
3417 assert!(args.self_type.is_some());
3418 let tiers = args.tiers.unwrap();
3419 assert_eq!(tiers, vec!["v3", "neon"]);
3420 }
3421
3422 #[test]
3423 fn autoversion_args_tiers_then_self() {
3424 let args: AutoversionArgs = syn::parse_str("v3, neon, _self = MyType").unwrap();
3426 assert!(args.self_type.is_some());
3427 let tiers = args.tiers.unwrap();
3428 assert_eq!(tiers, vec!["v3", "neon"]);
3429 }
3430
3431 #[test]
3432 fn autoversion_args_self_with_path_type() {
3433 let args: AutoversionArgs = syn::parse_str("_self = crate::MyType").unwrap();
3434 assert!(args.self_type.is_some());
3435 assert!(args.tiers.is_none());
3436 }
3437
3438 #[test]
3439 fn autoversion_args_self_with_generic_type() {
3440 let args: AutoversionArgs = syn::parse_str("_self = Vec<u8>").unwrap();
3441 assert!(args.self_type.is_some());
3442 let ty_str = args.self_type.unwrap().to_token_stream().to_string();
3443 assert!(ty_str.contains("Vec"), "Expected Vec<u8>, got: {}", ty_str);
3444 }
3445
3446 #[test]
3447 fn autoversion_args_self_trailing_comma() {
3448 let args: AutoversionArgs = syn::parse_str("_self = MyType,").unwrap();
3449 assert!(args.self_type.is_some());
3450 assert!(args.tiers.is_none());
3451 }
3452
3453 #[test]
3458 fn find_simd_token_param_first_position() {
3459 let f: ItemFn =
3460 syn::parse_str("fn process(token: SimdToken, data: &[f32]) -> f32 {}").unwrap();
3461 let param = find_simd_token_param(&f.sig).unwrap();
3462 assert_eq!(param.index, 0);
3463 assert_eq!(param.ident, "token");
3464 }
3465
3466 #[test]
3467 fn find_simd_token_param_second_position() {
3468 let f: ItemFn =
3469 syn::parse_str("fn process(data: &[f32], token: SimdToken) -> f32 {}").unwrap();
3470 let param = find_simd_token_param(&f.sig).unwrap();
3471 assert_eq!(param.index, 1);
3472 assert_eq!(param.ident, "token");
3473 }
3474
3475 #[test]
3476 fn find_simd_token_param_underscore_prefix() {
3477 let f: ItemFn =
3478 syn::parse_str("fn process(_token: SimdToken, data: &[f32]) -> f32 {}").unwrap();
3479 let param = find_simd_token_param(&f.sig).unwrap();
3480 assert_eq!(param.index, 0);
3481 assert_eq!(param.ident, "_token");
3482 }
3483
3484 #[test]
3485 fn find_simd_token_param_wildcard() {
3486 let f: ItemFn = syn::parse_str("fn process(_: SimdToken, data: &[f32]) -> f32 {}").unwrap();
3487 let param = find_simd_token_param(&f.sig).unwrap();
3488 assert_eq!(param.index, 0);
3489 assert_eq!(param.ident, "__autoversion_token");
3490 }
3491
3492 #[test]
3493 fn find_simd_token_param_not_found() {
3494 let f: ItemFn = syn::parse_str("fn process(data: &[f32]) -> f32 {}").unwrap();
3495 assert!(find_simd_token_param(&f.sig).is_none());
3496 }
3497
3498 #[test]
3499 fn find_simd_token_param_no_params() {
3500 let f: ItemFn = syn::parse_str("fn process() {}").unwrap();
3501 assert!(find_simd_token_param(&f.sig).is_none());
3502 }
3503
3504 #[test]
3505 fn find_simd_token_param_concrete_token_not_matched() {
3506 let f: ItemFn =
3508 syn::parse_str("fn process(token: X64V3Token, data: &[f32]) -> f32 {}").unwrap();
3509 assert!(find_simd_token_param(&f.sig).is_none());
3510 }
3511
3512 #[test]
3513 fn find_simd_token_param_scalar_token_not_matched() {
3514 let f: ItemFn =
3515 syn::parse_str("fn process(token: ScalarToken, data: &[f32]) -> f32 {}").unwrap();
3516 assert!(find_simd_token_param(&f.sig).is_none());
3517 }
3518
3519 #[test]
3520 fn find_simd_token_param_among_many() {
3521 let f: ItemFn = syn::parse_str(
3522 "fn process(a: i32, b: f64, token: SimdToken, c: &str, d: bool) -> f32 {}",
3523 )
3524 .unwrap();
3525 let param = find_simd_token_param(&f.sig).unwrap();
3526 assert_eq!(param.index, 2);
3527 assert_eq!(param.ident, "token");
3528 }
3529
3530 #[test]
3531 fn find_simd_token_param_with_generics() {
3532 let f: ItemFn =
3533 syn::parse_str("fn process<T: Clone>(token: SimdToken, data: &[T]) -> T {}").unwrap();
3534 let param = find_simd_token_param(&f.sig).unwrap();
3535 assert_eq!(param.index, 0);
3536 assert_eq!(param.ident, "token");
3537 }
3538
3539 #[test]
3540 fn find_simd_token_param_with_where_clause() {
3541 let f: ItemFn = syn::parse_str(
3542 "fn process<T>(token: SimdToken, data: &[T]) -> T where T: Copy + Default {}",
3543 )
3544 .unwrap();
3545 let param = find_simd_token_param(&f.sig).unwrap();
3546 assert_eq!(param.index, 0);
3547 }
3548
3549 #[test]
3550 fn find_simd_token_param_with_lifetime() {
3551 let f: ItemFn =
3552 syn::parse_str("fn process<'a>(token: SimdToken, data: &'a [f32]) -> &'a f32 {}")
3553 .unwrap();
3554 let param = find_simd_token_param(&f.sig).unwrap();
3555 assert_eq!(param.index, 0);
3556 }
3557
3558 #[test]
3563 fn autoversion_default_tiers_all_resolve() {
3564 let names: Vec<String> = DEFAULT_TIER_NAMES.iter().map(|s| s.to_string()).collect();
3565 let tiers = resolve_tiers(&names, proc_macro2::Span::call_site()).unwrap();
3566 assert!(!tiers.is_empty());
3567 assert!(tiers.iter().any(|t| t.name == "scalar"));
3569 }
3570
3571 #[test]
3572 fn autoversion_scalar_always_appended() {
3573 let names = vec!["v3".to_string(), "neon".to_string()];
3574 let tiers = resolve_tiers(&names, proc_macro2::Span::call_site()).unwrap();
3575 assert!(
3576 tiers.iter().any(|t| t.name == "scalar"),
3577 "scalar must be auto-appended"
3578 );
3579 }
3580
3581 #[test]
3582 fn autoversion_scalar_not_duplicated() {
3583 let names = vec!["v3".to_string(), "scalar".to_string()];
3584 let tiers = resolve_tiers(&names, proc_macro2::Span::call_site()).unwrap();
3585 let scalar_count = tiers.iter().filter(|t| t.name == "scalar").count();
3586 assert_eq!(scalar_count, 1, "scalar must not be duplicated");
3587 }
3588
3589 #[test]
3590 fn autoversion_tiers_sorted_by_priority() {
3591 let names = vec!["neon".to_string(), "v4".to_string(), "v3".to_string()];
3592 let tiers = resolve_tiers(&names, proc_macro2::Span::call_site()).unwrap();
3593 let priorities: Vec<u32> = tiers.iter().map(|t| t.priority).collect();
3595 for window in priorities.windows(2) {
3596 assert!(
3597 window[0] >= window[1],
3598 "Tiers not sorted by priority: {:?}",
3599 priorities
3600 );
3601 }
3602 }
3603
3604 #[test]
3605 fn autoversion_unknown_tier_errors() {
3606 let names = vec!["v3".to_string(), "avx9000".to_string()];
3607 let result = resolve_tiers(&names, proc_macro2::Span::call_site());
3608 match result {
3609 Ok(_) => panic!("Expected error for unknown tier 'avx9000'"),
3610 Err(e) => {
3611 let err_msg = e.to_string();
3612 assert!(
3613 err_msg.contains("avx9000"),
3614 "Error should mention unknown tier: {}",
3615 err_msg
3616 );
3617 }
3618 }
3619 }
3620
3621 #[test]
3622 fn autoversion_all_known_tiers_resolve() {
3623 for tier in ALL_TIERS {
3625 assert!(
3626 find_tier(tier.name).is_some(),
3627 "Tier '{}' should be findable by name",
3628 tier.name
3629 );
3630 }
3631 }
3632
3633 #[test]
3634 fn autoversion_default_tier_list_is_sensible() {
3635 let names: Vec<String> = DEFAULT_TIER_NAMES.iter().map(|s| s.to_string()).collect();
3637 let tiers = resolve_tiers(&names, proc_macro2::Span::call_site()).unwrap();
3638
3639 let has_x86 = tiers.iter().any(|t| t.target_arch == Some("x86_64"));
3640 let has_arm = tiers.iter().any(|t| t.target_arch == Some("aarch64"));
3641 let has_wasm = tiers.iter().any(|t| t.target_arch == Some("wasm32"));
3642 let has_scalar = tiers.iter().any(|t| t.name == "scalar");
3643
3644 assert!(has_x86, "Default tiers should include an x86_64 tier");
3645 assert!(has_arm, "Default tiers should include an aarch64 tier");
3646 assert!(has_wasm, "Default tiers should include a wasm32 tier");
3647 assert!(has_scalar, "Default tiers should include scalar");
3648 }
3649
3650 fn do_variant_replacement(func: &str, tier_name: &str, has_self: bool) -> ItemFn {
3658 let mut f: ItemFn = syn::parse_str(func).unwrap();
3659 let fn_name = f.sig.ident.to_string();
3660
3661 let tier = find_tier(tier_name).unwrap();
3662
3663 f.sig.ident = format_ident!("{}_{}", fn_name, tier.suffix);
3665
3666 let token_idx = find_simd_token_param(&f.sig)
3668 .unwrap_or_else(|| panic!("No SimdToken param in: {}", func))
3669 .index;
3670 let concrete_type: Type = syn::parse_str(tier.token_path).unwrap();
3671 if let FnArg::Typed(pt) = &mut f.sig.inputs[token_idx] {
3672 *pt.ty = concrete_type;
3673 }
3674
3675 if tier_name == "scalar" && has_self {
3677 let preamble: syn::Stmt = syn::parse_quote!(let _self = self;);
3678 f.block.stmts.insert(0, preamble);
3679 }
3680
3681 f
3682 }
3683
3684 #[test]
3685 fn variant_replacement_v3_renames_function() {
3686 let f = do_variant_replacement(
3687 "fn process(token: SimdToken, data: &[f32]) -> f32 { 0.0 }",
3688 "v3",
3689 false,
3690 );
3691 assert_eq!(f.sig.ident, "process_v3");
3692 }
3693
3694 #[test]
3695 fn variant_replacement_v3_replaces_token_type() {
3696 let f = do_variant_replacement(
3697 "fn process(token: SimdToken, data: &[f32]) -> f32 { 0.0 }",
3698 "v3",
3699 false,
3700 );
3701 let first_param_ty = match &f.sig.inputs[0] {
3702 FnArg::Typed(pt) => pt.ty.to_token_stream().to_string(),
3703 _ => panic!("Expected typed param"),
3704 };
3705 assert!(
3706 first_param_ty.contains("X64V3Token"),
3707 "Expected X64V3Token, got: {}",
3708 first_param_ty
3709 );
3710 }
3711
3712 #[test]
3713 fn variant_replacement_neon_produces_valid_fn() {
3714 let f = do_variant_replacement(
3715 "fn compute(token: SimdToken, data: &[f32]) -> f32 { 0.0 }",
3716 "neon",
3717 false,
3718 );
3719 assert_eq!(f.sig.ident, "compute_neon");
3720 let first_param_ty = match &f.sig.inputs[0] {
3721 FnArg::Typed(pt) => pt.ty.to_token_stream().to_string(),
3722 _ => panic!("Expected typed param"),
3723 };
3724 assert!(
3725 first_param_ty.contains("NeonToken"),
3726 "Expected NeonToken, got: {}",
3727 first_param_ty
3728 );
3729 }
3730
3731 #[test]
3732 fn variant_replacement_wasm128_produces_valid_fn() {
3733 let f = do_variant_replacement(
3734 "fn compute(_t: SimdToken, data: &[f32]) -> f32 { 0.0 }",
3735 "wasm128",
3736 false,
3737 );
3738 assert_eq!(f.sig.ident, "compute_wasm128");
3739 }
3740
3741 #[test]
3742 fn variant_replacement_scalar_produces_valid_fn() {
3743 let f = do_variant_replacement(
3744 "fn compute(token: SimdToken, data: &[f32]) -> f32 { 0.0 }",
3745 "scalar",
3746 false,
3747 );
3748 assert_eq!(f.sig.ident, "compute_scalar");
3749 let first_param_ty = match &f.sig.inputs[0] {
3750 FnArg::Typed(pt) => pt.ty.to_token_stream().to_string(),
3751 _ => panic!("Expected typed param"),
3752 };
3753 assert!(
3754 first_param_ty.contains("ScalarToken"),
3755 "Expected ScalarToken, got: {}",
3756 first_param_ty
3757 );
3758 }
3759
3760 #[test]
3761 fn variant_replacement_v4_produces_valid_fn() {
3762 let f = do_variant_replacement(
3763 "fn transform(token: SimdToken, data: &mut [f32]) { }",
3764 "v4",
3765 false,
3766 );
3767 assert_eq!(f.sig.ident, "transform_v4");
3768 let first_param_ty = match &f.sig.inputs[0] {
3769 FnArg::Typed(pt) => pt.ty.to_token_stream().to_string(),
3770 _ => panic!("Expected typed param"),
3771 };
3772 assert!(
3773 first_param_ty.contains("X64V4Token"),
3774 "Expected X64V4Token, got: {}",
3775 first_param_ty
3776 );
3777 }
3778
3779 #[test]
3780 fn variant_replacement_v4x_produces_valid_fn() {
3781 let f = do_variant_replacement(
3782 "fn transform(token: SimdToken, data: &mut [f32]) { }",
3783 "v4x",
3784 false,
3785 );
3786 assert_eq!(f.sig.ident, "transform_v4x");
3787 }
3788
3789 #[test]
3790 fn variant_replacement_arm_v2_produces_valid_fn() {
3791 let f = do_variant_replacement(
3792 "fn transform(token: SimdToken, data: &mut [f32]) { }",
3793 "arm_v2",
3794 false,
3795 );
3796 assert_eq!(f.sig.ident, "transform_arm_v2");
3797 }
3798
3799 #[test]
3800 fn variant_replacement_preserves_generics() {
3801 let f = do_variant_replacement(
3802 "fn process<T: Copy + Default>(token: SimdToken, data: &[T]) -> T { T::default() }",
3803 "v3",
3804 false,
3805 );
3806 assert_eq!(f.sig.ident, "process_v3");
3807 assert!(
3809 !f.sig.generics.params.is_empty(),
3810 "Generics should be preserved"
3811 );
3812 }
3813
3814 #[test]
3815 fn variant_replacement_preserves_where_clause() {
3816 let f = do_variant_replacement(
3817 "fn process<T>(token: SimdToken, data: &[T]) -> T where T: Copy + Default { T::default() }",
3818 "v3",
3819 false,
3820 );
3821 assert!(
3822 f.sig.generics.where_clause.is_some(),
3823 "Where clause should be preserved"
3824 );
3825 }
3826
3827 #[test]
3828 fn variant_replacement_preserves_return_type() {
3829 let f = do_variant_replacement(
3830 "fn process(token: SimdToken, data: &[f32]) -> Vec<f32> { vec![] }",
3831 "neon",
3832 false,
3833 );
3834 let ret = f.sig.output.to_token_stream().to_string();
3835 assert!(
3836 ret.contains("Vec"),
3837 "Return type should be preserved, got: {}",
3838 ret
3839 );
3840 }
3841
3842 #[test]
3843 fn variant_replacement_preserves_multiple_params() {
3844 let f = do_variant_replacement(
3845 "fn process(token: SimdToken, a: &[f32], b: &[f32], scale: f32) -> f32 { 0.0 }",
3846 "v3",
3847 false,
3848 );
3849 assert_eq!(f.sig.inputs.len(), 4);
3851 }
3852
3853 #[test]
3854 fn variant_replacement_preserves_no_return_type() {
3855 let f = do_variant_replacement(
3856 "fn transform(token: SimdToken, data: &mut [f32]) { }",
3857 "v3",
3858 false,
3859 );
3860 assert!(
3861 matches!(f.sig.output, ReturnType::Default),
3862 "No return type should remain as Default"
3863 );
3864 }
3865
3866 #[test]
3867 fn variant_replacement_preserves_lifetime_params() {
3868 let f = do_variant_replacement(
3869 "fn process<'a>(token: SimdToken, data: &'a [f32]) -> &'a [f32] { data }",
3870 "v3",
3871 false,
3872 );
3873 assert!(!f.sig.generics.params.is_empty());
3874 }
3875
3876 #[test]
3877 fn variant_replacement_scalar_self_injects_preamble() {
3878 let f = do_variant_replacement(
3879 "fn method(token: SimdToken, data: &[f32]) -> f32 { 0.0 }",
3880 "scalar",
3881 true, );
3883 assert_eq!(f.sig.ident, "method_scalar");
3884
3885 let body_str = f.block.to_token_stream().to_string();
3887 assert!(
3888 body_str.contains("let _self = self"),
3889 "Scalar+self variant should have _self preamble, got: {}",
3890 body_str
3891 );
3892 }
3893
3894 #[test]
3895 fn variant_replacement_all_default_tiers_produce_valid_fns() {
3896 let names: Vec<String> = DEFAULT_TIER_NAMES.iter().map(|s| s.to_string()).collect();
3897 let tiers = resolve_tiers(&names, proc_macro2::Span::call_site()).unwrap();
3898
3899 for tier in &tiers {
3900 let f = do_variant_replacement(
3901 "fn process(token: SimdToken, data: &[f32]) -> f32 { 0.0 }",
3902 tier.name,
3903 false,
3904 );
3905 let expected_name = format!("process_{}", tier.suffix);
3906 assert_eq!(
3907 f.sig.ident.to_string(),
3908 expected_name,
3909 "Tier '{}' should produce function '{}'",
3910 tier.name,
3911 expected_name
3912 );
3913 }
3914 }
3915
3916 #[test]
3917 fn variant_replacement_all_known_tiers_produce_valid_fns() {
3918 for tier in ALL_TIERS {
3919 let f = do_variant_replacement(
3920 "fn compute(token: SimdToken, data: &[f32]) -> f32 { 0.0 }",
3921 tier.name,
3922 false,
3923 );
3924 let expected_name = format!("compute_{}", tier.suffix);
3925 assert_eq!(
3926 f.sig.ident.to_string(),
3927 expected_name,
3928 "Tier '{}' should produce function '{}'",
3929 tier.name,
3930 expected_name
3931 );
3932 }
3933 }
3934
3935 #[test]
3936 fn variant_replacement_no_simdtoken_remains() {
3937 for tier in ALL_TIERS {
3938 let f = do_variant_replacement(
3939 "fn compute(token: SimdToken, data: &[f32]) -> f32 { 0.0 }",
3940 tier.name,
3941 false,
3942 );
3943 let full_str = f.to_token_stream().to_string();
3944 assert!(
3945 !full_str.contains("SimdToken"),
3946 "Tier '{}' variant still contains 'SimdToken': {}",
3947 tier.name,
3948 full_str
3949 );
3950 }
3951 }
3952
3953 #[test]
3958 fn tier_v3_targets_x86_64() {
3959 let tier = find_tier("v3").unwrap();
3960 assert_eq!(tier.target_arch, Some("x86_64"));
3961 }
3962
3963 #[test]
3964 fn tier_v4_targets_x86_64() {
3965 let tier = find_tier("v4").unwrap();
3966 assert_eq!(tier.target_arch, Some("x86_64"));
3967 }
3968
3969 #[test]
3970 fn tier_v4x_targets_x86_64() {
3971 let tier = find_tier("v4x").unwrap();
3972 assert_eq!(tier.target_arch, Some("x86_64"));
3973 }
3974
3975 #[test]
3976 fn tier_neon_targets_aarch64() {
3977 let tier = find_tier("neon").unwrap();
3978 assert_eq!(tier.target_arch, Some("aarch64"));
3979 }
3980
3981 #[test]
3982 fn tier_wasm128_targets_wasm32() {
3983 let tier = find_tier("wasm128").unwrap();
3984 assert_eq!(tier.target_arch, Some("wasm32"));
3985 }
3986
3987 #[test]
3988 fn tier_scalar_has_no_guards() {
3989 let tier = find_tier("scalar").unwrap();
3990 assert_eq!(tier.target_arch, None);
3991 assert_eq!(tier.priority, 0);
3992 }
3993
3994 #[test]
3995 fn tier_priorities_are_consistent() {
3996 let v2 = find_tier("v2").unwrap();
3998 let v3 = find_tier("v3").unwrap();
3999 let v4 = find_tier("v4").unwrap();
4000 assert!(v4.priority > v3.priority);
4001 assert!(v3.priority > v2.priority);
4002
4003 let neon = find_tier("neon").unwrap();
4004 let arm_v2 = find_tier("arm_v2").unwrap();
4005 let arm_v3 = find_tier("arm_v3").unwrap();
4006 assert!(arm_v3.priority > arm_v2.priority);
4007 assert!(arm_v2.priority > neon.priority);
4008
4009 let scalar = find_tier("scalar").unwrap();
4011 assert!(neon.priority > scalar.priority);
4012 assert!(v2.priority > scalar.priority);
4013 }
4014
4015 #[test]
4020 fn dispatcher_param_removal_free_fn() {
4021 let f: ItemFn =
4023 syn::parse_str("fn process(token: SimdToken, data: &[f32], scale: f32) -> f32 { 0.0 }")
4024 .unwrap();
4025
4026 let token_param = find_simd_token_param(&f.sig).unwrap();
4027 let mut dispatcher_inputs: Vec<FnArg> = f.sig.inputs.iter().cloned().collect();
4028 dispatcher_inputs.remove(token_param.index);
4029
4030 assert_eq!(dispatcher_inputs.len(), 2);
4032
4033 for arg in &dispatcher_inputs {
4035 if let FnArg::Typed(pt) = arg {
4036 let ty_str = pt.ty.to_token_stream().to_string();
4037 assert!(
4038 !ty_str.contains("SimdToken"),
4039 "SimdToken should be removed from dispatcher, found: {}",
4040 ty_str
4041 );
4042 }
4043 }
4044 }
4045
4046 #[test]
4047 fn dispatcher_param_removal_token_only() {
4048 let f: ItemFn = syn::parse_str("fn process(token: SimdToken) -> f32 { 0.0 }").unwrap();
4049
4050 let token_param = find_simd_token_param(&f.sig).unwrap();
4051 let mut dispatcher_inputs: Vec<FnArg> = f.sig.inputs.iter().cloned().collect();
4052 dispatcher_inputs.remove(token_param.index);
4053
4054 assert_eq!(dispatcher_inputs.len(), 0);
4056 }
4057
4058 #[test]
4059 fn dispatcher_param_removal_token_last() {
4060 let f: ItemFn =
4061 syn::parse_str("fn process(data: &[f32], scale: f32, token: SimdToken) -> f32 { 0.0 }")
4062 .unwrap();
4063
4064 let token_param = find_simd_token_param(&f.sig).unwrap();
4065 assert_eq!(token_param.index, 2);
4066
4067 let mut dispatcher_inputs: Vec<FnArg> = f.sig.inputs.iter().cloned().collect();
4068 dispatcher_inputs.remove(token_param.index);
4069
4070 assert_eq!(dispatcher_inputs.len(), 2);
4071 }
4072
4073 #[test]
4074 fn dispatcher_dispatch_args_extraction() {
4075 let f: ItemFn =
4077 syn::parse_str("fn process(data: &[f32], scale: f32) -> f32 { 0.0 }").unwrap();
4078
4079 let dispatch_args: Vec<String> = f
4080 .sig
4081 .inputs
4082 .iter()
4083 .filter_map(|arg| {
4084 if let FnArg::Typed(PatType { pat, .. }) = arg {
4085 if let syn::Pat::Ident(pi) = pat.as_ref() {
4086 return Some(pi.ident.to_string());
4087 }
4088 }
4089 None
4090 })
4091 .collect();
4092
4093 assert_eq!(dispatch_args, vec!["data", "scale"]);
4094 }
4095
4096 #[test]
4097 fn dispatcher_wildcard_params_get_renamed() {
4098 let f: ItemFn = syn::parse_str("fn process(_: &[f32], _: f32) -> f32 { 0.0 }").unwrap();
4099
4100 let mut dispatcher_inputs: Vec<FnArg> = f.sig.inputs.iter().cloned().collect();
4101
4102 let mut wild_counter = 0u32;
4103 for arg in &mut dispatcher_inputs {
4104 if let FnArg::Typed(pat_type) = arg {
4105 if matches!(pat_type.pat.as_ref(), syn::Pat::Wild(_)) {
4106 let ident = format_ident!("__autoversion_wild_{}", wild_counter);
4107 wild_counter += 1;
4108 *pat_type.pat = syn::Pat::Ident(syn::PatIdent {
4109 attrs: vec![],
4110 by_ref: None,
4111 mutability: None,
4112 ident,
4113 subpat: None,
4114 });
4115 }
4116 }
4117 }
4118
4119 assert_eq!(wild_counter, 2);
4121
4122 let names: Vec<String> = dispatcher_inputs
4123 .iter()
4124 .filter_map(|arg| {
4125 if let FnArg::Typed(PatType { pat, .. }) = arg {
4126 if let syn::Pat::Ident(pi) = pat.as_ref() {
4127 return Some(pi.ident.to_string());
4128 }
4129 }
4130 None
4131 })
4132 .collect();
4133
4134 assert_eq!(names, vec!["__autoversion_wild_0", "__autoversion_wild_1"]);
4135 }
4136
4137 #[test]
4142 fn suffix_path_simple() {
4143 let path: syn::Path = syn::parse_str("process").unwrap();
4144 let suffixed = suffix_path(&path, "v3");
4145 assert_eq!(suffixed.to_token_stream().to_string(), "process_v3");
4146 }
4147
4148 #[test]
4149 fn suffix_path_qualified() {
4150 let path: syn::Path = syn::parse_str("module::process").unwrap();
4151 let suffixed = suffix_path(&path, "neon");
4152 let s = suffixed.to_token_stream().to_string();
4153 assert!(
4154 s.contains("process_neon"),
4155 "Expected process_neon, got: {}",
4156 s
4157 );
4158 }
4159}