1use proc_macro::TokenStream;
2
3use digest::Digest;
4use quote::{format_ident, quote};
5use syn::{parse_macro_input, parse_quote, FnArg, Ident, ItemFn, ReturnType, Signature};
6
7#[proc_macro_attribute]
8pub fn wasm_split(args: TokenStream, input: TokenStream) -> TokenStream {
9 let module_ident = parse_macro_input!(args as Ident);
10 let item_fn = parse_macro_input!(input as ItemFn);
11
12 if item_fn.sig.asyncness.is_none() {
13 panic!("wasm_split functions must be async. Use a LazyLoader with synchronous functions instead.");
14 }
15
16 let LoaderNames {
17 split_loader_ident,
18 impl_import_ident,
19 impl_export_ident,
20 load_module_ident,
21 ..
22 } = LoaderNames::new(item_fn.sig.ident.clone(), module_ident.to_string());
23
24 let mut desugard_async_sig = item_fn.sig.clone();
25 desugard_async_sig.asyncness = None;
26 desugard_async_sig.output = match &desugard_async_sig.output {
27 ReturnType::Default => {
28 parse_quote! { -> ::std::pin::Pin<Box<dyn ::std::future::Future<Output = ()>>> }
29 }
30 ReturnType::Type(_, ty) => {
31 parse_quote! { -> ::std::pin::Pin<Box<dyn ::std::future::Future<Output = #ty>>> }
32 }
33 };
34
35 let import_sig = Signature {
36 ident: impl_import_ident.clone(),
37 ..desugard_async_sig.clone()
38 };
39
40 let export_sig = Signature {
41 ident: impl_export_ident.clone(),
42 ..desugard_async_sig.clone()
43 };
44
45 let default_item = item_fn.clone();
46
47 let mut wrapper_sig = item_fn.sig;
48 wrapper_sig.asyncness = Some(Default::default());
49
50 let mut args = Vec::new();
51 for (i, param) in wrapper_sig.inputs.iter_mut().enumerate() {
52 match param {
53 syn::FnArg::Receiver(_) => args.push(format_ident!("self")),
54 syn::FnArg::Typed(pat_type) => {
55 let param_ident = format_ident!("__wasm_split_arg_{i}");
56 args.push(param_ident.clone());
57 *pat_type.pat = syn::Pat::Ident(syn::PatIdent {
58 attrs: vec![],
59 by_ref: None,
60 mutability: None,
61 ident: param_ident,
62 subpat: None,
63 });
64 }
65 }
66 }
67
68 let attrs = &item_fn.attrs;
69 let stmts = &item_fn.block.stmts;
70
71 quote! {
72 #[cfg(target_arch = "wasm32")]
73 #wrapper_sig {
74 #(#attrs)*
75 #[allow(improper_ctypes_definitions)]
76 #[no_mangle]
77 pub extern "C" #export_sig {
78 Box::pin(async move { #(#stmts)* })
79 }
80
81 #[link(wasm_import_module = "./__wasm_split.js")]
82 extern "C" {
83 #[no_mangle]
84 fn #load_module_ident (
85 callback: unsafe extern "C" fn(*const ::std::ffi::c_void, bool),
86 data: *const ::std::ffi::c_void
87 );
88
89 #[allow(improper_ctypes)]
90 #[no_mangle]
91 #import_sig;
92 }
93
94 thread_local! {
95 static #split_loader_ident: wasm_split::LazySplitLoader = unsafe {
96 wasm_split::LazySplitLoader::new(#load_module_ident)
97 };
98 }
99
100 if !wasm_split::LazySplitLoader::ensure_loaded(&#split_loader_ident).await {
102 panic!("Failed to load wasm-split module");
103 }
104
105 unsafe { #impl_import_ident( #(#args),* ) }.await
106 }
107
108 #[cfg(not(target_arch = "wasm32"))]
109 #default_item
110 }
111 .into()
112}
113
114#[proc_macro]
125pub fn lazy_loader(input: TokenStream) -> TokenStream {
126 let sig = parse_macro_input!(input as Signature);
128 let params = sig.inputs.clone();
129 let outputs = sig.output.clone();
130 let Some(FnArg::Typed(arg)) = params.first().cloned() else {
131 panic!(
132 "Lazy Loader must define a single input argument to satisfy the LazyLoader signature"
133 )
134 };
135 let arg_ty = arg.ty.clone();
136 let LoaderNames {
137 name,
138 split_loader_ident,
139 impl_import_ident,
140 impl_export_ident,
141 load_module_ident,
142 ..
143 } = LoaderNames::new(
144 sig.ident.clone(),
145 sig.abi
146 .as_ref()
147 .and_then(|abi| abi.name.as_ref().map(|f| f.value()))
148 .expect("abi to be module name")
149 .to_string(),
150 );
151
152 quote! {
153 {
154 #[cfg(target_arch = "wasm32")]
155 {
156 #[link(wasm_import_module = "./__wasm_split.js")]
157 extern "C" {
158 #[no_mangle]
160 fn #load_module_ident(
161 callback: unsafe extern "C" fn(*const ::std::ffi::c_void, bool),
162 data: *const ::std::ffi::c_void,
163 );
164
165 #[allow(improper_ctypes)]
166 #[no_mangle]
167 fn #impl_import_ident(arg: #arg_ty) #outputs;
168 }
169
170
171 #[allow(improper_ctypes_definitions)]
172 #[no_mangle]
173 pub extern "C" fn #impl_export_ident(arg: #arg_ty) #outputs {
174 #name(arg)
175 }
176
177 thread_local! {
178 static #split_loader_ident: wasm_split::LazySplitLoader = unsafe {
179 wasm_split::LazySplitLoader::new(#load_module_ident)
180 };
181 };
182
183 unsafe {
184 wasm_split::LazyLoader::new(#impl_import_ident, &#split_loader_ident)
185 }
186 }
187
188 #[cfg(not(target_arch = "wasm32"))]
189 {
190 wasm_split::LazyLoader::preloaded(#name)
191 }
192 }
193 }
194 .into()
195}
196
197struct LoaderNames {
198 name: Ident,
199 split_loader_ident: Ident,
200 impl_import_ident: Ident,
201 impl_export_ident: Ident,
202 load_module_ident: Ident,
203}
204
205impl LoaderNames {
206 fn new(name: Ident, module: String) -> Self {
207 let unique_identifier = base16::encode_lower(
208 &sha2::Sha256::digest(format!("{name} {span:?}", name = name, span = name.span()))
209 [..16],
210 );
211
212 Self {
213 split_loader_ident: format_ident!("__wasm_split_loader_{module}"),
214 impl_export_ident: format_ident!(
215 "__wasm_split_00___{module}___00_export_{unique_identifier}_{name}"
216 ),
217 impl_import_ident: format_ident!(
218 "__wasm_split_00___{module}___00_import_{unique_identifier}_{name}"
219 ),
220 load_module_ident: format_ident!(
221 "__wasm_split_load_{module}_{unique_identifier}_{name}"
222 ),
223 name,
224 }
225 }
226}