1use proc_macro::{Span, TokenStream};
2
3use quote::{format_ident, quote, quote_spanned};
4use sha2::Digest;
5use syn::ext::IdentExt;
6use syn::parse::{Parse, ParseStream};
7use syn::{parenthesized, parse_quote, Attribute, Block, Pat, Path, ReturnType};
8use syn::{parse_macro_input, spanned::Spanned, Ident, ItemFn, LitStr, Signature, Token};
9
10mod magic_constants;
11use magic_constants::PLACEHOLDER_IMPORT_MODULE;
12
13struct ReturnWrapper {
14 pattern: Pat,
15 output: ReturnType,
16 postlude: Block,
17}
18
19struct PreloadDefinition {
20 attrs: Vec<Attribute>,
21 name: Ident,
22}
23
24struct Args {
25 module_ident: Ident,
26 link_name: Option<(Ident, LitStr)>,
27 wasm_split_path: Option<Path>,
28 return_wrapper: Option<ReturnWrapper>,
29 preload_def: Option<PreloadDefinition>,
30}
31
32impl Parse for Args {
33 fn parse(input: ParseStream) -> syn::Result<Self> {
34 let module_ident = input.call(Ident::parse_any)?;
35 let mut link_name = None;
36 let mut wasm_split_path = None;
37 let mut return_wrapper = None;
38 let mut preload_def = None;
39 while !input.is_empty() {
40 let _: Token![,] = input.parse()?;
41 if input.is_empty() {
42 break;
43 }
44 let option: Ident = input.call(Ident::parse_any)?;
45 match () {
46 _ if option == "wasm_import_module" => {
47 let _: Token![=] = input.parse()?;
48 link_name = Some((option, input.parse()?));
49 }
50 _ if option == "wasm_split_path" => {
51 let _: Token![=] = input.parse()?;
52 wasm_split_path = Some(input.parse()?);
53 }
54 _ if option == "return_wrapper" => {
55 let wrap_spec;
56 let _parens = parenthesized!(wrap_spec in input);
57 let _: Token![let] = wrap_spec.parse()?;
58 let pattern = Pat::parse_multi_with_leading_vert(&wrap_spec)?;
59 let _: Token![=] = wrap_spec.parse()?;
60 let _: Token![_] = wrap_spec.parse()?;
61 let _: Token![;] = wrap_spec.parse()?;
62 return_wrapper = Some(ReturnWrapper {
63 pattern,
64 postlude: wrap_spec.parse()?,
65 output: wrap_spec.parse()?,
66 });
67 }
68 _ if option == "preload" => {
69 let wrap_spec;
70 let _parens = parenthesized!(wrap_spec in input);
71 let attrs = wrap_spec.call(Attribute::parse_outer)?;
72 let name = wrap_spec.parse()?;
73 preload_def = Some(PreloadDefinition { attrs, name });
74 }
75 _ => {
76 return Err(syn::Error::new(
77 option.span(),
78 "No such option for the `split` macro.",
79 ))
80 }
81 }
82 }
83 Ok(Self {
84 module_ident,
85 link_name,
86 wasm_split_path,
87 return_wrapper,
88 preload_def,
89 })
90 }
91}
92
93#[proc_macro_attribute]
135pub fn wasm_split(args: TokenStream, input: TokenStream) -> TokenStream {
136 let Args {
137 module_ident,
138 link_name,
139 wasm_split_path,
140 return_wrapper,
141 preload_def,
142 } = parse_macro_input!(args as Args);
143 let (deprecated_link_opt, link_name) = if let Some((option, link_name)) = link_name {
144 (Some(option), link_name)
145 } else {
146 (
147 None,
148 LitStr::new(PLACEHOLDER_IMPORT_MODULE, Span::call_site().into()),
149 )
150 };
151 let wasm_split_path = wasm_split_path.unwrap_or(parse_quote!(::wasm_split_helpers));
152
153 let mut item_fn: ItemFn = parse_macro_input!(input as ItemFn);
154 let mut declared_abi = item_fn.sig.abi.take();
155 declared_abi.get_or_insert(parse_quote!( extern "Rust" ));
156 let declared_async = item_fn.sig.asyncness.take();
157
158 let mut wrapper_sig = Signature {
159 asyncness: Some(Default::default()),
160 ..item_fn.sig.clone()
161 };
162
163 if let Some(not_sync) = declared_async {
164 return quote_spanned! {not_sync.span()=>
165 ::core::compile_error!("Split functions can not be `async`");
166
167 #wrapper_sig {
168 ::core::todo!()
169 }
170 }
171 .into();
172 }
173
174 let name = &item_fn.sig.ident;
175 let PreloadDefinition {
176 attrs: preload_attrs,
177 name: preload_name,
178 } = preload_def.unwrap_or_else(|| PreloadDefinition {
179 attrs: vec![
180 parse_quote!(#[automatically_derived]),
181 parse_quote!(#[doc(hidden)]),
182 ],
183 name: format_ident!("__wasm_split_preload_{name}"),
184 });
185 let vis = item_fn.vis;
186
187 let unique_identifier = base16::encode_lower(
188 &sha2::Sha256::digest(format!("{name} {span:?}", span = name.span()))[..16],
189 );
190
191 let load_module_ident = format_ident!("__wasm_split_load_{module_ident}");
192 let impl_import_ident =
193 format_ident!("__wasm_split_00{module_ident}00_import_{unique_identifier}_{name}");
194 let impl_export_ident =
195 format_ident!("__wasm_split_00{module_ident}00_export_{unique_identifier}_{name}");
196
197 let export_sig = Signature {
198 abi: declared_abi.clone(),
199 ident: impl_export_ident.clone(),
200 ..item_fn.sig.clone()
201 };
202
203 let wasm_export_sig = Signature {
210 abi: parse_quote!(extern "C"),
211 ident: impl_export_ident.clone(),
212 ..item_fn.sig.clone()
213 };
214
215 let mut args = Vec::new();
216 for (i, param) in wrapper_sig.inputs.iter_mut().enumerate() {
217 match param {
218 syn::FnArg::Typed(pat_type) => {
219 let param_ident = format_ident!("__wasm_split_arg_{i}");
220 args.push(param_ident.clone());
221 *pat_type.pat = syn::Pat::Ident(syn::PatIdent {
222 attrs: vec![],
223 by_ref: None,
224 mutability: None,
225 ident: param_ident,
226 subpat: None,
227 });
228 }
229 syn::FnArg::Receiver(_) => {
231 return quote_spanned! {param.span()=>
232 ::core::compile_error!("Split functions can not have a receiver argument");
233
234 #wrapper_sig {
235 ::core::todo!()
236 }
237 }
238 .into();
239 }
240 }
241 }
242 let import_sig = Signature {
243 asyncness: None,
245 ident: impl_import_ident.clone(),
246 ..wrapper_sig.clone()
247 };
248
249 let attrs = item_fn.attrs;
250 let stmts = &item_fn.block.stmts;
251
252 let mut compute_result = quote! {
253 #[cfg(target_family = "wasm")]
254 use #impl_import_ident as callee;
255 #[cfg(not(target_family = "wasm"))]
256 use #impl_export_ident as callee;
257 callee( #(#args),* )
258 };
259
260 if let Some(ReturnWrapper {
261 output,
262 pattern: output_pat,
263 postlude,
264 }) = return_wrapper
265 {
266 wrapper_sig.output = output;
267 let postlude = postlude.stmts;
268 compute_result = quote! {{
269 let #output_pat = { #compute_result };
270 #( #postlude )*
271 }};
272 }
273
274 let mut extra_code = quote! {};
275 if let Some(deprecated_opt) = deprecated_link_opt {
276 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.");
277 extra_code.extend(quote! {
278 const _: () = {
279 #[allow(nonstandard_style)]
280 #[deprecated(note = #deprecation_note)]
281 const #deprecated_opt: () = ();
282 let _ = #deprecated_opt;
283 };
284 });
285 }
286
287 quote! {
288 #( #preload_attrs )*
290 #vis async fn #preload_name () {
291 #[cfg(target_family = "wasm")]
292 #[link(wasm_import_module = #link_name)]
293 unsafe extern "C" {
294 #[unsafe(no_mangle)]
295 fn #load_module_ident (callback: #wasm_split_path::rt::LoadCallbackFn, data: *const ::std::ffi::c_void) -> ();
296 }
297 #[cfg(target_family = "wasm")]
298 {
299 #wasm_split_path::rt::ensure_loaded(::core::pin::Pin::static_ref({
300 static LOADER: #wasm_split_path::rt::LazySplitLoader = unsafe { #wasm_split_path::rt::LazySplitLoader::new(#load_module_ident) };
302 &LOADER
303 })).await;
304 }
305 }
306 #(#attrs)*
307 #vis #wrapper_sig {
308 #[cfg(target_family = "wasm")]
311 #[link(wasm_import_module = #link_name)]
312 #[allow(improper_ctypes)]
313 unsafe extern "C" {
314 #[unsafe(no_mangle)]
316 safe #import_sig;
317 }
318
319 #[cfg(target_family = "wasm")]
321 #(#attrs)*
322 #[allow(improper_ctypes_definitions)]
323 #[unsafe(no_mangle)]
324 #wasm_export_sig {
325 #(#stmts)*
326 }
327
328 #[cfg(not(target_family = "wasm"))]
330 #(#attrs)*
331 #export_sig {
332 #(#stmts)*
333 }
334
335 #preload_name ().await;
336 #compute_result
337 }
338 #extra_code
339 }
340 .into()
341}
342
343#[doc(hidden)]
347#[proc_macro]
348pub fn version_stamp(_args: TokenStream) -> TokenStream {
349 let unique_path = std::env::var_os("CARGO_MANIFEST_PATH").unwrap();
350 let unique_id = base16::encode_lower(&sha2::Sha256::digest(unique_path.as_encoded_bytes()));
351 let id = format!("_WASM_SPLIT_MARKER_{}", &unique_id[0..16]);
352 let id = syn::LitStr::new(&id, Span::call_site().into());
353 quote! { #id }.into()
354}