From a2aa28e4d31948b458c442e51f79057f5cab091a Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 28 Nov 2018 09:25:51 -0800 Subject: [PATCH] Add a `#[wasm_bindgen(start)]` attribute This commit adds a new attribute to `#[wasm_bindgen]`: `start`. The `start` attribute can be used to indicate that a function should be executed when the module is loaded, configuring the `start` function of the wasm executable. While this doesn't necessarily literally configure the `start` section, it does its best! Only one crate in a crate graph may indicate `#[wasm_bindgen(start)]`, so it's not recommended to be used in libraries but only end-user applications. Currently this still must be used with the `crate-type = ["cdylib"]` annotation in `Cargo.toml`. The implementation here is somewhat tricky because of the circular dependency between our generated JS and the wasm file that we emit. This circular dependency makes running initialization routines (like the `start` shim) particularly fraught with complications because one may need to run before the other but bundlers may not necessarily respect it. Workarounds have been implemented for various emission strategies, for example calling the start function directly after exports are wired up with `--no-modules` and otherwise working around what appears to be a Webpack bug with initializers running in a different order than we'd like. In any case, this in theory doesn't show up to the end user! Closes #74 --- crates/backend/src/ast.rs | 3 + crates/backend/src/codegen.rs | 9 + crates/backend/src/encode.rs | 1 + crates/cli-support/src/js/closures.rs | 141 ++----------- crates/cli-support/src/js/mod.rs | 185 +++++++++++++++++- crates/cli-support/src/lib.rs | 10 + crates/cli-support/src/wasm_utils.rs | 104 ++++++++++ .../src/bin/wasm-bindgen-test-runner/main.rs | 1 + crates/macro-support/src/parser.rs | 17 ++ crates/macro/ui-tests/start-function.rs | 12 ++ crates/macro/ui-tests/start-function.stderr | 14 ++ crates/shared/src/lib.rs | 1 + examples/canvas/index.js | 1 - examples/canvas/src/lib.rs | 4 +- examples/closures/index.js | 4 +- examples/closures/src/lib.rs | 2 +- examples/console_log/index.js | 5 +- examples/console_log/src/lib.rs | 2 +- examples/dom/index.js | 4 +- examples/dom/src/lib.rs | 2 +- examples/import_js/index.js | 5 +- examples/import_js/src/lib.rs | 2 +- examples/no_modules/index.html | 1 - examples/no_modules/src/lib.rs | 4 +- examples/paint/index.js | 1 - examples/paint/src/lib.rs | 4 +- examples/performance/index.js | 4 +- examples/performance/src/lib.rs | 3 +- examples/wasm-in-wasm/index.js | 4 +- examples/wasm-in-wasm/src/lib.rs | 2 +- examples/webgl/index.js | 1 - examples/webgl/src/lib.rs | 4 +- guide/src/SUMMARY.md | 1 + .../attributes/on-rust-exports/start.md | 31 +++ tests/wasm/main.rs | 8 + 35 files changed, 430 insertions(+), 167 deletions(-) create mode 100644 crates/cli-support/src/wasm_utils.rs create mode 100644 crates/macro/ui-tests/start-function.rs create mode 100644 crates/macro/ui-tests/start-function.stderr create mode 100644 guide/src/reference/attributes/on-rust-exports/start.md diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index 1543304182e..c02bd9b1fbb 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -46,6 +46,9 @@ pub struct Export { pub comments: Vec, /// The name of the rust function/method on the rust side. pub rust_name: Ident, + /// Whether or not this function should be flagged as the wasm start + /// function. + pub start: bool, } /// The 3 types variations of `self`. diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs index 507cb97438f..3fb5748555a 100644 --- a/crates/backend/src/codegen.rs +++ b/crates/backend/src/codegen.rs @@ -450,12 +450,21 @@ impl TryToTokens for ast::Export { let argtys = self.function.arguments.iter().map(|arg| &arg.ty); let attrs = &self.function.rust_attrs; + let start_check = if self.start { + quote! { + const _ASSERT: fn() = || #ret_ty { loop {} }; + } + } else { + quote! {} + }; + (quote! { #(#attrs)* #[export_name = #export_name] #[allow(non_snake_case)] #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] pub extern "C" fn #generated_name(#(#args),*) #ret_ty { + #start_check // Scope all local variables to be destroyed after we call the // function to ensure that `#convert_ret`, if it panics, doesn't // leak anything. diff --git a/crates/backend/src/encode.rs b/crates/backend/src/encode.rs index 0f8512f53c8..adaec24d017 100644 --- a/crates/backend/src/encode.rs +++ b/crates/backend/src/encode.rs @@ -82,6 +82,7 @@ fn shared_export<'a>(export: &'a ast::Export, intern: &'a Interner) -> Export<'a is_constructor: export.is_constructor, function: shared_function(&export.function, intern), comments: export.comments.iter().map(|s| &**s).collect(), + start: export.start, } } diff --git a/crates/cli-support/src/js/closures.rs b/crates/cli-support/src/js/closures.rs index 377bd5aa84f..a3193de18ee 100644 --- a/crates/cli-support/src/js/closures.rs +++ b/crates/cli-support/src/js/closures.rs @@ -18,6 +18,7 @@ use parity_wasm::elements::*; use descriptor::Descriptor; use js::js2rust::Js2Rust; use js::Context; +use wasm_utils::Remap; pub fn rewrite(input: &mut Context) -> Result<(), Error> { let info = ClosureDescriptors::new(input); @@ -37,15 +38,21 @@ pub fn rewrite(input: &mut Context) -> Result<(), Error> { // function indices. We're going to be injecting a few imported functions // below which will shift the index space for all defined functions. input.parse_wasm_names(); - Remap { - code_idx_to_descriptor: &info.code_idx_to_descriptor, - old_num_imports: input - .module - .import_section() - .map(|s| s.functions()) - .unwrap_or(0) as u32, - } - .remap_module(input.module); + let old_num_imports = input + .module + .import_section() + .map(|s| s.functions()) + .unwrap_or(0) as u32; + Remap(|idx| { + // If this was an imported function we didn't reorder those, so nothing + // to do. + if idx < old_num_imports { + return idx + } + // ... otherwise we're injecting a number of new imports, so offset + // everything. + idx + info.code_idx_to_descriptor.len() as u32 + }).remap_module(input.module); info.delete_function_table_entries(input); info.inject_imports(input)?; @@ -298,119 +305,3 @@ impl ClosureDescriptors { } } } - -struct Remap<'a> { - code_idx_to_descriptor: &'a BTreeMap, - old_num_imports: u32, -} - -impl<'a> Remap<'a> { - fn remap_module(&self, module: &mut Module) { - for section in module.sections_mut() { - match section { - Section::Export(e) => self.remap_export_section(e), - Section::Element(e) => self.remap_element_section(e), - Section::Code(e) => self.remap_code_section(e), - Section::Start(i) => { - self.remap_idx(i); - } - Section::Name(n) => self.remap_name_section(n), - _ => {} - } - } - } - - fn remap_export_section(&self, section: &mut ExportSection) { - for entry in section.entries_mut() { - self.remap_export_entry(entry); - } - } - - fn remap_export_entry(&self, entry: &mut ExportEntry) { - match entry.internal_mut() { - Internal::Function(i) => { - self.remap_idx(i); - } - _ => {} - } - } - - fn remap_element_section(&self, section: &mut ElementSection) { - for entry in section.entries_mut() { - self.remap_element_entry(entry); - } - } - - fn remap_element_entry(&self, entry: &mut ElementSegment) { - for member in entry.members_mut() { - self.remap_idx(member); - } - } - - fn remap_code_section(&self, section: &mut CodeSection) { - for body in section.bodies_mut() { - self.remap_func_body(body); - } - } - - fn remap_func_body(&self, body: &mut FuncBody) { - self.remap_instructions(body.code_mut()); - } - - fn remap_instructions(&self, code: &mut Instructions) { - for instr in code.elements_mut() { - self.remap_instruction(instr); - } - } - - fn remap_instruction(&self, instr: &mut Instruction) { - match instr { - Instruction::Call(i) => { - self.remap_idx(i); - } - _ => {} - } - } - - fn remap_name_section(&self, names: &mut NameSection) { - match names { - NameSection::Function(f) => self.remap_function_name_section(f), - NameSection::Local(f) => self.remap_local_name_section(f), - _ => {} - } - } - - fn remap_function_name_section(&self, names: &mut FunctionNameSection) { - let map = names.names_mut(); - let new = IndexMap::with_capacity(map.len()); - for (mut idx, name) in mem::replace(map, new) { - if !self.remap_idx(&mut idx) { - map.insert(idx, name); - } - } - } - - fn remap_local_name_section(&self, names: &mut LocalNameSection) { - let map = names.local_names_mut(); - let new = IndexMap::with_capacity(map.len()); - for (mut idx, name) in mem::replace(map, new) { - if !self.remap_idx(&mut idx) { - map.insert(idx, name); - } - } - } - - /// Returns whether `idx` pointed to a previously known descriptor function - /// that we're switching to an import - fn remap_idx(&self, idx: &mut u32) -> bool { - // If this was an imported function we didn't reorder those, so nothing - // to do. - if *idx < self.old_num_imports { - return false; - } - // ... otherwise we're injecting a number of new imports, so offset - // everything. - *idx += self.code_idx_to_descriptor.len() as u32; - false - } -} diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index d485048448a..382ab39ad32 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -11,6 +11,7 @@ use shared; use super::Bindgen; use descriptor::{Descriptor, VectorKind}; use wasm_interpreter::Interpreter; +use wasm_utils::Remap; mod js2rust; use self::js2rust::Js2Rust; @@ -30,6 +31,7 @@ pub struct Context<'a> { pub imported_statics: HashSet<&'a str>, pub config: &'a Bindgen, pub module: &'a mut Module, + pub start: Option, /// A map which maintains a list of what identifiers we've imported and what /// they're named locally. @@ -163,7 +165,6 @@ impl<'a> Context<'a> { } pub fn finalize(&mut self, module_name: &str) -> Result<(String, String), Error> { - self.parse_wasm_names(); self.write_classes()?; self.bind("__wbindgen_object_clone_ref", &|me| { @@ -460,6 +461,37 @@ impl<'a> Context<'a> { self.unexport_unused_internal_exports(); closures::rewrite(self)?; + + // Handle the `start` function, if one was specified. If we're in a + // --test mode (such as wasm-bindgen-test-runner) then we skip this + // entirely. Otherwise we want to first add a start function to the + // `start` section if one is specified. + // + // Afterwards, we need to perform what's a bit of a hack. Right after we + // added the start function, we remove it again because no current + // strategy for bundlers and deployment works well enough with it. For + // `--no-modules` output we need to be sure to call the start function + // after our exports are wired up (or most imported functions won't + // work). + // + // For ESM outputs bundlers like webpack also don't work because + // currently they run the wasm initialization before the JS glue + // initialization, meaning that if the wasm start function calls + // imported functions the JS glue isn't ready to go just yet. + // + // To handle `--no-modules` we just unstart the start function and call + // it manually. To handle the ESM use case we switch the start function + // to calling an imported function which defers the start function via + // `Promise.resolve().then(...)` to execute on the next microtask tick. + let mut has_start_function = false; + if self.config.emit_start { + self.add_start_function()?; + has_start_function = self.unstart_start_function(); + if has_start_function && !self.config.no_modules { + self.inject_start_shim(); + } + } + self.gc(); // Note that it's important `throw` comes last *after* we gc. The @@ -537,6 +569,7 @@ impl<'a> Context<'a> { init.__wbindgen_wasm_instance = instance; init.__wbindgen_wasm_module = module; init.__wbindgen_wasm_memory = __exports.memory; + {start} }}); }}; self.{global_name} = Object.assign(init, __exports); @@ -550,6 +583,11 @@ impl<'a> Context<'a> { .map(|s| &**s) .unwrap_or("wasm_bindgen"), init_memory = memory, + start = if has_start_function { + "wasm.__wbindgen_start();" + } else { + "" + }, ) } else if self.config.no_modules { format!( @@ -578,7 +616,7 @@ impl<'a> Context<'a> { }} return instantiation.then(({{instance}}) => {{ wasm = init.wasm = instance.exports; - return; + {start} }}); }}; self.{global_name} = Object.assign(init, __exports); @@ -591,6 +629,11 @@ impl<'a> Context<'a> { .as_ref() .map(|s| &**s) .unwrap_or("wasm_bindgen"), + start = if has_start_function { + "wasm.__wbindgen_start();" + } else { + "" + }, ) } else { let import_wasm = if self.globals.len() == 0 { @@ -1774,7 +1817,7 @@ impl<'a> Context<'a> { .run(&mut self.module); } - fn parse_wasm_names(&mut self) { + pub fn parse_wasm_names(&mut self) { let module = mem::replace(self.module, Module::default()); let module = module.parse_names().unwrap_or_else(|p| p.1); *self.module = module; @@ -2148,6 +2191,128 @@ impl<'a> Context<'a> { Ok(()) } } + + fn add_start_function(&mut self) -> Result<(), Error> { + let start = match &self.start { + Some(name) => name.clone(), + None => return Ok(()), + }; + let idx = { + let exports = self.module.export_section() + .ok_or_else(|| format_err!("no export section found"))?; + let entry = exports + .entries() + .iter() + .find(|e| e.field() == start) + .ok_or_else(|| format_err!("export `{}` not found", start))?; + match entry.internal() { + Internal::Function(i) => *i, + _ => bail!("export `{}` wasn't a function", start), + } + }; + if let Some(prev_start) = self.module.start_section() { + if let Some(NameSection::Function(n)) = self.module.names_section() { + if let Some(prev) = n.names().get(prev_start) { + bail!("cannot flag `{}` as start function as `{}` is \ + already the start function", start, prev); + } + } + + bail!("cannot flag `{}` as start function as another \ + function is already the start function", start); + } + + self.set_start_section(idx); + Ok(()) + } + + fn set_start_section(&mut self, start: u32) { + let mut pos = None; + // See http://webassembly.github.io/spec/core/binary/modules.html#binary-module + // for section ordering + for (i, section) in self.module.sections().iter().enumerate() { + match section { + Section::Type(_) | + Section::Import(_) | + Section::Function(_) | + Section::Table(_) | + Section::Memory(_) | + Section::Global(_) | + Section::Export(_) => continue, + _ => { + pos = Some(i); + break + } + } + } + let pos = pos.unwrap_or(self.module.sections().len() - 1); + self.module.sections_mut().insert(pos, Section::Start(start)); + } + + /// If a start function is present, it removes it from the `start` section + /// of the wasm module and then moves it to an exported function, named + /// `__wbindgen_start`. + fn unstart_start_function(&mut self) -> bool { + let mut pos = None; + let mut start = 0; + for (i, section) in self.module.sections().iter().enumerate() { + if let Section::Start(idx) = section { + start = *idx; + pos = Some(i); + break; + } + } + match pos { + Some(i) => { + self.module.sections_mut().remove(i); + let entry = ExportEntry::new( + "__wbindgen_start".to_string(), + Internal::Function(start), + ); + self.module.export_section_mut().unwrap().entries_mut().push(entry); + true + } + None => false, + } + } + + /// Injects a `start` function into the wasm module. This start function + /// calls a shim in the generated JS which defers the actual start function + /// to the next microtask tick of the event queue. + /// + /// See docs above at callsite for why this happens. + fn inject_start_shim(&mut self) { + let body = "function() { + Promise.resolve().then(() => wasm.__wbindgen_start()); + }"; + self.export("__wbindgen_defer_start", body, None); + + let imports = self.module.import_section() + .map(|s| s.functions() as u32) + .unwrap_or(0); + Remap(|idx| { + if idx < imports { idx } else { idx + 1 } + }).remap_module(self.module); + + let type_idx = { + let types = self.module.type_section_mut().unwrap(); + let ty = Type::Function(FunctionType::new(Vec::new(), None)); + types.types_mut().push(ty); + (types.types_mut().len() - 1) as u32 + }; + + let entry = ImportEntry::new( + "__wbindgen_placeholder__".to_string(), + "__wbindgen_defer_start".to_string(), + External::Function(type_idx), + ); + self.module + .import_section_mut() + .unwrap() + .entries_mut() + .push(entry); + self.set_start_section(imports); + } } impl<'a, 'b> SubContext<'a, 'b> { @@ -2184,6 +2349,7 @@ impl<'a, 'b> SubContext<'a, 'b> { fn generate_export(&mut self, export: &decode::Export<'b>) -> Result<(), Error> { if let Some(ref class) = export.class { + assert!(!export.start); return self.generate_export_for_class(class, export); } @@ -2192,6 +2358,10 @@ impl<'a, 'b> SubContext<'a, 'b> { Some(d) => d, }; + if export.start { + self.set_start_function(export.function.name)?; + } + let (js, ts, js_doc) = Js2Rust::new(&export.function.name, self.cx) .process(descriptor.unwrap_function())? .finish("function", &format!("wasm.{}", export.function.name)); @@ -2207,6 +2377,15 @@ impl<'a, 'b> SubContext<'a, 'b> { Ok(()) } + fn set_start_function(&mut self, start: &str) -> Result<(), Error> { + if let Some(prev) = &self.cx.start { + bail!("cannot flag `{}` as start function as `{}` is \ + already the start function", start, prev); + } + self.cx.start = Some(start.to_string()); + Ok(()) + } + fn generate_export_for_class( &mut self, class_name: &'b str, diff --git a/crates/cli-support/src/lib.rs b/crates/cli-support/src/lib.rs index 14a543c3615..f5e46dc038e 100644 --- a/crates/cli-support/src/lib.rs +++ b/crates/cli-support/src/lib.rs @@ -22,6 +22,7 @@ use parity_wasm::elements::*; mod decode; mod descriptor; mod js; +mod wasm_utils; pub mod wasm2es6js; pub struct Bindgen { @@ -36,6 +37,7 @@ pub struct Bindgen { demangle: bool, keep_debug: bool, remove_name_section: bool, + emit_start: bool, // Experimental support for `WeakRefGroup`, an upcoming ECMAScript feature. // Currently only enable-able through an env var. weak_refs: bool, @@ -64,6 +66,7 @@ impl Bindgen { demangle: true, keep_debug: false, remove_name_section: false, + emit_start: true, weak_refs: env::var("WASM_BINDGEN_WEAKREF").is_ok(), threads: threads_config(), } @@ -131,6 +134,11 @@ impl Bindgen { self } + pub fn emit_start(&mut self, emit: bool) -> &mut Bindgen { + self.emit_start = emit; + self + } + pub fn generate>(&mut self, path: P) -> Result<(), Error> { self._generate(path.as_ref()) } @@ -195,7 +203,9 @@ impl Bindgen { imported_functions: Default::default(), imported_statics: Default::default(), direct_imports: Default::default(), + start: None, }; + cx.parse_wasm_names(); for program in programs.iter() { js::SubContext { program, diff --git a/crates/cli-support/src/wasm_utils.rs b/crates/cli-support/src/wasm_utils.rs new file mode 100644 index 00000000000..3d81857c2d7 --- /dev/null +++ b/crates/cli-support/src/wasm_utils.rs @@ -0,0 +1,104 @@ +use std::mem; + +use parity_wasm::elements::*; + +pub struct Remap(pub F); + +impl Remap where F: FnMut(u32) -> u32 { + pub fn remap_module(&mut self, module: &mut Module) { + for section in module.sections_mut() { + match section { + Section::Export(e) => self.remap_export_section(e), + Section::Element(e) => self.remap_element_section(e), + Section::Code(e) => self.remap_code_section(e), + Section::Start(i) => { + self.remap_idx(i); + } + Section::Name(n) => self.remap_name_section(n), + _ => {} + } + } + } + + fn remap_export_section(&mut self, section: &mut ExportSection) { + for entry in section.entries_mut() { + self.remap_export_entry(entry); + } + } + + fn remap_export_entry(&mut self, entry: &mut ExportEntry) { + match entry.internal_mut() { + Internal::Function(i) => { + self.remap_idx(i); + } + _ => {} + } + } + + fn remap_element_section(&mut self, section: &mut ElementSection) { + for entry in section.entries_mut() { + self.remap_element_entry(entry); + } + } + + fn remap_element_entry(&mut self, entry: &mut ElementSegment) { + for member in entry.members_mut() { + self.remap_idx(member); + } + } + + fn remap_code_section(&mut self, section: &mut CodeSection) { + for body in section.bodies_mut() { + self.remap_func_body(body); + } + } + + fn remap_func_body(&mut self, body: &mut FuncBody) { + self.remap_instructions(body.code_mut()); + } + + fn remap_instructions(&mut self, code: &mut Instructions) { + for instr in code.elements_mut() { + self.remap_instruction(instr); + } + } + + fn remap_instruction(&mut self, instr: &mut Instruction) { + match instr { + Instruction::Call(i) => { + self.remap_idx(i); + } + _ => {} + } + } + + fn remap_name_section(&mut self, names: &mut NameSection) { + match names { + NameSection::Function(f) => self.remap_function_name_section(f), + NameSection::Local(f) => self.remap_local_name_section(f), + _ => {} + } + } + + fn remap_function_name_section(&mut self, names: &mut FunctionNameSection) { + let map = names.names_mut(); + let new = IndexMap::with_capacity(map.len()); + for (mut idx, name) in mem::replace(map, new) { + self.remap_idx(&mut idx); + map.insert(idx, name); + } + } + + fn remap_local_name_section(&mut self, names: &mut LocalNameSection) { + let map = names.local_names_mut(); + let new = IndexMap::with_capacity(map.len()); + for (mut idx, name) in mem::replace(map, new) { + self.remap_idx(&mut idx); + map.insert(idx, name); + } + } + + fn remap_idx(&mut self, idx: &mut u32) { + *idx = (self.0)(*idx); + } +} diff --git a/crates/cli/src/bin/wasm-bindgen-test-runner/main.rs b/crates/cli/src/bin/wasm-bindgen-test-runner/main.rs index 9bd8b672f67..522f6310d80 100644 --- a/crates/cli/src/bin/wasm-bindgen-test-runner/main.rs +++ b/crates/cli/src/bin/wasm-bindgen-test-runner/main.rs @@ -132,6 +132,7 @@ fn rmain() -> Result<(), Error> { .nodejs(node) .input_module(module, wasm) .keep_debug(false) + .emit_start(false) .generate(&tmpdir) .context("executing `wasm-bindgen` over the wasm file")?; shell.clear(); diff --git a/crates/macro-support/src/parser.rs b/crates/macro-support/src/parser.rs index d11d77d156a..d5d1c6acf85 100644 --- a/crates/macro-support/src/parser.rs +++ b/crates/macro-support/src/parser.rs @@ -47,6 +47,7 @@ macro_rules! attrgen { (vendor_prefix, VendorPrefix(Span, Ident)), (variadic, Variadic(Span)), (typescript_custom_section, TypescriptCustomSection(Span)), + (start, Start(Span)), } ) } @@ -730,6 +731,20 @@ impl<'a> MacroParse<(Option, &'a mut TokenStream)> for syn::Item { let comments = extract_doc_comments(&f.attrs); f.to_tokens(tokens); let opts = opts.unwrap_or_default(); + if opts.start().is_some() { + if f.decl.generics.params.len() > 0 { + bail_span!( + &f.decl.generics, + "the start function cannot have generics", + ); + } + if f.decl.inputs.len() > 0 { + bail_span!( + &f.decl.inputs, + "the start function cannot have arguments", + ); + } + } program.exports.push(ast::Export { rust_class: None, js_class: None, @@ -737,6 +752,7 @@ impl<'a> MacroParse<(Option, &'a mut TokenStream)> for syn::Item { is_constructor: false, comments, rust_name: f.ident.clone(), + start: opts.start().is_some(), function: f.convert(opts)?, }); } @@ -898,6 +914,7 @@ impl<'a, 'b> MacroParse<&'a BindgenAttrs> for (&'a Ident, &'b mut syn::ImplItem) is_constructor, function, comments, + start: false, rust_name: method.sig.ident.clone(), }); opts.check_used()?; diff --git a/crates/macro/ui-tests/start-function.rs b/crates/macro/ui-tests/start-function.rs new file mode 100644 index 00000000000..de7e51d056c --- /dev/null +++ b/crates/macro/ui-tests/start-function.rs @@ -0,0 +1,12 @@ +extern crate wasm_bindgen; + +use wasm_bindgen::prelude::*; + +#[wasm_bindgen(start)] +pub fn foo() {} + +#[wasm_bindgen(start)] +pub fn foo2(x: u32) {} + +#[wasm_bindgen(start)] +pub fn foo3() {} diff --git a/crates/macro/ui-tests/start-function.stderr b/crates/macro/ui-tests/start-function.stderr new file mode 100644 index 00000000000..0b772187bfc --- /dev/null +++ b/crates/macro/ui-tests/start-function.stderr @@ -0,0 +1,14 @@ +error: the start function cannot have arguments + --> $DIR/start-function.rs:9:13 + | +9 | pub fn foo2(x: u32) {} + | ^^^^^^ + +error: the start function cannot have generics + --> $DIR/start-function.rs:12:12 + | +12 | pub fn foo3() {} + | ^^^ + +error: aborting due to 2 previous errors + diff --git a/crates/shared/src/lib.rs b/crates/shared/src/lib.rs index 5cb245c60ea..546620b5fc9 100644 --- a/crates/shared/src/lib.rs +++ b/crates/shared/src/lib.rs @@ -84,6 +84,7 @@ macro_rules! shared_api { is_constructor: bool, function: Function<'a>, comments: Vec<&'a str>, + start: bool, } struct Enum<'a> { diff --git a/examples/canvas/index.js b/examples/canvas/index.js index 3439a276a0c..4b586d9a0f6 100644 --- a/examples/canvas/index.js +++ b/examples/canvas/index.js @@ -1,5 +1,4 @@ // For more comments about what's going on here, check out the `hello_world` // example. import('./canvas') - .then(canvas => canvas.draw()) .catch(console.error); diff --git a/examples/canvas/src/lib.rs b/examples/canvas/src/lib.rs index 3e31f1baa16..9d03a4787e8 100644 --- a/examples/canvas/src/lib.rs +++ b/examples/canvas/src/lib.rs @@ -6,8 +6,8 @@ use std::f64; use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; -#[wasm_bindgen] -pub fn draw() { +#[wasm_bindgen(start)] +pub fn start() { let document = web_sys::window().unwrap().document().unwrap(); let canvas = document.get_element_by_id("canvas").unwrap(); let canvas: web_sys::HtmlCanvasElement = canvas diff --git a/examples/closures/index.js b/examples/closures/index.js index 26cd9d482f6..cc27e0576d7 100644 --- a/examples/closures/index.js +++ b/examples/closures/index.js @@ -1,6 +1,4 @@ // For more comments about what's going on here, check out the `hello_world` // example -const rust = import('./closures'); -rust - .then(m => m.run()) +import('./closures') .catch(console.error); diff --git a/examples/closures/src/lib.rs b/examples/closures/src/lib.rs index a12d7b52377..30a28380f70 100644 --- a/examples/closures/src/lib.rs +++ b/examples/closures/src/lib.rs @@ -7,7 +7,7 @@ use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; use web_sys::{Document, Element, HtmlElement, Window}; -#[wasm_bindgen] +#[wasm_bindgen(start)] pub fn run() -> Result<(), JsValue> { let window = web_sys::window().expect("should have a window in this context"); let document = window.document().expect("window should have a document"); diff --git a/examples/console_log/index.js b/examples/console_log/index.js index 39d597afe9d..824c1371baa 100644 --- a/examples/console_log/index.js +++ b/examples/console_log/index.js @@ -1,7 +1,4 @@ // For more comments about what's going on here, check out the `hello_world` // example -const rust = import('./console_log'); - -rust - .then(m => m.run()) +import('./console_log') .catch(console.error); diff --git a/examples/console_log/src/lib.rs b/examples/console_log/src/lib.rs index b03de921d7c..a530259adbc 100644 --- a/examples/console_log/src/lib.rs +++ b/examples/console_log/src/lib.rs @@ -3,7 +3,7 @@ extern crate web_sys; use wasm_bindgen::prelude::*; -#[wasm_bindgen] +#[wasm_bindgen(start)] pub fn run() { bare_bones(); using_a_macro(); diff --git a/examples/dom/index.js b/examples/dom/index.js index 2447c51cc09..0d74f2fa82f 100644 --- a/examples/dom/index.js +++ b/examples/dom/index.js @@ -1,6 +1,4 @@ // For more comments about what's going on here, check out the `hello_world` // example -const rust = import('./dom'); -rust - .then(m => m.run()) +import('./dom') .catch(console.error); diff --git a/examples/dom/src/lib.rs b/examples/dom/src/lib.rs index a30af3bc814..fc6a9628ee0 100644 --- a/examples/dom/src/lib.rs +++ b/examples/dom/src/lib.rs @@ -4,7 +4,7 @@ extern crate web_sys; use wasm_bindgen::prelude::*; // Called by our JS entry point to run the example -#[wasm_bindgen] +#[wasm_bindgen(start)] pub fn run() -> Result<(), JsValue> { // Use `web_sys`'s global `window` function to get a handle on the global // window object. diff --git a/examples/import_js/index.js b/examples/import_js/index.js index 5b2ef4ec338..9d7e3b1a1ca 100644 --- a/examples/import_js/index.js +++ b/examples/import_js/index.js @@ -1,7 +1,4 @@ // For more comments about what's going on here, check out the `hello_world` // example -const rust = import('./import_js'); - -rust - .then(m => m.run()) +import('./import_js') .catch(console.error); diff --git a/examples/import_js/src/lib.rs b/examples/import_js/src/lib.rs index 6ea0cbd0286..9033e8089b3 100644 --- a/examples/import_js/src/lib.rs +++ b/examples/import_js/src/lib.rs @@ -26,7 +26,7 @@ extern "C" { fn log(s: &str); } -#[wasm_bindgen] +#[wasm_bindgen(start)] pub fn run() { log(&format!("Hello, {}!", name())); diff --git a/examples/no_modules/index.html b/examples/no_modules/index.html index 90147d23fdc..50193bc3629 100644 --- a/examples/no_modules/index.html +++ b/examples/no_modules/index.html @@ -27,7 +27,6 @@ // initialization and return to us a promise when it's done // also, we can use 'await' on the returned promise await wasm_bindgen('./no_modules_bg.wasm'); - wasm_bindgen.run(); }); diff --git a/examples/no_modules/src/lib.rs b/examples/no_modules/src/lib.rs index a30af3bc814..7bed734843c 100644 --- a/examples/no_modules/src/lib.rs +++ b/examples/no_modules/src/lib.rs @@ -4,8 +4,8 @@ extern crate web_sys; use wasm_bindgen::prelude::*; // Called by our JS entry point to run the example -#[wasm_bindgen] -pub fn run() -> Result<(), JsValue> { +#[wasm_bindgen(start)] +pub fn main() -> Result<(), JsValue> { // Use `web_sys`'s global `window` function to get a handle on the global // window object. let window = web_sys::window().expect("no global `window` exists"); diff --git a/examples/paint/index.js b/examples/paint/index.js index 6d88e81001d..6775ddb4cb4 100644 --- a/examples/paint/index.js +++ b/examples/paint/index.js @@ -1,5 +1,4 @@ // For more comments about what's going on here, check out the `hello_world` // example. import('./wasm_bindgen_paint') - .then(paint => paint.main()) .catch(console.error); diff --git a/examples/paint/src/lib.rs b/examples/paint/src/lib.rs index c69a9b67204..cdd923338dd 100644 --- a/examples/paint/src/lib.rs +++ b/examples/paint/src/lib.rs @@ -7,8 +7,8 @@ use std::rc::Rc; use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; -#[wasm_bindgen] -pub fn main() -> Result<(), JsValue> { +#[wasm_bindgen(start)] +pub fn start() -> Result<(), JsValue> { let document = web_sys::window().unwrap().document().unwrap(); let canvas = document .create_element("canvas")? diff --git a/examples/performance/index.js b/examples/performance/index.js index 48e692d750f..0c7c097cab5 100644 --- a/examples/performance/index.js +++ b/examples/performance/index.js @@ -1,6 +1,4 @@ // For more comments about what's going on here, check out the `hello_world` // example -const rust = import('./performance'); -rust - .then(m => m.run()) +import('./performance') .catch(console.error); diff --git a/examples/performance/src/lib.rs b/examples/performance/src/lib.rs index 0dad2a6b0df..dad7fad591e 100644 --- a/examples/performance/src/lib.rs +++ b/examples/performance/src/lib.rs @@ -17,8 +17,7 @@ macro_rules! console_log { ($($t:tt)*) => (log(&format_args!($($t)*).to_string())) } -// Called by our JS entry point to run the example -#[wasm_bindgen] +#[wasm_bindgen(start)] pub fn run() { let window = web_sys::window().expect("should have a window in this context"); let performance = window diff --git a/examples/wasm-in-wasm/index.js b/examples/wasm-in-wasm/index.js index e68f8a60a29..a8df06e590e 100644 --- a/examples/wasm-in-wasm/index.js +++ b/examples/wasm-in-wasm/index.js @@ -1,6 +1,4 @@ // For more comments about what's going on here, check out the `hello_world` // example -const rust = import('./wasm_in_wasm'); -rust - .then(m => m.run()) +import('./wasm_in_wasm') .catch(console.error); diff --git a/examples/wasm-in-wasm/src/lib.rs b/examples/wasm-in-wasm/src/lib.rs index 3496a3e75be..2aecb9091a0 100644 --- a/examples/wasm-in-wasm/src/lib.rs +++ b/examples/wasm-in-wasm/src/lib.rs @@ -18,7 +18,7 @@ macro_rules! console_log { const WASM: &[u8] = include_bytes!("add.wasm"); -#[wasm_bindgen] +#[wasm_bindgen(start)] pub fn run() -> Result<(), JsValue> { console_log!("instantiating a new wasm module directly"); let my_memory = wasm_bindgen::memory() diff --git a/examples/webgl/index.js b/examples/webgl/index.js index f717a2937cc..dc67e61039a 100644 --- a/examples/webgl/index.js +++ b/examples/webgl/index.js @@ -1,5 +1,4 @@ // For more comments about what's going on here, check out the `hello_world` // example. import('./webgl') - .then(webgl => webgl.draw()) .catch(console.error); diff --git a/examples/webgl/src/lib.rs b/examples/webgl/src/lib.rs index 16bcf557fda..1916ee7ce99 100644 --- a/examples/webgl/src/lib.rs +++ b/examples/webgl/src/lib.rs @@ -7,8 +7,8 @@ use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; use web_sys::{WebGlProgram, WebGlRenderingContext, WebGlShader}; -#[wasm_bindgen] -pub fn draw() -> Result<(), JsValue> { +#[wasm_bindgen(start)] +pub fn start() -> Result<(), JsValue> { let document = web_sys::window().unwrap().document().unwrap(); let canvas = document.get_element_by_id("canvas").unwrap(); let canvas: web_sys::HtmlCanvasElement = canvas.dyn_into::()?; diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md index 26b36caf241..5ed5b1e5102 100644 --- a/guide/src/SUMMARY.md +++ b/guide/src/SUMMARY.md @@ -76,6 +76,7 @@ - [`constructor`](./reference/attributes/on-rust-exports/constructor.md) - [`js_name = Blah`](./reference/attributes/on-rust-exports/js_name.md) - [`readonly`](./reference/attributes/on-rust-exports/readonly.md) + - [`start`](./reference/attributes/on-rust-exports/start.md) - [`typescript_custom_section`](./reference/attributes/on-rust-exports/typescript_custom_section.md) -------------------------------------------------------------------------------- diff --git a/guide/src/reference/attributes/on-rust-exports/start.md b/guide/src/reference/attributes/on-rust-exports/start.md new file mode 100644 index 00000000000..8dd52f7388f --- /dev/null +++ b/guide/src/reference/attributes/on-rust-exports/start.md @@ -0,0 +1,31 @@ +# `start` + +When attached to a `pub` function this attribute will configure the `start` +section of the wasm executable to be emitted, executing the tagged function as +soon as the wasm module is instantiated. + +```rust +#[wasm_bindgen(start)] +pub fn main() { + // executed automatically ... +} +``` + +The `start` section of the wasm executable will be configured to execute the +`main` function here as soon as it can. Note that due to various practical +limitations today the start section of the executable may not literally point to +`main`, but the `main` function here should be started up automatically when the +wasm module is loaded. + +There's a few caveats to be aware of when using the `start` attribute: + +* The `start` function must take no arguments and must either return `()` or + `Result<(), JsValue>` +* Only one `start` function can be placed into a module, including its + dependencies. If more than one is specified then `wasm-bindgen` will fail when + the CLI is run. It's recommended that only applications use this attribute. +* The `start` function will not be executed when testing. +* If you're experimenting with WebAssembly threads, the `start` function is + executed *once per thread*, not once globally! +* Note that the `start` function is relatively new, so if you find any bugs with + it, please feel free to report an issue! diff --git a/tests/wasm/main.rs b/tests/wasm/main.rs index e0793a78b1f..283ecbd581b 100644 --- a/tests/wasm/main.rs +++ b/tests/wasm/main.rs @@ -10,6 +10,8 @@ extern crate wasm_bindgen_test_crate_b; #[macro_use] extern crate serde_derive; +use wasm_bindgen::prelude::*; + pub mod api; pub mod char; pub mod classes; @@ -36,3 +38,9 @@ pub mod u64; pub mod validate_prt; pub mod variadic; pub mod vendor_prefix; + +// should not be executed +#[wasm_bindgen(start)] +pub fn start() { + panic!(); +}