diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 59a10704d6b..b82358ebd5f 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -62,6 +62,8 @@ pub struct Context<'a> { /// A flag to track if the stack pointer setter shim has been injected. stack_pointer_shim_injected: bool, + + start_found: bool, } #[derive(Default)] @@ -93,6 +95,7 @@ impl<'a> Context<'a> { config: &'a Bindgen, wit: &'a NonstandardWitSection, aux: &'a WasmBindgenAux, + start_found: bool, ) -> Result, Error> { Ok(Context { globals: String::new(), @@ -113,6 +116,7 @@ impl<'a> Context<'a> { memories: Default::default(), table_indices: Default::default(), stack_pointer_shim_injected: false, + start_found, }) } @@ -381,7 +385,7 @@ impl<'a> Context<'a> { js.push_str("let wasm;\n"); init = self.gen_init(needs_manual_start, None)?; footer.push_str(&format!( - "{} = Object.assign(init, {{ initSync }}, __exports);\n", + "{} = Object.assign(init, {{ initWithoutStart }}, {{ initSync }}, __exports);\n", global )); } @@ -414,6 +418,10 @@ impl<'a> Context<'a> { if needs_manual_start { footer.push_str("\nwasm.__wbindgen_start();\n"); } + + if self.start_found { + footer.push_str("\nwasm.__wbindgen_main();\n"); + } } OutputMode::Deno => { @@ -428,6 +436,10 @@ impl<'a> Context<'a> { if needs_manual_start { footer.push_str("\nwasm.__wbindgen_start();\n"); } + + if self.start_found { + footer.push_str("\nwasm.__wbindgen_main();\n"); + } } // With Bundlers and modern ES6 support in Node we can simply import @@ -462,6 +474,12 @@ impl<'a> Context<'a> { if needs_manual_start { start = Some("\nwasm.__wbindgen_start();\n".to_string()); } + + if self.start_found { + start + .get_or_insert_with(String::default) + .push_str("\nwasm.__wbindgen_main();\n"); + } } // With a browser-native output we're generating an ES module, but @@ -471,7 +489,7 @@ impl<'a> Context<'a> { OutputMode::Web => { self.imports_post.push_str("let wasm;\n"); init = self.gen_init(needs_manual_start, Some(&mut imports))?; - footer.push_str("export { initSync }\n"); + footer.push_str("export { initSync, initWithoutStart }\n"); footer.push_str("export default init;"); } } @@ -610,6 +628,7 @@ impl<'a> Context<'a> { // Also in (at least) the NoModules, the `init()` method is renamed to `wasm_bindgen()`. let setup_function_declaration; let mut sync_init_function = String::new(); + let mut without_start_init_function = String::new(); let declare_or_export; if self.config.mode.no_modules() { declare_or_export = "declare"; @@ -635,6 +654,23 @@ impl<'a> Context<'a> { memory_param = memory_param )); + without_start_init_function.push_str(&format!( + "\ + /**\n\ + * If `module_or_path` is {{RequestInfo}} or {{URL}}, makes a request and\n\ + * for everything else, calls `WebAssembly.instantiate` directly.\n\ + *\n\ + * @param {{InitInput | Promise}} module_or_path\n\ + {}\ + *\n\ + * @returns {{Promise}}\n\ + */\n\ + export function initWithoutStart \ + (module_or_path{}: InitInput | Promise{}): Promise;\n\n\ + ", + memory_doc, arg_optional, memory_param, + )); + setup_function_declaration = "export default function init"; } Ok(format!( @@ -647,7 +683,8 @@ impl<'a> Context<'a> { {sync_init_function}\ /**\n\ * If `module_or_path` is {{RequestInfo}} or {{URL}}, makes a request and\n\ - * for everything else, calls `WebAssembly.instantiate` directly.\n\ + * for everything else, calls `WebAssembly.instantiate` directly and runs\n\ + * the start function.\n\ *\n\ * @param {{InitInput | Promise}} module_or_path\n\ {}\ @@ -834,11 +871,12 @@ impl<'a> Context<'a> { {init_memory} }} - function finalizeInit(instance, module) {{ + function finalizeInit(instance, module, start) {{ wasm = instance.exports; init.__wbindgen_wasm_module = module; {init_memviews} {start} + {main} return wasm; }} @@ -853,10 +891,10 @@ impl<'a> Context<'a> { const instance = new WebAssembly.Instance(module, imports); - return finalizeInit(instance, module); + return finalizeInit(instance, module, true); }} - async function init(input{init_memory_arg}) {{ + async function initInternal(input{init_memory_arg}, start) {{ {default_module_path} const imports = getImports(); @@ -868,7 +906,15 @@ impl<'a> Context<'a> { const {{ instance, module }} = await load(await input, imports); - return finalizeInit(instance, module); + return finalizeInit(instance, module, start); + }} + + async function initWithoutStart(input{init_memory_arg}) {{ + return initInternal(input{init_memory_arg}, false); + }} + + async function init(input{init_memory_arg}) {{ + return initInternal(input{init_memory_arg}, true); }} ", init_memory_arg = init_memory_arg, @@ -880,6 +926,11 @@ impl<'a> Context<'a> { } else { "" }, + main = if self.start_found { + "if (start == true) { wasm.__wbindgen_main(); }" + } else { + "" + }, imports_init = imports_init, ); diff --git a/crates/cli-support/src/lib.rs b/crates/cli-support/src/lib.rs index e77137c941a..2d6f68a97cc 100755 --- a/crates/cli-support/src/lib.rs +++ b/crates/cli-support/src/lib.rs @@ -359,7 +359,7 @@ impl Bindgen { // auxiliary section for all sorts of miscellaneous information and // features #[wasm_bindgen] supports that aren't covered by wasm // interface types. - wit::process( + let wit::ProcessResult { start_found, .. } = wit::process( &mut module, self.externref, self.wasm_interface_types, @@ -441,7 +441,7 @@ impl Bindgen { .customs .delete_typed::() .unwrap(); - let mut cx = js::Context::new(&mut module, self, &adapters, &aux)?; + let mut cx = js::Context::new(&mut module, self, &adapters, &aux, start_found)?; cx.generate()?; let (js, ts, start) = cx.finalize(stem)?; Generated::Js(JsGenerated { diff --git a/crates/cli-support/src/wit/mod.rs b/crates/cli-support/src/wit/mod.rs index f5b56e0079d..b4a2084eaa6 100644 --- a/crates/cli-support/src/wit/mod.rs +++ b/crates/cli-support/src/wit/mod.rs @@ -43,12 +43,18 @@ struct InstructionBuilder<'a, 'b> { return_position: bool, } +pub struct ProcessResult { + pub adapters: NonstandardWitSectionId, + pub aux: WasmBindgenAuxId, + pub start_found: bool, +} + pub fn process( module: &mut Module, externref_enabled: bool, wasm_interface_types: bool, support_start: bool, -) -> Result<(NonstandardWitSectionId, WasmBindgenAuxId), Error> { +) -> Result { let mut storage = Vec::new(); let programs = extract_programs(module, &mut storage)?; @@ -88,7 +94,11 @@ pub fn process( let adapters = cx.module.customs.add(cx.adapters); let aux = cx.module.customs.add(cx.aux); - Ok((adapters, aux)) + Ok(ProcessResult { + adapters, + aux, + start_found: cx.start_found && cx.support_start, + }) } impl<'a> Context<'a> { @@ -476,22 +486,7 @@ impl<'a> Context<'a> { return Ok(()); } - let prev_start = match self.module.start { - Some(f) => f, - None => { - self.module.start = Some(id); - return Ok(()); - } - }; - - // Note that we call the previous start function, if any, first. This is - // because the start function currently only shows up when it's injected - // through thread/externref transforms. These injected start functions - // need to happen before user code, so we always schedule them first. - let mut builder = walrus::FunctionBuilder::new(&mut self.module.types, &[], &[]); - builder.func_body().call(prev_start).call(id); - let new_start = builder.finish(Vec::new(), &mut self.module.funcs); - self.module.start = Some(new_start); + self.module.exports.add("__wbindgen_main", id); Ok(()) } @@ -1464,7 +1459,7 @@ impl<'a> Context<'a> { let mut to_remove = Vec::new(); for export in self.module.exports.iter() { match export.name.as_str() { - n if n.starts_with("__wbindgen") => { + n if n.starts_with("__wbindgen") && n != "__wbindgen_main" => { to_remove.push(export.id()); } _ => {} diff --git a/crates/cli/tests/wasm-bindgen/main.rs b/crates/cli/tests/wasm-bindgen/main.rs index b52f14e64c3..5651d01fa9d 100644 --- a/crates/cli/tests/wasm-bindgen/main.rs +++ b/crates/cli/tests/wasm-bindgen/main.rs @@ -239,7 +239,7 @@ fn default_module_path_target_web() { let contents = fs::read_to_string(out_dir.join("default_module_path_target_web.js")).unwrap(); assert!(contents.contains( "\ -async function init(input) { +async function initInternal(input, start) { if (typeof input === 'undefined') { input = new URL('default_module_path_target_web_bg.wasm', import.meta.url); }", @@ -260,7 +260,7 @@ fn default_module_path_target_no_modules() { fs::read_to_string(out_dir.join("default_module_path_target_no_modules.js")).unwrap(); assert!(contents.contains( "\ - async function init(input) { + async function initInternal(input, start) { if (typeof input === 'undefined') { let src; if (typeof document === 'undefined') { @@ -287,7 +287,7 @@ fn omit_default_module_path_target_web() { fs::read_to_string(out_dir.join("omit_default_module_path_target_web.js")).unwrap(); assert!(contents.contains( "\ -async function init(input) { +async function initInternal(input, start) { const imports = getImports();", )); @@ -307,7 +307,7 @@ fn omit_default_module_path_target_no_modules() { fs::read_to_string(out_dir.join("omit_default_module_path_target_no_modules.js")).unwrap(); assert!(contents.contains( "\ - async function init(input) { + async function initInternal(input, start) { const imports = getImports();", ));