use proc_macro::TokenStream;
use quote::{ToTokens, format_ident, quote};
use syn::{
Attribute, FnArg, Ident, Token, Type,
parse::{Parse, ParseStream},
parse_quote,
};
use crate::common::*;
use crate::token_discovery::*;
#[derive(Default)]
pub(crate) struct ArcaneArgs {
inline_always: bool,
pub(crate) self_type: Option<Type>,
pub(crate) stub: bool,
pub(crate) nested: bool,
pub(crate) import_intrinsics: bool,
pub(crate) import_magetypes: bool,
pub(crate) cfg_feature: Option<String>,
}
impl Parse for ArcaneArgs {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut args = ArcaneArgs::default();
while !input.is_empty() {
let ident: Ident = input.parse()?;
match ident.to_string().as_str() {
"inline_always" => args.inline_always = true,
"stub" => {
return Err(syn::Error::new(
ident.span(),
"`stub` has been removed. Use `incant!` for cross-arch dispatch \
instead — it cfg-gates each architecture automatically.\n\
\n\
Before: #[arcane(stub)] fn process(token: X64V3Token, ...) { ... }\n\
After: #[arcane] fn process_v3(token: X64V3Token, ...) { ... }\n\
\x20 fn dispatch(...) { incant!(process(...)) }",
));
}
"nested" => args.nested = true,
"import_intrinsics" => args.import_intrinsics = true,
"import_magetypes" => args.import_magetypes = true,
"cfg" => {
let content;
syn::parenthesized!(content in input);
let feat: Ident = content.parse()?;
args.cfg_feature = Some(feat.to_string());
}
"_self" => {
let _: Token![=] = input.parse()?;
args.self_type = Some(input.parse()?);
}
other => {
return Err(syn::Error::new(
ident.span(),
format!("unknown arcane argument: `{}`", other),
));
}
}
if input.peek(Token![,]) {
let _: Token![,] = input.parse()?;
}
}
if args.self_type.is_some() {
args.nested = true;
}
Ok(args)
}
}
pub(crate) enum SelfReceiver {
Owned,
Ref,
RefMut,
}
pub(crate) fn arcane_impl(
mut input_fn: LightFn,
macro_name: &str,
args: ArcaneArgs,
) -> TokenStream {
let has_self_receiver = input_fn
.sig
.inputs
.first()
.map(|arg| matches!(arg, FnArg::Receiver(_)))
.unwrap_or(false);
if has_self_receiver && args.nested && args.self_type.is_none() {
let msg = format!(
"{} with self receiver in nested mode requires `_self = Type` argument.\n\
Example: #[{}(nested, _self = MyType)]\n\
Use `_self` (not `self`) in the function body to refer to self.\n\
\n\
Alternatively, remove `nested` to use sibling expansion (default), \
which handles self/Self naturally.",
macro_name, macro_name
);
return syn::Error::new_spanned(&input_fn.sig, msg)
.to_compile_error()
.into();
}
let TokenParamInfo {
ident: _token_ident,
features,
target_arch,
token_type_name,
magetypes_namespace,
} = match find_token_param(&input_fn.sig) {
Some(result) => result,
None => {
if let Some(trait_name) = diagnose_featureless_token(&input_fn.sig) {
let msg = format!(
"`{trait_name}` cannot be used as a token bound in #[{macro_name}] \
because it doesn't specify any CPU features.\n\
\n\
#[{macro_name}] needs concrete features to generate #[target_feature]. \
Use a concrete token or a feature trait:\n\
\n\
Concrete tokens: X64V3Token, Desktop64, NeonToken, Arm64V2Token, ...\n\
Feature traits: impl HasX64V2, impl HasNeon, impl HasArm64V3, ..."
);
return syn::Error::new_spanned(&input_fn.sig, msg)
.to_compile_error()
.into();
}
let msg = format!(
"{} requires a token parameter. Supported forms:\n\
- Concrete: `token: X64V3Token`\n\
- impl Trait: `token: impl HasX64V2`\n\
- Generic: `fn foo<T: HasX64V2>(token: T, ...)`\n\
- With self: `#[{}(_self = Type)] fn method(&self, token: impl HasNeon, ...)`",
macro_name, macro_name
);
return syn::Error::new_spanned(&input_fn.sig, msg)
.to_compile_error()
.into();
}
};
#[cfg(not(feature = "avx512"))]
if args.import_intrinsics && features.iter().any(|f| f.starts_with("avx512")) {
let token_desc = token_type_name.as_deref().unwrap_or("an AVX-512 token");
let msg = format!(
"Using {token_desc} with `import_intrinsics` requires the `avx512` feature.\n\
\n\
Add to your Cargo.toml:\n\
\x20 archmage = {{ version = \"...\", features = [\"avx512\"] }}\n\
\n\
Without it, 512-bit safe memory ops (_mm512_loadu_ps etc.) are not available.\n\
If you only need value intrinsics (no memory ops), remove `import_intrinsics`."
);
return syn::Error::new_spanned(&input_fn.sig, msg)
.to_compile_error()
.into();
}
let body_imports = generate_imports(
target_arch,
magetypes_namespace,
args.import_intrinsics,
args.import_magetypes,
);
if !body_imports.is_empty() {
let original_body = &input_fn.body;
input_fn.body = quote! {
#body_imports
#original_body
};
}
if let Some(ref type_name) = token_type_name
&& let Some(tier_suffix) = crate::generated::canonical_token_to_tier_suffix(type_name)
&& let Some(tier) = crate::tiers::find_tier(tier_suffix)
{
let ctx = crate::rewrite::CallerContext {
tier_suffix: tier_suffix.to_string(),
target_arch: tier.target_arch,
token_ident: _token_ident.clone(),
};
input_fn.body = crate::rewrite::rewrite_incant_in_body(input_fn.body.clone(), &ctx);
}
let features_csv = features.join(",");
let target_feature_attrs: Vec<Attribute> =
vec![parse_quote!(#[target_feature(enable = #features_csv)])];
let mut pattern_rename_counter = 0u32;
let mut pattern_rebinds = Vec::new();
for arg in &mut input_fn.sig.inputs {
if let FnArg::Typed(pat_type) = arg {
let needs_rename = !matches!(pat_type.pat.as_ref(), syn::Pat::Ident(_));
if needs_rename {
let generated = format_ident!("__archmage_arg_{}", pattern_rename_counter);
pattern_rename_counter += 1;
let original_pat = pat_type.pat.clone();
let ty = &pat_type.ty;
pattern_rebinds.push(quote! { let #original_pat: #ty = #generated; });
*pat_type.pat = syn::Pat::Ident(syn::PatIdent {
attrs: vec![],
by_ref: None,
mutability: None,
ident: generated,
subpat: None,
});
}
}
}
if !pattern_rebinds.is_empty() {
let original_body = &input_fn.body;
input_fn.body = quote! {
#(#pattern_rebinds)*
#original_body
};
}
let inline_attr: Attribute = if args.inline_always {
parse_quote!(#[inline(always)])
} else {
parse_quote!(#[inline])
};
if target_arch == Some("wasm32") {
return arcane_impl_wasm_safe(
input_fn,
&args,
token_type_name,
target_feature_attrs,
inline_attr,
);
}
if args.nested {
arcane_impl_nested(
input_fn,
&args,
target_arch,
token_type_name,
target_feature_attrs,
inline_attr,
)
} else {
arcane_impl_sibling(
input_fn,
&args,
target_arch,
token_type_name,
target_feature_attrs,
inline_attr,
)
}
}
pub(crate) fn arcane_impl_wasm_safe(
input_fn: LightFn,
args: &ArcaneArgs,
token_type_name: Option<String>,
target_feature_attrs: Vec<Attribute>,
inline_attr: Attribute,
) -> TokenStream {
let vis = &input_fn.vis;
let sig = &input_fn.sig;
let fn_name = &sig.ident;
let attrs = &input_fn.attrs;
let token_type_str = token_type_name.as_deref().unwrap_or("UnknownToken");
let body = if args.self_type.is_some() {
let original_body = &input_fn.body;
quote! {
let _self = self;
#original_body
}
} else {
input_fn.body.clone()
};
let mut new_attrs = target_feature_attrs;
new_attrs.push(inline_attr);
for attr in filter_inline_attrs(attrs) {
new_attrs.push(attr.clone());
}
let stub = if args.stub {
let stub_args: Vec<proc_macro2::TokenStream> = sig
.inputs
.iter()
.filter_map(|arg| match arg {
FnArg::Typed(pat_type) => {
if let syn::Pat::Ident(pat_ident) = pat_type.pat.as_ref() {
let ident = &pat_ident.ident;
Some(quote!(#ident))
} else {
None
}
}
FnArg::Receiver(_) => None,
})
.collect();
quote! {
#[cfg(not(target_arch = "wasm32"))]
#vis #sig {
let _ = (#(#stub_args),*);
unreachable!(
"BUG: {}() was called but requires {} (target_arch = \"wasm32\"). \
{}::summon() returns None on this architecture, so this function \
is unreachable in safe code. If you used forge_token_dangerously(), \
that is the bug.",
stringify!(#fn_name),
#token_type_str,
#token_type_str,
)
}
}
} else {
quote! {}
};
let expanded = quote! {
#[cfg(target_arch = "wasm32")]
#(#new_attrs)*
#vis #sig {
#body
}
#stub
};
expanded.into()
}
pub(crate) fn arcane_impl_sibling(
input_fn: LightFn,
args: &ArcaneArgs,
target_arch: Option<&str>,
token_type_name: Option<String>,
target_feature_attrs: Vec<Attribute>,
inline_attr: Attribute,
) -> TokenStream {
let vis = &input_fn.vis;
let sig = &input_fn.sig;
let fn_name = &sig.ident;
let generics = &sig.generics;
let where_clause = &generics.where_clause;
let inputs = &sig.inputs;
let output = &sig.output;
let body = &input_fn.body;
let attrs = filter_inline_attrs(&input_fn.attrs);
let lint_attrs = filter_lint_attrs(&input_fn.attrs);
let sibling_name = format_ident!("__arcane_{}", fn_name);
let has_self_receiver = inputs
.first()
.map(|arg| matches!(arg, FnArg::Receiver(_)))
.unwrap_or(false);
let sibling_sig_inputs = inputs;
let turbofish = build_turbofish(generics);
let sibling_call = if has_self_receiver {
let other_args: Vec<proc_macro2::TokenStream> = inputs
.iter()
.skip(1) .filter_map(|arg| {
if let FnArg::Typed(pat_type) = arg
&& let syn::Pat::Ident(pat_ident) = pat_type.pat.as_ref()
{
let ident = &pat_ident.ident;
Some(quote!(#ident))
} else {
None
}
})
.collect();
quote! { self.#sibling_name #turbofish(#(#other_args),*) }
} else {
let all_args: Vec<proc_macro2::TokenStream> = inputs
.iter()
.filter_map(|arg| {
if let FnArg::Typed(pat_type) = arg
&& let syn::Pat::Ident(pat_ident) = pat_type.pat.as_ref()
{
let ident = &pat_ident.ident;
Some(quote!(#ident))
} else {
None
}
})
.collect();
quote! { #sibling_name #turbofish(#(#all_args),*) }
};
let stub_args: Vec<proc_macro2::TokenStream> = inputs
.iter()
.filter_map(|arg| match arg {
FnArg::Typed(pat_type) => {
if let syn::Pat::Ident(pat_ident) = pat_type.pat.as_ref() {
let ident = &pat_ident.ident;
Some(quote!(#ident))
} else {
None
}
}
FnArg::Receiver(_) => None, })
.collect();
let token_type_str = token_type_name.as_deref().unwrap_or("UnknownToken");
let cfg_guard = gen_cfg_guard(target_arch, args.cfg_feature.as_deref());
let expanded = if target_arch.is_some() {
let sibling_fn = quote! {
#cfg_guard
#[doc(hidden)]
#(#lint_attrs)*
#(#target_feature_attrs)*
#inline_attr
fn #sibling_name #generics (#sibling_sig_inputs) #output #where_clause {
#body
}
};
let wrapper_fn = quote! {
#cfg_guard
#(#attrs)*
#[inline(always)]
#vis #sig {
unsafe { #sibling_call }
}
};
let stub = if args.stub {
let arch_str = target_arch.unwrap_or("unknown");
let not_cfg = match (target_arch, args.cfg_feature.as_deref()) {
(Some(arch), Some(feat)) => {
quote! { #[cfg(not(all(target_arch = #arch, feature = #feat)))] }
}
(Some(arch), None) => quote! { #[cfg(not(target_arch = #arch))] },
_ => quote! {},
};
quote! {
#not_cfg
#(#attrs)*
#vis #sig {
let _ = (#(#stub_args),*);
unreachable!(
"BUG: {}() was called but requires {} (target_arch = \"{}\"). \
{}::summon() returns None on this architecture, so this function \
is unreachable in safe code. If you used forge_token_dangerously(), \
that is the bug.",
stringify!(#fn_name),
#token_type_str,
#arch_str,
#token_type_str,
)
}
}
} else {
quote! {}
};
quote! {
#sibling_fn
#wrapper_fn
#stub
}
} else {
let sibling_fn = quote! {
#[doc(hidden)]
#(#lint_attrs)*
#(#target_feature_attrs)*
#inline_attr
fn #sibling_name #generics (#sibling_sig_inputs) #output #where_clause {
#body
}
};
let wrapper_fn = quote! {
#(#attrs)*
#[inline(always)]
#vis #sig {
unsafe { #sibling_call }
}
};
quote! {
#sibling_fn
#wrapper_fn
}
};
expanded.into()
}
pub(crate) fn arcane_impl_nested(
input_fn: LightFn,
args: &ArcaneArgs,
target_arch: Option<&str>,
token_type_name: Option<String>,
target_feature_attrs: Vec<Attribute>,
inline_attr: Attribute,
) -> TokenStream {
let vis = &input_fn.vis;
let sig = &input_fn.sig;
let fn_name = &sig.ident;
let generics = &sig.generics;
let where_clause = &generics.where_clause;
let inputs = &sig.inputs;
let output = &sig.output;
let body = &input_fn.body;
let attrs = filter_inline_attrs(&input_fn.attrs);
let lint_attrs = filter_lint_attrs(&input_fn.attrs);
let self_receiver_kind: Option<SelfReceiver> = inputs.first().and_then(|arg| match arg {
FnArg::Receiver(receiver) => {
if receiver.reference.is_none() {
Some(SelfReceiver::Owned)
} else if receiver.mutability.is_some() {
Some(SelfReceiver::RefMut)
} else {
Some(SelfReceiver::Ref)
}
}
_ => None,
});
let inner_params: Vec<proc_macro2::TokenStream> = inputs
.iter()
.map(|arg| match arg {
FnArg::Receiver(_) => {
let self_ty = args.self_type.as_ref().unwrap();
match self_receiver_kind.as_ref().unwrap() {
SelfReceiver::Owned => quote!(_self: #self_ty),
SelfReceiver::Ref => quote!(_self: &#self_ty),
SelfReceiver::RefMut => quote!(_self: &mut #self_ty),
}
}
FnArg::Typed(pat_type) => {
if let Some(ref self_ty) = args.self_type {
replace_self_in_tokens(quote!(#pat_type), self_ty)
} else {
quote!(#pat_type)
}
}
})
.collect();
let inner_args: Vec<proc_macro2::TokenStream> = inputs
.iter()
.filter_map(|arg| match arg {
FnArg::Typed(pat_type) => {
if let syn::Pat::Ident(pat_ident) = pat_type.pat.as_ref() {
let ident = &pat_ident.ident;
Some(quote!(#ident))
} else {
None
}
}
FnArg::Receiver(_) => Some(quote!(self)), })
.collect();
let inner_fn_name = format_ident!("__simd_inner_{}", fn_name);
let turbofish = build_turbofish(generics);
let (inner_output, inner_body, inner_where_clause): (
proc_macro2::TokenStream,
proc_macro2::TokenStream,
proc_macro2::TokenStream,
) = if let Some(ref self_ty) = args.self_type {
let transformed_output = replace_self_in_tokens(output.to_token_stream(), self_ty);
let transformed_body = replace_self_in_tokens(body.clone(), self_ty);
let transformed_where = where_clause
.as_ref()
.map(|wc| replace_self_in_tokens(wc.to_token_stream(), self_ty))
.unwrap_or_default();
(transformed_output, transformed_body, transformed_where)
} else {
(
output.to_token_stream(),
body.clone(),
where_clause
.as_ref()
.map(|wc| wc.to_token_stream())
.unwrap_or_default(),
)
};
let token_type_str = token_type_name.as_deref().unwrap_or("UnknownToken");
let cfg_guard = gen_cfg_guard(target_arch, args.cfg_feature.as_deref());
let expanded = if target_arch.is_some() {
let stub = if args.stub {
let arch_str = target_arch.unwrap_or("unknown");
let not_cfg = match (target_arch, args.cfg_feature.as_deref()) {
(Some(arch), Some(feat)) => {
quote! { #[cfg(not(all(target_arch = #arch, feature = #feat)))] }
}
(Some(arch), None) => quote! { #[cfg(not(target_arch = #arch))] },
_ => quote! {},
};
quote! {
#not_cfg
#(#attrs)*
#vis #sig {
let _ = (#(#inner_args),*);
unreachable!(
"BUG: {}() was called but requires {} (target_arch = \"{}\"). \
{}::summon() returns None on this architecture, so this function \
is unreachable in safe code. If you used forge_token_dangerously(), \
that is the bug.",
stringify!(#fn_name),
#token_type_str,
#arch_str,
#token_type_str,
)
}
}
} else {
quote! {}
};
quote! {
#cfg_guard
#(#attrs)*
#[inline(always)]
#vis #sig {
#(#target_feature_attrs)*
#inline_attr
#(#lint_attrs)*
fn #inner_fn_name #generics (#(#inner_params),*) #inner_output #inner_where_clause {
#inner_body
}
unsafe { #inner_fn_name #turbofish(#(#inner_args),*) }
}
#stub
}
} else {
quote! {
#(#attrs)*
#[inline(always)]
#vis #sig {
#(#target_feature_attrs)*
#inline_attr
#(#lint_attrs)*
fn #inner_fn_name #generics (#(#inner_params),*) #inner_output #inner_where_clause {
#inner_body
}
unsafe { #inner_fn_name #turbofish(#(#inner_args),*) }
}
}
};
expanded.into()
}