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
This commit is contained in:
Alex Crichton
2018-11-28 09:25:51 -08:00
parent f4c1c64078
commit a2aa28e4d3
35 changed files with 430 additions and 167 deletions

View File

@ -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<u32, DescribeInstruction>,
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
}
}

View File

@ -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<String>,
/// 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,

View File

@ -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<P: AsRef<Path>>(&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,

View File

@ -0,0 +1,104 @@
use std::mem;
use parity_wasm::elements::*;
pub struct Remap<F>(pub F);
impl<F> Remap<F> 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);
}
}