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!(); +}