lib.rs 7.1 KB

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