Add first-class support for binary crates (#1843)

* autodiscover an exported `main` if possible

this allows for first-class support of binary crates

* wrap `main` to zero out arguments and suppress return value

* add test for bin crate support

* process only the export of the generated main wrapper

* skip most of `export` since only one line of that is needed
This commit is contained in:
Melody Horn 2019-11-04 12:34:42 -07:00 committed by Alex Crichton
parent b29c110d01
commit 79cf4f6198
2 changed files with 88 additions and 0 deletions

View File

@ -512,6 +512,10 @@ pub fn process(
cx.program(program)?; cx.program(program)?;
} }
if !cx.start_found {
cx.discover_main()?;
}
if let Some(standard) = cx.module.customs.delete_typed::<ast::WebidlBindings>() { if let Some(standard) = cx.module.customs.delete_typed::<ast::WebidlBindings>() {
cx.standard(&standard)?; cx.standard(&standard)?;
} }
@ -623,6 +627,44 @@ impl<'a> Context<'a> {
Ok(()) Ok(())
} }
// Discover a function `main(i32, i32) -> i32` and, if it exists, make that function run at module start.
fn discover_main(&mut self) -> Result<(), Error> {
// find a `main(i32, i32) -> i32`
let main_id = self
.module
.functions()
.find(|x| {
use walrus::ValType::I32;
// name has to be `main`
let name_matches = x.name.as_ref().map_or(false, |x| x == "main");
// type has to be `(i32, i32) -> i32`
let ty = self.module.types.get(x.ty());
let type_matches = ty.params() == [I32, I32] && ty.results() == [I32];
name_matches && type_matches
})
.map(|x| x.id());
let main_id = match main_id {
Some(x) => x,
None => return Ok(()),
};
// build a wrapper to zero out the arguments and ignore the return value
let mut wrapper = walrus::FunctionBuilder::new(&mut self.module.types, &[], &[]);
wrapper
.func_body()
.i32_const(0)
.i32_const(0)
.call(main_id)
.drop()
.return_();
let wrapper = wrapper.finish(vec![], &mut self.module.funcs);
// call that wrapper when the module starts
self.add_start_function(wrapper)?;
Ok(())
}
// Ensure that the `start` function for this module calls the // Ensure that the `start` function for this module calls the
// `__wbindgen_init_anyref_table` function. This'll ensure that all // `__wbindgen_init_anyref_table` function. This'll ensure that all
// instances of this module have the initial slots of the anyref table // instances of this module have the initial slots of the anyref table

View File

@ -151,3 +151,49 @@ fn one_export_works() {
.wasm_bindgen(""); .wasm_bindgen("");
cmd.assert().success(); cmd.assert().success();
} }
#[test]
fn bin_crate_works() {
let (mut cmd, out_dir) = Project::new("bin_crate_works")
.file(
"src/main.rs",
r#"
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn log(data: &str);
}
fn main() {
log("hello, world");
}
"#,
)
.file(
"Cargo.toml",
&format!(
"
[package]
name = \"bin_crate_works\"
authors = []
version = \"1.0.0\"
edition = '2018'
[dependencies]
wasm-bindgen = {{ path = '{}' }}
[workspace]
",
repo_root().display(),
),
)
.wasm_bindgen("--target nodejs");
cmd.assert().success();
Command::new("node")
.arg("bin_crate_works.js")
.current_dir(out_dir)
.assert()
.success()
.stdout("hello, world\n");
}