use proc_macro::{Span, TokenStream};
use quote::{format_ident, quote, quote_spanned};
use sha2::Digest;
use syn::ext::IdentExt;
use syn::parse::{Parse, ParseStream};
use syn::{parenthesized, parse_quote, Attribute, Block, Pat, Path, ReturnType};
use syn::{parse_macro_input, spanned::Spanned, Ident, ItemFn, LitStr, Signature, Token};
mod magic_constants;
use magic_constants::PLACEHOLDER_IMPORT_MODULE;
struct ReturnWrapper {
pattern: Pat,
output: ReturnType,
postlude: Block,
}
struct PreloadDefinition {
attrs: Vec<Attribute>,
name: Ident,
}
struct Args {
module_ident: Ident,
link_name: Option<(Ident, LitStr)>,
wasm_split_path: Option<Path>,
return_wrapper: Option<ReturnWrapper>,
preload_def: Option<PreloadDefinition>,
}
impl Parse for Args {
fn parse(input: ParseStream) -> syn::Result<Self> {
let module_ident = input.call(Ident::parse_any)?;
let mut link_name = None;
let mut wasm_split_path = None;
let mut return_wrapper = None;
let mut preload_def = None;
while !input.is_empty() {
let _: Token![,] = input.parse()?;
if input.is_empty() {
break;
}
let option: Ident = input.call(Ident::parse_any)?;
match () {
_ if option == "wasm_import_module" => {
let _: Token![=] = input.parse()?;
link_name = Some((option, input.parse()?));
}
_ if option == "wasm_split_path" => {
let _: Token![=] = input.parse()?;
wasm_split_path = Some(input.parse()?);
}
_ if option == "return_wrapper" => {
let wrap_spec;
let _parens = parenthesized!(wrap_spec in input);
let _: Token![let] = wrap_spec.parse()?;
let pattern = Pat::parse_multi_with_leading_vert(&wrap_spec)?;
let _: Token![=] = wrap_spec.parse()?;
let _: Token![_] = wrap_spec.parse()?;
let _: Token![;] = wrap_spec.parse()?;
return_wrapper = Some(ReturnWrapper {
pattern,
postlude: wrap_spec.parse()?,
output: wrap_spec.parse()?,
});
}
_ if option == "preload" => {
let wrap_spec;
let _parens = parenthesized!(wrap_spec in input);
let attrs = wrap_spec.call(Attribute::parse_outer)?;
let name = wrap_spec.parse()?;
preload_def = Some(PreloadDefinition { attrs, name });
}
_ => {
return Err(syn::Error::new(
option.span(),
"No such option for the `split` macro.",
))
}
}
}
Ok(Self {
module_ident,
link_name,
wasm_split_path,
return_wrapper,
preload_def,
})
}
}
#[proc_macro_attribute]
pub fn wasm_split(args: TokenStream, input: TokenStream) -> TokenStream {
let Args {
module_ident,
link_name,
wasm_split_path,
return_wrapper,
preload_def,
} = parse_macro_input!(args as Args);
let (deprecated_link_opt, link_name) = if let Some((option, link_name)) = link_name {
(Some(option), link_name)
} else {
(
None,
LitStr::new(PLACEHOLDER_IMPORT_MODULE, Span::call_site().into()),
)
};
let wasm_split_path = wasm_split_path.unwrap_or(parse_quote!(::wasm_split_helpers));
let mut item_fn: ItemFn = parse_macro_input!(input as ItemFn);
let mut declared_abi = item_fn.sig.abi.take();
declared_abi.get_or_insert(parse_quote!( extern "Rust" ));
let declared_async = item_fn.sig.asyncness.take();
let mut wrapper_sig = Signature {
asyncness: Some(Default::default()),
..item_fn.sig.clone()
};
if let Some(not_sync) = declared_async {
return quote_spanned! {not_sync.span()=>
::core::compile_error!("Split functions can not be `async`");
#wrapper_sig {
::core::todo!()
}
}
.into();
}
let name = &item_fn.sig.ident;
let PreloadDefinition {
attrs: preload_attrs,
name: preload_name,
} = preload_def.unwrap_or_else(|| PreloadDefinition {
attrs: vec![
parse_quote!(#[automatically_derived]),
parse_quote!(#[doc(hidden)]),
],
name: format_ident!("__wasm_split_preload_{name}"),
});
let vis = item_fn.vis;
let unique_identifier = base16::encode_lower(
&sha2::Sha256::digest(format!("{name} {span:?}", span = name.span()))[..16],
);
let load_module_ident = format_ident!("__wasm_split_load_{module_ident}");
let impl_import_ident =
format_ident!("__wasm_split_00{module_ident}00_import_{unique_identifier}_{name}");
let impl_export_ident =
format_ident!("__wasm_split_00{module_ident}00_export_{unique_identifier}_{name}");
let export_sig = Signature {
abi: declared_abi.clone(),
ident: impl_export_ident.clone(),
..item_fn.sig.clone()
};
let wasm_export_sig = Signature {
abi: parse_quote!(extern "C"),
ident: impl_export_ident.clone(),
..item_fn.sig.clone()
};
let mut args = Vec::new();
for (i, param) in wrapper_sig.inputs.iter_mut().enumerate() {
match param {
syn::FnArg::Typed(pat_type) => {
let param_ident = format_ident!("__wasm_split_arg_{i}");
args.push(param_ident.clone());
*pat_type.pat = syn::Pat::Ident(syn::PatIdent {
attrs: vec![],
by_ref: None,
mutability: None,
ident: param_ident,
subpat: None,
});
}
syn::FnArg::Receiver(_) => {
return quote_spanned! {param.span()=>
::core::compile_error!("Split functions can not have a receiver argument");
#wrapper_sig {
::core::todo!()
}
}
.into();
}
}
}
let import_sig = Signature {
asyncness: None,
ident: impl_import_ident.clone(),
..wrapper_sig.clone()
};
let attrs = item_fn.attrs;
let stmts = &item_fn.block.stmts;
let mut compute_result = quote! {
#[cfg(target_family = "wasm")]
use #impl_import_ident as callee;
#[cfg(not(target_family = "wasm"))]
use #impl_export_ident as callee;
callee( #(#args),* )
};
if let Some(ReturnWrapper {
output,
pattern: output_pat,
postlude,
}) = return_wrapper
{
wrapper_sig.output = output;
let postlude = postlude.stmts;
compute_result = quote! {{
let #output_pat = { #compute_result };
#( #postlude )*
}};
}
let mut extra_code = quote! {};
if let Some(deprecated_opt) = deprecated_link_opt {
let deprecation_note = format!("The `{deprecated_opt}` option should not be used, since the wasm_split_cli fixes the import path with improved target knowledge.");
extra_code.extend(quote! {
const _: () = {
#[allow(nonstandard_style)]
#[deprecated(note = #deprecation_note)]
const #deprecated_opt: () = ();
let _ = #deprecated_opt;
};
});
}
quote! {
#( #preload_attrs )*
#vis async fn #preload_name () {
#[cfg(target_family = "wasm")]
#[link(wasm_import_module = #link_name)]
unsafe extern "C" {
#[unsafe(no_mangle)]
fn #load_module_ident (callback: #wasm_split_path::rt::LoadCallbackFn, data: *const ::std::ffi::c_void) -> ();
}
#[cfg(target_family = "wasm")]
{
#wasm_split_path::rt::ensure_loaded(::core::pin::Pin::static_ref({
static LOADER: #wasm_split_path::rt::LazySplitLoader = unsafe { #wasm_split_path::rt::LazySplitLoader::new(#load_module_ident) };
&LOADER
})).await;
}
}
#(#attrs)*
#vis #wrapper_sig {
#[cfg(target_family = "wasm")]
#[link(wasm_import_module = #link_name)]
#[allow(improper_ctypes)]
unsafe extern "C" {
#[unsafe(no_mangle)]
safe #import_sig;
}
#[cfg(target_family = "wasm")]
#(#attrs)*
#[allow(improper_ctypes_definitions)]
#[unsafe(no_mangle)]
#wasm_export_sig {
#(#stmts)*
}
#[cfg(not(target_family = "wasm"))]
#(#attrs)*
#export_sig {
#(#stmts)*
}
#preload_name ().await;
#compute_result
}
#extra_code
}
.into()
}
#[doc(hidden)]
#[proc_macro]
pub fn version_stamp(_args: TokenStream) -> TokenStream {
let unique_path = std::env::var_os("CARGO_MANIFEST_PATH").unwrap();
let unique_id = base16::encode_lower(&sha2::Sha256::digest(unique_path.as_encoded_bytes()));
let id = format!("_WASM_SPLIT_MARKER_{}", &unique_id[0..16]);
let id = syn::LitStr::new(&id, Span::call_site().into());
quote! { #id }.into()
}