Skip to content

Commit a2aa28e

Browse files
committed
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
1 parent f4c1c64 commit a2aa28e

File tree

35 files changed

+430
-167
lines changed

35 files changed

+430
-167
lines changed

crates/backend/src/ast.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ pub struct Export {
4646
pub comments: Vec<String>,
4747
/// The name of the rust function/method on the rust side.
4848
pub rust_name: Ident,
49+
/// Whether or not this function should be flagged as the wasm start
50+
/// function.
51+
pub start: bool,
4952
}
5053

5154
/// The 3 types variations of `self`.

crates/backend/src/codegen.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,12 +450,21 @@ impl TryToTokens for ast::Export {
450450
let argtys = self.function.arguments.iter().map(|arg| &arg.ty);
451451
let attrs = &self.function.rust_attrs;
452452

453+
let start_check = if self.start {
454+
quote! {
455+
const _ASSERT: fn() = || #ret_ty { loop {} };
456+
}
457+
} else {
458+
quote! {}
459+
};
460+
453461
(quote! {
454462
#(#attrs)*
455463
#[export_name = #export_name]
456464
#[allow(non_snake_case)]
457465
#[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))]
458466
pub extern "C" fn #generated_name(#(#args),*) #ret_ty {
467+
#start_check
459468
// Scope all local variables to be destroyed after we call the
460469
// function to ensure that `#convert_ret`, if it panics, doesn't
461470
// leak anything.

crates/backend/src/encode.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ fn shared_export<'a>(export: &'a ast::Export, intern: &'a Interner) -> Export<'a
8282
is_constructor: export.is_constructor,
8383
function: shared_function(&export.function, intern),
8484
comments: export.comments.iter().map(|s| &**s).collect(),
85+
start: export.start,
8586
}
8687
}
8788

crates/cli-support/src/js/closures.rs

Lines changed: 16 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use parity_wasm::elements::*;
1818
use descriptor::Descriptor;
1919
use js::js2rust::Js2Rust;
2020
use js::Context;
21+
use wasm_utils::Remap;
2122

2223
pub fn rewrite(input: &mut Context) -> Result<(), Error> {
2324
let info = ClosureDescriptors::new(input);
@@ -37,15 +38,21 @@ pub fn rewrite(input: &mut Context) -> Result<(), Error> {
3738
// function indices. We're going to be injecting a few imported functions
3839
// below which will shift the index space for all defined functions.
3940
input.parse_wasm_names();
40-
Remap {
41-
code_idx_to_descriptor: &info.code_idx_to_descriptor,
42-
old_num_imports: input
43-
.module
44-
.import_section()
45-
.map(|s| s.functions())
46-
.unwrap_or(0) as u32,
47-
}
48-
.remap_module(input.module);
41+
let old_num_imports = input
42+
.module
43+
.import_section()
44+
.map(|s| s.functions())
45+
.unwrap_or(0) as u32;
46+
Remap(|idx| {
47+
// If this was an imported function we didn't reorder those, so nothing
48+
// to do.
49+
if idx < old_num_imports {
50+
return idx
51+
}
52+
// ... otherwise we're injecting a number of new imports, so offset
53+
// everything.
54+
idx + info.code_idx_to_descriptor.len() as u32
55+
}).remap_module(input.module);
4956

5057
info.delete_function_table_entries(input);
5158
info.inject_imports(input)?;
@@ -298,119 +305,3 @@ impl ClosureDescriptors {
298305
}
299306
}
300307
}
301-
302-
struct Remap<'a> {
303-
code_idx_to_descriptor: &'a BTreeMap<u32, DescribeInstruction>,
304-
old_num_imports: u32,
305-
}
306-
307-
impl<'a> Remap<'a> {
308-
fn remap_module(&self, module: &mut Module) {
309-
for section in module.sections_mut() {
310-
match section {
311-
Section::Export(e) => self.remap_export_section(e),
312-
Section::Element(e) => self.remap_element_section(e),
313-
Section::Code(e) => self.remap_code_section(e),
314-
Section::Start(i) => {
315-
self.remap_idx(i);
316-
}
317-
Section::Name(n) => self.remap_name_section(n),
318-
_ => {}
319-
}
320-
}
321-
}
322-
323-
fn remap_export_section(&self, section: &mut ExportSection) {
324-
for entry in section.entries_mut() {
325-
self.remap_export_entry(entry);
326-
}
327-
}
328-
329-
fn remap_export_entry(&self, entry: &mut ExportEntry) {
330-
match entry.internal_mut() {
331-
Internal::Function(i) => {
332-
self.remap_idx(i);
333-
}
334-
_ => {}
335-
}
336-
}
337-
338-
fn remap_element_section(&self, section: &mut ElementSection) {
339-
for entry in section.entries_mut() {
340-
self.remap_element_entry(entry);
341-
}
342-
}
343-
344-
fn remap_element_entry(&self, entry: &mut ElementSegment) {
345-
for member in entry.members_mut() {
346-
self.remap_idx(member);
347-
}
348-
}
349-
350-
fn remap_code_section(&self, section: &mut CodeSection) {
351-
for body in section.bodies_mut() {
352-
self.remap_func_body(body);
353-
}
354-
}
355-
356-
fn remap_func_body(&self, body: &mut FuncBody) {
357-
self.remap_instructions(body.code_mut());
358-
}
359-
360-
fn remap_instructions(&self, code: &mut Instructions) {
361-
for instr in code.elements_mut() {
362-
self.remap_instruction(instr);
363-
}
364-
}
365-
366-
fn remap_instruction(&self, instr: &mut Instruction) {
367-
match instr {
368-
Instruction::Call(i) => {
369-
self.remap_idx(i);
370-
}
371-
_ => {}
372-
}
373-
}
374-
375-
fn remap_name_section(&self, names: &mut NameSection) {
376-
match names {
377-
NameSection::Function(f) => self.remap_function_name_section(f),
378-
NameSection::Local(f) => self.remap_local_name_section(f),
379-
_ => {}
380-
}
381-
}
382-
383-
fn remap_function_name_section(&self, names: &mut FunctionNameSection) {
384-
let map = names.names_mut();
385-
let new = IndexMap::with_capacity(map.len());
386-
for (mut idx, name) in mem::replace(map, new) {
387-
if !self.remap_idx(&mut idx) {
388-
map.insert(idx, name);
389-
}
390-
}
391-
}
392-
393-
fn remap_local_name_section(&self, names: &mut LocalNameSection) {
394-
let map = names.local_names_mut();
395-
let new = IndexMap::with_capacity(map.len());
396-
for (mut idx, name) in mem::replace(map, new) {
397-
if !self.remap_idx(&mut idx) {
398-
map.insert(idx, name);
399-
}
400-
}
401-
}
402-
403-
/// Returns whether `idx` pointed to a previously known descriptor function
404-
/// that we're switching to an import
405-
fn remap_idx(&self, idx: &mut u32) -> bool {
406-
// If this was an imported function we didn't reorder those, so nothing
407-
// to do.
408-
if *idx < self.old_num_imports {
409-
return false;
410-
}
411-
// ... otherwise we're injecting a number of new imports, so offset
412-
// everything.
413-
*idx += self.code_idx_to_descriptor.len() as u32;
414-
false
415-
}
416-
}

0 commit comments

Comments
 (0)