Skip to content

Commit

Permalink
Add a #[wasm_bindgen(start)] attribute
Browse files Browse the repository at this point in the history
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
  • Loading branch information
alexcrichton committed Nov 29, 2018
1 parent f4c1c64 commit a2aa28e
Show file tree
Hide file tree
Showing 35 changed files with 430 additions and 167 deletions.
3 changes: 3 additions & 0 deletions crates/backend/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ pub struct Export {
pub comments: Vec<String>,
/// 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`.
Expand Down
9 changes: 9 additions & 0 deletions crates/backend/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions crates/backend/src/encode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}

Expand Down
141 changes: 16 additions & 125 deletions crates/cli-support/src/js/closures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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)?;
Expand Down Expand Up @@ -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
}
}
Loading

0 comments on commit a2aa28e

Please sign in to comment.