1mod arcane;
6mod autoversion;
7mod common;
8mod generated;
9mod incant;
10mod magetypes;
11mod rewrite;
12mod rite;
13mod tiers;
14mod token_discovery;
15
16use proc_macro::TokenStream;
17use syn::parse_macro_input;
18
19use arcane::*;
20use autoversion::*;
21use common::*;
22use incant::*;
23use magetypes::*;
24use rite::*;
25use tiers::*;
26
27#[cfg(test)]
29use generated::{token_to_features, trait_to_features};
30#[cfg(test)]
31use quote::{ToTokens, format_ident};
32#[cfg(test)]
33use syn::{FnArg, PatType, Type};
34#[cfg(test)]
35use token_discovery::*;
36
37#[proc_macro_attribute]
202pub fn arcane(attr: TokenStream, item: TokenStream) -> TokenStream {
203 let args = parse_macro_input!(attr as ArcaneArgs);
204 let input_fn = parse_macro_input!(item as LightFn);
205 arcane_impl(input_fn, "arcane", args)
206}
207
208#[proc_macro_attribute]
212#[doc(hidden)]
213pub fn simd_fn(attr: TokenStream, item: TokenStream) -> TokenStream {
214 let args = parse_macro_input!(attr as ArcaneArgs);
215 let input_fn = parse_macro_input!(item as LightFn);
216 arcane_impl(input_fn, "simd_fn", args)
217}
218
219#[proc_macro_attribute]
232pub fn token_target_features_boundary(attr: TokenStream, item: TokenStream) -> TokenStream {
233 let args = parse_macro_input!(attr as ArcaneArgs);
234 let input_fn = parse_macro_input!(item as LightFn);
235 arcane_impl(input_fn, "token_target_features_boundary", args)
236}
237
238#[proc_macro_attribute]
317pub fn rite(attr: TokenStream, item: TokenStream) -> TokenStream {
318 let args = parse_macro_input!(attr as RiteArgs);
319 let input_fn = parse_macro_input!(item as LightFn);
320 rite_impl(input_fn, args)
321}
322
323#[proc_macro_attribute]
334pub fn token_target_features(attr: TokenStream, item: TokenStream) -> TokenStream {
335 let args = parse_macro_input!(attr as RiteArgs);
336 let input_fn = parse_macro_input!(item as LightFn);
337 rite_impl(input_fn, args)
338}
339
340#[proc_macro_attribute]
401pub fn magetypes(attr: TokenStream, item: TokenStream) -> TokenStream {
402 let input_fn = parse_macro_input!(item as LightFn);
403
404 let tier_names: Vec<String> = if attr.is_empty() {
406 DEFAULT_TIER_NAMES.iter().map(|s| s.to_string()).collect()
407 } else {
408 match syn::parse::Parser::parse(parse_tier_names, attr) {
409 Ok(names) => names,
410 Err(e) => return e.to_compile_error().into(),
411 }
412 };
413
414 let tiers = match resolve_tiers(
416 &tier_names,
417 input_fn.sig.ident.span(),
418 true, ) {
420 Ok(t) => t,
421 Err(e) => return e.to_compile_error().into(),
422 };
423
424 magetypes_impl(input_fn, &tiers)
425}
426
427#[proc_macro]
537pub fn incant(input: TokenStream) -> TokenStream {
538 let input = parse_macro_input!(input as IncantInput);
539 incant_impl(input)
540}
541
542#[proc_macro]
544pub fn simd_route(input: TokenStream) -> TokenStream {
545 let input = parse_macro_input!(input as IncantInput);
546 incant_impl(input)
547}
548
549#[proc_macro]
557pub fn dispatch_variant(input: TokenStream) -> TokenStream {
558 let input = parse_macro_input!(input as IncantInput);
559 incant_impl(input)
560}
561
562#[proc_macro_attribute]
693pub fn autoversion(attr: TokenStream, item: TokenStream) -> TokenStream {
694 let args = parse_macro_input!(attr as AutoversionArgs);
695 let input_fn = parse_macro_input!(item as LightFn);
696 autoversion_impl(input_fn, args)
697}
698
699#[cfg(test)]
704mod tests {
705 use super::*;
706
707 use super::generated::{ALL_CONCRETE_TOKENS, ALL_TRAIT_NAMES};
708 use syn::{ItemFn, ReturnType};
709
710 #[test]
711 fn every_concrete_token_is_in_token_to_features() {
712 for &name in ALL_CONCRETE_TOKENS {
713 assert!(
714 token_to_features(name).is_some(),
715 "Token `{}` exists in runtime crate but is NOT recognized by \
716 token_to_features() in the proc macro. Add it!",
717 name
718 );
719 }
720 }
721
722 #[test]
723 fn every_trait_is_in_trait_to_features() {
724 for &name in ALL_TRAIT_NAMES {
725 assert!(
726 trait_to_features(name).is_some(),
727 "Trait `{}` exists in runtime crate but is NOT recognized by \
728 trait_to_features() in the proc macro. Add it!",
729 name
730 );
731 }
732 }
733
734 #[test]
735 fn token_aliases_map_to_same_features() {
736 assert_eq!(
738 token_to_features("Desktop64"),
739 token_to_features("X64V3Token"),
740 "Desktop64 and X64V3Token should map to identical features"
741 );
742
743 assert_eq!(
745 token_to_features("Server64"),
746 token_to_features("X64V4Token"),
747 "Server64 and X64V4Token should map to identical features"
748 );
749 assert_eq!(
750 token_to_features("X64V4Token"),
751 token_to_features("Avx512Token"),
752 "X64V4Token and Avx512Token should map to identical features"
753 );
754
755 assert_eq!(
757 token_to_features("Arm64"),
758 token_to_features("NeonToken"),
759 "Arm64 and NeonToken should map to identical features"
760 );
761 }
762
763 #[test]
764 fn trait_to_features_includes_tokens_as_bounds() {
765 let tier_tokens = [
769 "X64V2Token",
770 "X64CryptoToken",
771 "X64V3Token",
772 "Desktop64",
773 "Avx2FmaToken",
774 "X64V4Token",
775 "Avx512Token",
776 "Server64",
777 "X64V4xToken",
778 "Avx512Fp16Token",
779 "NeonToken",
780 "Arm64",
781 "NeonAesToken",
782 "NeonSha3Token",
783 "NeonCrcToken",
784 "Arm64V2Token",
785 "Arm64V3Token",
786 ];
787
788 for &name in &tier_tokens {
789 assert!(
790 trait_to_features(name).is_some(),
791 "Tier token `{}` should also be recognized in trait_to_features() \
792 for use as a generic bound. Add it!",
793 name
794 );
795 }
796 }
797
798 #[test]
799 fn trait_features_are_cumulative() {
800 let v2_features = trait_to_features("HasX64V2").unwrap();
802 let v4_features = trait_to_features("HasX64V4").unwrap();
803
804 for &f in v2_features {
805 assert!(
806 v4_features.contains(&f),
807 "HasX64V4 should include v2 feature `{}` but doesn't",
808 f
809 );
810 }
811
812 assert!(
814 v4_features.len() > v2_features.len(),
815 "HasX64V4 should have more features than HasX64V2"
816 );
817 }
818
819 #[test]
820 fn x64v3_trait_features_include_v2() {
821 let v2 = trait_to_features("HasX64V2").unwrap();
823 let v3 = trait_to_features("X64V3Token").unwrap();
824
825 for &f in v2 {
826 assert!(
827 v3.contains(&f),
828 "X64V3Token trait features should include v2 feature `{}` but don't",
829 f
830 );
831 }
832 }
833
834 #[test]
835 fn has_neon_aes_includes_neon() {
836 let neon = trait_to_features("HasNeon").unwrap();
837 let neon_aes = trait_to_features("HasNeonAes").unwrap();
838
839 for &f in neon {
840 assert!(
841 neon_aes.contains(&f),
842 "HasNeonAes should include NEON feature `{}`",
843 f
844 );
845 }
846 }
847
848 #[test]
849 fn no_removed_traits_are_recognized() {
850 let removed = [
852 "HasSse",
853 "HasSse2",
854 "HasSse41",
855 "HasSse42",
856 "HasAvx",
857 "HasAvx2",
858 "HasFma",
859 "HasAvx512f",
860 "HasAvx512bw",
861 "HasAvx512vl",
862 "HasAvx512vbmi2",
863 "HasSve",
864 "HasSve2",
865 ];
866
867 for &name in &removed {
868 assert!(
869 trait_to_features(name).is_none(),
870 "Removed trait `{}` should NOT be in trait_to_features(). \
871 It was removed in 0.3.0 — users should migrate to tier traits.",
872 name
873 );
874 }
875 }
876
877 #[test]
878 fn no_nonexistent_tokens_are_recognized() {
879 let fake = [
881 "SveToken",
882 "Sve2Token",
883 "Avx512VnniToken",
884 "X64V4ModernToken",
885 "NeonFp16Token",
886 ];
887
888 for &name in &fake {
889 assert!(
890 token_to_features(name).is_none(),
891 "Non-existent token `{}` should NOT be in token_to_features()",
892 name
893 );
894 }
895 }
896
897 #[test]
898 fn featureless_traits_are_not_in_registries() {
899 for &name in FEATURELESS_TRAIT_NAMES {
902 assert!(
903 token_to_features(name).is_none(),
904 "`{}` should NOT be in token_to_features() — it has no CPU features",
905 name
906 );
907 assert!(
908 trait_to_features(name).is_none(),
909 "`{}` should NOT be in trait_to_features() — it has no CPU features",
910 name
911 );
912 }
913 }
914
915 #[test]
916 fn find_featureless_trait_detects_simdtoken() {
917 let names = vec!["SimdToken".to_string()];
918 assert_eq!(find_featureless_trait(&names), Some("SimdToken"));
919
920 let names = vec!["IntoConcreteToken".to_string()];
921 assert_eq!(find_featureless_trait(&names), Some("IntoConcreteToken"));
922
923 let names = vec!["HasX64V2".to_string()];
925 assert_eq!(find_featureless_trait(&names), None);
926
927 let names = vec!["HasNeon".to_string()];
928 assert_eq!(find_featureless_trait(&names), None);
929
930 let names = vec!["SimdToken".to_string(), "HasX64V2".to_string()];
932 assert_eq!(find_featureless_trait(&names), Some("SimdToken"));
933 }
934
935 #[test]
936 fn arm64_v2_v3_traits_are_cumulative() {
937 let v2_features = trait_to_features("HasArm64V2").unwrap();
938 let v3_features = trait_to_features("HasArm64V3").unwrap();
939
940 for &f in v2_features {
941 assert!(
942 v3_features.contains(&f),
943 "HasArm64V3 should include v2 feature `{}` but doesn't",
944 f
945 );
946 }
947
948 assert!(
949 v3_features.len() > v2_features.len(),
950 "HasArm64V3 should have more features than HasArm64V2"
951 );
952 }
953
954 fn resolve_tier_names(names: &[&str], default_gates: bool) -> Vec<String> {
959 let names: Vec<String> = names.iter().map(|s| s.to_string()).collect();
960 resolve_tiers(&names, proc_macro2::Span::call_site(), default_gates)
961 .unwrap()
962 .iter()
963 .map(|rt| {
964 if let Some(ref gate) = rt.feature_gate {
965 format!("{}({})", rt.name, gate)
966 } else {
967 rt.name.to_string()
968 }
969 })
970 .collect()
971 }
972
973 #[test]
974 fn resolve_defaults() {
975 let tiers = resolve_tier_names(&["v4", "v3", "neon", "wasm128", "scalar"], true);
976 assert!(tiers.contains(&"v3".to_string()));
977 assert!(tiers.contains(&"scalar".to_string()));
978 assert!(tiers.contains(&"v4(avx512)".to_string()));
980 }
981
982 #[test]
983 fn resolve_additive_appends() {
984 let tiers = resolve_tier_names(&["+v1"], true);
985 assert!(tiers.contains(&"v1".to_string()));
986 assert!(tiers.contains(&"v3".to_string())); assert!(tiers.contains(&"scalar".to_string())); }
989
990 #[test]
991 fn resolve_additive_v4_overrides_gate() {
992 let tiers = resolve_tier_names(&["+v4"], true);
994 assert!(tiers.contains(&"v4".to_string())); assert!(!tiers.iter().any(|t| t == "v4(avx512)")); }
997
998 #[test]
999 fn resolve_additive_default_replaces_scalar() {
1000 let tiers = resolve_tier_names(&["+default"], true);
1001 assert!(tiers.contains(&"default".to_string()));
1002 assert!(!tiers.iter().any(|t| t == "scalar")); }
1004
1005 #[test]
1006 fn resolve_subtractive_removes() {
1007 let tiers = resolve_tier_names(&["-neon", "-wasm128"], true);
1008 assert!(!tiers.iter().any(|t| t == "neon"));
1009 assert!(!tiers.iter().any(|t| t == "wasm128"));
1010 assert!(tiers.contains(&"v3".to_string())); }
1012
1013 #[test]
1014 fn resolve_mixed_add_remove() {
1015 let tiers = resolve_tier_names(&["-neon", "-wasm128", "+v1"], true);
1016 assert!(tiers.contains(&"v1".to_string()));
1017 assert!(!tiers.iter().any(|t| t == "neon"));
1018 assert!(!tiers.iter().any(|t| t == "wasm128"));
1019 assert!(tiers.contains(&"v3".to_string()));
1020 assert!(tiers.contains(&"scalar".to_string()));
1021 }
1022
1023 #[test]
1024 fn resolve_additive_duplicate_is_noop() {
1025 let tiers = resolve_tier_names(&["+v3"], true);
1027 let v3_count = tiers.iter().filter(|t| t.as_str() == "v3").count();
1028 assert_eq!(v3_count, 1);
1029 }
1030
1031 #[test]
1032 fn resolve_mixing_plus_and_plain_is_error() {
1033 let names: Vec<String> = vec!["+v1".into(), "v3".into()];
1034 let result = resolve_tiers(&names, proc_macro2::Span::call_site(), true);
1035 assert!(result.is_err());
1036 }
1037
1038 #[test]
1039 fn resolve_underscore_tier_name() {
1040 let tiers = resolve_tier_names(&["_v3", "_neon", "_scalar"], false);
1041 assert!(tiers.contains(&"v3".to_string()));
1042 assert!(tiers.contains(&"neon".to_string()));
1043 assert!(tiers.contains(&"scalar".to_string()));
1044 }
1045
1046 #[test]
1051 fn autoversion_args_empty() {
1052 let args: AutoversionArgs = syn::parse_str("").unwrap();
1053 assert!(args.self_type.is_none());
1054 assert!(args.tiers.is_none());
1055 }
1056
1057 #[test]
1058 fn autoversion_args_single_tier() {
1059 let args: AutoversionArgs = syn::parse_str("v3").unwrap();
1060 assert!(args.self_type.is_none());
1061 assert_eq!(args.tiers.as_ref().unwrap(), &["v3"]);
1062 }
1063
1064 #[test]
1065 fn autoversion_args_tiers_only() {
1066 let args: AutoversionArgs = syn::parse_str("v3, v4, neon").unwrap();
1067 assert!(args.self_type.is_none());
1068 let tiers = args.tiers.unwrap();
1069 assert_eq!(tiers, vec!["v3", "v4", "neon"]);
1070 }
1071
1072 #[test]
1073 fn autoversion_args_many_tiers() {
1074 let args: AutoversionArgs =
1075 syn::parse_str("v1, v2, v3, v4, v4x, neon, arm_v2, wasm128").unwrap();
1076 assert_eq!(
1077 args.tiers.unwrap(),
1078 vec!["v1", "v2", "v3", "v4", "v4x", "neon", "arm_v2", "wasm128"]
1079 );
1080 }
1081
1082 #[test]
1083 fn autoversion_args_trailing_comma() {
1084 let args: AutoversionArgs = syn::parse_str("v3, v4,").unwrap();
1085 assert_eq!(args.tiers.as_ref().unwrap(), &["v3", "v4"]);
1086 }
1087
1088 #[test]
1089 fn autoversion_args_self_only() {
1090 let args: AutoversionArgs = syn::parse_str("_self = MyType").unwrap();
1091 assert!(args.self_type.is_some());
1092 assert!(args.tiers.is_none());
1093 }
1094
1095 #[test]
1096 fn autoversion_args_self_and_tiers() {
1097 let args: AutoversionArgs = syn::parse_str("_self = MyType, v3, neon").unwrap();
1098 assert!(args.self_type.is_some());
1099 let tiers = args.tiers.unwrap();
1100 assert_eq!(tiers, vec!["v3", "neon"]);
1101 }
1102
1103 #[test]
1104 fn autoversion_args_tiers_then_self() {
1105 let args: AutoversionArgs = syn::parse_str("v3, neon, _self = MyType").unwrap();
1107 assert!(args.self_type.is_some());
1108 let tiers = args.tiers.unwrap();
1109 assert_eq!(tiers, vec!["v3", "neon"]);
1110 }
1111
1112 #[test]
1113 fn autoversion_args_self_with_path_type() {
1114 let args: AutoversionArgs = syn::parse_str("_self = crate::MyType").unwrap();
1115 assert!(args.self_type.is_some());
1116 assert!(args.tiers.is_none());
1117 }
1118
1119 #[test]
1120 fn autoversion_args_self_with_generic_type() {
1121 let args: AutoversionArgs = syn::parse_str("_self = Vec<u8>").unwrap();
1122 assert!(args.self_type.is_some());
1123 let ty_str = args.self_type.unwrap().to_token_stream().to_string();
1124 assert!(ty_str.contains("Vec"), "Expected Vec<u8>, got: {}", ty_str);
1125 }
1126
1127 #[test]
1128 fn autoversion_args_self_trailing_comma() {
1129 let args: AutoversionArgs = syn::parse_str("_self = MyType,").unwrap();
1130 assert!(args.self_type.is_some());
1131 assert!(args.tiers.is_none());
1132 }
1133
1134 #[test]
1139 fn find_autoversion_token_param_simdtoken_first() {
1140 let f: ItemFn =
1141 syn::parse_str("fn process(token: SimdToken, data: &[f32]) -> f32 {}").unwrap();
1142 let param = find_autoversion_token_param(&f.sig).unwrap().unwrap();
1143 assert_eq!(param.index, 0);
1144 assert_eq!(param.ident, "token");
1145 assert_eq!(param.kind, AutoversionTokenKind::SimdToken);
1146 }
1147
1148 #[test]
1149 fn find_autoversion_token_param_simdtoken_second() {
1150 let f: ItemFn =
1151 syn::parse_str("fn process(data: &[f32], token: SimdToken) -> f32 {}").unwrap();
1152 let param = find_autoversion_token_param(&f.sig).unwrap().unwrap();
1153 assert_eq!(param.index, 1);
1154 assert_eq!(param.kind, AutoversionTokenKind::SimdToken);
1155 }
1156
1157 #[test]
1158 fn find_autoversion_token_param_underscore_prefix() {
1159 let f: ItemFn =
1160 syn::parse_str("fn process(_token: SimdToken, data: &[f32]) -> f32 {}").unwrap();
1161 let param = find_autoversion_token_param(&f.sig).unwrap().unwrap();
1162 assert_eq!(param.index, 0);
1163 assert_eq!(param.ident, "_token");
1164 }
1165
1166 #[test]
1167 fn find_autoversion_token_param_wildcard() {
1168 let f: ItemFn = syn::parse_str("fn process(_: SimdToken, data: &[f32]) -> f32 {}").unwrap();
1169 let param = find_autoversion_token_param(&f.sig).unwrap().unwrap();
1170 assert_eq!(param.index, 0);
1171 assert_eq!(param.ident, "__autoversion_token");
1172 }
1173
1174 #[test]
1175 fn find_autoversion_token_param_scalar_token() {
1176 let f: ItemFn =
1177 syn::parse_str("fn process_scalar(_: ScalarToken, data: &[f32]) -> f32 {}").unwrap();
1178 let param = find_autoversion_token_param(&f.sig).unwrap().unwrap();
1179 assert_eq!(param.index, 0);
1180 assert_eq!(param.kind, AutoversionTokenKind::ScalarToken);
1181 }
1182
1183 #[test]
1184 fn find_autoversion_token_param_not_found() {
1185 let f: ItemFn = syn::parse_str("fn process(data: &[f32]) -> f32 {}").unwrap();
1186 assert!(find_autoversion_token_param(&f.sig).unwrap().is_none());
1187 }
1188
1189 #[test]
1190 fn find_autoversion_token_param_no_params() {
1191 let f: ItemFn = syn::parse_str("fn process() {}").unwrap();
1192 assert!(find_autoversion_token_param(&f.sig).unwrap().is_none());
1193 }
1194
1195 #[test]
1196 fn find_autoversion_token_param_concrete_token_errors() {
1197 let f: ItemFn =
1198 syn::parse_str("fn process(token: X64V3Token, data: &[f32]) -> f32 {}").unwrap();
1199 let err = find_autoversion_token_param(&f.sig).unwrap_err();
1200 let msg = err.to_string();
1201 assert!(
1202 msg.contains("concrete token"),
1203 "error should mention concrete token: {msg}"
1204 );
1205 assert!(
1206 msg.contains("#[arcane]"),
1207 "error should suggest #[arcane]: {msg}"
1208 );
1209 }
1210
1211 #[test]
1212 fn find_autoversion_token_param_neon_token_errors() {
1213 let f: ItemFn =
1214 syn::parse_str("fn process(token: NeonToken, data: &[f32]) -> f32 {}").unwrap();
1215 assert!(find_autoversion_token_param(&f.sig).is_err());
1216 }
1217
1218 #[test]
1219 fn find_autoversion_token_param_unknown_type_ignored() {
1220 let f: ItemFn = syn::parse_str("fn process(data: &[f32], scale: f32) -> f32 {}").unwrap();
1222 assert!(find_autoversion_token_param(&f.sig).unwrap().is_none());
1223 }
1224
1225 #[test]
1226 fn find_autoversion_token_param_among_many() {
1227 let f: ItemFn = syn::parse_str(
1228 "fn process(a: i32, b: f64, token: SimdToken, c: &str, d: bool) -> f32 {}",
1229 )
1230 .unwrap();
1231 let param = find_autoversion_token_param(&f.sig).unwrap().unwrap();
1232 assert_eq!(param.index, 2);
1233 assert_eq!(param.ident, "token");
1234 }
1235
1236 #[test]
1237 fn find_autoversion_token_param_with_generics() {
1238 let f: ItemFn =
1239 syn::parse_str("fn process<T: Clone>(token: SimdToken, data: &[T]) -> T {}").unwrap();
1240 let param = find_autoversion_token_param(&f.sig).unwrap().unwrap();
1241 assert_eq!(param.index, 0);
1242 }
1243
1244 #[test]
1245 fn find_autoversion_token_param_with_where_clause() {
1246 let f: ItemFn = syn::parse_str(
1247 "fn process<T>(token: SimdToken, data: &[T]) -> T where T: Copy + Default {}",
1248 )
1249 .unwrap();
1250 let param = find_autoversion_token_param(&f.sig).unwrap().unwrap();
1251 assert_eq!(param.index, 0);
1252 }
1253
1254 #[test]
1255 fn find_autoversion_token_param_with_lifetime() {
1256 let f: ItemFn =
1257 syn::parse_str("fn process<'a>(token: SimdToken, data: &'a [f32]) -> &'a f32 {}")
1258 .unwrap();
1259 let param = find_autoversion_token_param(&f.sig).unwrap().unwrap();
1260 assert_eq!(param.index, 0);
1261 }
1262
1263 #[test]
1268 fn autoversion_default_tiers_all_resolve() {
1269 let names: Vec<String> = DEFAULT_TIER_NAMES.iter().map(|s| s.to_string()).collect();
1270 let tiers = resolve_tiers(&names, proc_macro2::Span::call_site(), false).unwrap();
1271 assert!(!tiers.is_empty());
1272 assert!(tiers.iter().any(|t| t.name == "scalar"));
1274 }
1275
1276 #[test]
1277 fn autoversion_scalar_always_appended() {
1278 let names = vec!["v3".to_string(), "neon".to_string()];
1279 let tiers = resolve_tiers(&names, proc_macro2::Span::call_site(), false).unwrap();
1280 assert!(
1281 tiers.iter().any(|t| t.name == "scalar"),
1282 "scalar must be auto-appended"
1283 );
1284 }
1285
1286 #[test]
1287 fn autoversion_scalar_not_duplicated() {
1288 let names = vec!["v3".to_string(), "scalar".to_string()];
1289 let tiers = resolve_tiers(&names, proc_macro2::Span::call_site(), false).unwrap();
1290 let scalar_count = tiers.iter().filter(|t| t.name == "scalar").count();
1291 assert_eq!(scalar_count, 1, "scalar must not be duplicated");
1292 }
1293
1294 #[test]
1295 fn autoversion_tiers_sorted_by_priority() {
1296 let names = vec!["neon".to_string(), "v4".to_string(), "v3".to_string()];
1297 let tiers = resolve_tiers(&names, proc_macro2::Span::call_site(), false).unwrap();
1298 let priorities: Vec<u32> = tiers.iter().map(|t| t.priority).collect();
1300 for window in priorities.windows(2) {
1301 assert!(
1302 window[0] >= window[1],
1303 "Tiers not sorted by priority: {:?}",
1304 priorities
1305 );
1306 }
1307 }
1308
1309 #[test]
1310 fn autoversion_unknown_tier_errors() {
1311 let names = vec!["v3".to_string(), "avx9000".to_string()];
1312 let result = resolve_tiers(&names, proc_macro2::Span::call_site(), false);
1313 match result {
1314 Ok(_) => panic!("Expected error for unknown tier 'avx9000'"),
1315 Err(e) => {
1316 let err_msg = e.to_string();
1317 assert!(
1318 err_msg.contains("avx9000"),
1319 "Error should mention unknown tier: {}",
1320 err_msg
1321 );
1322 }
1323 }
1324 }
1325
1326 #[test]
1327 fn autoversion_all_known_tiers_resolve() {
1328 for tier in ALL_TIERS {
1330 assert!(
1331 find_tier(tier.name).is_some(),
1332 "Tier '{}' should be findable by name",
1333 tier.name
1334 );
1335 }
1336 }
1337
1338 #[test]
1339 fn autoversion_default_tier_list_is_sensible() {
1340 let names: Vec<String> = DEFAULT_TIER_NAMES.iter().map(|s| s.to_string()).collect();
1342 let tiers = resolve_tiers(&names, proc_macro2::Span::call_site(), false).unwrap();
1343
1344 let has_x86 = tiers.iter().any(|t| t.target_arch == Some("x86_64"));
1345 let has_arm = tiers.iter().any(|t| t.target_arch == Some("aarch64"));
1346 let has_wasm = tiers.iter().any(|t| t.target_arch == Some("wasm32"));
1347 let has_scalar = tiers.iter().any(|t| t.name == "scalar");
1348
1349 assert!(has_x86, "Default tiers should include an x86_64 tier");
1350 assert!(has_arm, "Default tiers should include an aarch64 tier");
1351 assert!(has_wasm, "Default tiers should include a wasm32 tier");
1352 assert!(has_scalar, "Default tiers should include scalar");
1353 }
1354
1355 fn do_variant_replacement(func: &str, tier_name: &str, has_self: bool) -> ItemFn {
1363 let mut f: ItemFn = syn::parse_str(func).unwrap();
1364 let fn_name = f.sig.ident.to_string();
1365
1366 let tier = find_tier(tier_name).unwrap();
1367
1368 f.sig.ident = format_ident!("{}_{}", fn_name, tier.suffix);
1370
1371 let token_idx = find_autoversion_token_param(&f.sig)
1373 .expect("should not error on SimdToken")
1374 .unwrap_or_else(|| panic!("No SimdToken param in: {}", func))
1375 .index;
1376 if tier_name == "default" {
1377 let stmts = f.block.stmts.clone();
1379 let mut inputs: Vec<FnArg> = f.sig.inputs.iter().cloned().collect();
1380 inputs.remove(token_idx);
1381 f.sig.inputs = inputs.into_iter().collect();
1382 f.block.stmts = stmts;
1383 } else {
1384 let concrete_type: Type = syn::parse_str(tier.token_path).unwrap();
1385 if let FnArg::Typed(pt) = &mut f.sig.inputs[token_idx] {
1386 *pt.ty = concrete_type;
1387 }
1388 }
1389
1390 if (tier_name == "scalar" || tier_name == "default") && has_self {
1392 let preamble: syn::Stmt = syn::parse_quote!(let _self = self;);
1393 f.block.stmts.insert(0, preamble);
1394 }
1395
1396 f
1397 }
1398
1399 #[test]
1400 fn variant_replacement_v3_renames_function() {
1401 let f = do_variant_replacement(
1402 "fn process(token: SimdToken, data: &[f32]) -> f32 { 0.0 }",
1403 "v3",
1404 false,
1405 );
1406 assert_eq!(f.sig.ident, "process_v3");
1407 }
1408
1409 #[test]
1410 fn variant_replacement_v3_replaces_token_type() {
1411 let f = do_variant_replacement(
1412 "fn process(token: SimdToken, data: &[f32]) -> f32 { 0.0 }",
1413 "v3",
1414 false,
1415 );
1416 let first_param_ty = match &f.sig.inputs[0] {
1417 FnArg::Typed(pt) => pt.ty.to_token_stream().to_string(),
1418 _ => panic!("Expected typed param"),
1419 };
1420 assert!(
1421 first_param_ty.contains("X64V3Token"),
1422 "Expected X64V3Token, got: {}",
1423 first_param_ty
1424 );
1425 }
1426
1427 #[test]
1428 fn variant_replacement_neon_produces_valid_fn() {
1429 let f = do_variant_replacement(
1430 "fn compute(token: SimdToken, data: &[f32]) -> f32 { 0.0 }",
1431 "neon",
1432 false,
1433 );
1434 assert_eq!(f.sig.ident, "compute_neon");
1435 let first_param_ty = match &f.sig.inputs[0] {
1436 FnArg::Typed(pt) => pt.ty.to_token_stream().to_string(),
1437 _ => panic!("Expected typed param"),
1438 };
1439 assert!(
1440 first_param_ty.contains("NeonToken"),
1441 "Expected NeonToken, got: {}",
1442 first_param_ty
1443 );
1444 }
1445
1446 #[test]
1447 fn variant_replacement_wasm128_produces_valid_fn() {
1448 let f = do_variant_replacement(
1449 "fn compute(_t: SimdToken, data: &[f32]) -> f32 { 0.0 }",
1450 "wasm128",
1451 false,
1452 );
1453 assert_eq!(f.sig.ident, "compute_wasm128");
1454 }
1455
1456 #[test]
1457 fn variant_replacement_scalar_produces_valid_fn() {
1458 let f = do_variant_replacement(
1459 "fn compute(token: SimdToken, data: &[f32]) -> f32 { 0.0 }",
1460 "scalar",
1461 false,
1462 );
1463 assert_eq!(f.sig.ident, "compute_scalar");
1464 let first_param_ty = match &f.sig.inputs[0] {
1465 FnArg::Typed(pt) => pt.ty.to_token_stream().to_string(),
1466 _ => panic!("Expected typed param"),
1467 };
1468 assert!(
1469 first_param_ty.contains("ScalarToken"),
1470 "Expected ScalarToken, got: {}",
1471 first_param_ty
1472 );
1473 }
1474
1475 #[test]
1476 fn variant_replacement_v4_produces_valid_fn() {
1477 let f = do_variant_replacement(
1478 "fn transform(token: SimdToken, data: &mut [f32]) { }",
1479 "v4",
1480 false,
1481 );
1482 assert_eq!(f.sig.ident, "transform_v4");
1483 let first_param_ty = match &f.sig.inputs[0] {
1484 FnArg::Typed(pt) => pt.ty.to_token_stream().to_string(),
1485 _ => panic!("Expected typed param"),
1486 };
1487 assert!(
1488 first_param_ty.contains("X64V4Token"),
1489 "Expected X64V4Token, got: {}",
1490 first_param_ty
1491 );
1492 }
1493
1494 #[test]
1495 fn variant_replacement_v4x_produces_valid_fn() {
1496 let f = do_variant_replacement(
1497 "fn transform(token: SimdToken, data: &mut [f32]) { }",
1498 "v4x",
1499 false,
1500 );
1501 assert_eq!(f.sig.ident, "transform_v4x");
1502 }
1503
1504 #[test]
1505 fn variant_replacement_arm_v2_produces_valid_fn() {
1506 let f = do_variant_replacement(
1507 "fn transform(token: SimdToken, data: &mut [f32]) { }",
1508 "arm_v2",
1509 false,
1510 );
1511 assert_eq!(f.sig.ident, "transform_arm_v2");
1512 }
1513
1514 #[test]
1515 fn variant_replacement_preserves_generics() {
1516 let f = do_variant_replacement(
1517 "fn process<T: Copy + Default>(token: SimdToken, data: &[T]) -> T { T::default() }",
1518 "v3",
1519 false,
1520 );
1521 assert_eq!(f.sig.ident, "process_v3");
1522 assert!(
1524 !f.sig.generics.params.is_empty(),
1525 "Generics should be preserved"
1526 );
1527 }
1528
1529 #[test]
1530 fn variant_replacement_preserves_where_clause() {
1531 let f = do_variant_replacement(
1532 "fn process<T>(token: SimdToken, data: &[T]) -> T where T: Copy + Default { T::default() }",
1533 "v3",
1534 false,
1535 );
1536 assert!(
1537 f.sig.generics.where_clause.is_some(),
1538 "Where clause should be preserved"
1539 );
1540 }
1541
1542 #[test]
1543 fn variant_replacement_preserves_return_type() {
1544 let f = do_variant_replacement(
1545 "fn process(token: SimdToken, data: &[f32]) -> Vec<f32> { vec![] }",
1546 "neon",
1547 false,
1548 );
1549 let ret = f.sig.output.to_token_stream().to_string();
1550 assert!(
1551 ret.contains("Vec"),
1552 "Return type should be preserved, got: {}",
1553 ret
1554 );
1555 }
1556
1557 #[test]
1558 fn variant_replacement_preserves_multiple_params() {
1559 let f = do_variant_replacement(
1560 "fn process(token: SimdToken, a: &[f32], b: &[f32], scale: f32) -> f32 { 0.0 }",
1561 "v3",
1562 false,
1563 );
1564 assert_eq!(f.sig.inputs.len(), 4);
1566 }
1567
1568 #[test]
1569 fn variant_replacement_preserves_no_return_type() {
1570 let f = do_variant_replacement(
1571 "fn transform(token: SimdToken, data: &mut [f32]) { }",
1572 "v3",
1573 false,
1574 );
1575 assert!(
1576 matches!(f.sig.output, ReturnType::Default),
1577 "No return type should remain as Default"
1578 );
1579 }
1580
1581 #[test]
1582 fn variant_replacement_preserves_lifetime_params() {
1583 let f = do_variant_replacement(
1584 "fn process<'a>(token: SimdToken, data: &'a [f32]) -> &'a [f32] { data }",
1585 "v3",
1586 false,
1587 );
1588 assert!(!f.sig.generics.params.is_empty());
1589 }
1590
1591 #[test]
1592 fn variant_replacement_scalar_self_injects_preamble() {
1593 let f = do_variant_replacement(
1594 "fn method(token: SimdToken, data: &[f32]) -> f32 { 0.0 }",
1595 "scalar",
1596 true, );
1598 assert_eq!(f.sig.ident, "method_scalar");
1599
1600 let body_str = f.block.to_token_stream().to_string();
1602 assert!(
1603 body_str.contains("let _self = self"),
1604 "Scalar+self variant should have _self preamble, got: {}",
1605 body_str
1606 );
1607 }
1608
1609 #[test]
1610 fn variant_replacement_all_default_tiers_produce_valid_fns() {
1611 let names: Vec<String> = DEFAULT_TIER_NAMES.iter().map(|s| s.to_string()).collect();
1612 let tiers = resolve_tiers(&names, proc_macro2::Span::call_site(), false).unwrap();
1613
1614 for tier in &tiers {
1615 let f = do_variant_replacement(
1616 "fn process(token: SimdToken, data: &[f32]) -> f32 { 0.0 }",
1617 tier.name,
1618 false,
1619 );
1620 let expected_name = format!("process_{}", tier.suffix);
1621 assert_eq!(
1622 f.sig.ident.to_string(),
1623 expected_name,
1624 "Tier '{}' should produce function '{}'",
1625 tier.name,
1626 expected_name
1627 );
1628 }
1629 }
1630
1631 #[test]
1632 fn variant_replacement_all_known_tiers_produce_valid_fns() {
1633 for tier in ALL_TIERS {
1634 let f = do_variant_replacement(
1635 "fn compute(token: SimdToken, data: &[f32]) -> f32 { 0.0 }",
1636 tier.name,
1637 false,
1638 );
1639 let expected_name = format!("compute_{}", tier.suffix);
1640 assert_eq!(
1641 f.sig.ident.to_string(),
1642 expected_name,
1643 "Tier '{}' should produce function '{}'",
1644 tier.name,
1645 expected_name
1646 );
1647 }
1648 }
1649
1650 #[test]
1651 fn variant_replacement_no_simdtoken_remains() {
1652 for tier in ALL_TIERS {
1653 let f = do_variant_replacement(
1654 "fn compute(token: SimdToken, data: &[f32]) -> f32 { 0.0 }",
1655 tier.name,
1656 false,
1657 );
1658 let full_str = f.to_token_stream().to_string();
1659 assert!(
1660 !full_str.contains("SimdToken"),
1661 "Tier '{}' variant still contains 'SimdToken': {}",
1662 tier.name,
1663 full_str
1664 );
1665 }
1666 }
1667
1668 #[test]
1673 fn tier_v3_targets_x86_64() {
1674 let tier = find_tier("v3").unwrap();
1675 assert_eq!(tier.target_arch, Some("x86_64"));
1676 }
1677
1678 #[test]
1679 fn tier_v4_targets_x86_64() {
1680 let tier = find_tier("v4").unwrap();
1681 assert_eq!(tier.target_arch, Some("x86_64"));
1682 }
1683
1684 #[test]
1685 fn tier_v4x_targets_x86_64() {
1686 let tier = find_tier("v4x").unwrap();
1687 assert_eq!(tier.target_arch, Some("x86_64"));
1688 }
1689
1690 #[test]
1691 fn tier_neon_targets_aarch64() {
1692 let tier = find_tier("neon").unwrap();
1693 assert_eq!(tier.target_arch, Some("aarch64"));
1694 }
1695
1696 #[test]
1697 fn tier_wasm128_targets_wasm32() {
1698 let tier = find_tier("wasm128").unwrap();
1699 assert_eq!(tier.target_arch, Some("wasm32"));
1700 }
1701
1702 #[test]
1703 fn tier_scalar_has_no_guards() {
1704 let tier = find_tier("scalar").unwrap();
1705 assert_eq!(tier.target_arch, None);
1706 assert_eq!(tier.priority, 0);
1707 }
1708
1709 #[test]
1710 fn tier_priorities_are_consistent() {
1711 let v2 = find_tier("v2").unwrap();
1713 let v3 = find_tier("v3").unwrap();
1714 let v4 = find_tier("v4").unwrap();
1715 assert!(v4.priority > v3.priority);
1716 assert!(v3.priority > v2.priority);
1717
1718 let neon = find_tier("neon").unwrap();
1719 let arm_v2 = find_tier("arm_v2").unwrap();
1720 let arm_v3 = find_tier("arm_v3").unwrap();
1721 assert!(arm_v3.priority > arm_v2.priority);
1722 assert!(arm_v2.priority > neon.priority);
1723
1724 let scalar = find_tier("scalar").unwrap();
1726 assert!(neon.priority > scalar.priority);
1727 assert!(v2.priority > scalar.priority);
1728 }
1729
1730 #[test]
1735 fn dispatcher_param_removal_free_fn() {
1736 let f: ItemFn =
1738 syn::parse_str("fn process(token: SimdToken, data: &[f32], scale: f32) -> f32 { 0.0 }")
1739 .unwrap();
1740
1741 let token_param = find_autoversion_token_param(&f.sig).unwrap().unwrap();
1742 assert_eq!(token_param.kind, AutoversionTokenKind::SimdToken);
1744 let mut dispatcher_inputs: Vec<FnArg> = f.sig.inputs.iter().cloned().collect();
1745 dispatcher_inputs.remove(token_param.index);
1746 assert_eq!(dispatcher_inputs.len(), 2);
1747 }
1748
1749 #[test]
1750 fn dispatcher_param_removal_token_only() {
1751 let f: ItemFn = syn::parse_str("fn process(token: SimdToken) -> f32 { 0.0 }").unwrap();
1752 let token_param = find_autoversion_token_param(&f.sig).unwrap().unwrap();
1753 let mut dispatcher_inputs: Vec<FnArg> = f.sig.inputs.iter().cloned().collect();
1754 dispatcher_inputs.remove(token_param.index);
1755 assert_eq!(dispatcher_inputs.len(), 0);
1756 }
1757
1758 #[test]
1759 fn dispatcher_param_removal_token_last() {
1760 let f: ItemFn =
1761 syn::parse_str("fn process(data: &[f32], scale: f32, token: SimdToken) -> f32 { 0.0 }")
1762 .unwrap();
1763 let token_param = find_autoversion_token_param(&f.sig).unwrap().unwrap();
1764 assert_eq!(token_param.index, 2);
1765 let mut dispatcher_inputs: Vec<FnArg> = f.sig.inputs.iter().cloned().collect();
1766 dispatcher_inputs.remove(token_param.index);
1767 assert_eq!(dispatcher_inputs.len(), 2);
1768 }
1769
1770 #[test]
1771 fn dispatcher_scalar_token_kept() {
1772 let f: ItemFn =
1774 syn::parse_str("fn process_scalar(_: ScalarToken, data: &[f32]) -> f32 { 0.0 }")
1775 .unwrap();
1776 let token_param = find_autoversion_token_param(&f.sig).unwrap().unwrap();
1777 assert_eq!(token_param.kind, AutoversionTokenKind::ScalarToken);
1778 assert_eq!(f.sig.inputs.len(), 2);
1780 }
1781
1782 #[test]
1783 fn dispatcher_dispatch_args_extraction() {
1784 let f: ItemFn =
1786 syn::parse_str("fn process(data: &[f32], scale: f32) -> f32 { 0.0 }").unwrap();
1787
1788 let dispatch_args: Vec<String> = f
1789 .sig
1790 .inputs
1791 .iter()
1792 .filter_map(|arg| {
1793 if let FnArg::Typed(PatType { pat, .. }) = arg {
1794 if let syn::Pat::Ident(pi) = pat.as_ref() {
1795 return Some(pi.ident.to_string());
1796 }
1797 }
1798 None
1799 })
1800 .collect();
1801
1802 assert_eq!(dispatch_args, vec!["data", "scale"]);
1803 }
1804
1805 #[test]
1806 fn dispatcher_wildcard_params_get_renamed() {
1807 let f: ItemFn = syn::parse_str("fn process(_: &[f32], _: f32) -> f32 { 0.0 }").unwrap();
1808
1809 let mut dispatcher_inputs: Vec<FnArg> = f.sig.inputs.iter().cloned().collect();
1810
1811 let mut wild_counter = 0u32;
1812 for arg in &mut dispatcher_inputs {
1813 if let FnArg::Typed(pat_type) = arg {
1814 if matches!(pat_type.pat.as_ref(), syn::Pat::Wild(_)) {
1815 let ident = format_ident!("__autoversion_wild_{}", wild_counter);
1816 wild_counter += 1;
1817 *pat_type.pat = syn::Pat::Ident(syn::PatIdent {
1818 attrs: vec![],
1819 by_ref: None,
1820 mutability: None,
1821 ident,
1822 subpat: None,
1823 });
1824 }
1825 }
1826 }
1827
1828 assert_eq!(wild_counter, 2);
1830
1831 let names: Vec<String> = dispatcher_inputs
1832 .iter()
1833 .filter_map(|arg| {
1834 if let FnArg::Typed(PatType { pat, .. }) = arg {
1835 if let syn::Pat::Ident(pi) = pat.as_ref() {
1836 return Some(pi.ident.to_string());
1837 }
1838 }
1839 None
1840 })
1841 .collect();
1842
1843 assert_eq!(names, vec!["__autoversion_wild_0", "__autoversion_wild_1"]);
1844 }
1845
1846 #[test]
1851 fn suffix_path_simple() {
1852 let path: syn::Path = syn::parse_str("process").unwrap();
1853 let suffixed = suffix_path(&path, "v3");
1854 assert_eq!(suffixed.to_token_stream().to_string(), "process_v3");
1855 }
1856
1857 #[test]
1858 fn suffix_path_qualified() {
1859 let path: syn::Path = syn::parse_str("module::process").unwrap();
1860 let suffixed = suffix_path(&path, "neon");
1861 let s = suffixed.to_token_stream().to_string();
1862 assert!(
1863 s.contains("process_neon"),
1864 "Expected process_neon, got: {}",
1865 s
1866 );
1867 }
1868}