mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-06-14 13:31:22 +00:00
wasm2es6js: Fix handling of start function
This is split out from #1002 and is intended to fix the tool's handling of the `start` function. For the most accurate emulation of the wasm ESM spec I believe we need to defer execution of the start function until all our exports are wired up which should allow valid cyclical references during instantiation. The fix here is to remove the start function, if one is present, and inject an invocation of it at the end of initialization (after our exports are wired up). This fixes tests on #1002, but doesn't have any direct analogue for tests here just yet. Along the way because multiple files now come out of `wasm2es6js` by default I've added an `--out-dir` argument as well as `-o` to ensure that a folder for all outputs can be specified.
This commit is contained in:
@ -121,7 +121,7 @@ impl Output {
|
|||||||
return ts
|
return ts
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn js(self) -> Result<String, Error> {
|
pub fn js_and_wasm(mut self) -> Result<(String, Option<Vec<u8>>), Error> {
|
||||||
let mut js_imports = String::new();
|
let mut js_imports = String::new();
|
||||||
let mut exports = String::new();
|
let mut exports = String::new();
|
||||||
let mut set_exports = String::new();
|
let mut set_exports = String::new();
|
||||||
@ -136,7 +136,7 @@ impl Output {
|
|||||||
|
|
||||||
let name = (b'a' + (set.len() as u8)) as char;
|
let name = (b'a' + (set.len() as u8)) as char;
|
||||||
js_imports.push_str(&format!(
|
js_imports.push_str(&format!(
|
||||||
"import * as import_{} from '{}';",
|
"import * as import_{} from '{}';\n",
|
||||||
name,
|
name,
|
||||||
entry.module()
|
entry.module()
|
||||||
));
|
));
|
||||||
@ -155,6 +155,29 @@ impl Output {
|
|||||||
set_exports.push_str(";\n");
|
set_exports.push_str(";\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is sort of tricky, but the gist of it is that if there's a start
|
||||||
|
// function we want to defer execution of the start function until after
|
||||||
|
// all our module's exports are bound. That way we'll execute it as soon
|
||||||
|
// as we're ready, but the module's imports and such will be able to
|
||||||
|
// work as everything is wired up.
|
||||||
|
//
|
||||||
|
// This ends up helping out in situations such as:
|
||||||
|
//
|
||||||
|
// * The start function calls an imported function
|
||||||
|
// * That imported function in turn tries to access the wasm module
|
||||||
|
//
|
||||||
|
// If we don't do this then the second step won't work because the start
|
||||||
|
// function is automatically executed before the promise of
|
||||||
|
// instantiation resolves, meaning that we won't actually have anything
|
||||||
|
// bound for it to access.
|
||||||
|
//
|
||||||
|
// If we remove the start function here (via `unstart`) then we'll
|
||||||
|
// reexport it as `__wasm2es6js_start` so be manually executed here.
|
||||||
|
if self.unstart() {
|
||||||
|
set_exports.push_str("wasm.exports.__wasm2es6js_start();\n");
|
||||||
|
}
|
||||||
|
|
||||||
let inst = format!(
|
let inst = format!(
|
||||||
"
|
"
|
||||||
WebAssembly.instantiate(bytes,{{ {imports} }})
|
WebAssembly.instantiate(bytes,{{ {imports} }})
|
||||||
@ -166,8 +189,8 @@ impl Output {
|
|||||||
imports = imports,
|
imports = imports,
|
||||||
set_exports = set_exports,
|
set_exports = set_exports,
|
||||||
);
|
);
|
||||||
|
let wasm = serialize(self.module).expect("failed to serialize");
|
||||||
let (bytes, booted) = if self.base64 {
|
let (bytes, booted) = if self.base64 {
|
||||||
let wasm = serialize(self.module).expect("failed to serialize");
|
|
||||||
(
|
(
|
||||||
format!(
|
format!(
|
||||||
"
|
"
|
||||||
@ -199,8 +222,8 @@ impl Output {
|
|||||||
} else {
|
} else {
|
||||||
bail!("the option --base64 or --fetch is required");
|
bail!("the option --base64 or --fetch is required");
|
||||||
};
|
};
|
||||||
Ok(format!(
|
let js = format!(
|
||||||
"
|
"\
|
||||||
{js_imports}
|
{js_imports}
|
||||||
{bytes}
|
{bytes}
|
||||||
export const booted = {booted};
|
export const booted = {booted};
|
||||||
@ -210,6 +233,32 @@ impl Output {
|
|||||||
booted = booted,
|
booted = booted,
|
||||||
js_imports = js_imports,
|
js_imports = js_imports,
|
||||||
exports = exports,
|
exports = exports,
|
||||||
))
|
);
|
||||||
|
let wasm = if self.base64 { None } else { Some(wasm) };
|
||||||
|
Ok((js, wasm))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See comments above for what this is doing, but in a nutshell this
|
||||||
|
/// removes the start section, if any, and moves it to an exported function.
|
||||||
|
/// Returns whether a start function was found and removed.
|
||||||
|
fn unstart(&mut self) -> bool {
|
||||||
|
let mut start = None;
|
||||||
|
for (i, section) in self.module.sections().iter().enumerate() {
|
||||||
|
if let Section::Start(idx) = section {
|
||||||
|
start = Some((i, *idx));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let (i, idx) = match start {
|
||||||
|
Some(p) => p,
|
||||||
|
None => return false,
|
||||||
|
};
|
||||||
|
self.module.sections_mut().remove(i);
|
||||||
|
let entry = ExportEntry::new(
|
||||||
|
"__wasm2es6js_start".to_string(),
|
||||||
|
Internal::Function(idx),
|
||||||
|
);
|
||||||
|
self.module.export_section_mut().unwrap().entries_mut().push(entry);
|
||||||
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -68,8 +68,10 @@ pub fn spawn(
|
|||||||
let output = Config::new()
|
let output = Config::new()
|
||||||
.fetch(Some(format!("/{}", wasm_name)))
|
.fetch(Some(format!("/{}", wasm_name)))
|
||||||
.generate(&wasm)?;
|
.generate(&wasm)?;
|
||||||
let js = output.js()?;
|
let (js, wasm) = output.js_and_wasm()?;
|
||||||
|
let wasm = wasm.unwrap();
|
||||||
fs::write(tmpdir.join(format!("{}_bg.js", module)), js).context("failed to write JS file")?;
|
fs::write(tmpdir.join(format!("{}_bg.js", module)), js).context("failed to write JS file")?;
|
||||||
|
fs::write(tmpdir.join(format!("{}_bg.wasm", module)), wasm).context("failed to write wasm file")?;
|
||||||
|
|
||||||
// For now, always run forever on this port. We may update this later!
|
// For now, always run forever on this port. We may update this later!
|
||||||
let tmpdir = tmpdir.to_path_buf();
|
let tmpdir = tmpdir.to_path_buf();
|
||||||
|
@ -25,6 +25,7 @@ Usage:
|
|||||||
Options:
|
Options:
|
||||||
-h --help Show this screen.
|
-h --help Show this screen.
|
||||||
-o --output FILE File to place output in
|
-o --output FILE File to place output in
|
||||||
|
--out-dir DIR Directory to place ouptut in
|
||||||
--typescript Output a `*.d.ts` file next to the JS output
|
--typescript Output a `*.d.ts` file next to the JS output
|
||||||
--base64 Inline the wasm module using base64 encoding
|
--base64 Inline the wasm module using base64 encoding
|
||||||
--fetch PATH Load module by passing the PATH argument to `fetch()`
|
--fetch PATH Load module by passing the PATH argument to `fetch()`
|
||||||
@ -37,6 +38,7 @@ bundlers for working with wasm. Use this program with care!
|
|||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct Args {
|
struct Args {
|
||||||
flag_output: Option<PathBuf>,
|
flag_output: Option<PathBuf>,
|
||||||
|
flag_out_dir: Option<PathBuf>,
|
||||||
flag_typescript: bool,
|
flag_typescript: bool,
|
||||||
flag_base64: bool,
|
flag_base64: bool,
|
||||||
flag_fetch: Option<String>,
|
flag_fetch: Option<String>,
|
||||||
@ -68,22 +70,34 @@ fn rmain(args: &Args) -> Result<(), Error> {
|
|||||||
.generate(&wasm)?;
|
.generate(&wasm)?;
|
||||||
|
|
||||||
if args.flag_typescript {
|
if args.flag_typescript {
|
||||||
if let Some(ref p) = args.flag_output {
|
let ts = object.typescript();
|
||||||
let dst = p.with_extension("d.ts");
|
write(&args, "d.ts", ts.as_bytes(), false)?;
|
||||||
let ts = object.typescript();
|
|
||||||
fs::write(&dst, ts).with_context(|_| format!("failed to write `{}`", dst.display()))?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let js = object.js()?;
|
let (js, wasm) = object.js_and_wasm()?;
|
||||||
|
|
||||||
match args.flag_output {
|
write(args, "js", js.as_bytes(), false)?;
|
||||||
Some(ref p) => {
|
if let Some(wasm) = wasm {
|
||||||
fs::write(p, js).with_context(|_| format!("failed to write `{}`", p.display()))?;
|
write(args, "wasm", &wasm, false)?;
|
||||||
}
|
}
|
||||||
None => {
|
Ok(())
|
||||||
println!("{}", js);
|
}
|
||||||
}
|
|
||||||
|
fn write(
|
||||||
|
args: &Args,
|
||||||
|
extension: &str,
|
||||||
|
contents: &[u8],
|
||||||
|
print_fallback: bool,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
if let Some(p) = &args.flag_output {
|
||||||
|
let dst = p.with_extension(extension);
|
||||||
|
fs::write(&dst, contents).with_context(|_| format!("failed to write `{}`", dst.display()))?;
|
||||||
|
} else if let Some(p) = &args.flag_out_dir {
|
||||||
|
let filename = args.arg_input.file_name().unwrap();
|
||||||
|
let dst = p.join(filename).with_extension(extension);
|
||||||
|
fs::write(&dst, contents).with_context(|_| format!("failed to write `{}`", dst.display()))?;
|
||||||
|
} else if print_fallback {
|
||||||
|
println!("{}", String::from_utf8_lossy(contents))
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
Reference in New Issue
Block a user