diff --git a/azure-pipelines.yml b/azure-pipelines.yml index c0f30506..fd4321a2 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -96,6 +96,10 @@ jobs: displayName: "wasm-bindgen-cli-support tests" - script: cargo test -p wasm-bindgen-cli displayName: "wasm-bindgen-cli tests" + - script: cargo test -p wasm-bindgen-anyref-xform + displayName: "wasm-bindgen-anyref-xform tests" + - script: cargo test -p wasm-bindgen-multi-value-xform + displayName: "wasm-bindgen-multi-value-xform tests" - job: test_web_sys displayName: "Run web-sys crate tests" diff --git a/crates/anyref-xform/Cargo.toml b/crates/anyref-xform/Cargo.toml index b2c7db7a..e06899a0 100644 --- a/crates/anyref-xform/Cargo.toml +++ b/crates/anyref-xform/Cargo.toml @@ -13,4 +13,14 @@ edition = '2018' [dependencies] anyhow = "1.0" -walrus = "0.13.0" +walrus = "0.14.0" + +[dev-dependencies] +rayon = "1.0" +wasmprinter = "0.2" +wast = "3.0" +wat = "1.0" + +[[test]] +name = "all" +harness = false diff --git a/crates/anyref-xform/src/lib.rs b/crates/anyref-xform/src/lib.rs index bd981519..90d63872 100644 --- a/crates/anyref-xform/src/lib.rs +++ b/crates/anyref-xform/src/lib.rs @@ -44,6 +44,12 @@ pub struct Context { table: Option, } +pub struct Meta { + pub table: TableId, + pub alloc: Option, + pub drop_slice: Option, +} + struct Transform<'a> { cx: &'a mut Context, @@ -161,7 +167,7 @@ impl Context { }) } - pub fn run(&mut self, module: &mut Module) -> Result<(), Error> { + pub fn run(&mut self, module: &mut Module) -> Result { let table = self.table.unwrap(); // Inject a stack pointer global which will be used for managing the @@ -171,6 +177,7 @@ impl Context { let mut heap_alloc = None; let mut heap_dealloc = None; + let mut drop_slice = None; // Find exports of some intrinsics which we only need for a runtime // implementation. @@ -182,7 +189,8 @@ impl Context { match export.name.as_str() { "__wbindgen_anyref_table_alloc" => heap_alloc = Some(f), "__wbindgen_anyref_table_dealloc" => heap_dealloc = Some(f), - _ => {} + "__wbindgen_drop_anyref_slice" => drop_slice = Some(f), + _ => continue, } } let mut clone_ref = None; @@ -226,7 +234,13 @@ impl Context { heap_dealloc, stack_pointer, } - .run(module) + .run(module)?; + + Ok(Meta { + table, + alloc: heap_alloc, + drop_slice, + }) } } @@ -619,7 +633,7 @@ impl Transform<'_> { // with a fresh type we've been calculating so far. Give the function a // nice name for debugging and then we're good to go! let id = builder.finish(params, funcs); - let name = format!("{}_anyref_shim", name); + let name = format!("{} anyref shim", name); funcs.get_mut(id).name = Some(name); self.shims.insert(id); Ok((id, anyref_ty)) diff --git a/crates/anyref-xform/tests/all.rs b/crates/anyref-xform/tests/all.rs new file mode 100644 index 00000000..7d07cf1b --- /dev/null +++ b/crates/anyref-xform/tests/all.rs @@ -0,0 +1,258 @@ +//! A small test framework to execute a test function over all files in a +//! directory. +//! +//! Each file in the directory has its own `CHECK-ALL` annotation indicating the +//! expected output of the test. That can be automatically updated with +//! `BLESS=1` in the environment. Otherwise the test are checked against the +//! listed expectation. + +use anyhow::{anyhow, bail, Context, Result}; +use rayon::prelude::*; +use std::env; +use std::fs; +use std::path::{Path, PathBuf}; +use wast::parser::{Parse, Parser}; + +fn main() { + run("tests".as_ref(), runtest); +} + +fn runtest(test: &Test) -> Result { + let wasm = wat::parse_file(&test.file)?; + let mut walrus = walrus::Module::from_buffer(&wasm)?; + let mut cx = wasm_bindgen_anyref_xform::Context::default(); + cx.prepare(&mut walrus)?; + for directive in test.directives.iter() { + match &directive.kind { + DirectiveKind::Export(name) => { + let export = walrus + .exports + .iter() + .find(|e| e.name == *name) + .ok_or_else(|| anyhow!("failed to find export"))?; + cx.export_xform(export.id(), &directive.args, directive.ret_anyref); + } + DirectiveKind::Import(module, field) => { + let import = walrus + .imports + .iter() + .find(|e| e.module == *module && e.name == *field) + .ok_or_else(|| anyhow!("failed to find export"))?; + cx.import_xform(import.id(), &directive.args, directive.ret_anyref); + } + DirectiveKind::Table(idx) => { + cx.table_element_xform(*idx, &directive.args, directive.ret_anyref); + } + } + } + cx.run(&mut walrus)?; + walrus::passes::gc::run(&mut walrus); + let printed = wasmprinter::print_bytes(&walrus.emit_wasm())?; + Ok(printed) +} + +fn run(dir: &Path, run: fn(&Test) -> Result) { + let mut tests = Vec::new(); + find_tests(dir, &mut tests); + let filter = std::env::args().nth(1); + + let bless = env::var("BLESS").is_ok(); + let tests = tests + .iter() + .filter(|test| { + if let Some(filter) = &filter { + if let Some(s) = test.file_name().and_then(|s| s.to_str()) { + if !s.contains(filter) { + return false; + } + } + } + true + }) + .collect::>(); + + println!("\nrunning {} tests\n", tests.len()); + + let errors = tests + .par_iter() + .filter_map(|test| run_test(test, bless, run).err()) + .collect::>(); + + if !errors.is_empty() { + for msg in errors.iter() { + eprintln!("error: {:?}", msg); + } + + panic!("{} tests failed", errors.len()) + } + + println!("test result: ok. {} passed\n", tests.len()); +} + +fn run_test(test: &Path, bless: bool, run: fn(&Test) -> anyhow::Result) -> Result<()> { + (|| -> Result<_> { + let expected = Test::from_file(test)?; + let actual = run(&expected)?; + expected.check(&actual, bless)?; + Ok(()) + })() + .context(format!("test failed - {}", test.display()))?; + Ok(()) +} + +fn find_tests(path: &Path, tests: &mut Vec) { + for f in path.read_dir().unwrap() { + let f = f.unwrap(); + if f.file_type().unwrap().is_dir() { + find_tests(&f.path(), tests); + continue; + } + match f.path().extension().and_then(|s| s.to_str()) { + Some("wat") => {} + _ => continue, + } + tests.push(f.path()); + } +} + +struct Test { + file: PathBuf, + directives: Vec, + assertion: Option, +} + +struct Directive { + args: Vec<(usize, bool)>, + ret_anyref: bool, + kind: DirectiveKind, +} + +enum DirectiveKind { + Import(String, String), + Export(String), + Table(u32), +} + +impl Test { + fn from_file(path: &Path) -> Result { + let contents = fs::read_to_string(path)?; + let mut iter = contents.lines(); + let mut assertion = None; + let mut directives = Vec::new(); + while let Some(line) = iter.next() { + if line.starts_with("(; CHECK-ALL:") { + let mut pattern = String::new(); + while let Some(line) = iter.next() { + if line == ";)" { + break; + } + pattern.push_str(line); + pattern.push_str("\n"); + } + while pattern.ends_with("\n") { + pattern.pop(); + } + if iter.next().is_some() { + bail!("CHECK-ALL must be at the end of the file"); + } + assertion = Some(pattern); + continue; + } + + if !line.starts_with(";; @xform") { + continue; + } + let directive = &line[9..]; + let buf = wast::parser::ParseBuffer::new(directive)?; + directives.push(wast::parser::parse::(&buf)?); + } + Ok(Test { + file: path.to_path_buf(), + directives, + assertion, + }) + } + + fn check(&self, output: &str, bless: bool) -> Result<()> { + if bless { + update_output(&self.file, output) + } else if let Some(pattern) = &self.assertion { + if output == pattern { + return Ok(()); + } + bail!( + "expected\n {}\n\nactual\n {}", + pattern.replace("\n", "\n "), + output.replace("\n", "\n ") + ); + } else { + bail!( + "no test assertions were found in this file, but you can \ + rerun tests with `BLESS=1` to automatically add assertions \ + to this file" + ); + } + } +} + +fn update_output(path: &Path, output: &str) -> Result<()> { + let contents = fs::read_to_string(path)?; + let start = contents.find("(; CHECK-ALL:").unwrap_or(contents.len()); + + let mut new_output = String::new(); + for line in output.lines() { + new_output.push_str(line); + new_output.push_str("\n"); + } + let new = format!( + "{}\n\n(; CHECK-ALL:\n{}\n;)\n", + contents[..start].trim(), + new_output.trim_end() + ); + fs::write(path, new)?; + Ok(()) +} + +impl<'a> Parse<'a> for Directive { + fn parse(parser: Parser<'a>) -> wast::parser::Result { + use wast::kw; + wast::custom_keyword!(anyref_owned); + wast::custom_keyword!(anyref_borrowed); + wast::custom_keyword!(other); + + let kind = if parser.peek::() { + parser.parse::()?; + DirectiveKind::Import(parser.parse()?, parser.parse()?) + } else if parser.peek::() { + parser.parse::()?; + DirectiveKind::Export(parser.parse()?) + } else { + parser.parse::()?; + DirectiveKind::Table(parser.parse()?) + }; + let mut args = Vec::new(); + parser.parens(|p| { + let mut i = 0; + while !p.is_empty() { + if parser.peek::() { + parser.parse::()?; + args.push((i, true)); + } else if parser.peek::() { + parser.parse::()?; + args.push((i, false)); + } else { + parser.parse::()?; + } + i += 1; + } + Ok(()) + })?; + + let ret_anyref = parser.parse::>()?.is_some(); + Ok(Directive { + args, + ret_anyref, + kind, + }) + } +} diff --git a/crates/anyref-xform/tests/anyref-param-owned.wat b/crates/anyref-xform/tests/anyref-param-owned.wat new file mode 100644 index 00000000..fa3148b9 --- /dev/null +++ b/crates/anyref-xform/tests/anyref-param-owned.wat @@ -0,0 +1,31 @@ +;; @xform export "foo" (anyref_owned) + +(module + (func $foo (export "foo") (param i32)) + (func $alloc (export "__wbindgen_anyref_table_alloc") (result i32) + i32.const 0) + (func $dealloc (export "__wbindgen_anyref_table_dealloc") (param i32)) +) + +(; CHECK-ALL: +(module + (type (;0;) (func (result i32))) + (type (;1;) (func (param i32))) + (type (;2;) (func (param anyref))) + (func $foo anyref shim (type 2) (param anyref) + (local i32) + call $alloc + local.tee 1 + local.get 0 + table.set 0 + local.get 1 + call $foo) + (func $alloc (type 0) (result i32) + i32.const 0) + (func $foo (type 1) (param i32)) + (func $dealloc (type 1) (param i32)) + (table (;0;) 32 anyref) + (export "foo" (func $foo anyref shim)) + (export "__wbindgen_anyref_table_alloc" (func $alloc)) + (export "__wbindgen_anyref_table_dealloc" (func $dealloc))) +;) diff --git a/crates/anyref-xform/tests/anyref-param.wat b/crates/anyref-xform/tests/anyref-param.wat new file mode 100644 index 00000000..a9058858 --- /dev/null +++ b/crates/anyref-xform/tests/anyref-param.wat @@ -0,0 +1,43 @@ +;; @xform export "foo" (anyref_borrowed) + +(module + (func $foo (export "foo") (param i32)) + (func $alloc (export "__wbindgen_anyref_table_alloc") (result i32) + i32.const 0) + (func $dealloc (export "__wbindgen_anyref_table_dealloc") (param i32)) +) + +(; CHECK-ALL: +(module + (type (;0;) (func (result i32))) + (type (;1;) (func (param i32))) + (type (;2;) (func (param anyref))) + (func $foo anyref shim (type 2) (param anyref) + (local i32) + global.get 0 + i32.const 1 + i32.sub + local.tee 1 + global.set 0 + local.get 1 + local.get 0 + table.set 0 + local.get 1 + call $foo + local.get 1 + ref.null + table.set 0 + local.get 1 + i32.const 1 + i32.add + global.set 0) + (func $alloc (type 0) (result i32) + i32.const 0) + (func $foo (type 1) (param i32)) + (func $dealloc (type 1) (param i32)) + (table (;0;) 32 anyref) + (global (;0;) (mut i32) (i32.const 32)) + (export "foo" (func $foo anyref shim)) + (export "__wbindgen_anyref_table_alloc" (func $alloc)) + (export "__wbindgen_anyref_table_dealloc" (func $dealloc))) +;) diff --git a/crates/anyref-xform/tests/clone-ref-intrinsic.wat b/crates/anyref-xform/tests/clone-ref-intrinsic.wat new file mode 100644 index 00000000..47d384df --- /dev/null +++ b/crates/anyref-xform/tests/clone-ref-intrinsic.wat @@ -0,0 +1,50 @@ +;; @xform export "foo" (anyref_owned) anyref_owned + +(module + (import "__wbindgen_placeholder__" "__wbindgen_object_clone_ref" + (func $clone (param i32) (result i32))) + (func $foo (export "foo") (param i32) (result i32) + local.get 0 + call $clone) + (func $alloc (export "__wbindgen_anyref_table_alloc") (result i32) + i32.const 0) + (func $dealloc (export "__wbindgen_anyref_table_dealloc") (param i32)) +) + +(; CHECK-ALL: +(module + (type (;0;) (func (result i32))) + (type (;1;) (func (param i32))) + (type (;2;) (func (param i32) (result i32))) + (type (;3;) (func (param anyref) (result anyref))) + (func $foo anyref shim (type 3) (param anyref) (result anyref) + (local i32) + call $alloc + local.tee 1 + local.get 0 + table.set 0 + local.get 1 + call $foo + local.tee 1 + table.get 0 + local.get 1 + call $dealloc) + (func $__wbindgen_object_clone_ref (type 2) (param i32) (result i32) + (local i32) + call $alloc + local.tee 1 + local.get 0 + table.get 0 + table.set 0 + local.get 1) + (func $foo (type 2) (param i32) (result i32) + local.get 0 + call $__wbindgen_object_clone_ref) + (func $alloc (type 0) (result i32) + i32.const 0) + (func $dealloc (type 1) (param i32)) + (table (;0;) 32 anyref) + (export "foo" (func $foo anyref shim)) + (export "__wbindgen_anyref_table_alloc" (func $alloc)) + (export "__wbindgen_anyref_table_dealloc" (func $dealloc))) +;) diff --git a/crates/anyref-xform/tests/drop-ref-intrinsic.wat b/crates/anyref-xform/tests/drop-ref-intrinsic.wat new file mode 100644 index 00000000..f1d2a2e6 --- /dev/null +++ b/crates/anyref-xform/tests/drop-ref-intrinsic.wat @@ -0,0 +1,37 @@ +;; @xform export "foo" (anyref_owned) + +(module + (import "__wbindgen_placeholder__" "__wbindgen_object_drop_ref" + (func $drop (param i32))) + (func $foo (export "foo") (param i32) + local.get 0 + call $drop) + (func $alloc (export "__wbindgen_anyref_table_alloc") (result i32) + i32.const 0) + (func $dealloc (export "__wbindgen_anyref_table_dealloc") (param i32)) +) + +(; CHECK-ALL: +(module + (type (;0;) (func (result i32))) + (type (;1;) (func (param i32))) + (type (;2;) (func (param anyref))) + (func $foo anyref shim (type 2) (param anyref) + (local i32) + call $alloc + local.tee 1 + local.get 0 + table.set 0 + local.get 1 + call $foo) + (func $foo (type 1) (param i32) + local.get 0 + call $dealloc) + (func $alloc (type 0) (result i32) + i32.const 0) + (func $dealloc (type 1) (param i32)) + (table (;0;) 32 anyref) + (export "foo" (func $foo anyref shim)) + (export "__wbindgen_anyref_table_alloc" (func $alloc)) + (export "__wbindgen_anyref_table_dealloc" (func $dealloc))) +;) diff --git a/crates/anyref-xform/tests/import-anyref-owned.wat b/crates/anyref-xform/tests/import-anyref-owned.wat new file mode 100644 index 00000000..36060be8 --- /dev/null +++ b/crates/anyref-xform/tests/import-anyref-owned.wat @@ -0,0 +1,36 @@ +;; @xform import "" "a" (anyref_owned) + +(module + (import "" "a" (func $a (param i32))) + (func (export "foo") + i32.const 0 + call $a) + (func $alloc (export "__wbindgen_anyref_table_alloc") (result i32) + i32.const 0) + (func $dealloc (export "__wbindgen_anyref_table_dealloc") (param i32)) +) + +(; CHECK-ALL: +(module + (type (;0;) (func)) + (type (;1;) (func (result i32))) + (type (;2;) (func (param i32))) + (type (;3;) (func (param anyref))) + (import "" "a" (func $a (type 3))) + (func $a anyref shim (type 2) (param i32) + local.get 0 + table.get 0 + local.get 0 + call $dealloc + call $a) + (func (;2;) (type 0) + i32.const 0 + call $a anyref shim) + (func $alloc (type 1) (result i32) + i32.const 0) + (func $dealloc (type 2) (param i32)) + (table (;0;) 32 anyref) + (export "foo" (func 2)) + (export "__wbindgen_anyref_table_alloc" (func $alloc)) + (export "__wbindgen_anyref_table_dealloc" (func $dealloc))) +;) diff --git a/crates/anyref-xform/tests/import-anyref-ret.wat b/crates/anyref-xform/tests/import-anyref-ret.wat new file mode 100644 index 00000000..4f692b88 --- /dev/null +++ b/crates/anyref-xform/tests/import-anyref-ret.wat @@ -0,0 +1,36 @@ +;; @xform import "" "a" () anyref_owned + +(module + (import "" "a" (func $a (result i32))) + (func (export "foo") (result i32) + call $a) + (func $alloc (export "__wbindgen_anyref_table_alloc") (result i32) + i32.const 0) + (func $dealloc (export "__wbindgen_anyref_table_dealloc") (param i32)) +) + +(; CHECK-ALL: +(module + (type (;0;) (func (result i32))) + (type (;1;) (func (result anyref))) + (type (;2;) (func (param i32))) + (import "" "a" (func $a (type 1))) + (func $a anyref shim (type 0) (result i32) + (local i32 anyref) + call $a + local.set 1 + call $alloc + local.tee 0 + local.get 1 + table.set 0 + local.get 0) + (func (;2;) (type 0) (result i32) + call $a anyref shim) + (func $alloc (type 0) (result i32) + i32.const 0) + (func $dealloc (type 2) (param i32)) + (table (;0;) 32 anyref) + (export "foo" (func 2)) + (export "__wbindgen_anyref_table_alloc" (func $alloc)) + (export "__wbindgen_anyref_table_dealloc" (func $dealloc))) +;) diff --git a/crates/anyref-xform/tests/import-anyref.wat b/crates/anyref-xform/tests/import-anyref.wat new file mode 100644 index 00000000..0a70f01d --- /dev/null +++ b/crates/anyref-xform/tests/import-anyref.wat @@ -0,0 +1,34 @@ +;; @xform import "" "a" (anyref_borrowed) + +(module + (import "" "a" (func $a (param i32))) + (func (export "foo") + i32.const 0 + call $a) + (func $alloc (export "__wbindgen_anyref_table_alloc") (result i32) + i32.const 0) + (func $dealloc (export "__wbindgen_anyref_table_dealloc") (param i32)) +) + +(; CHECK-ALL: +(module + (type (;0;) (func)) + (type (;1;) (func (result i32))) + (type (;2;) (func (param i32))) + (type (;3;) (func (param anyref))) + (import "" "a" (func $a (type 3))) + (func $a anyref shim (type 2) (param i32) + local.get 0 + table.get 0 + call $a) + (func (;2;) (type 0) + i32.const 0 + call $a anyref shim) + (func $alloc (type 1) (result i32) + i32.const 0) + (func $dealloc (type 2) (param i32)) + (table (;0;) 32 anyref) + (export "foo" (func 2)) + (export "__wbindgen_anyref_table_alloc" (func $alloc)) + (export "__wbindgen_anyref_table_dealloc" (func $dealloc))) +;) diff --git a/crates/anyref-xform/tests/mixed-export.wat b/crates/anyref-xform/tests/mixed-export.wat new file mode 100644 index 00000000..85439757 --- /dev/null +++ b/crates/anyref-xform/tests/mixed-export.wat @@ -0,0 +1,52 @@ +;; @xform export "a" (other anyref_borrowed other anyref_owned other) + +(module + (func $a (export "a") (param f32 i32 i64 i32 i32)) + (func $alloc (export "__wbindgen_anyref_table_alloc") (result i32) + i32.const 0) + (func $dealloc (export "__wbindgen_anyref_table_dealloc") (param i32)) +) + +(; CHECK-ALL: +(module + (type (;0;) (func (result i32))) + (type (;1;) (func (param i32))) + (type (;2;) (func (param f32 i32 i64 i32 i32))) + (type (;3;) (func (param f32 anyref i64 anyref i32))) + (func $a anyref shim (type 3) (param f32 anyref i64 anyref i32) + (local i32 i32) + global.get 0 + i32.const 1 + i32.sub + local.tee 5 + global.set 0 + local.get 0 + local.get 5 + local.get 1 + table.set 0 + local.get 5 + local.get 2 + call $alloc + local.tee 6 + local.get 3 + table.set 0 + local.get 6 + local.get 4 + call $a + local.get 5 + ref.null + table.set 0 + local.get 5 + i32.const 1 + i32.add + global.set 0) + (func $alloc (type 0) (result i32) + i32.const 0) + (func $a (type 2) (param f32 i32 i64 i32 i32)) + (func $dealloc (type 1) (param i32)) + (table (;0;) 32 anyref) + (global (;0;) (mut i32) (i32.const 32)) + (export "a" (func $a anyref shim)) + (export "__wbindgen_anyref_table_alloc" (func $alloc)) + (export "__wbindgen_anyref_table_dealloc" (func $dealloc))) +;) diff --git a/crates/anyref-xform/tests/mixed.wat b/crates/anyref-xform/tests/mixed.wat new file mode 100644 index 00000000..d220a861 --- /dev/null +++ b/crates/anyref-xform/tests/mixed.wat @@ -0,0 +1,50 @@ +;; @xform import "" "a" (other anyref_borrowed other anyref_owned other) + +(module + (import "" "a" (func $a (param f32 i32 i64 i32 i32))) + (func (export "foo") + f32.const 1 + i32.const 2 + i64.const 3 + i32.const 4 + i32.const 5 + call $a) + (func $alloc (export "__wbindgen_anyref_table_alloc") (result i32) + i32.const 0) + (func $dealloc (export "__wbindgen_anyref_table_dealloc") (param i32)) +) + +(; CHECK-ALL: +(module + (type (;0;) (func)) + (type (;1;) (func (result i32))) + (type (;2;) (func (param i32))) + (type (;3;) (func (param f32 i32 i64 i32 i32))) + (type (;4;) (func (param f32 anyref i64 anyref i32))) + (import "" "a" (func $a (type 4))) + (func $a anyref shim (type 3) (param f32 i32 i64 i32 i32) + local.get 0 + local.get 1 + table.get 0 + local.get 2 + local.get 3 + table.get 0 + local.get 3 + call $dealloc + local.get 4 + call $a) + (func (;2;) (type 0) + f32.const 0x1p+0 (;=1;) + i32.const 2 + i64.const 3 + i32.const 4 + i32.const 5 + call $a anyref shim) + (func $alloc (type 1) (result i32) + i32.const 0) + (func $dealloc (type 2) (param i32)) + (table (;0;) 32 anyref) + (export "foo" (func 2)) + (export "__wbindgen_anyref_table_alloc" (func $alloc)) + (export "__wbindgen_anyref_table_dealloc" (func $dealloc))) +;) diff --git a/crates/anyref-xform/tests/ret-anyref.wat b/crates/anyref-xform/tests/ret-anyref.wat new file mode 100644 index 00000000..77df8ab6 --- /dev/null +++ b/crates/anyref-xform/tests/ret-anyref.wat @@ -0,0 +1,33 @@ +;; @xform export "foo" () anyref_owned + +(module + (func $foo (export "foo") (result i32) + i32.const 0) + + (func $alloc (export "__wbindgen_anyref_table_alloc") (result i32) + i32.const 0) + (func $dealloc (export "__wbindgen_anyref_table_dealloc") (param i32)) +) + +(; CHECK-ALL: +(module + (type (;0;) (func (result i32))) + (type (;1;) (func (result anyref))) + (type (;2;) (func (param i32))) + (func $foo anyref shim (type 1) (result anyref) + (local i32) + call $foo + local.tee 0 + table.get 0 + local.get 0 + call $dealloc) + (func $foo (type 0) (result i32) + i32.const 0) + (func $alloc (type 0) (result i32) + i32.const 0) + (func $dealloc (type 2) (param i32)) + (table (;0;) 32 anyref) + (export "foo" (func $foo anyref shim)) + (export "__wbindgen_anyref_table_alloc" (func $alloc)) + (export "__wbindgen_anyref_table_dealloc" (func $dealloc))) +;) diff --git a/crates/anyref-xform/tests/table-grow-intrinsic.wat b/crates/anyref-xform/tests/table-grow-intrinsic.wat new file mode 100644 index 00000000..363165b1 --- /dev/null +++ b/crates/anyref-xform/tests/table-grow-intrinsic.wat @@ -0,0 +1,40 @@ +;; @xform export "foo" (anyref_owned) + +(module + (import "__wbindgen_anyref_xform__" "__wbindgen_anyref_table_grow" + (func $grow (param i32) (result i32))) + (func $foo (export "foo") (param i32) + i32.const 0 + call $grow + drop) + (func $alloc (export "__wbindgen_anyref_table_alloc") (result i32) + i32.const 0) + (func $dealloc (export "__wbindgen_anyref_table_dealloc") (param i32)) +) + +(; CHECK-ALL: +(module + (type (;0;) (func (result i32))) + (type (;1;) (func (param i32))) + (type (;2;) (func (param anyref))) + (func $foo anyref shim (type 2) (param anyref) + (local i32) + call $alloc + local.tee 1 + local.get 0 + table.set 0 + local.get 1 + call $foo) + (func $foo (type 1) (param i32) + ref.null + i32.const 0 + table.grow 0 + drop) + (func $alloc (type 0) (result i32) + i32.const 0) + (func $dealloc (type 1) (param i32)) + (table (;0;) 32 anyref) + (export "foo" (func $foo anyref shim)) + (export "__wbindgen_anyref_table_alloc" (func $alloc)) + (export "__wbindgen_anyref_table_dealloc" (func $dealloc))) +;) diff --git a/crates/anyref-xform/tests/table-set-null-intrinsic.wat b/crates/anyref-xform/tests/table-set-null-intrinsic.wat new file mode 100644 index 00000000..17988bff --- /dev/null +++ b/crates/anyref-xform/tests/table-set-null-intrinsic.wat @@ -0,0 +1,38 @@ +;; @xform export "foo" (anyref_owned) + +(module + (import "__wbindgen_anyref_xform__" "__wbindgen_anyref_table_set_null" + (func $set-null (param i32))) + (func $foo (export "foo") (param i32) + local.get 0 + call $set-null) + (func $alloc (export "__wbindgen_anyref_table_alloc") (result i32) + i32.const 0) + (func $dealloc (export "__wbindgen_anyref_table_dealloc") (param i32)) +) + +(; CHECK-ALL: +(module + (type (;0;) (func (result i32))) + (type (;1;) (func (param i32))) + (type (;2;) (func (param anyref))) + (func $foo anyref shim (type 2) (param anyref) + (local i32) + call $alloc + local.tee 1 + local.get 0 + table.set 0 + local.get 1 + call $foo) + (func $foo (type 1) (param i32) + local.get 0 + ref.null + table.set 0) + (func $alloc (type 0) (result i32) + i32.const 0) + (func $dealloc (type 1) (param i32)) + (table (;0;) 32 anyref) + (export "foo" (func $foo anyref shim)) + (export "__wbindgen_anyref_table_alloc" (func $alloc)) + (export "__wbindgen_anyref_table_dealloc" (func $dealloc))) +;) diff --git a/crates/anyref-xform/tests/table.wat b/crates/anyref-xform/tests/table.wat new file mode 100644 index 00000000..a19b7cca --- /dev/null +++ b/crates/anyref-xform/tests/table.wat @@ -0,0 +1,35 @@ +;; @xform table 0 (anyref_owned) + +(module + (func $foo (param i32)) + (table (export "func") 0 funcref) + (elem (i32.const 0) 0) + (func $alloc (export "__wbindgen_anyref_table_alloc") (result i32) + i32.const 0) + (func $dealloc (export "__wbindgen_anyref_table_dealloc") (param i32)) +) + +(; CHECK-ALL: +(module + (type (;0;) (func (result i32))) + (type (;1;) (func (param i32))) + (type (;2;) (func (param anyref))) + (func $closure0 anyref shim (type 2) (param anyref) + (local i32) + call $alloc + local.tee 1 + local.get 0 + table.set 1 + local.get 1 + call $foo) + (func $alloc (type 0) (result i32) + i32.const 0) + (func $foo (type 1) (param i32)) + (func $dealloc (type 1) (param i32)) + (table (;0;) 2 funcref) + (table (;1;) 32 anyref) + (export "func" (table 0)) + (export "__wbindgen_anyref_table_alloc" (func $alloc)) + (export "__wbindgen_anyref_table_dealloc" (func $dealloc)) + (elem (;0;) (i32.const 0) $foo $closure0 anyref shim)) +;) diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs index 442e243f..16ceda1a 100644 --- a/crates/backend/src/codegen.rs +++ b/crates/backend/src/codegen.rs @@ -618,12 +618,16 @@ impl ToTokens for ast::ImportType { impl OptionIntoWasmAbi for #rust_name { #[inline] - fn none() -> Self::Abi { 0 } + fn none() -> Self::Abi { + 0 + } } impl<'a> OptionIntoWasmAbi for &'a #rust_name { #[inline] - fn none() -> Self::Abi { 0 } + fn none() -> Self::Abi { + 0 + } } impl FromWasmAbi for #rust_name { diff --git a/crates/cli-support/Cargo.toml b/crates/cli-support/Cargo.toml index b505efb9..82e3f9e8 100644 --- a/crates/cli-support/Cargo.toml +++ b/crates/cli-support/Cargo.toml @@ -18,11 +18,11 @@ log = "0.4" rustc-demangle = "0.1.13" serde_json = "1.0" tempfile = "3.0" -walrus = "0.13.0" +walrus = "0.14.0" wasm-bindgen-anyref-xform = { path = '../anyref-xform', version = '=0.2.55' } wasm-bindgen-shared = { path = "../shared", version = '=0.2.55' } wasm-bindgen-multi-value-xform = { path = '../multi-value-xform', version = '=0.2.55' } wasm-bindgen-threads-xform = { path = '../threads-xform', version = '=0.2.55' } wasm-bindgen-wasm-conventions = { path = '../wasm-conventions', version = '=0.2.55' } wasm-bindgen-wasm-interpreter = { path = "../wasm-interpreter", version = '=0.2.55' } -wasm-webidl-bindings = "0.6.0" +wit-walrus = "0.1.0" diff --git a/crates/cli-support/src/anyref.rs b/crates/cli-support/src/anyref.rs index 82ac0d17..19d85c2c 100644 --- a/crates/cli-support/src/anyref.rs +++ b/crates/cli-support/src/anyref.rs @@ -1,160 +1,269 @@ -use crate::webidl::{NonstandardIncoming, NonstandardOutgoing}; -use crate::webidl::{NonstandardWebidlSection, WasmBindgenAux}; +use crate::wit::{AdapterKind, Instruction, NonstandardWitSection}; +use crate::wit::{AdapterType, InstructionData, StackChange, WasmBindgenAux}; use anyhow::Error; -use std::collections::HashSet; +use std::collections::HashMap; use walrus::Module; use wasm_bindgen_anyref_xform::Context; -use wasm_webidl_bindings::ast; -pub fn process(module: &mut Module, wasm_interface_types: bool) -> Result<(), Error> { +pub fn process(module: &mut Module) -> Result<(), Error> { let mut cfg = Context::default(); cfg.prepare(module)?; - let bindings = module + let section = module .customs - .get_typed_mut::() - .expect("webidl custom section should exist"); + .get_typed_mut::() + .expect("wit custom section should exist"); + + let implements = section + .implements + .iter() + .cloned() + .map(|(core, adapter)| (adapter, core)) + .collect::>(); // Transform all exported functions in the module, using the bindings listed // for each exported function. - for (export, binding) in bindings.exports.iter_mut() { - let ty = module.types.get(binding.wasm_ty); - let args = Arguments::Incoming(&mut binding.incoming); - let (args, ret) = extract_anyrefs(ty, args); - cfg.export_xform(*export, &args, ret); - } - - // Transform all imported functions in the module, using the bindings listed - // for each imported function. - for (import, binding) in bindings.imports.iter_mut() { - let ty = module.types.get(binding.wasm_ty); - let args = Arguments::Outgoing(&mut binding.outgoing); - let (args, ret) = extract_anyrefs(ty, args); - cfg.import_xform(*import, &args, ret); - } - - // And finally transform all table elements that are used as function - // pointers for closures and such. - for (idx, binding) in bindings.elems.iter_mut() { - let ty = module.types.get(binding.wasm_ty); - let args = Arguments::Incoming(&mut binding.incoming); - let (args, ret) = extract_anyrefs(ty, args); - if let Some(new) = cfg.table_element_xform(*idx, &args, ret) { - *idx = new; + for (id, adapter) in section.adapters.iter_mut() { + let instructions = match &mut adapter.kind { + AdapterKind::Local { instructions } => instructions, + AdapterKind::Import { .. } => continue, + }; + if let Some(id) = implements.get(&id) { + import_xform( + &mut cfg, + *id, + instructions, + &mut adapter.params, + &mut adapter.results, + ); + continue; + } + if let Some(id) = find_call_export(instructions) { + export_xform(&mut cfg, id, instructions); + continue; } } - cfg.run(module)?; + let meta = cfg.run(module)?; - // If our output is using WebAssembly interface types then our bindings will - // never use this table, so no need to export it. Otherwise it's highly - // likely in web/JS embeddings this will be used, so make sure we export it - // to avoid it getting gc'd accidentally. - if !wasm_interface_types { - // Make sure to export the `anyref` table for the JS bindings since it - // will need to be initialized. If it doesn't exist though then the - // module must not use it, so we skip it. - let table = module.tables.iter().find(|t| match t.kind { - walrus::TableKind::Anyref(_) => true, - _ => false, - }); - let table = match table { - Some(t) => t.id(), - None => return Ok(()), - }; - module.exports.add("__wbg_anyref_table", table); - } - - // Clean up now-unused intrinsics and shims and such - walrus::passes::gc::run(module); - - // The GC pass above may end up removing some imported intrinsics. For - // example `__wbindgen_object_clone_ref` is no longer needed after the - // anyref pass. Make sure to delete the associated metadata for those - // intrinsics so we don't try to access stale intrinsics later on. - let remaining_imports = module - .imports - .iter() - .map(|i| i.id()) - .collect::>(); - module - .customs - .get_typed_mut::() - .expect("webidl custom section should exist") - .imports - .retain(|id, _| remaining_imports.contains(id)); - module + let section = module .customs .get_typed_mut::() - .expect("wasm-bindgen aux section should exist") - .import_map - .retain(|id, _| remaining_imports.contains(id)); + .expect("wit custom section should exist"); + section.anyref_table = Some(meta.table); + section.anyref_alloc = meta.alloc; + section.anyref_drop_slice = meta.drop_slice; Ok(()) } -enum Arguments<'a> { - Incoming(&'a mut [NonstandardIncoming]), - Outgoing(&'a mut [NonstandardOutgoing]), +fn find_call_export(instrs: &[InstructionData]) -> Option { + instrs + .iter() + .enumerate() + .filter_map(|(i, instr)| match instr.instr { + Instruction::CallExport(e) => Some(Export::Export(e)), + Instruction::CallTableElement(e) => Some(Export::TableElement { + idx: e, + call_idx: i, + }), + _ => None, + }) + .next() } -/// Extract a description of the anyref arguments from the function signature -/// described by `f`. -/// -/// The returned values are expected to be passed to the anyref transformation -/// pass, and indicate which arguments (by index) in the wasm signature should -/// be transformed from `i32` to `anyref` as well as whether the returned value -/// is an `anyref` or not. -/// -/// The `offset` argument here is typically 0 and indicates the offset at which -/// the wasm abi arguments described by `f` start at. For closures this is 2 -/// because two synthetic arguments are injected into the wasm signature which -/// aren't present in the `Function` signature. -fn extract_anyrefs(ty: &walrus::Type, args: Arguments<'_>) -> (Vec<(usize, bool)>, bool) { - let mut ret = Vec::new(); +enum Export { + Export(walrus::ExportId), + TableElement { + /// Table element that we're calling + idx: u32, + /// Index in the instruction stream where the call instruction is found + call_idx: usize, + }, +} - // First find all the `anyref` arguments in the input type, and we'll - // assume that they're owned anyref arguments for now (the `true`) - for (i, arg) in ty.params().iter().enumerate() { - if *arg == walrus::ValType::Anyref { - ret.push((i, true)); +/// Adapts the `instrs` given which are an implementation of the import of `id`. +/// +/// This function will pattern match outgoing arguments and update the +/// instruction stream to remove any anyref-management instructions since +/// we'll be sinking those into the WebAssembly module. +fn import_xform( + cx: &mut Context, + id: walrus::ImportId, + instrs: &mut Vec, + params: &mut [AdapterType], + results: &mut [AdapterType], +) { + struct Arg { + idx: usize, + // Some(false) for a borrowed anyref, Some(true) for an owned one + anyref: Option, + } + + let mut to_delete = Vec::new(); + let mut iter = instrs.iter().enumerate(); + let mut args = Vec::new(); + while let Some((i, instr)) = iter.next() { + match instr.instr { + Instruction::CallAdapter(_) => break, + Instruction::AnyrefLoadOwned | Instruction::TableGet => { + let owned = match instr.instr { + Instruction::TableGet => false, + _ => true, + }; + let mut arg: Arg = match args.pop().unwrap() { + Some(arg) => arg, + None => panic!("previous instruction must be `arg.get`"), + }; + arg.anyref = Some(owned); + match params[arg.idx] { + AdapterType::I32 => {} + _ => panic!("must be `i32` type"), + } + params[arg.idx] = AdapterType::Anyref; + args.push(Some(arg)); + to_delete.push(i); + } + Instruction::Standard(wit_walrus::Instruction::ArgGet(n)) => { + args.push(Some(Arg { + idx: n as usize, + anyref: None, + })); + } + _ => match instr.stack_change { + StackChange::Modified { pushed, popped } => { + for _ in 0..popped { + args.pop(); + } + for _ in 0..pushed { + args.push(None); + } + } + StackChange::Unknown => { + panic!("must have stack change data"); + } + }, } } - // Afterwards look through the argument list (specified with various - // bindings) to find any borrowed anyref values and update our - // transformation metadata accordingly. if we find one then the binding no - // longer needs to remember its borrowed but rather it's just a simple cast - // from wasm anyref to JS any. - match args { - Arguments::Incoming(incoming) => { - for binding in incoming { - let expr = match binding { - NonstandardIncoming::BorrowedAnyref { - val: ast::IncomingBindingExpression::Get(expr), - } => expr.clone(), - _ => continue, - }; - ret.iter_mut().find(|p| p.0 == expr.idx as usize).unwrap().1 = false; - let new_binding = ast::IncomingBindingExpressionAs { - ty: walrus::ValType::Anyref, - expr: Box::new(expr.into()), - }; - *binding = NonstandardIncoming::Standard(new_binding.into()); + let mut ret_anyref = false; + while let Some((i, instr)) = iter.next() { + match instr.instr { + Instruction::I32FromAnyrefOwned => { + assert_eq!(results.len(), 1); + match results[0] { + AdapterType::I32 => {} + _ => panic!("must be `i32` type"), + } + results[0] = AdapterType::Anyref; + ret_anyref = true; + to_delete.push(i); } + _ => {} } - Arguments::Outgoing(outgoing) => { - for binding in outgoing { - let idx = match binding { - NonstandardOutgoing::BorrowedAnyref { idx } => *idx, - _ => continue, - }; - ret.iter_mut().find(|p| p.0 == idx as usize).unwrap().1 = false; - let new_binding = ast::OutgoingBindingExpressionAs { - idx, - ty: ast::WebidlScalarType::Any.into(), - }; - *binding = NonstandardOutgoing::Standard(new_binding.into()); + } + + // Delete all unnecessary anyref management insructions + for idx in to_delete.into_iter().rev() { + instrs.remove(idx); + } + + // Filter down our list of arguments to just the ones that are anyref + // values. + let args = args + .iter() + .filter_map(|arg| arg.as_ref()) + .filter_map(|arg| arg.anyref.map(|owned| (arg.idx, owned))) + .collect::>(); + + // ... and register this entire transformation with the anyref + // transformation pass. + cx.import_xform(id, &args, ret_anyref); +} + +/// Adapts the `instrs` of an adapter function that calls an export. +/// +/// The `instrs` must be generated by wasm-bindgen itself and follow the +/// pattern matched below to pass off to the anyref transformation pass. The +/// signature of the adapter doesn't change (it remains as anyref-aware) but the +/// signature of the export we're calling will change during the transformation. +fn export_xform(cx: &mut Context, export: Export, instrs: &mut Vec) { + let mut to_delete = Vec::new(); + let mut iter = instrs.iter().enumerate(); + let mut args = Vec::new(); + + // Mutate instructions leading up to the `CallExport` instruction. We + // maintain a stack of indicators whether the element at that stack slot is + // unknown (`None`) or whether it's an owned/borrowed anyref + // (`Some(owned)`). + // + // Note that we're going to delete the `I32FromAnyref*` instructions, so we + // also maintain indices of the instructions to delete. + while let Some((i, instr)) = iter.next() { + match instr.instr { + Instruction::CallExport(_) | Instruction::CallTableElement(_) => break, + Instruction::I32FromAnyrefOwned => { + args.pop(); + args.push(Some(true)); + to_delete.push(i); + } + Instruction::I32FromAnyrefBorrow => { + args.pop(); + args.push(Some(false)); + to_delete.push(i); + } + _ => match instr.stack_change { + StackChange::Modified { pushed, popped } => { + for _ in 0..popped { + args.pop(); + } + for _ in 0..pushed { + args.push(None); + } + } + StackChange::Unknown => { + panic!("must have stack change data"); + } + }, + } + } + + // If one of the instructions after the call is an `AnyrefLoadOwned` then we + // know that the function returned an anyref. Currently `&'static Anyref` + // can't be done as a return value, so this is the only case we handle here. + let mut ret_anyref = false; + while let Some((i, instr)) = iter.next() { + match instr.instr { + Instruction::AnyrefLoadOwned => { + ret_anyref = true; + to_delete.push(i); + } + _ => {} + } + } + + // Filter down our list of arguments to just the ones that are anyref + // values. + let args = args + .iter() + .enumerate() + .filter_map(|(i, owned)| owned.map(|owned| (i, owned))) + .collect::>(); + + // ... and register this entire transformation with the anyref + // transformation pass. + match export { + Export::Export(id) => { + cx.export_xform(id, &args, ret_anyref); + } + Export::TableElement { idx, call_idx } => { + if let Some(new_idx) = cx.table_element_xform(idx, &args, ret_anyref) { + instrs[call_idx].instr = Instruction::CallTableElement(new_idx); } } } - (ret, ty.results() == &[walrus::ValType::Anyref]) + + // Delete all unnecessary anyref management instructions. We're going to + // sink these instructions into the wasm module itself. + for idx in to_delete.into_iter().rev() { + instrs.remove(idx); + } } diff --git a/crates/cli-support/src/intrinsic.rs b/crates/cli-support/src/intrinsic.rs index a54926f3..d7db4fc9 100644 --- a/crates/cli-support/src/intrinsic.rs +++ b/crates/cli-support/src/intrinsic.rs @@ -13,7 +13,7 @@ macro_rules! intrinsics { (pub enum Intrinsic { $( #[symbol = $sym:tt] - #[signature = fn($($arg:expr),*) -> $ret:ident] + #[signature = fn($($arg:expr),*) -> $ret:expr] $name:ident, )* }) => { @@ -36,7 +36,7 @@ macro_rules! intrinsics { /// Returns the expected signature of this intrinsic, used for /// generating a JS shim. - pub fn binding(&self) -> Function { + pub fn signature(&self) -> Function { use crate::descriptor::Descriptor::*; match self { $( @@ -71,6 +71,14 @@ fn ref_string() -> Descriptor { Descriptor::Ref(Box::new(Descriptor::String)) } +fn opt_string() -> Descriptor { + Descriptor::Option(Box::new(Descriptor::String)) +} + +fn opt_f64() -> Descriptor { + Descriptor::Option(Box::new(Descriptor::F64)) +} + intrinsics! { pub enum Intrinsic { #[symbol = "__wbindgen_jsval_eq"] @@ -122,10 +130,10 @@ intrinsics! { #[signature = fn(ref_string()) -> Anyref] SymbolNamedNew, #[symbol = "__wbindgen_number_get"] - #[signature = fn(ref_anyref(), I32) -> F64] + #[signature = fn(ref_anyref()) -> opt_f64()] NumberGet, #[symbol = "__wbindgen_string_get"] - #[signature = fn(ref_anyref(), I32) -> I32] + #[signature = fn(ref_anyref()) -> opt_string()] StringGet, #[symbol = "__wbindgen_boolean_get"] #[signature = fn(ref_anyref()) -> I32] diff --git a/crates/cli-support/src/js/binding.rs b/crates/cli-support/src/js/binding.rs index 2154d761..45b5c7c0 100644 --- a/crates/cli-support/src/js/binding.rs +++ b/crates/cli-support/src/js/binding.rs @@ -4,35 +4,17 @@ //! exported functions, table elements, imports, etc. All function shims //! generated by `wasm-bindgen` run through this type. -use crate::js::incoming; -use crate::js::outgoing; use crate::js::Context; -use crate::webidl::Binding; -use anyhow::{bail, Error}; -use std::collections::HashSet; -use wasm_webidl_bindings::ast; +use crate::wit::InstructionData; +use crate::wit::{Adapter, AdapterId, AdapterKind, AdapterType, Instruction}; +use anyhow::{anyhow, bail, Error}; +use walrus::Module; /// A one-size-fits-all builder for processing WebIDL bindings and generating /// JS. pub struct Builder<'a, 'b> { /// Parent context used to expose helper functions and such. - cx: &'a mut Context<'b>, - /// Prelude JS which is present before the main invocation to prepare - /// arguments. - args_prelude: String, - /// Finally block to be executed regardless of the call's status, mostly - /// used for cleanups like free'ing. - finally: String, - /// Code to execute after return value is materialized. - ret_finally: String, - /// Argument names to the JS function shim that we're generating. - function_args: Vec, - /// JS expressions that are arguments to the function that we're calling. - invoc_args: Vec, - /// JS to execute just before the return value is materialized. - ret_prelude: String, - /// The JS expression of the actual return value. - ret_js: String, + pub cx: &'a mut Context<'b>, /// The TypeScript definition for each argument to this function. pub ts_args: Vec, /// The TypeScript return value for this function. @@ -50,13 +32,38 @@ pub struct Builder<'a, 'b> { log_error: bool, } -/// Helper struct used in incoming/outgoing to generate JS. -pub struct JsBuilder { +/// Helper struct used to create JS to process all instructions in an adapter +/// function. +pub struct JsBuilder<'a, 'b> { + /// General context for building JS, used to manage intrinsic names, exposed + /// JS functions, etc. + cx: &'a mut Context<'b>, + + /// The list of typescript arguments that we're going to have to this + /// function. typescript: Vec, + + /// The "prelude" of the function, or largely just the JS function we've + /// built so far. prelude: String, + + /// JS code to execute in a `finally` block in case any exceptions happen. finally: String, + + /// An index used to manage allocation of temporary indices, used to name + /// variables to ensure nothing clashes with anything else. tmp: usize, + + /// Names or expressions representing the arguments to the adapter. This is + /// use to translate the `arg.get` instruction. args: Vec, + + /// The wasm interface types "stack". The expressions pushed onto this stack + /// are intended to be *pure*, and if they're not, they should be pushed + /// into the `prelude`, assigned to a variable, and the variable should be + /// pushed to the stack. We're not super principled about this though, so + /// improvements will likely happen here over time. + stack: Vec, } pub struct TypescriptArg { @@ -68,15 +75,8 @@ pub struct TypescriptArg { impl<'a, 'b> Builder<'a, 'b> { pub fn new(cx: &'a mut Context<'b>) -> Builder<'a, 'b> { Builder { - log_error: cx.config.debug, + log_error: false, cx, - args_prelude: String::new(), - finally: String::new(), - ret_finally: String::new(), - function_args: Vec::new(), - invoc_args: Vec::new(), - ret_prelude: String::new(), - ret_js: String::new(), ts_args: Vec::new(), ts_ret: None, constructor: None, @@ -93,306 +93,116 @@ impl<'a, 'b> Builder<'a, 'b> { self.constructor = Some(class.to_string()); } - pub fn catch(&mut self, catch: bool) -> Result<(), Error> { - if catch { - self.cx.expose_handle_error()?; - } + pub fn catch(&mut self, catch: bool) { self.catch = catch; - Ok(()) } - pub fn disable_log_error(&mut self, disable: bool) { - if disable { - self.log_error = false; - } + pub fn log_error(&mut self, log: bool) { + self.log_error = log; } pub fn process( &mut self, - binding: &Binding, - webidl: &ast::WebidlFunction, - incoming_args: bool, + adapter: &Adapter, + instructions: &[InstructionData], explicit_arg_names: &Option>, - invoke: &mut dyn FnMut(&mut Context, &mut String, &[String]) -> Result, ) -> Result { - // used in `finalize` below - if self.log_error { - self.cx.expose_log_error(); + if self.cx.aux.imports_with_assert_no_shim.contains(&adapter.id) { + bail!("generating a shim for something asserted to have no shim"); } - // First up we handle all the arguments. Depending on whether incoming - // or outgoing ar the arguments this is pretty different. - let mut arg_names = Vec::new(); - let mut js; - if incoming_args { - let mut webidl_params = webidl.params.iter(); + let mut params = adapter.params.iter(); + let mut function_args = Vec::new(); - // If we're returning via an out pointer then it's guaranteed to be - // the first argument. This isn't an argument of the function shim - // we're generating so synthesize the parameter and its value. - // - // For the actual value of the return pointer we just pick the first - // properly aligned nonzero address. We use the address for a - // BigInt64Array sometimes which means it needs to be 8-byte - // aligned. Otherwise valid code is unlikely to ever be working - // around address 8, so this should be a safe address to use for - // returning data through. - if binding.return_via_outptr.is_some() { - drop(webidl_params.next()); - self.args_prelude.push_str("const retptr = 8;\n"); - arg_names.push("retptr".to_string()); - } - - // If this is a method then we're generating this as part of a class - // method, so the leading parameter is the this pointer stored on - // the JS object, so synthesize that here. - match self.method { - Some(consumes_self) => { - drop(webidl_params.next()); - if self.cx.config.debug { - self.args_prelude.push_str( - "if (this.ptr == 0) throw new Error('Attempt to use a moved value');\n", - ); - } - if consumes_self { - self.args_prelude.push_str("const ptr = this.ptr;\n"); - self.args_prelude.push_str("this.ptr = 0;\n"); - arg_names.push("ptr".to_string()); - } else { - arg_names.push("this.ptr".to_string()); - } + // If this is a method then we're generating this as part of a class + // method, so the leading parameter is the this pointer stored on + // the JS object, so synthesize that here. + let mut js = JsBuilder::new(self.cx); + match self.method { + Some(consumes_self) => { + drop(params.next()); + if js.cx.config.debug { + js.prelude( + "if (this.ptr == 0) throw new Error('Attempt to use a moved value');\n", + ); + } + if consumes_self { + js.prelude("var ptr = this.ptr;"); + js.prelude("this.ptr = 0;"); + js.args.push("ptr".to_string()); + } else { + js.args.push("this.ptr".to_string()); } - None => {} - } - - // And now take the rest of the parameters and generate a name for them. - for (i, _) in webidl_params.enumerate() { - let arg = match explicit_arg_names { - Some(list) => list[i].clone(), - None => format!("arg{}", i), - }; - self.function_args.push(arg.clone()); - arg_names.push(arg); - } - js = JsBuilder::new(arg_names); - let mut args = incoming::Incoming::new(self.cx, &webidl.params, &mut js); - for argument in binding.incoming.iter() { - self.invoc_args.extend(args.process(argument)?); - } - } else { - // If we're getting arguments from outgoing values then the ret ptr - // is actually an argument of the function itself. That means that - // `arg0` we generate below is the ret ptr, and we shouldn't - // generate a JS binding for it and instead skip the first binding - // listed. - let mut skip = 0; - if binding.return_via_outptr.is_some() { - skip = 1; - } - - // And now take the rest of the parameters and generate a name for them. - for i in 0..self.cx.module.types.get(binding.wasm_ty).params().len() { - let arg = format!("arg{}", i); - self.function_args.push(arg.clone()); - arg_names.push(arg); - } - js = JsBuilder::new(arg_names); - let mut args = outgoing::Outgoing::new(self.cx, &mut js); - for argument in binding.outgoing.iter().skip(skip) { - self.invoc_args.push(args.process(argument)?); } + None => {} + } + for (i, _param) in params.enumerate() { + let arg = match explicit_arg_names { + Some(list) => list[i].clone(), + None => format!("arg{}", i), + }; + js.args.push(arg.clone()); + function_args.push(arg); } - // Save off the results of JS generation for the arguments. - self.args_prelude.push_str(&js.prelude); - self.finally.push_str(&js.finally); - self.ts_args.extend(js.typescript); + // Translate all instructions, the fun loop! + // + // This loop will process all instructions for this adapter function. + // Each instruction will push/pop from the `js.stack` variable, and will + // eventually build up the entire `js.prelude` variable with all the JS + // code that we're going to be adding. Note that the stack at the end + // represents all returned values. + // + // We don't actually manage a literal stack at runtime, but instead we + // act as more of a compiler to generate straight-line code to make it + // more JIT-friendly. The generated code should be equivalent to the + // wasm interface types stack machine, however. + for instr in instructions { + instruction(&mut js, &instr.instr, &mut self.log_error)?; + } + + assert_eq!(js.stack.len(), adapter.results.len()); + match js.stack.len() { + 0 => {} + 1 => { + self.ts_ret = js.typescript.pop(); + let val = js.pop(); + js.prelude(&format!("return {};", val)); + } + + // TODO: this should be pretty trivial to support (commented out + // code below), but we should be sure to have a test for this + // somewhere. Currently I don't think it's possible to actually + // exercise this with just Rust code, so let's wait until we get + // some tests to enable this path. + _ => bail!("multi-value returns from adapters not supported yet"), + // _ => { + // let expr = js.stack.join(", "); + // js.stack.truncate(0); + // js.prelude(&format!("return [{}];", expr)); + // } + } + assert!(js.stack.is_empty()); + self.ts_args = js.typescript; // Remove extraneous typescript args which were synthesized and aren't // part of our function shim. - while self.ts_args.len() > self.function_args.len() { + while self.ts_args.len() > function_args.len() { self.ts_args.remove(0); } - // Handle the special case where there is no return value. In this case - // we can skip all the logic below and go straight to the end. - if incoming_args { - if binding.outgoing.len() == 0 { - assert!(binding.return_via_outptr.is_none()); - assert!(self.constructor.is_none()); - let invoc = invoke(self.cx, &mut self.args_prelude, &self.invoc_args)?; - return Ok(self.finalize(&invoc)); - } - assert_eq!(binding.outgoing.len(), 1); - } else { - if binding.incoming.len() == 0 { - assert!(binding.return_via_outptr.is_none()); - assert!(self.constructor.is_none()); - let invoc = invoke(self.cx, &mut self.args_prelude, &self.invoc_args)?; - return Ok(self.finalize(&invoc)); - } - assert_eq!(binding.incoming.len(), 1); - } + let mut ret = String::new(); + ret.push_str("("); + ret.push_str(&function_args.join(", ")); + ret.push_str(") {\n"); - // Like above handling the return value is quite different based on - // whether it's an outgoing argument or an incoming argument. - let mut ret_args = Vec::new(); - let mut js; - if incoming_args { - match &binding.return_via_outptr { - // If we have an outgoing value that requires multiple - // aggregates then we're passing a return pointer (a global one) - // to a wasm function, and then afterwards we're going to read - // the results of that return pointer. Here we generate an - // expression effectively which represents reading each value of - // the return pointer that was filled in. These values are then - // used by the outgoing builder as inputs to generate the final - // actual return value. - Some(list) => { - let mut exposed = HashSet::new(); - for (i, ty) in list.iter().enumerate() { - let (mem, size) = match ty { - walrus::ValType::I32 => { - if exposed.insert(*ty) { - self.cx.expose_int32_memory(); - self.ret_prelude - .push_str("const memi32 = getInt32Memory();\n"); - } - ("memi32", 4) - } - walrus::ValType::F32 => { - if exposed.insert(*ty) { - self.cx.expose_f32_memory(); - self.ret_prelude - .push_str("const memf32 = getFloat32Memory();\n"); - } - ("memf32", 4) - } - walrus::ValType::F64 => { - if exposed.insert(*ty) { - self.cx.expose_f64_memory(); - self.ret_prelude - .push_str("const memf64 = getFloat64Memory();\n"); - } - ("memf64", 8) - } - _ => bail!("invalid aggregate return type"), - }; - ret_args.push(format!("{}[retptr / {} + {}]", mem, size, i)); - } - } - - // No return pointer? That's much easier! - // - // If there's one return value we just have one input of `ret` - // which is created in the JS shim below. If there's multiple - // return values (a multi-value module) then we'll pull results - // from the returned array. - None => { - let amt = self.cx.module.types.get(binding.wasm_ty).results().len(); - if amt == 1 { - ret_args.push("ret".to_string()); - } else { - for i in 0..amt { - ret_args.push(format!("ret[{}]", i)); - } - } - } - } - js = JsBuilder::new(ret_args); - let mut ret = outgoing::Outgoing::new(self.cx, &mut js); - let ret_js = ret.process(&binding.outgoing[0])?; - self.ret_js.push_str(&ret_js); - } else { - // If there's an out ptr for an incoming argument then it means that - // the first argument to our function is the return pointer, and we - // need to fill that in. After we process the value we then write - // each result of the processed value into the corresponding typed - // array. - js = JsBuilder::new(vec!["ret".to_string()]); - let results = match &webidl.result { - Some(ptr) => std::slice::from_ref(ptr), - None => &[], - }; - let mut ret = incoming::Incoming::new(self.cx, results, &mut js); - let ret_js = ret.process(&binding.incoming[0])?; - match &binding.return_via_outptr { - Some(list) => { - assert_eq!(list.len(), ret_js.len()); - for (i, js) in ret_js.iter().enumerate() { - self.ret_finally - .push_str(&format!("const ret{} = {};\n", i, js)); - } - for (i, ty) in list.iter().enumerate() { - let (mem, size) = match ty { - walrus::ValType::I32 => { - self.cx.expose_int32_memory(); - ("getInt32Memory()", 4) - } - walrus::ValType::F32 => { - self.cx.expose_f32_memory(); - ("getFloat32Memory()", 4) - } - walrus::ValType::F64 => { - self.cx.expose_f64_memory(); - ("getFloat64Memory()", 8) - } - _ => bail!("invalid aggregate return type"), - }; - self.ret_finally - .push_str(&format!("{}[arg0 / {} + {}] = ret{};\n", mem, size, i, i)); - } - } - None => { - assert_eq!(ret_js.len(), 1); - self.ret_js.push_str(&ret_js[0]); - } - } - } - self.ret_finally.push_str(&js.finally); - self.ret_prelude.push_str(&js.prelude); - self.ts_ret = Some(js.typescript.remove(0)); - let invoc = invoke(self.cx, &mut self.args_prelude, &self.invoc_args)?; - Ok(self.finalize(&invoc)) - } - - // This method... is a mess. Refactorings and improvements are more than - // welcome :) - fn finalize(&self, invoc: &str) -> String { - let mut js = String::new(); - js.push_str("("); - js.push_str(&self.function_args.join(", ")); - js.push_str(") {\n"); - if self.args_prelude.len() > 0 { - js.push_str(self.args_prelude.trim()); - js.push_str("\n"); - } - - let mut call = String::new(); - if self.ts_ret.is_some() { - call.push_str("const ret = "); - } - call.push_str(invoc); - call.push_str(";\n"); - - if self.ret_prelude.len() > 0 { - call.push_str(self.ret_prelude.trim()); - call.push_str("\n"); - } - - if self.ret_js.len() > 0 { - assert!(self.ts_ret.is_some()); - // Having a this field isn't supported yet, but shouldn't come up - assert!(self.ret_finally.len() == 0); - call.push_str("return "); - call.push_str(&self.ret_js); - call.push_str(";\n"); - } else if self.ret_finally.len() > 0 { - call.push_str(self.ret_finally.trim()); - call.push_str("\n"); + let mut call = js.prelude; + if js.finally.len() != 0 { + call = format!("try {{\n{}}} finally {{\n{}}}\n", call, js.finally); } if self.catch { + js.cx.expose_handle_error()?; call = format!("try {{\n{}}} catch (e) {{\n handleError(e)\n}}\n", call); } @@ -401,18 +211,14 @@ impl<'a, 'b> Builder<'a, 'b> { // logs what happened, but keeps the exception being thrown to propagate // elsewhere. if self.log_error { + js.cx.expose_log_error(); call = format!("try {{\n{}}} catch (e) {{\n logError(e)\n}}\n", call); } - let finally = self.finally.trim(); - if finally.len() != 0 { - call = format!("try {{\n{}}} finally {{\n{}\n}}\n", call, finally); - } + ret.push_str(&call); + ret.push_str("}"); - js.push_str(&call); - js.push_str("}"); - - return js; + return Ok(ret); } /// Returns the typescript signature of the binding that this has described. @@ -480,27 +286,25 @@ impl<'a, 'b> Builder<'a, 'b> { } } -impl JsBuilder { - pub fn new(args: Vec) -> JsBuilder { +impl<'a, 'b> JsBuilder<'a, 'b> { + pub fn new(cx: &'a mut Context<'b>) -> JsBuilder<'a, 'b> { JsBuilder { - args, + cx, + args: Vec::new(), tmp: 0, finally: String::new(), prelude: String::new(), typescript: Vec::new(), + stack: Vec::new(), } } - pub fn typescript_len(&self) -> usize { - self.typescript.len() - } - pub fn arg(&self, idx: u32) -> &str { &self.args[idx as usize] } pub fn typescript_required(&mut self, ty: &str) { - let name = self.args[self.typescript.len()].clone(); + let name = self.arg_name(); self.typescript.push(TypescriptArg { ty: ty.to_string(), optional: false, @@ -509,7 +313,7 @@ impl JsBuilder { } pub fn typescript_optional(&mut self, ty: &str) { - let name = self.args[self.typescript.len()].clone(); + let name = self.arg_name(); self.typescript.push(TypescriptArg { ty: ty.to_string(), optional: true, @@ -517,17 +321,28 @@ impl JsBuilder { }); } + fn arg_name(&self) -> String { + self.args + .get(self.typescript.len()) + .cloned() + .unwrap_or_else(|| format!("arg{}", self.typescript.len())) + } + pub fn prelude(&mut self, prelude: &str) { - for line in prelude.trim().lines() { - self.prelude.push_str(line); - self.prelude.push_str("\n"); + for line in prelude.trim().lines().map(|l| l.trim()) { + if !line.is_empty() { + self.prelude.push_str(line); + self.prelude.push_str("\n"); + } } } pub fn finally(&mut self, finally: &str) { - for line in finally.trim().lines() { - self.finally.push_str(line); - self.finally.push_str("\n"); + for line in finally.trim().lines().map(|l| l.trim()) { + if !line.is_empty() { + self.finally.push_str(line); + self.finally.push_str("\n"); + } } } @@ -536,4 +351,849 @@ impl JsBuilder { self.tmp += 1; return ret; } + + fn pop(&mut self) -> String { + self.stack.pop().unwrap() + } + + fn push(&mut self, arg: String) { + self.stack.push(arg); + } + + fn assert_class(&mut self, arg: &str, class: &str) { + self.cx.expose_assert_class(); + self.prelude(&format!("_assertClass({}, {});", arg, class)); + } + + fn assert_number(&mut self, arg: &str) { + if !self.cx.config.debug { + return; + } + self.cx.expose_assert_num(); + self.prelude(&format!("_assertNum({});", arg)); + } + + fn assert_bool(&mut self, arg: &str) { + if !self.cx.config.debug { + return; + } + self.cx.expose_assert_bool(); + self.prelude(&format!("_assertBoolean({});", arg)); + } + + fn assert_optional_number(&mut self, arg: &str) { + if !self.cx.config.debug { + return; + } + self.cx.expose_is_like_none(); + self.prelude(&format!("if (!isLikeNone({})) {{", arg)); + self.assert_number(arg); + self.prelude("}"); + } + + fn assert_optional_bool(&mut self, arg: &str) { + if !self.cx.config.debug { + return; + } + self.cx.expose_is_like_none(); + self.prelude(&format!("if (!isLikeNone({})) {{", arg)); + self.assert_bool(arg); + self.prelude("}"); + } + + fn assert_not_moved(&mut self, arg: &str) { + if !self.cx.config.debug { + return; + } + self.prelude(&format!( + "\ + if ({0}.ptr === 0) {{ + throw new Error('Attempt to use a moved value'); + }} + ", + arg, + )); + } +} + +fn instruction(js: &mut JsBuilder, instr: &Instruction, log_error: &mut bool) -> Result<(), Error> { + // Here first properly aligned nonzero address is chosen to be the + // out-pointer. We use the address for a BigInt64Array sometimes which + // means it needs to be 8-byte aligned. Otherwise valid code is + // unlikely to ever be working around address 8, so this should be a + // safe address to use for returning data through. + let retptr_val = 8; + + match instr { + Instruction::Standard(wit_walrus::Instruction::ArgGet(n)) => { + let arg = js.arg(*n).to_string(); + js.push(arg); + } + + Instruction::Standard(wit_walrus::Instruction::CallAdapter(_)) => { + panic!("standard call adapter functions should be mapped to our adapters"); + } + + Instruction::Standard(wit_walrus::Instruction::CallCore(_)) + | Instruction::CallExport(_) + | Instruction::CallAdapter(_) + | Instruction::CallTableElement(_) + | Instruction::Standard(wit_walrus::Instruction::DeferCallCore(_)) => { + let invoc = Invocation::from(instr, js.cx.module)?; + let (params, results) = invoc.params_results(js.cx); + + // Pop off the number of parameters for the function we're calling + let mut args = Vec::new(); + for _ in 0..params { + args.push(js.pop()); + } + args.reverse(); + + // Call the function through an export of the underlying module. + let call = invoc.invoke(js.cx, &args, &mut js.prelude, log_error)?; + + // And then figure out how to actually handle where the call + // happens. This is pretty conditional depending on the number of + // return values of the function. + match (invoc.defer(), results) { + (true, 0) => { + js.finally(&format!("{};", call)); + js.stack.extend(args); + } + (true, _) => panic!("deferred calls must have no results"), + (false, 0) => js.prelude(&format!("{};", call)), + (false, n) => { + js.prelude(&format!("var ret = {};", call)); + if n == 1 { + js.push("ret".to_string()); + } else { + for i in 0..n { + js.push(format!("ret[{}]", i)); + } + } + } + } + } + + Instruction::Standard(wit_walrus::Instruction::IntToWasm { trap: false, .. }) => { + js.typescript_required("number"); + let val = js.pop(); + js.assert_number(&val); + js.push(val); + } + + // When converting to a JS number we need to specially handle the `u32` + // case because if the high bit is set then it comes out as a negative + // number, but we want to switch that to an unsigned representation. + Instruction::Standard(wit_walrus::Instruction::WasmToInt { + trap: false, + output, + .. + }) => { + js.typescript_required("number"); + let val = js.pop(); + match output { + wit_walrus::ValType::U32 => js.push(format!("{} >>> 0", val)), + _ => js.push(val), + } + } + + Instruction::Standard(wit_walrus::Instruction::WasmToInt { trap: true, .. }) + | Instruction::Standard(wit_walrus::Instruction::IntToWasm { trap: true, .. }) => { + bail!("trapping wasm-to-int and int-to-wasm instructions not supported") + } + + Instruction::Standard(wit_walrus::Instruction::MemoryToString(mem)) => { + js.typescript_required("string"); + let len = js.pop(); + let ptr = js.pop(); + let get = js.cx.expose_get_string_from_wasm(*mem)?; + js.push(format!("{}({}, {})", get, ptr, len)); + } + + Instruction::Standard(wit_walrus::Instruction::StringToMemory { mem, malloc }) => { + js.typescript_required("string"); + let pass = js.cx.expose_pass_string_to_wasm(*mem)?; + let val = js.pop(); + let malloc = js.cx.export_name_of(*malloc); + let i = js.tmp(); + js.prelude(&format!( + "var ptr{i} = {f}({0}, wasm.{malloc});", + val, + i = i, + f = pass, + malloc = malloc, + )); + js.prelude(&format!("var len{} = WASM_VECTOR_LEN;", i)); + js.push(format!("ptr{}", i)); + js.push(format!("len{}", i)); + } + + Instruction::Retptr => js.stack.push(retptr_val.to_string()), + + Instruction::StoreRetptr { ty, offset, mem } => { + let (mem, size) = match ty { + AdapterType::I32 => (js.cx.expose_int32_memory(*mem), 4), + AdapterType::F32 => (js.cx.expose_f32_memory(*mem), 4), + AdapterType::F64 => (js.cx.expose_f64_memory(*mem), 8), + other => bail!("invalid aggregate return type {:?}", other), + }; + // Note that we always assume the return pointer is argument 0, + // which is currently the case for LLVM. + let val = js.pop(); + let expr = format!( + "{}()[{} / {} + {}] = {};", + mem, + js.arg(0), + size, + offset, + val, + ); + js.prelude(&expr); + } + + Instruction::LoadRetptr { ty, offset, mem } => { + let (mem, size) = match ty { + AdapterType::I32 => (js.cx.expose_int32_memory(*mem), 4), + AdapterType::F32 => (js.cx.expose_f32_memory(*mem), 4), + AdapterType::F64 => (js.cx.expose_f64_memory(*mem), 8), + other => bail!("invalid aggregate return type {:?}", other), + }; + // If we're loading from the return pointer then we must have pushed + // it earlier, and we always push the same value, so load that value + // here + let expr = format!("{}()[{} / {} + {}]", mem, retptr_val, size, offset); + js.prelude(&format!("var r{} = {};", offset, expr)); + js.push(format!("r{}", offset)); + } + + Instruction::I32FromBool => { + js.typescript_required("boolean"); + let val = js.pop(); + js.assert_bool(&val); + // JS will already coerce booleans into numbers for us + js.push(val); + } + + Instruction::I32FromStringFirstChar => { + js.typescript_required("string"); + let val = js.pop(); + js.push(format!("{}.codePointAt(0)", val)); + } + + Instruction::I32FromAnyrefOwned => { + js.typescript_required("any"); + js.cx.expose_add_heap_object(); + let val = js.pop(); + js.push(format!("addHeapObject({})", val)); + } + + Instruction::I32FromAnyrefBorrow => { + js.typescript_required("any"); + js.cx.expose_borrowed_objects(); + js.cx.expose_global_stack_pointer(); + let val = js.pop(); + js.push(format!("addBorrowedObject({})", val)); + js.finally("heap[stack_pointer++] = undefined;"); + } + + Instruction::I32FromAnyrefRustOwned { class } => { + js.typescript_required(class); + let val = js.pop(); + js.assert_class(&val, &class); + js.assert_not_moved(&val); + let i = js.tmp(); + js.prelude(&format!("var ptr{} = {}.ptr;", i, val)); + js.prelude(&format!("{}.ptr = 0;", val)); + js.push(format!("ptr{}", i)); + } + + Instruction::I32FromAnyrefRustBorrow { class } => { + js.typescript_required(class); + let val = js.pop(); + js.assert_class(&val, &class); + js.assert_not_moved(&val); + js.push(format!("{}.ptr", val)); + } + + Instruction::I32FromOptionRust { class } => { + js.typescript_optional(class); + let val = js.pop(); + js.cx.expose_is_like_none(); + let i = js.tmp(); + js.prelude(&format!("let ptr{} = 0;", i)); + js.prelude(&format!("if (!isLikeNone({0})) {{", val)); + js.assert_class(&val, class); + js.assert_not_moved(&val); + js.prelude(&format!("ptr{} = {}.ptr;", i, val)); + js.prelude(&format!("{}.ptr = 0;", val)); + js.prelude("}"); + js.push(format!("ptr{}", i)); + } + + Instruction::I32Split64 { signed } => { + js.typescript_required("BigInt"); + let val = js.pop(); + let f = if *signed { + js.cx.expose_int64_cvt_shim() + } else { + js.cx.expose_uint64_cvt_shim() + }; + let i = js.tmp(); + js.prelude(&format!( + " + {f}[0] = {val}; + const low{i} = u32CvtShim[0]; + const high{i} = u32CvtShim[1]; + ", + i = i, + f = f, + val = val, + )); + js.push(format!("low{}", i)); + js.push(format!("high{}", i)); + } + + Instruction::I32SplitOption64 { signed } => { + js.typescript_optional("BigInt"); + let val = js.pop(); + js.cx.expose_is_like_none(); + let f = if *signed { + js.cx.expose_int64_cvt_shim() + } else { + js.cx.expose_uint64_cvt_shim() + }; + let i = js.tmp(); + js.prelude(&format!( + "\ + {f}[0] = isLikeNone({val}) ? BigInt(0) : {val}; + const low{i} = u32CvtShim[0]; + const high{i} = u32CvtShim[1]; + ", + i = i, + f = f, + val = val, + )); + js.push(format!("!isLikeNone({0})", val)); + js.push(format!("low{}", i)); + js.push(format!("high{}", i)); + } + + Instruction::I32FromOptionAnyref => { + js.typescript_optional("any"); + let val = js.pop(); + js.cx.expose_is_like_none(); + match (js.cx.aux.anyref_table, js.cx.aux.anyref_alloc) { + (Some(table), Some(alloc)) => { + let alloc = js.cx.expose_add_to_anyref_table(table, alloc)?; + js.push(format!("isLikeNone({0}) ? 0 : {1}({0})", val, alloc)); + } + _ => { + js.cx.expose_add_heap_object(); + js.push(format!("isLikeNone({0}) ? 0 : addHeapObject({0})", val)); + } + } + } + + Instruction::I32FromOptionU32Sentinel => { + js.typescript_optional("number"); + let val = js.pop(); + js.cx.expose_is_like_none(); + js.assert_optional_number(&val); + js.push(format!("isLikeNone({0}) ? 0xFFFFFF : {0}", val)); + } + + Instruction::I32FromOptionBool => { + js.typescript_optional("boolean"); + let val = js.pop(); + js.cx.expose_is_like_none(); + js.assert_optional_bool(&val); + js.push(format!("isLikeNone({0}) ? 0xFFFFFF : {0} ? 1 : 0", val)); + } + + Instruction::I32FromOptionChar => { + js.typescript_optional("string"); + let val = js.pop(); + js.cx.expose_is_like_none(); + js.push(format!( + "isLikeNone({0}) ? 0xFFFFFF : {0}.codePointAt(0)", + val + )); + } + + Instruction::I32FromOptionEnum { hole } => { + js.typescript_optional("number"); + let val = js.pop(); + js.cx.expose_is_like_none(); + js.assert_optional_number(&val); + js.push(format!("isLikeNone({0}) ? {1} : {0}", val, hole)); + } + + Instruction::FromOptionNative { .. } => { + js.typescript_optional("number"); + let val = js.pop(); + js.cx.expose_is_like_none(); + js.assert_optional_number(&val); + js.push(format!("!isLikeNone({0})", val)); + js.push(format!("isLikeNone({0}) ? 0 : {0}", val)); + } + + Instruction::VectorToMemory { kind, malloc, mem } => { + js.typescript_required(kind.js_ty()); + let val = js.pop(); + let func = js.cx.pass_to_wasm_function(*kind, *mem)?; + let malloc = js.cx.export_name_of(*malloc); + let i = js.tmp(); + js.prelude(&format!( + "var ptr{i} = {f}({0}, wasm.{malloc});", + val, + i = i, + f = func, + malloc = malloc, + )); + js.prelude(&format!("var len{} = WASM_VECTOR_LEN;", i)); + js.push(format!("ptr{}", i)); + js.push(format!("len{}", i)); + } + + Instruction::OptionVector { kind, mem, malloc } => { + js.typescript_optional(kind.js_ty()); + let func = js.cx.pass_to_wasm_function(*kind, *mem)?; + js.cx.expose_is_like_none(); + let i = js.tmp(); + let malloc = js.cx.export_name_of(*malloc); + let val = js.pop(); + js.prelude(&format!( + "var ptr{i} = isLikeNone({0}) ? 0 : {f}({0}, wasm.{malloc});", + val, + i = i, + f = func, + malloc = malloc, + )); + js.prelude(&format!("var len{} = WASM_VECTOR_LEN;", i)); + js.push(format!("ptr{}", i)); + js.push(format!("len{}", i)); + } + + Instruction::MutableSliceToMemory { + kind, + malloc, + mem, + free, + } => { + js.typescript_required(kind.js_ty()); + // First up, pass the JS value into wasm, getting out a pointer and + // a length. These two pointer/length values get pushed onto the + // value stack. + let val = js.pop(); + let func = js.cx.pass_to_wasm_function(*kind, *mem)?; + let malloc = js.cx.export_name_of(*malloc); + let i = js.tmp(); + js.prelude(&format!( + "var ptr{i} = {f}({val}, wasm.{malloc});", + val = val, + i = i, + f = func, + malloc = malloc, + )); + js.prelude(&format!("var len{} = WASM_VECTOR_LEN;", i)); + js.push(format!("ptr{}", i)); + js.push(format!("len{}", i)); + + // Next we set up a `finally` clause which will both update the + // original mutable slice with any modifications, and then free the + // Rust-backed memory. + let free = js.cx.export_name_of(*free); + let get = js.cx.memview_function(*kind, *mem); + js.finally(&format!( + " + {val}.set({get}().subarray(ptr{i} / {size}, ptr{i} / {size} + len{i})); + wasm.{free}(ptr{i}, len{i} * {size}); + ", + val = val, + get = get, + free = free, + size = kind.size(), + i = i, + )); + } + + Instruction::BoolFromI32 => { + js.typescript_required("bool"); + let val = js.pop(); + js.push(format!("{} !== 0", val)); + } + + Instruction::AnyrefLoadOwned => { + js.typescript_required("any"); + js.cx.expose_take_object(); + let val = js.pop(); + js.push(format!("takeObject({})", val)); + } + + Instruction::StringFromChar => { + js.typescript_required("string"); + let val = js.pop(); + js.push(format!("String.fromCodePoint({})", val)); + } + + Instruction::I64FromLoHi { signed } => { + js.typescript_required("BigInt"); + let f = if *signed { + js.cx.expose_int64_cvt_shim() + } else { + js.cx.expose_uint64_cvt_shim() + }; + let i = js.tmp(); + let high = js.pop(); + let low = js.pop(); + js.prelude(&format!( + "\ + u32CvtShim[0] = {low}; + u32CvtShim[1] = {high}; + const n{i} = {f}[0]; + ", + low = low, + high = high, + f = f, + i = i, + )); + js.push(format!("n{}", i)) + } + + Instruction::RustFromI32 { class } => { + js.typescript_required(class); + js.cx.require_class_wrap(class); + let val = js.pop(); + js.push(format!("{}.__wrap({})", class, val)); + } + + Instruction::OptionRustFromI32 { class } => { + js.typescript_optional(class); + js.cx.require_class_wrap(class); + let val = js.pop(); + js.push(format!( + "{0} === 0 ? undefined : {1}.__wrap({0})", + val, class, + )) + } + + Instruction::CachedStringLoad { + owned, + optional, + mem, + free, + } => { + if *optional { + js.typescript_optional("string"); + } else { + js.typescript_required("string"); + } + + let len = js.pop(); + let ptr = js.pop(); + let tmp = js.tmp(); + + let get = js.cx.expose_get_cached_string_from_wasm(*mem)?; + + js.prelude(&format!("var v{} = {}({}, {});", tmp, get, ptr, len)); + + if *owned { + let free = js.cx.export_name_of(*free); + js.prelude(&format!( + "if ({ptr} !== 0) {{ wasm.{}({ptr}, {len}); }}", + free, + ptr = ptr, + len = len, + )); + } + + js.push(format!("v{}", tmp)); + } + + Instruction::TableGet => { + js.typescript_required("any"); + let val = js.pop(); + js.cx.expose_get_object(); + js.push(format!("getObject({})", val)); + } + + Instruction::StackClosure { + adapter, + nargs, + mutable, + } => { + js.typescript_optional("any"); + let i = js.tmp(); + let b = js.pop(); + let a = js.pop(); + js.prelude(&format!("var state{} = {{a: {}, b: {}}};", i, a, b)); + let args = (0..*nargs) + .map(|i| format!("arg{}", i)) + .collect::>() + .join(", "); + let wrapper = js.cx.adapter_name(*adapter); + if *mutable { + // Mutable closures need protection against being called + // recursively, so ensure that we clear out one of the + // internal pointers while it's being invoked. + js.prelude(&format!( + "var cb{i} = ({args}) => {{ + const a = state{i}.a; + state{i}.a = 0; + try {{ + return {name}(a, state{i}.b, {args}); + }} finally {{ + state{i}.a = a; + }} + }};", + i = i, + args = args, + name = wrapper, + )); + } else { + js.prelude(&format!( + "var cb{i} = ({args}) => {wrapper}(state{i}.a, state{i}.b, {args});", + i = i, + args = args, + wrapper = wrapper, + )); + } + + // Make sure to null out our internal pointers when we return + // back to Rust to ensure that any lingering references to the + // closure will fail immediately due to null pointers passed in + // to Rust. + js.finally(&format!("state{}.a = state{0}.b = 0;", i)); + js.push(format!("cb{}", i)); + } + + Instruction::VectorLoad { kind, mem, free } => { + js.typescript_required(kind.js_ty()); + let len = js.pop(); + let ptr = js.pop(); + let f = js.cx.expose_get_vector_from_wasm(*kind, *mem)?; + let i = js.tmp(); + let free = js.cx.export_name_of(*free); + js.prelude(&format!("var v{} = {}({}, {}).slice();", i, f, ptr, len)); + js.prelude(&format!( + "wasm.{}({}, {} * {});", + free, + ptr, + len, + kind.size() + )); + js.push(format!("v{}", i)) + } + + Instruction::OptionVectorLoad { kind, mem, free } => { + js.typescript_optional(kind.js_ty()); + let len = js.pop(); + let ptr = js.pop(); + let f = js.cx.expose_get_vector_from_wasm(*kind, *mem)?; + let i = js.tmp(); + let free = js.cx.export_name_of(*free); + js.prelude(&format!("let v{};", i)); + js.prelude(&format!("if ({} !== 0) {{", ptr)); + js.prelude(&format!("v{} = {}({}, {}).slice();", i, f, ptr, len)); + js.prelude(&format!( + "wasm.{}({}, {} * {});", + free, + ptr, + len, + kind.size() + )); + js.prelude("}"); + js.push(format!("v{}", i)); + } + + Instruction::View { kind, mem } => { + js.typescript_required(kind.js_ty()); + let len = js.pop(); + let ptr = js.pop(); + let f = js.cx.expose_get_vector_from_wasm(*kind, *mem)?; + js.push(format!("{f}({ptr}, {len})", ptr = ptr, len = len, f = f)); + } + + Instruction::OptionView { kind, mem } => { + js.typescript_optional(kind.js_ty()); + let len = js.pop(); + let ptr = js.pop(); + let f = js.cx.expose_get_vector_from_wasm(*kind, *mem)?; + js.push(format!( + "{ptr} === 0 ? undefined : {f}({ptr}, {len})", + ptr = ptr, + len = len, + f = f + )); + } + + Instruction::OptionU32Sentinel => { + js.typescript_optional("number"); + let val = js.pop(); + js.push(format!("{0} === 0xFFFFFF ? undefined : {0}", val)); + } + + Instruction::ToOptionNative { ty: _, signed } => { + js.typescript_optional("number"); + let val = js.pop(); + let present = js.pop(); + js.push(format!( + "{} === 0 ? undefined : {}{}", + present, + val, + if *signed { "" } else { " >>> 0" }, + )); + } + + Instruction::OptionBoolFromI32 => { + js.typescript_optional("boolean"); + let val = js.pop(); + js.push(format!("{0} === 0xFFFFFF ? undefined : {0} !== 0", val)); + } + + Instruction::OptionCharFromI32 => { + js.typescript_optional("string"); + let val = js.pop(); + js.push(format!( + "{0} === 0xFFFFFF ? undefined : String.fromCodePoint({0})", + val, + )); + } + + Instruction::OptionEnumFromI32 { hole } => { + js.typescript_optional("number"); + let val = js.pop(); + js.push(format!("{0} === {1} ? undefined : {0}", val, hole)); + } + + Instruction::Option64FromI32 { signed } => { + js.typescript_optional("BigInt"); + let f = if *signed { + js.cx.expose_int64_cvt_shim() + } else { + js.cx.expose_uint64_cvt_shim() + }; + let i = js.tmp(); + let high = js.pop(); + let low = js.pop(); + let present = js.pop(); + js.prelude(&format!( + " + u32CvtShim[0] = {low}; + u32CvtShim[1] = {high}; + const n{i} = {present} === 0 ? undefined : {f}[0]; + ", + present = present, + low = low, + high = high, + f = f, + i = i, + )); + js.push(format!("n{}", i)); + } + } + Ok(()) +} + +enum Invocation { + Core { id: walrus::FunctionId, defer: bool }, + Adapter(AdapterId), +} + +impl Invocation { + fn from(instr: &Instruction, module: &Module) -> Result { + use Instruction::*; + Ok(match instr { + Standard(wit_walrus::Instruction::CallCore(f)) => Invocation::Core { + id: *f, + defer: false, + }, + + Standard(wit_walrus::Instruction::DeferCallCore(f)) => Invocation::Core { + id: *f, + defer: true, + }, + + CallExport(e) => match module.exports.get(*e).item { + walrus::ExportItem::Function(id) => Invocation::Core { id, defer: false }, + _ => panic!("can only call exported function"), + }, + + // The function table never changes right now, so we can statically + // look up the desired function. + CallTableElement(idx) => { + let table = module + .tables + .main_function_table()? + .ok_or_else(|| anyhow!("no function table found"))?; + let functions = match &module.tables.get(table).kind { + walrus::TableKind::Function(f) => f, + _ => bail!("should have found a function table"), + }; + let id = functions + .elements + .get(*idx as usize) + .and_then(|id| *id) + .ok_or_else(|| anyhow!("function table wasn't filled in a {}", idx))?; + Invocation::Core { id, defer: false } + } + + CallAdapter(id) => Invocation::Adapter(*id), + + // this function is only called for the above instructions + _ => unreachable!(), + }) + } + + fn params_results(&self, cx: &Context) -> (usize, usize) { + match self { + Invocation::Core { id, .. } => { + let ty = cx.module.funcs.get(*id).ty(); + let ty = cx.module.types.get(ty); + (ty.params().len(), ty.results().len()) + } + Invocation::Adapter(id) => { + let adapter = &cx.wit.adapters[id]; + (adapter.params.len(), adapter.results.len()) + } + } + } + + fn invoke( + &self, + cx: &mut Context, + args: &[String], + prelude: &mut String, + log_error: &mut bool, + ) -> Result { + match self { + Invocation::Core { id, .. } => { + let name = cx.export_name_of(*id); + Ok(format!("wasm.{}({})", name, args.join(", "))) + } + Invocation::Adapter(id) => { + let adapter = &cx.wit.adapters[id]; + let kind = match adapter.kind { + AdapterKind::Import { kind, .. } => kind, + AdapterKind::Local { .. } => { + bail!("adapter-to-adapter calls not supported yet"); + } + }; + let import = &cx.aux.import_map[id]; + let variadic = cx.aux.imports_with_variadic.contains(id); + if cx.import_never_log_error(import) { + *log_error = false; + } + cx.invoke_import(import, kind, args, variadic, prelude) + } + } + } + + fn defer(&self) -> bool { + match self { + Invocation::Core { defer, .. } => *defer, + _ => false, + } + } } diff --git a/crates/cli-support/src/js/incoming.rs b/crates/cli-support/src/js/incoming.rs deleted file mode 100644 index e5592318..00000000 --- a/crates/cli-support/src/js/incoming.rs +++ /dev/null @@ -1,556 +0,0 @@ -//! Implementation of taking a `NonstandardIncoming` binding and generating JS -//! which represents it and executes it for what we need. -//! -//! This module is used to generate JS for all our incoming bindings which -//! includes arguments going into exports or return values from imports. - -use crate::descriptor::VectorKind; -use crate::js::binding::JsBuilder; -use crate::js::Context; -use crate::webidl::NonstandardIncoming; -use anyhow::{bail, Error}; -use wasm_webidl_bindings::ast; - -pub struct Incoming<'a, 'b> { - cx: &'a mut Context<'b>, - types: &'a [ast::WebidlTypeRef], - js: &'a mut JsBuilder, -} - -impl<'a, 'b> Incoming<'a, 'b> { - pub fn new( - cx: &'a mut Context<'b>, - types: &'a [ast::WebidlTypeRef], - js: &'a mut JsBuilder, - ) -> Incoming<'a, 'b> { - Incoming { cx, types, js } - } - - pub fn process(&mut self, incoming: &NonstandardIncoming) -> Result, Error> { - let before = self.js.typescript_len(); - let ret = self.nonstandard(incoming)?; - assert_eq!(before + 1, self.js.typescript_len()); - Ok(ret) - } - - fn nonstandard(&mut self, incoming: &NonstandardIncoming) -> Result, Error> { - let single = match incoming { - NonstandardIncoming::Standard(val) => return self.standard(val), - - // Evaluate the `val` binding, store it into a one-element `BigInt` - // array (appropriately typed) and then use a 32-bit view into the - // `BigInt` array to extract the high/low bits and pass them through - // in the ABI. - NonstandardIncoming::Int64 { val, signed } => { - self.js.typescript_required("BigInt"); - let (expr, ty) = self.standard_typed(val)?; - assert_eq!(ty, ast::WebidlScalarType::Any.into()); - let f = if *signed { - self.cx.expose_int64_cvt_shim() - } else { - self.cx.expose_uint64_cvt_shim() - }; - self.cx.expose_uint32_memory(); - let i = self.js.tmp(); - self.js.prelude(&format!( - " - {f}[0] = {expr}; - const low{i} = u32CvtShim[0]; - const high{i} = u32CvtShim[1]; - ", - i = i, - f = f, - expr = expr, - )); - return Ok(vec![format!("low{}", i), format!("high{}", i)]); - } - - // Same as `IncomingBindingExpressionAllocCopy`, except we use a - // different `VectorKind` - NonstandardIncoming::AllocCopyInt64 { - alloc_func_name: _, - expr, - signed, - } => { - let (expr, ty) = self.standard_typed(expr)?; - assert_eq!(ty, ast::WebidlScalarType::Any.into()); - let kind = if *signed { - VectorKind::I64 - } else { - VectorKind::U64 - }; - let func = self.cx.pass_to_wasm_function(kind)?; - self.js.typescript_required(kind.js_ty()); - return Ok(vec![ - format!("{}({})", func, expr), - "WASM_VECTOR_LEN".to_string(), - ]); - } - - // Same as `IncomingBindingExpressionAllocCopy`, except we use a - // different `VectorKind` - NonstandardIncoming::AllocCopyAnyrefArray { - alloc_func_name: _, - expr, - } => { - let (expr, ty) = self.standard_typed(expr)?; - assert_eq!(ty, ast::WebidlScalarType::Any.into()); - let func = self.cx.pass_to_wasm_function(VectorKind::Anyref)?; - self.js.typescript_required(VectorKind::Anyref.js_ty()); - return Ok(vec![ - format!("{}({})", func, expr), - "WASM_VECTOR_LEN".to_string(), - ]); - } - - // There's no `char` in JS, so we take a string instead and just - // forward along the first code point to Rust. - NonstandardIncoming::Char { val } => { - let (expr, ty) = self.standard_typed(val)?; - assert_eq!(ty, ast::WebidlScalarType::DomString.into()); - self.js.typescript_required("string"); - format!("{}.codePointAt(0)", expr) - } - - // When moving a type back into Rust we need to clear out the - // internal pointer in JS to prevent it from being reused again in - // the future. - NonstandardIncoming::RustType { class, val } => { - let (expr, ty) = self.standard_typed(val)?; - assert_eq!(ty, ast::WebidlScalarType::Any.into()); - self.assert_class(&expr, &class); - self.assert_not_moved(&expr); - let i = self.js.tmp(); - self.js.prelude(&format!("const ptr{} = {}.ptr;", i, expr)); - self.js.prelude(&format!("{}.ptr = 0;", expr)); - self.js.typescript_required(class); - format!("ptr{}", i) - } - - // Here we can simply pass along the pointer with no extra fluff - // needed. - NonstandardIncoming::RustTypeRef { class, val } => { - let (expr, ty) = self.standard_typed(val)?; - assert_eq!(ty, ast::WebidlScalarType::Any.into()); - self.assert_class(&expr, &class); - self.assert_not_moved(&expr); - self.js.typescript_required(class); - format!("{}.ptr", expr) - } - - // the "stack-ful" nature means that we're always popping from the - // stack, and make sure that we actually clear our reference to - // allow stale values to get GC'd - NonstandardIncoming::BorrowedAnyref { val } => { - let (expr, ty) = self.standard_typed(val)?; - assert_eq!(ty, ast::WebidlScalarType::Any.into()); - self.cx.expose_borrowed_objects(); - self.cx.expose_global_stack_pointer(); - self.js.finally("heap[stack_pointer++] = undefined;"); - self.js.typescript_required("any"); - format!("addBorrowedObject({})", expr) - } - - // Similar to `AllocCopy`, except that we deallocate in a finally - // block. - NonstandardIncoming::MutableSlice { kind, val } => { - let (expr, ty) = self.standard_typed(val)?; - assert_eq!(ty, ast::WebidlScalarType::Any.into()); - let func = self.cx.pass_to_wasm_function(*kind)?; - let i = self.js.tmp(); - self.js - .prelude(&format!("const ptr{} = {}({});", i, func, expr)); - self.js - .prelude(&format!("const len{} = WASM_VECTOR_LEN;", i)); - self.finally_free_slice(&expr, i, *kind, true)?; - self.js.typescript_required(kind.js_ty()); - return Ok(vec![format!("ptr{}", i), format!("len{}", i)]); - } - - // Pass `None` as a sentinel value that `val` will never take on. - // This is only manufactured for specific underlying types. - NonstandardIncoming::OptionU32Sentinel { val } => { - let (expr, ty) = self.standard_typed(val)?; - assert_eq!(ty, ast::WebidlScalarType::Any.into()); - self.cx.expose_is_like_none(); - self.js.typescript_optional("number"); - self.assert_optional_number(&expr); - format!("isLikeNone({0}) ? 0xFFFFFF : {0}", expr) - } - - // Pass `true` as 1, `false` as 0, and `None` as a sentinel value. - NonstandardIncoming::OptionBool { val } => { - let (expr, ty) = self.standard_typed(val)?; - assert_eq!(ty, ast::WebidlScalarType::Any.into()); - self.cx.expose_is_like_none(); - self.js.typescript_optional("boolean"); - self.assert_optional_bool(&expr); - format!("isLikeNone({0}) ? 0xFFFFFF : {0} ? 1 : 0", expr) - } - - // Pass `None` as a sentinel value a character can never have - NonstandardIncoming::OptionChar { val } => { - let (expr, ty) = self.standard_typed(val)?; - assert_eq!(ty, ast::WebidlScalarType::Any.into()); - self.cx.expose_is_like_none(); - self.js.typescript_optional("string"); - format!("isLikeNone({0}) ? 0xFFFFFF : {0}.codePointAt(0)", expr) - } - - // Pass `None` as the hole in the enum which no valid value can ever - // take - NonstandardIncoming::OptionIntegerEnum { val, hole } => { - let (expr, ty) = self.standard_typed(val)?; - assert_eq!(ty, ast::WebidlScalarType::Any.into()); - self.cx.expose_is_like_none(); - self.js.typescript_optional("number"); - self.assert_optional_number(&expr); - format!("isLikeNone({0}) ? {1} : {0}", expr, hole) - } - - // `None` here is zero, but if `Some` then we need to clear out the - // internal pointer because the value is being moved. - NonstandardIncoming::OptionRustType { class, val } => { - let (expr, ty) = self.standard_typed(val)?; - assert_eq!(ty, ast::WebidlScalarType::Any.into()); - self.cx.expose_is_like_none(); - let i = self.js.tmp(); - self.js.prelude(&format!("let ptr{} = 0;", i)); - self.js.prelude(&format!("if (!isLikeNone({0})) {{", expr)); - self.assert_class(&expr, class); - self.assert_not_moved(&expr); - self.js.prelude(&format!("ptr{} = {}.ptr;", i, expr)); - self.js.prelude(&format!("{}.ptr = 0;", expr)); - self.js.prelude("}"); - self.js.typescript_optional(class); - format!("ptr{}", i) - } - - // The ABI produces four values here, all zero for `None` and 1 in - // the first for the last two being the low/high bits - NonstandardIncoming::OptionInt64 { val, signed } => { - let (expr, ty) = self.standard_typed(val)?; - assert_eq!(ty, ast::WebidlScalarType::Any.into()); - self.cx.expose_is_like_none(); - let f = if *signed { - self.cx.expose_int64_cvt_shim() - } else { - self.cx.expose_uint64_cvt_shim() - }; - self.cx.expose_uint32_memory(); - let i = self.js.tmp(); - self.js.prelude(&format!( - "\ - {f}[0] = isLikeNone({expr}) ? BigInt(0) : {expr}; - const low{i} = isLikeNone({expr}) ? 0 : u32CvtShim[0]; - const high{i} = isLikeNone({expr}) ? 0 : u32CvtShim[1]; - ", - i = i, - f = f, - expr = expr, - )); - self.js.typescript_optional("BigInt"); - return Ok(vec![ - format!("!isLikeNone({0})", expr), - "0".to_string(), - format!("low{}", i), - format!("high{}", i), - ]); - } - - // The ABI here is always an integral index into the anyref table, - // and the anyref table just differs based on whether we ran the - // anyref pass or not. - NonstandardIncoming::OptionAnyref { val } => { - let (expr, ty) = self.standard_typed(val)?; - assert_eq!(ty, ast::WebidlScalarType::Any.into()); - self.cx.expose_is_like_none(); - self.js.typescript_optional("any"); - if self.cx.config.anyref { - self.cx.expose_add_to_anyref_table()?; - format!("isLikeNone({0}) ? 0 : addToAnyrefTable({0})", expr) - } else { - self.cx.expose_add_heap_object(); - format!("isLikeNone({0}) ? 0 : addHeapObject({0})", expr) - } - } - - // Native types of wasm take a leading discriminant to indicate - // whether the next value is valid or not. - NonstandardIncoming::OptionNative { val } => { - let (expr, ty) = self.standard_typed(val)?; - assert_eq!(ty, ast::WebidlScalarType::Any.into()); - self.cx.expose_is_like_none(); - self.js.typescript_optional("number"); - self.assert_optional_number(&expr); - return Ok(vec![ - format!("!isLikeNone({0})", expr), - format!("isLikeNone({0}) ? 0 : {0}", expr), - ]); - } - - // Similar to `AllocCopy`, except we're handling the undefined case - // and passing null for the pointer value. - NonstandardIncoming::OptionVector { kind, val } => { - let (expr, ty) = self.standard_typed(val)?; - assert_eq!(ty, ast::WebidlScalarType::Any.into()); - let func = self.cx.pass_to_wasm_function(*kind)?; - self.cx.expose_is_like_none(); - let i = self.js.tmp(); - self.js.prelude(&format!( - "const ptr{i} = isLikeNone({0}) ? 0 : {f}({0});", - expr, - i = i, - f = func, - )); - self.js - .prelude(&format!("const len{} = WASM_VECTOR_LEN;", i)); - self.js.typescript_optional(kind.js_ty()); - return Ok(vec![format!("ptr{}", i), format!("len{}", i)]); - } - - // An unfortunate smorgasboard of handling slices, transfers if - // mutable, etc. Not the prettiest binding option here, and of - // course never going to be standardized. - NonstandardIncoming::OptionSlice { kind, val, mutable } => { - let (expr, ty) = self.standard_typed(val)?; - assert_eq!(ty, ast::WebidlScalarType::Any.into()); - let func = self.cx.pass_to_wasm_function(*kind)?; - self.cx.expose_is_like_none(); - let i = self.js.tmp(); - self.js.prelude(&format!( - "const ptr{i} = isLikeNone({0}) ? 0 : {f}({0});", - expr, - i = i, - f = func, - )); - self.js - .prelude(&format!("const len{} = WASM_VECTOR_LEN;", i)); - self.js.finally(&format!("if (ptr{} !== 0) {{", i)); - self.finally_free_slice(&expr, i, *kind, *mutable)?; - self.js.finally("}"); - self.js.typescript_optional(kind.js_ty()); - return Ok(vec![format!("ptr{}", i), format!("len{}", i)]); - } - }; - Ok(vec![single]) - } - - /// Evaluates the `standard` binding expression, returning the JS expression - /// needed to evaluate the binding. - fn standard( - &mut self, - standard: &ast::IncomingBindingExpression, - ) -> Result, Error> { - let single = match standard { - ast::IncomingBindingExpression::As(as_) => { - let (expr, ty) = self.standard_typed(&as_.expr)?; - match ty { - ast::WebidlTypeRef::Scalar(ast::WebidlScalarType::Any) => { - self.js.typescript_required("any"); - - // If the type here is anyref but we didn't run the - // anyref pass that means we have to instead actually - // pass in an index - // - // TODO: we should ideally move this `addHeapObject` - // into a nonstanard binding whenever the anyref pass - // doesn't already run rather than implicitly picking - // it up here - if self.cx.config.anyref { - expr - } else { - self.cx.expose_add_heap_object(); - format!("addHeapObject({})", expr) - } - } - ast::WebidlTypeRef::Scalar(ast::WebidlScalarType::Boolean) => { - self.js.typescript_required("boolean"); - self.assert_bool(&expr); - // JS will already coerce booleans into numbers for us - expr - } - _ => { - self.js.typescript_required("number"); - self.assert_number(&expr); - expr - } - } - } - ast::IncomingBindingExpression::Get(_) => { - bail!("unsupported bare `get` in webidl bindings"); - } - ast::IncomingBindingExpression::AllocUtf8Str(expr) => { - let (expr, ty) = self.standard_typed(&expr.expr)?; - assert_eq!(ty, ast::WebidlScalarType::DomString.into()); - self.js.typescript_required("string"); - self.cx.expose_pass_string_to_wasm()?; - return Ok(vec![ - format!("passStringToWasm({})", expr), - "WASM_VECTOR_LEN".to_string(), - ]); - } - ast::IncomingBindingExpression::AllocCopy(expr) => { - let (expr, ty) = self.standard_typed(&expr.expr)?; - let scalar = match ty { - ast::WebidlTypeRef::Scalar(s) => s, - ast::WebidlTypeRef::Id(_) => { - bail!("unsupported type passed to `alloc-copy` in webidl binding") - } - }; - let kind = match scalar { - ast::WebidlScalarType::Int8Array => VectorKind::I8, - ast::WebidlScalarType::Uint8Array => VectorKind::U8, - ast::WebidlScalarType::Uint8ClampedArray => VectorKind::ClampedU8, - ast::WebidlScalarType::Int16Array => VectorKind::I16, - ast::WebidlScalarType::Uint16Array => VectorKind::U16, - ast::WebidlScalarType::Int32Array => VectorKind::I32, - ast::WebidlScalarType::Uint32Array => VectorKind::U32, - ast::WebidlScalarType::Float32Array => VectorKind::F32, - ast::WebidlScalarType::Float64Array => VectorKind::F64, - _ => bail!("unsupported type passed to alloc-copy: {:?}", scalar), - }; - self.js.typescript_required(kind.js_ty()); - let func = self.cx.pass_to_wasm_function(kind)?; - return Ok(vec![ - format!("{}({})", func, expr), - "WASM_VECTOR_LEN".to_string(), - ]); - } - ast::IncomingBindingExpression::EnumToI32(_) => { - bail!("unsupported enum-to-i32 conversion in webidl binding"); - } - ast::IncomingBindingExpression::Field(_) => { - bail!("unsupported field accessor in webidl binding"); - } - ast::IncomingBindingExpression::BindImport(_) => { - bail!("unsupported import binding in webidl binding"); - } - }; - Ok(vec![single]) - } - - /// Evaluates the `standard` binding expression, returning both the - /// JS expression to evaluate along with the WebIDL type of the expression. - /// - /// Currently only supports `Get`. - fn standard_typed( - &mut self, - standard: &ast::IncomingBindingExpression, - ) -> Result<(String, ast::WebidlTypeRef), Error> { - match standard { - ast::IncomingBindingExpression::As(_) => { - bail!("unsupported as in webidl binding"); - } - ast::IncomingBindingExpression::Get(expr) => { - let arg = self.js.arg(expr.idx).to_string(); - let ty = self.types[expr.idx as usize]; - Ok((arg, ty)) - } - ast::IncomingBindingExpression::AllocUtf8Str(_) => { - bail!("unsupported alloc-utf8-str in webidl binding"); - } - ast::IncomingBindingExpression::AllocCopy(_) => { - bail!("unsupported alloc-copy in webidl binding"); - } - ast::IncomingBindingExpression::EnumToI32(_) => { - bail!("unsupported enum-to-i32 in webidl binding"); - } - ast::IncomingBindingExpression::Field(_) => { - bail!("unsupported field accessor in webidl binding"); - } - ast::IncomingBindingExpression::BindImport(_) => { - bail!("unsupported import binding in webidl binding"); - } - } - } - - fn assert_class(&mut self, arg: &str, class: &str) { - self.cx.expose_assert_class(); - self.js - .prelude(&format!("_assertClass({}, {});", arg, class)); - } - - fn assert_number(&mut self, arg: &str) { - if !self.cx.config.debug { - return; - } - self.cx.expose_assert_num(); - self.js.prelude(&format!("_assertNum({});", arg)); - } - - fn assert_bool(&mut self, arg: &str) { - if !self.cx.config.debug { - return; - } - self.cx.expose_assert_bool(); - self.js.prelude(&format!("_assertBoolean({});", arg)); - } - - fn assert_optional_number(&mut self, arg: &str) { - if !self.cx.config.debug { - return; - } - self.cx.expose_is_like_none(); - self.js.prelude(&format!("if (!isLikeNone({})) {{", arg)); - self.assert_number(arg); - self.js.prelude("}"); - } - - fn assert_optional_bool(&mut self, arg: &str) { - if !self.cx.config.debug { - return; - } - self.cx.expose_is_like_none(); - self.js.prelude(&format!("if (!isLikeNone({})) {{", arg)); - self.assert_bool(arg); - self.js.prelude("}"); - } - - fn assert_not_moved(&mut self, arg: &str) { - if !self.cx.config.debug { - return; - } - self.js.prelude(&format!( - "\ - if ({0}.ptr === 0) {{ - throw new Error('Attempt to use a moved value'); - }} - ", - arg, - )); - } - - fn finally_free_slice( - &mut self, - expr: &str, - i: usize, - kind: VectorKind, - mutable: bool, - ) -> Result<(), Error> { - // If the slice was mutable it's currently a feature that we - // mirror back updates to the original slice. This... is - // arguably a misfeature of wasm-bindgen... - if mutable { - let get = self.cx.memview_function(kind); - self.js.finally(&format!( - "\ - {arg}.set({get}().subarray(\ - ptr{i} / {size}, \ - ptr{i} / {size} + len{i}\ - ));\ - ", - i = i, - arg = expr, - get = get, - size = kind.size() - )); - } - self.js.finally(&format!( - "wasm.__wbindgen_free(ptr{i}, len{i} * {size});", - i = i, - size = kind.size(), - )); - self.cx.require_internal_export("__wbindgen_free") - } -} diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index d1dcfad8..8869f4e1 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -1,29 +1,31 @@ use crate::descriptor::VectorKind; use crate::intrinsic::Intrinsic; -use crate::webidl; -use crate::webidl::{AuxEnum, AuxExport, AuxExportKind, AuxImport, AuxStruct}; -use crate::webidl::{AuxValue, Binding}; -use crate::webidl::{JsImport, JsImportName, NonstandardWebidlSection, WasmBindgenAux}; +use crate::wit::{Adapter, AdapterId, AdapterJsImportKind, AuxValue}; +use crate::wit::{AdapterKind, Instruction, InstructionData}; +use crate::wit::{AuxEnum, AuxExport, AuxExportKind, AuxImport, AuxStruct}; +use crate::wit::{JsImport, JsImportName, NonstandardWitSection, WasmBindgenAux}; use crate::{Bindgen, EncodeInto, OutputMode}; -use anyhow::{bail, Context as _, Error}; +use anyhow::{anyhow, bail, Context as _, Error}; +use std::borrow::Cow; use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; +use std::fmt; use std::fs; use std::path::{Path, PathBuf}; -use walrus::{ExportId, ImportId, MemoryId, Module}; -use wasm_webidl_bindings::ast; +use walrus::{ExportId, FunctionId, ImportId, MemoryId, Module, TableId}; mod binding; -mod incoming; -mod outgoing; pub struct Context<'a> { globals: String, imports_post: String, typescript: String, - exposed_globals: Option>, - required_internal_exports: HashSet<&'static str>, + exposed_globals: Option>>, + required_internal_exports: HashSet>, + next_export_idx: usize, config: &'a Bindgen, pub module: &'a mut Module, + aux: &'a WasmBindgenAux, + wit: &'a NonstandardWitSection, /// A map representing the `import` statements we'll be generating in the JS /// glue. The key is the module we're importing from and the value is the @@ -43,11 +45,15 @@ pub struct Context<'a> { defined_identifiers: HashMap, exported_classes: Option>, - memory: MemoryId, /// A map of the name of npm dependencies we've loaded so far to the path /// they're defined in as well as their version specification. pub npm_dependencies: HashMap, + + /// A mapping of a index for memories as we see them. Used in function + /// names. + memory_indices: HashMap, + table_indices: HashMap, } #[derive(Default)] @@ -70,19 +76,12 @@ const INITIAL_HEAP_VALUES: &[&str] = &["undefined", "null", "true", "false"]; const INITIAL_HEAP_OFFSET: usize = 32; impl<'a> Context<'a> { - pub fn new(module: &'a mut Module, config: &'a Bindgen) -> Result, Error> { - // Find the single memory, if there is one, and for ease of use in our - // binding generation just inject one if there's not one already (and - // we'll clean it up later if we end up not using it). - let mut memories = module.memories.iter().map(|m| m.id()); - let memory = memories.next(); - if memories.next().is_some() { - bail!("multiple memories currently not supported"); - } - drop(memories); - let memory = memory.unwrap_or_else(|| module.memories.add_local(false, 1, None)); - - // And then we're good to go! + pub fn new( + module: &'a mut Module, + config: &'a Bindgen, + wit: &'a NonstandardWitSection, + aux: &'a WasmBindgenAux, + ) -> Result, Error> { Ok(Context { globals: String::new(), imports_post: String::new(), @@ -96,13 +95,17 @@ impl<'a> Context<'a> { exported_classes: Some(Default::default()), config, module, - memory, npm_dependencies: Default::default(), + next_export_idx: 0, + wit, + aux, + memory_indices: Default::default(), + table_indices: Default::default(), }) } - fn should_write_global(&mut self, name: &'static str) -> bool { - self.exposed_globals.as_mut().unwrap().insert(name) + fn should_write_global(&mut self, name: impl Into>) -> bool { + self.exposed_globals.as_mut().unwrap().insert(name.into()) } fn export( @@ -167,7 +170,7 @@ impl<'a> Context<'a> { } fn require_internal_export(&mut self, name: &'static str) -> Result<(), Error> { - if !self.required_internal_exports.insert(name) { + if !self.required_internal_exports.insert(name.into()) { return Ok(()); } @@ -442,31 +445,29 @@ impl<'a> Context<'a> { mut imports: Option<&mut String>, ) -> Result<(String, String), Error> { let module_name = "wbg"; - let mem = self.module.memories.get(self.memory); - let (init_memory1, init_memory2) = if let Some(id) = mem.import { - self.module.imports.get_mut(id).module = module_name.to_string(); - let mut memory = String::from("new WebAssembly.Memory({"); - memory.push_str(&format!("initial:{}", mem.initial)); - if let Some(max) = mem.maximum { - memory.push_str(&format!(",maximum:{}", max)); + let mut init_memory_arg = ""; + let mut init_memory1 = String::new(); + let mut init_memory2 = String::new(); + let mut has_memory = false; + if let Some(mem) = self.module.memories.iter().next() { + if let Some(id) = mem.import { + self.module.imports.get_mut(id).module = module_name.to_string(); + let mut memory = String::from("new WebAssembly.Memory({"); + memory.push_str(&format!("initial:{}", mem.initial)); + if let Some(max) = mem.maximum { + memory.push_str(&format!(",maximum:{}", max)); + } + if mem.shared { + memory.push_str(",shared:true"); + } + memory.push_str("})"); + self.imports_post.push_str("let memory;\n"); + init_memory1 = format!("memory = imports.{}.memory = maybe_memory;", module_name); + init_memory2 = format!("memory = imports.{}.memory = {};", module_name, memory); + init_memory_arg = ", maybe_memory"; + has_memory = true; } - if mem.shared { - memory.push_str(",shared:true"); - } - memory.push_str("})"); - self.imports_post.push_str("let memory;\n"); - ( - format!("memory = imports.{}.memory = maybe_memory;", module_name), - format!("memory = imports.{}.memory = {};", module_name, memory), - ) - } else { - (String::new(), String::new()) - }; - let init_memory_arg = if mem.import.is_some() { - ", maybe_memory" - } else { - "" - }; + } let default_module_path = match self.config.mode { OutputMode::Web => { @@ -478,7 +479,7 @@ impl<'a> Context<'a> { _ => "", }; - let ts = Self::ts_for_init_fn(mem.import.is_some(), !default_module_path.is_empty()); + let ts = Self::ts_for_init_fn(has_memory, !default_module_path.is_empty()); // Initialize the `imports` object for all import definitions that we're // directed to wire up. @@ -857,12 +858,7 @@ impl<'a> Context<'a> { self.global("let WASM_VECTOR_LEN = 0;"); } - fn expose_pass_string_to_wasm(&mut self) -> Result<(), Error> { - if !self.should_write_global("pass_string_to_wasm") { - return Ok(()); - } - - self.require_internal_export("__wbindgen_malloc")?; + fn expose_pass_string_to_wasm(&mut self, memory: MemoryId) -> Result { self.expose_wasm_vector_len(); let debug = if self.config.debug { @@ -878,25 +874,40 @@ impl<'a> Context<'a> { // doesn't require creating intermediate view using `subarray` // and also has `Buffer::byteLength` to calculate size upfront. if self.config.mode.nodejs() { - self.expose_node_buffer_memory(); + let get_buf = self.expose_node_buffer_memory(memory); + let ret = MemView { + name: "passStringToWasm", + num: get_buf.num, + }; + if !self.should_write_global(ret.to_string()) { + return Ok(ret); + } self.global(&format!( " - function passStringToWasm(arg) {{ + function {}(arg, malloc) {{ {} const len = Buffer.byteLength(arg); - const ptr = wasm.__wbindgen_malloc(len); - getNodeBufferMemory().write(arg, ptr, len); + const ptr = malloc(len); + {}().write(arg, ptr, len); WASM_VECTOR_LEN = len; return ptr; }} ", - debug, + ret, debug, get_buf, )); - return Ok(()); + return Ok(ret); } + let mem = self.expose_uint8_memory(memory); + let ret = MemView { + name: "passStringToWasm", + num: mem.num, + }; + if !self.should_write_global(ret.to_string()) { + return Ok(ret); + } self.expose_text_encoder()?; // The first implementation we have for this is to use @@ -920,7 +931,7 @@ impl<'a> Context<'a> { // Looks like `encodeInto` doesn't currently work when the memory passed // in is backed by a `SharedArrayBuffer`, so force usage of `encode` if // a `SharedArrayBuffer` is in use. - let shared = self.module.memories.get(self.memory).shared; + let shared = self.module.memories.get(memory).shared; match self.config.encode_into { EncodeInto::Always if !shared => { @@ -951,7 +962,6 @@ impl<'a> Context<'a> { } } - self.expose_uint8_memory(); self.require_internal_export("__wbindgen_realloc")?; // A fast path that directly writes char codes into WASM memory as long @@ -963,20 +973,23 @@ impl<'a> Context<'a> { // This might be not very intuitive, but such calls are usually more // expensive in mainstream engines than staying in the JS, and // charCodeAt on ASCII strings is usually optimised to raw bytes. - let encode_as_ascii = "\ - let len = arg.length; - let ptr = wasm.__wbindgen_malloc(len); + let encode_as_ascii = format!( + "\ + let len = arg.length; + let ptr = malloc(len); - const mem = getUint8Memory(); + const mem = {}(); - let offset = 0; + let offset = 0; - for (; offset < len; offset++) { - const code = arg.charCodeAt(offset); - if (code > 0x7F) break; - mem[ptr + offset] = code; - } - "; + for (; offset < len; offset++) {{ + const code = arg.charCodeAt(offset); + if (code > 0x7F) break; + mem[ptr + offset] = code; + }} + ", + mem + ); // TODO: // When converting a JS string to UTF-8, the maximum size is `arg.length * 3`, @@ -984,134 +997,146 @@ impl<'a> Context<'a> { // looping over the string to calculate the precise size, or perhaps using // `shrink_to_fit` on the Rust side. self.global(&format!( - "function passStringToWasm(arg) {{ - {} - {} + "function {name}(arg, malloc) {{ + {debug} + {ascii} if (offset !== len) {{ if (offset !== 0) {{ arg = arg.slice(offset); }} ptr = wasm.__wbindgen_realloc(ptr, len, len = offset + arg.length * 3); - const view = getUint8Memory().subarray(ptr + offset, ptr + len); + const view = {mem}().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - {} + {debug_end} offset += ret.written; }} WASM_VECTOR_LEN = offset; return ptr; }}", - debug, - encode_as_ascii, - if self.config.debug { + name = ret, + debug = debug, + ascii = encode_as_ascii, + mem = mem, + debug_end = if self.config.debug { "if (ret.read !== arg.length) throw new Error('failed to pass whole string');" } else { "" }, )); - Ok(()) + Ok(ret) } - fn expose_pass_array8_to_wasm(&mut self) -> Result<(), Error> { - self.expose_uint8_memory(); - self.pass_array_to_wasm("passArray8ToWasm", "getUint8Memory", 1) + fn expose_pass_array8_to_wasm(&mut self, memory: MemoryId) -> Result { + let view = self.expose_uint8_memory(memory); + self.pass_array_to_wasm("passArray8ToWasm", view, 1) } - fn expose_pass_array16_to_wasm(&mut self) -> Result<(), Error> { - self.expose_uint16_memory(); - self.pass_array_to_wasm("passArray16ToWasm", "getUint16Memory", 2) + fn expose_pass_array16_to_wasm(&mut self, memory: MemoryId) -> Result { + let view = self.expose_uint16_memory(memory); + self.pass_array_to_wasm("passArray16ToWasm", view, 2) } - fn expose_pass_array32_to_wasm(&mut self) -> Result<(), Error> { - self.expose_uint32_memory(); - self.pass_array_to_wasm("passArray32ToWasm", "getUint32Memory", 4) + fn expose_pass_array32_to_wasm(&mut self, memory: MemoryId) -> Result { + let view = self.expose_uint32_memory(memory); + self.pass_array_to_wasm("passArray32ToWasm", view, 4) } - fn expose_pass_array64_to_wasm(&mut self) -> Result<(), Error> { - self.expose_uint64_memory(); - self.pass_array_to_wasm("passArray64ToWasm", "getUint64Memory", 8) + fn expose_pass_array64_to_wasm(&mut self, memory: MemoryId) -> Result { + let view = self.expose_uint64_memory(memory); + self.pass_array_to_wasm("passArray64ToWasm", view, 8) } - fn expose_pass_array_f32_to_wasm(&mut self) -> Result<(), Error> { - self.expose_f32_memory(); - self.pass_array_to_wasm("passArrayF32ToWasm", "getFloat32Memory", 4) + fn expose_pass_array_f32_to_wasm(&mut self, memory: MemoryId) -> Result { + let view = self.expose_f32_memory(memory); + self.pass_array_to_wasm("passArrayF32ToWasm", view, 4) } - fn expose_pass_array_f64_to_wasm(&mut self) -> Result<(), Error> { - self.expose_f64_memory(); - self.pass_array_to_wasm("passArrayF64ToWasm", "getFloat64Memory", 8) + fn expose_pass_array_f64_to_wasm(&mut self, memory: MemoryId) -> Result { + let view = self.expose_f64_memory(memory); + self.pass_array_to_wasm("passArrayF64ToWasm", view, 8) } - fn expose_pass_array_jsvalue_to_wasm(&mut self) -> Result<(), Error> { - if !self.should_write_global("pass_array_jsvalue") { - return Ok(()); + fn expose_pass_array_jsvalue_to_wasm(&mut self, memory: MemoryId) -> Result { + let mem = self.expose_uint32_memory(memory); + let ret = MemView { + name: "passArrayJsValueToWasm", + num: mem.num, + }; + if !self.should_write_global(ret.to_string()) { + return Ok(ret); } - self.require_internal_export("__wbindgen_malloc")?; - self.expose_uint32_memory(); self.expose_wasm_vector_len(); - if self.config.anyref { - // TODO: using `addToAnyrefTable` goes back and forth between wasm - // and JS a lot, we should have a bulk operation for this. - self.expose_add_to_anyref_table()?; - self.global( - " - function passArrayJsValueToWasm(array) { - const ptr = wasm.__wbindgen_malloc(array.length * 4); - const mem = getUint32Memory(); - for (let i = 0; i < array.length; i++) { - mem[ptr / 4 + i] = addToAnyrefTable(array[i]); - } - WASM_VECTOR_LEN = array.length; - return ptr; - } - ", - ); - } else { - self.expose_add_heap_object(); - self.global( - " - function passArrayJsValueToWasm(array) { - const ptr = wasm.__wbindgen_malloc(array.length * 4); - const mem = getUint32Memory(); - for (let i = 0; i < array.length; i++) { - mem[ptr / 4 + i] = addHeapObject(array[i]); - } - WASM_VECTOR_LEN = array.length; - return ptr; - } - - ", - ); + match (self.aux.anyref_table, self.aux.anyref_alloc) { + (Some(table), Some(alloc)) => { + // TODO: using `addToAnyrefTable` goes back and forth between wasm + // and JS a lot, we should have a bulk operation for this. + let add = self.expose_add_to_anyref_table(table, alloc)?; + self.global(&format!( + " + function {}(array, malloc) {{ + const ptr = malloc(array.length * 4); + const mem = {}(); + for (let i = 0; i < array.length; i++) {{ + mem[ptr / 4 + i] = {}(array[i]); + }} + WASM_VECTOR_LEN = array.length; + return ptr; + }} + ", + ret, mem, add, + )); + } + _ => { + self.expose_add_heap_object(); + self.global(&format!( + " + function {}(array, malloc) {{ + const ptr = malloc(array.length * 4); + const mem = {}(); + for (let i = 0; i < array.length; i++) {{ + mem[ptr / 4 + i] = addHeapObject(array[i]); + }} + WASM_VECTOR_LEN = array.length; + return ptr; + }} + ", + ret, mem, + )); + } } - Ok(()) + Ok(ret) } fn pass_array_to_wasm( &mut self, name: &'static str, - delegate: &str, + view: MemView, size: usize, - ) -> Result<(), Error> { - if !self.should_write_global(name) { - return Ok(()); + ) -> Result { + let ret = MemView { + name, + num: view.num, + }; + if !self.should_write_global(ret.to_string()) { + return Ok(ret); } - self.require_internal_export("__wbindgen_malloc")?; self.expose_wasm_vector_len(); self.global(&format!( " - function {}(arg) {{ - const ptr = wasm.__wbindgen_malloc(arg.length * {size}); + function {}(arg, malloc) {{ + const ptr = malloc(arg.length * {size}); {}().set(arg, ptr / {size}); WASM_VECTOR_LEN = arg.length; return ptr; }} ", - name, - delegate, + ret, + view, size = size )); - Ok(()) + Ok(ret) } fn expose_text_encoder(&mut self) -> Result<(), Error> { @@ -1163,12 +1188,17 @@ impl<'a> Context<'a> { Ok(()) } - fn expose_get_string_from_wasm(&mut self) -> Result<(), Error> { - if !self.should_write_global("get_string_from_wasm") { - return Ok(()); - } + fn expose_get_string_from_wasm(&mut self, memory: MemoryId) -> Result { self.expose_text_decoder()?; - self.expose_uint8_memory(); + let mem = self.expose_uint8_memory(memory); + let ret = MemView { + name: "getStringFromWasm", + num: mem.num, + }; + + if !self.should_write_global(ret.to_string()) { + return Ok(ret); + } // Typically we try to give a raw view of memory out to `TextDecoder` to // avoid copying too much data. If, however, a `SharedArrayBuffer` is @@ -1178,27 +1208,31 @@ impl<'a> Context<'a> { // creates just a view. That way in shared mode we copy more data but in // non-shared mode there's no need to copy the data except for the // string itself. - let is_shared = self.module.memories.get(self.memory).shared; + let is_shared = self.module.memories.get(memory).shared; let method = if is_shared { "slice" } else { "subarray" }; self.global(&format!( " - function getStringFromWasm(ptr, len) {{ - return cachedTextDecoder.decode(getUint8Memory().{}(ptr, ptr + len)); + function {}(ptr, len) {{ + return cachedTextDecoder.decode({}().{}(ptr, ptr + len)); }} - ", - method + ", + ret, mem, method )); - Ok(()) + Ok(ret) } - fn expose_get_cached_string_from_wasm(&mut self) -> Result<(), Error> { - if !self.should_write_global("get_cached_string_from_wasm") { - return Ok(()); - } - + fn expose_get_cached_string_from_wasm(&mut self, memory: MemoryId) -> Result { self.expose_get_object(); - self.expose_get_string_from_wasm()?; + let get = self.expose_get_string_from_wasm(memory)?; + let ret = MemView { + name: "getCachedStringFromWasm", + num: get.num, + }; + + if !self.should_write_global(ret.to_string()) { + return Ok(ret); + } // This has support for both `&str` and `Option<&str>`. // @@ -1208,118 +1242,133 @@ impl<'a> Context<'a> { // // If `ptr` and `len` are both `0` then that means it's `None`, in that case we rely upon // the fact that `getObject(0)` is guaranteed to be `undefined`. - self.global( + self.global(&format!( " - function getCachedStringFromWasm(ptr, len) { - if (ptr === 0) { + function {}(ptr, len) {{ + if (ptr === 0) {{ return getObject(len); - } else { - return getStringFromWasm(ptr, len); - } + }} else {{ + return {}(ptr, len); + }} + }} + ", + ret, get, + )); + Ok(ret) + } + + fn expose_get_array_js_value_from_wasm(&mut self, memory: MemoryId) -> Result { + let mem = self.expose_uint32_memory(memory); + let ret = MemView { + name: "getArrayJsValueFromWasm", + num: mem.num, + }; + if !self.should_write_global(ret.to_string()) { + return Ok(ret); + } + match (self.aux.anyref_table, self.aux.anyref_drop_slice) { + (Some(table), Some(drop)) => { + let table = self.export_name_of(table); + let drop = self.export_name_of(drop); + self.global(&format!( + " + function {}(ptr, len) {{ + const mem = {}(); + const slice = mem.subarray(ptr / 4, ptr / 4 + len); + const result = []; + for (let i = 0; i < slice.length; i++) {{ + result.push(wasm.{}.get(slice[i])); + }} + wasm.{}(ptr, len); + return result; + }} + ", + ret, mem, table, drop, + )); + } + _ => { + self.expose_take_object(); + self.global(&format!( + " + function {}(ptr, len) {{ + const mem = {}(); + const slice = mem.subarray(ptr / 4, ptr / 4 + len); + const result = []; + for (let i = 0; i < slice.length; i++) {{ + result.push(takeObject(slice[i])); + }} + return result; + }} + ", + ret, mem, + )); } - ", - ); - Ok(()) - } - - fn expose_get_array_js_value_from_wasm(&mut self) -> Result<(), Error> { - if !self.should_write_global("get_array_js_value_from_wasm") { - return Ok(()); } - self.expose_uint32_memory(); - if self.config.anyref { - self.global( - " - function getArrayJsValueFromWasm(ptr, len) { - const mem = getUint32Memory(); - const slice = mem.subarray(ptr / 4, ptr / 4 + len); - const result = []; - for (let i = 0; i < slice.length; i++) { - result.push(wasm.__wbg_anyref_table.get(slice[i])); - } - wasm.__wbindgen_drop_anyref_slice(ptr, len); - return result; - } - ", - ); - self.require_internal_export("__wbindgen_drop_anyref_slice")?; - } else { - self.expose_take_object(); - self.global( - " - function getArrayJsValueFromWasm(ptr, len) { - const mem = getUint32Memory(); - const slice = mem.subarray(ptr / 4, ptr / 4 + len); - const result = []; - for (let i = 0; i < slice.length; i++) { - result.push(takeObject(slice[i])); - } - return result; - } - ", - ); - } - Ok(()) + Ok(ret) } - fn expose_get_array_i8_from_wasm(&mut self) { - self.expose_int8_memory(); - self.arrayget("getArrayI8FromWasm", "getInt8Memory", 1); + fn expose_get_array_i8_from_wasm(&mut self, memory: MemoryId) -> MemView { + let view = self.expose_int8_memory(memory); + self.arrayget("getArrayI8FromWasm", view, 1) } - fn expose_get_array_u8_from_wasm(&mut self) { - self.expose_uint8_memory(); - self.arrayget("getArrayU8FromWasm", "getUint8Memory", 1); + fn expose_get_array_u8_from_wasm(&mut self, memory: MemoryId) -> MemView { + let view = self.expose_uint8_memory(memory); + self.arrayget("getArrayU8FromWasm", view, 1) } - fn expose_get_clamped_array_u8_from_wasm(&mut self) { - self.expose_clamped_uint8_memory(); - self.arrayget("getClampedArrayU8FromWasm", "getUint8ClampedMemory", 1); + fn expose_get_clamped_array_u8_from_wasm(&mut self, memory: MemoryId) -> MemView { + let view = self.expose_clamped_uint8_memory(memory); + self.arrayget("getClampedArrayU8FromWasm", view, 1) } - fn expose_get_array_i16_from_wasm(&mut self) { - self.expose_int16_memory(); - self.arrayget("getArrayI16FromWasm", "getInt16Memory", 2); + fn expose_get_array_i16_from_wasm(&mut self, memory: MemoryId) -> MemView { + let view = self.expose_int16_memory(memory); + self.arrayget("getArrayI16FromWasm", view, 2) } - fn expose_get_array_u16_from_wasm(&mut self) { - self.expose_uint16_memory(); - self.arrayget("getArrayU16FromWasm", "getUint16Memory", 2); + fn expose_get_array_u16_from_wasm(&mut self, memory: MemoryId) -> MemView { + let view = self.expose_uint16_memory(memory); + self.arrayget("getArrayU16FromWasm", view, 2) } - fn expose_get_array_i32_from_wasm(&mut self) { - self.expose_int32_memory(); - self.arrayget("getArrayI32FromWasm", "getInt32Memory", 4); + fn expose_get_array_i32_from_wasm(&mut self, memory: MemoryId) -> MemView { + let view = self.expose_int32_memory(memory); + self.arrayget("getArrayI32FromWasm", view, 4) } - fn expose_get_array_u32_from_wasm(&mut self) { - self.expose_uint32_memory(); - self.arrayget("getArrayU32FromWasm", "getUint32Memory", 4); + fn expose_get_array_u32_from_wasm(&mut self, memory: MemoryId) -> MemView { + let view = self.expose_uint32_memory(memory); + self.arrayget("getArrayU32FromWasm", view, 4) } - fn expose_get_array_i64_from_wasm(&mut self) { - self.expose_int64_memory(); - self.arrayget("getArrayI64FromWasm", "getInt64Memory", 8); + fn expose_get_array_i64_from_wasm(&mut self, memory: MemoryId) -> MemView { + let view = self.expose_int64_memory(memory); + self.arrayget("getArrayI64FromWasm", view, 8) } - fn expose_get_array_u64_from_wasm(&mut self) { - self.expose_uint64_memory(); - self.arrayget("getArrayU64FromWasm", "getUint64Memory", 8); + fn expose_get_array_u64_from_wasm(&mut self, memory: MemoryId) -> MemView { + let view = self.expose_uint64_memory(memory); + self.arrayget("getArrayU64FromWasm", view, 8) } - fn expose_get_array_f32_from_wasm(&mut self) { - self.expose_f32_memory(); - self.arrayget("getArrayF32FromWasm", "getFloat32Memory", 4); + fn expose_get_array_f32_from_wasm(&mut self, memory: MemoryId) -> MemView { + let view = self.expose_f32_memory(memory); + self.arrayget("getArrayF32FromWasm", view, 4) } - fn expose_get_array_f64_from_wasm(&mut self) { - self.expose_f64_memory(); - self.arrayget("getArrayF64FromWasm", "getFloat64Memory", 8); + fn expose_get_array_f64_from_wasm(&mut self, memory: MemoryId) -> MemView { + let view = self.expose_f64_memory(memory); + self.arrayget("getArrayF64FromWasm", view, 8) } - fn arrayget(&mut self, name: &'static str, mem: &'static str, size: usize) { + fn arrayget(&mut self, name: &'static str, view: MemView, size: usize) -> MemView { + let ret = MemView { + name, + num: view.num, + }; if !self.should_write_global(name) { - return; + return ret; } self.global(&format!( " @@ -1327,136 +1376,112 @@ impl<'a> Context<'a> { return {mem}().subarray(ptr / {size}, ptr / {size} + len); }} ", - name = name, - mem = mem, + name = ret, + mem = view, size = size, )); + return ret; } - fn expose_node_buffer_memory(&mut self) { - self.memview("getNodeBufferMemory", "Buffer.from"); + fn expose_node_buffer_memory(&mut self, memory: MemoryId) -> MemView { + self.memview("getNodeBufferMemory", "Buffer.from", memory) } - fn expose_int8_memory(&mut self) { - self.memview("getInt8Memory", "new Int8Array"); + fn expose_int8_memory(&mut self, memory: MemoryId) -> MemView { + self.memview("getInt8Memory", "new Int8Array", memory) } - fn expose_uint8_memory(&mut self) { - self.memview("getUint8Memory", "new Uint8Array"); + fn expose_uint8_memory(&mut self, memory: MemoryId) -> MemView { + self.memview("getUint8Memory", "new Uint8Array", memory) } - fn expose_clamped_uint8_memory(&mut self) { - self.memview("getUint8ClampedMemory", "new Uint8ClampedArray"); + fn expose_clamped_uint8_memory(&mut self, memory: MemoryId) -> MemView { + self.memview("getUint8ClampedMemory", "new Uint8ClampedArray", memory) } - fn expose_int16_memory(&mut self) { - self.memview("getInt16Memory", "new Int16Array"); + fn expose_int16_memory(&mut self, memory: MemoryId) -> MemView { + self.memview("getInt16Memory", "new Int16Array", memory) } - fn expose_uint16_memory(&mut self) { - self.memview("getUint16Memory", "new Uint16Array"); + fn expose_uint16_memory(&mut self, memory: MemoryId) -> MemView { + self.memview("getUint16Memory", "new Uint16Array", memory) } - fn expose_int32_memory(&mut self) { - self.memview("getInt32Memory", "new Int32Array"); + fn expose_int32_memory(&mut self, memory: MemoryId) -> MemView { + self.memview("getInt32Memory", "new Int32Array", memory) } - fn expose_uint32_memory(&mut self) { - self.memview("getUint32Memory", "new Uint32Array"); + fn expose_uint32_memory(&mut self, memory: MemoryId) -> MemView { + self.memview("getUint32Memory", "new Uint32Array", memory) } - fn expose_int64_memory(&mut self) { - self.memview("getInt64Memory", "new BigInt64Array"); + fn expose_int64_memory(&mut self, memory: MemoryId) -> MemView { + self.memview("getInt64Memory", "new BigInt64Array", memory) } - fn expose_uint64_memory(&mut self) { - self.memview("getUint64Memory", "new BigUint64Array"); + fn expose_uint64_memory(&mut self, memory: MemoryId) -> MemView { + self.memview("getUint64Memory", "new BigUint64Array", memory) } - fn expose_f32_memory(&mut self) { - self.memview("getFloat32Memory", "new Float32Array"); + fn expose_f32_memory(&mut self, memory: MemoryId) -> MemView { + self.memview("getFloat32Memory", "new Float32Array", memory) } - fn expose_f64_memory(&mut self) { - self.memview("getFloat64Memory", "new Float64Array"); + fn expose_f64_memory(&mut self, memory: MemoryId) -> MemView { + self.memview("getFloat64Memory", "new Float64Array", memory) } - fn memview_function(&mut self, t: VectorKind) -> &'static str { + fn memview_function(&mut self, t: VectorKind, memory: MemoryId) -> MemView { match t { - VectorKind::String => { - self.expose_uint8_memory(); - "getUint8Memory" - } - VectorKind::I8 => { - self.expose_int8_memory(); - "getInt8Memory" - } - VectorKind::U8 => { - self.expose_uint8_memory(); - "getUint8Memory" - } - VectorKind::ClampedU8 => { - self.expose_clamped_uint8_memory(); - "getUint8ClampedMemory" - } - VectorKind::I16 => { - self.expose_int16_memory(); - "getInt16Memory" - } - VectorKind::U16 => { - self.expose_uint16_memory(); - "getUint16Memory" - } - VectorKind::I32 => { - self.expose_int32_memory(); - "getInt32Memory" - } - VectorKind::U32 => { - self.expose_uint32_memory(); - "getUint32Memory" - } - VectorKind::I64 => { - self.expose_int64_memory(); - "getInt64Memory" - } - VectorKind::U64 => { - self.expose_uint64_memory(); - "getUint64Memory" - } - VectorKind::F32 => { - self.expose_f32_memory(); - "getFloat32Memory" - } - VectorKind::F64 => { - self.expose_f64_memory(); - "getFloat64Memory" - } - VectorKind::Anyref => { - self.expose_uint32_memory(); - "getUint32Memory" - } + VectorKind::String => self.expose_uint8_memory(memory), + VectorKind::I8 => self.expose_int8_memory(memory), + VectorKind::U8 => self.expose_uint8_memory(memory), + VectorKind::ClampedU8 => self.expose_clamped_uint8_memory(memory), + VectorKind::I16 => self.expose_int16_memory(memory), + VectorKind::U16 => self.expose_uint16_memory(memory), + VectorKind::I32 => self.expose_int32_memory(memory), + VectorKind::U32 => self.expose_uint32_memory(memory), + VectorKind::I64 => self.expose_int64_memory(memory), + VectorKind::U64 => self.expose_uint64_memory(memory), + VectorKind::F32 => self.expose_f32_memory(memory), + VectorKind::F64 => self.expose_f64_memory(memory), + VectorKind::Anyref => self.expose_uint32_memory(memory), } } - fn memview(&mut self, name: &'static str, js: &str) { - if !self.should_write_global(name) { - return; + fn memview(&mut self, name: &'static str, js: &str, memory: walrus::MemoryId) -> MemView { + let view = self.memview_memory(name, memory); + if !self.should_write_global(name.to_string()) { + return view; } - let mem = self.memory(); + let mem = self.export_name_of(memory); self.global(&format!( " let cache{name} = null; function {name}() {{ - if (cache{name} === null || cache{name}.buffer !== {mem}.buffer) {{ - cache{name} = {js}({mem}.buffer); + if (cache{name} === null || cache{name}.buffer !== wasm.{mem}.buffer) {{ + cache{name} = {js}(wasm.{mem}.buffer); }} return cache{name}; }} ", - name = name, + name = view, js = js, mem = mem, )); + return view; + } + + fn memview_memory(&mut self, name: &'static str, memory: walrus::MemoryId) -> MemView { + let next = self.memory_indices.len(); + let num = *self.memory_indices.entry(memory).or_insert(next); + MemView { name, num } + } + + fn memview_table(&mut self, name: &'static str, table: walrus::TableId) -> MemView { + let next = self.table_indices.len(); + let num = *self.table_indices.entry(table).or_insert(next); + MemView { name, num } } fn expose_assert_class(&mut self) { @@ -1561,25 +1586,29 @@ impl<'a> Context<'a> { return Ok(()); } self.require_internal_export("__wbindgen_exn_store")?; - if self.config.anyref { - self.expose_add_to_anyref_table()?; - self.global( - " - function handleError(e) { - const idx = addToAnyrefTable(e); - wasm.__wbindgen_exn_store(idx); - } - ", - ); - } else { - self.expose_add_heap_object(); - self.global( - " - function handleError(e) { - wasm.__wbindgen_exn_store(addHeapObject(e)); - } - ", - ); + match (self.aux.anyref_table, self.aux.anyref_alloc) { + (Some(table), Some(alloc)) => { + let add = self.expose_add_to_anyref_table(table, alloc)?; + self.global(&format!( + " + function handleError(e) {{ + const idx = {}(e); + wasm.__wbindgen_exn_store(idx); + }} + ", + add, + )); + } + _ => { + self.expose_add_heap_object(); + self.global( + " + function handleError(e) { + wasm.__wbindgen_exn_store(addHeapObject(e)); + } + ", + ); + } } Ok(()) } @@ -1609,98 +1638,40 @@ impl<'a> Context<'a> { ); } - fn pass_to_wasm_function(&mut self, t: VectorKind) -> Result<&'static str, Error> { - let s = match t { - VectorKind::String => { - self.expose_pass_string_to_wasm()?; - "passStringToWasm" - } + fn pass_to_wasm_function(&mut self, t: VectorKind, memory: MemoryId) -> Result { + match t { + VectorKind::String => self.expose_pass_string_to_wasm(memory), VectorKind::I8 | VectorKind::U8 | VectorKind::ClampedU8 => { - self.expose_pass_array8_to_wasm()?; - "passArray8ToWasm" + self.expose_pass_array8_to_wasm(memory) } - VectorKind::U16 | VectorKind::I16 => { - self.expose_pass_array16_to_wasm()?; - "passArray16ToWasm" - } - VectorKind::I32 | VectorKind::U32 => { - self.expose_pass_array32_to_wasm()?; - "passArray32ToWasm" - } - VectorKind::I64 | VectorKind::U64 => { - self.expose_pass_array64_to_wasm()?; - "passArray64ToWasm" - } - VectorKind::F32 => { - self.expose_pass_array_f32_to_wasm()?; - "passArrayF32ToWasm" - } - VectorKind::F64 => { - self.expose_pass_array_f64_to_wasm()?; - "passArrayF64ToWasm" - } - VectorKind::Anyref => { - self.expose_pass_array_jsvalue_to_wasm()?; - "passArrayJsValueToWasm" - } - }; - Ok(s) + VectorKind::U16 | VectorKind::I16 => self.expose_pass_array16_to_wasm(memory), + VectorKind::I32 | VectorKind::U32 => self.expose_pass_array32_to_wasm(memory), + VectorKind::I64 | VectorKind::U64 => self.expose_pass_array64_to_wasm(memory), + VectorKind::F32 => self.expose_pass_array_f32_to_wasm(memory), + VectorKind::F64 => self.expose_pass_array_f64_to_wasm(memory), + VectorKind::Anyref => self.expose_pass_array_jsvalue_to_wasm(memory), + } } - fn expose_get_vector_from_wasm(&mut self, ty: VectorKind) -> Result<&'static str, Error> { + fn expose_get_vector_from_wasm( + &mut self, + ty: VectorKind, + memory: MemoryId, + ) -> Result { Ok(match ty { - VectorKind::String => { - self.expose_get_string_from_wasm()?; - "getStringFromWasm" - } - VectorKind::I8 => { - self.expose_get_array_i8_from_wasm(); - "getArrayI8FromWasm" - } - VectorKind::U8 => { - self.expose_get_array_u8_from_wasm(); - "getArrayU8FromWasm" - } - VectorKind::ClampedU8 => { - self.expose_get_clamped_array_u8_from_wasm(); - "getClampedArrayU8FromWasm" - } - VectorKind::I16 => { - self.expose_get_array_i16_from_wasm(); - "getArrayI16FromWasm" - } - VectorKind::U16 => { - self.expose_get_array_u16_from_wasm(); - "getArrayU16FromWasm" - } - VectorKind::I32 => { - self.expose_get_array_i32_from_wasm(); - "getArrayI32FromWasm" - } - VectorKind::U32 => { - self.expose_get_array_u32_from_wasm(); - "getArrayU32FromWasm" - } - VectorKind::I64 => { - self.expose_get_array_i64_from_wasm(); - "getArrayI64FromWasm" - } - VectorKind::U64 => { - self.expose_get_array_u64_from_wasm(); - "getArrayU64FromWasm" - } - VectorKind::F32 => { - self.expose_get_array_f32_from_wasm(); - "getArrayF32FromWasm" - } - VectorKind::F64 => { - self.expose_get_array_f64_from_wasm(); - "getArrayF64FromWasm" - } - VectorKind::Anyref => { - self.expose_get_array_js_value_from_wasm()?; - "getArrayJsValueFromWasm" - } + VectorKind::String => self.expose_get_string_from_wasm(memory)?, + VectorKind::I8 => self.expose_get_array_i8_from_wasm(memory), + VectorKind::U8 => self.expose_get_array_u8_from_wasm(memory), + VectorKind::ClampedU8 => self.expose_get_clamped_array_u8_from_wasm(memory), + VectorKind::I16 => self.expose_get_array_i16_from_wasm(memory), + VectorKind::U16 => self.expose_get_array_u16_from_wasm(memory), + VectorKind::I32 => self.expose_get_array_i32_from_wasm(memory), + VectorKind::U32 => self.expose_get_array_u32_from_wasm(memory), + VectorKind::I64 => self.expose_get_array_i64_from_wasm(memory), + VectorKind::U64 => self.expose_get_array_u64_from_wasm(memory), + VectorKind::F32 => self.expose_get_array_f32_from_wasm(memory), + VectorKind::F64 => self.expose_get_array_f64_from_wasm(memory), + VectorKind::Anyref => self.expose_get_array_js_value_from_wasm(memory)?, }) } @@ -1791,14 +1762,6 @@ impl<'a> Context<'a> { self.globals.push_str("\n"); } - fn memory(&mut self) -> &'static str { - if self.module.memories.get(self.memory).import.is_some() { - "memory" - } else { - "wasm.memory" - } - } - fn require_class_wrap(&mut self, name: &str) { require_class(&mut self.exported_classes, name).wrap_needed = true; } @@ -1909,235 +1872,189 @@ impl<'a> Context<'a> { true } - fn expose_add_to_anyref_table(&mut self) -> Result<(), Error> { + fn expose_add_to_anyref_table( + &mut self, + table: TableId, + alloc: FunctionId, + ) -> Result { + let view = self.memview_table("addToAnyrefTable", table); assert!(self.config.anyref); - if !self.should_write_global("add_to_anyref_table") { - return Ok(()); + if !self.should_write_global(view.to_string()) { + return Ok(view); } - self.require_internal_export("__wbindgen_anyref_table_alloc")?; - self.global( + let alloc = self.export_name_of(alloc); + let table = self.export_name_of(table); + self.global(&format!( " - function addToAnyrefTable(obj) { - const idx = wasm.__wbindgen_anyref_table_alloc(); - wasm.__wbg_anyref_table.set(idx, obj); + function {}(obj) {{ + const idx = wasm.{}(); + wasm.{}.set(idx, obj); return idx; - } + }} ", - ); + view, alloc, table, + )); - Ok(()) + Ok(view) } - pub fn generate( - &mut self, - aux: &WasmBindgenAux, - bindings: &NonstandardWebidlSection, - ) -> Result<(), Error> { - for (i, (idx, binding)) in bindings.elems.iter().enumerate() { - self.generate_elem_binding(i, *idx, binding, bindings)?; + pub fn generate(&mut self) -> Result<(), Error> { + for (id, adapter) in sorted_iter(&self.wit.adapters) { + let instrs = match &adapter.kind { + AdapterKind::Import { .. } => continue, + AdapterKind::Local { instructions } => instructions, + }; + self.generate_adapter(*id, adapter, instrs)?; } - let mut pairs = aux.export_map.iter().collect::>(); - pairs.sort_by_key(|(k, _)| *k); - check_duplicated_getter_and_setter_names(&pairs)?; - for (id, export) in pairs { - self.generate_export(*id, export, bindings) - .with_context(|| { - format!( - "failed to generate bindings for Rust export `{}`", - export.debug_name, - ) - })?; - } + // let mut pairs = aux.export_map.iter().collect::>(); + // pairs.sort_by_key(|(k, _)| *k); + // check_duplicated_getter_and_setter_names(&pairs)?; - for (id, import) in sorted_iter(&aux.import_map) { - let variadic = aux.imports_with_variadic.contains(&id); - let catch = aux.imports_with_catch.contains(&id); - let assert_no_shim = aux.imports_with_assert_no_shim.contains(&id); - self.generate_import(*id, import, bindings, variadic, catch, assert_no_shim) - .with_context(|| { - format!("failed to generate bindings for import `{:?}`", import,) - })?; - } - for e in aux.enums.iter() { + for e in self.aux.enums.iter() { self.generate_enum(e)?; } - for s in aux.structs.iter() { + for s in self.aux.structs.iter() { self.generate_struct(s)?; } - self.typescript.push_str(&aux.extra_typescript); + self.typescript.push_str(&self.aux.extra_typescript); - for path in aux.package_jsons.iter() { + for path in self.aux.package_jsons.iter() { self.process_package_json(path)?; } Ok(()) } - /// Generates a wrapper function for each bound element of the function - /// table. These wrapper functions have the expected WebIDL signature we'd - /// like them to have. This currently isn't part of the WebIDL bindings - /// proposal, but the thinking is that it'd look something like this if - /// added. - /// - /// Note that this is just an internal function shim used by closures and - /// such, so we're not actually exporting anything here. - fn generate_elem_binding( + fn generate_adapter( &mut self, - idx: usize, - elem_idx: u32, - binding: &Binding, - bindings: &NonstandardWebidlSection, + id: AdapterId, + adapter: &Adapter, + instrs: &[InstructionData], ) -> Result<(), Error> { - let webidl = bindings - .types - .get::(binding.webidl_ty) - .unwrap(); - self.export_function_table()?; - let mut builder = binding::Builder::new(self); - builder.disable_log_error(true); - let js = builder.process(&binding, &webidl, true, &None, &mut |_, _, args| { - Ok(format!( - "wasm.__wbg_function_table.get({})({})", - elem_idx, - args.join(", ") - )) - })?; - self.globals - .push_str(&format!("function __wbg_elem_binding{}{}\n", idx, js)); - Ok(()) - } + enum Kind<'a> { + Export(&'a AuxExport), + Import(walrus::ImportId), + Adapter, + } - fn generate_export( - &mut self, - id: ExportId, - export: &AuxExport, - bindings: &NonstandardWebidlSection, - ) -> Result<(), Error> { - let wasm_name = self.module.exports.get(id).name.clone(); - let binding = &bindings.exports[&id]; - let webidl = bindings - .types - .get::(binding.webidl_ty) - .unwrap(); + let kind = match self.aux.export_map.get(&id) { + Some(export) => Kind::Export(export), + None => { + let core = self.wit.implements.iter().find(|pair| pair.1 == id); + match core { + Some((core, _)) => Kind::Import(*core), + None => Kind::Adapter, + } + } + }; + + let catch = self.aux.imports_with_catch.contains(&id); + if let Kind::Import(core) = kind { + if !catch && self.attempt_direct_import(core, instrs)? { + return Ok(()); + } + } // Construct a JS shim builder, and configure it based on the kind of // export that we're generating. let mut builder = binding::Builder::new(self); - builder.disable_log_error(true); - match &export.kind { - AuxExportKind::Function(_) => {} - AuxExportKind::StaticFunction { .. } => {} - AuxExportKind::Constructor(class) => builder.constructor(class), - AuxExportKind::Getter { .. } | AuxExportKind::Setter { .. } => builder.method(false), - AuxExportKind::Method { consumed, .. } => builder.method(*consumed), + builder.log_error(match kind { + Kind::Export(_) | Kind::Adapter => false, + Kind::Import(_) => builder.cx.config.debug, + }); + builder.catch(catch); + let mut arg_names = &None; + match kind { + Kind::Export(export) => { + arg_names = &export.arg_names; + match &export.kind { + AuxExportKind::Function(_) => {} + AuxExportKind::StaticFunction { .. } => {} + AuxExportKind::Constructor(class) => builder.constructor(class), + AuxExportKind::Getter { .. } | AuxExportKind::Setter { .. } => { + builder.method(false) + } + AuxExportKind::Method { consumed, .. } => builder.method(*consumed), + } + } + Kind::Import(_) => {} + Kind::Adapter => {} } // Process the `binding` and generate a bunch of JS/TypeScript/etc. - let js = builder.process( - &binding, - &webidl, - true, - &export.arg_names, - &mut |_, _, args| Ok(format!("wasm.{}({})", wasm_name, args.join(", "))), - )?; + let js = builder + .process(&adapter, instrs, arg_names) + .with_context(|| match kind { + Kind::Export(e) => format!("failed to generate bindings for `{}`", e.debug_name), + Kind::Import(i) => { + let i = builder.cx.module.imports.get(i); + format!( + "failed to generate bindings for import of `{}::{}`", + i.module, i.name + ) + } + Kind::Adapter => format!("failed to generates bindings for adapter"), + })?; let ts = builder.typescript_signature(); let js_doc = builder.js_doc_comments(); - let docs = format_doc_comments(&export.comments, Some(js_doc)); // Once we've got all the JS then put it in the right location depending // on what's being exported. - match &export.kind { - AuxExportKind::Function(name) => { - self.export(&name, &format!("function{}", js), Some(docs))?; - self.globals.push_str("\n"); - self.typescript.push_str("export function "); - self.typescript.push_str(&name); - self.typescript.push_str(&ts); - self.typescript.push_str(";\n"); - } - AuxExportKind::Constructor(class) => { - let exported = require_class(&mut self.exported_classes, class); - if exported.has_constructor { - bail!("found duplicate constructor for class `{}`", class); + match kind { + Kind::Export(export) => { + let docs = format_doc_comments(&export.comments, Some(js_doc)); + match &export.kind { + AuxExportKind::Function(name) => { + self.export(&name, &format!("function{}", js), Some(docs))?; + self.globals.push_str("\n"); + self.typescript.push_str("export function "); + self.typescript.push_str(&name); + self.typescript.push_str(&ts); + self.typescript.push_str(";\n"); + } + AuxExportKind::Constructor(class) => { + let exported = require_class(&mut self.exported_classes, class); + if exported.has_constructor { + bail!("found duplicate constructor for class `{}`", class); + } + exported.has_constructor = true; + exported.push(&docs, "constructor", "", &js, &ts); + } + AuxExportKind::Getter { class, field } => { + let ret_ty = builder.ts_ret.as_ref().unwrap().ty.clone(); + let exported = require_class(&mut self.exported_classes, class); + exported.push_getter(&docs, field, &js, &ret_ty); + } + AuxExportKind::Setter { class, field } => { + let arg_ty = builder.ts_args[0].ty.clone(); + let exported = require_class(&mut self.exported_classes, class); + exported.push_setter(&docs, field, &js, &arg_ty); + } + AuxExportKind::StaticFunction { class, name } => { + let exported = require_class(&mut self.exported_classes, class); + exported.push(&docs, name, "static ", &js, &ts); + } + AuxExportKind::Method { class, name, .. } => { + let exported = require_class(&mut self.exported_classes, class); + exported.push(&docs, name, "", &js, &ts); + } } - exported.has_constructor = true; - exported.push(&docs, "constructor", "", &js, &ts); } - AuxExportKind::Getter { class, field } => { - let ret_ty = builder.ts_ret.as_ref().unwrap().ty.clone(); - let exported = require_class(&mut self.exported_classes, class); - exported.push_getter(&docs, field, &js, &ret_ty); - } - AuxExportKind::Setter { class, field } => { - let arg_ty = builder.ts_args[0].ty.clone(); - let exported = require_class(&mut self.exported_classes, class); - exported.push_setter(&docs, field, &js, &arg_ty); - } - AuxExportKind::StaticFunction { class, name } => { - let exported = require_class(&mut self.exported_classes, class); - exported.push(&docs, name, "static ", &js, &ts); - } - AuxExportKind::Method { class, name, .. } => { - let exported = require_class(&mut self.exported_classes, class); - exported.push(&docs, name, "", &js, &ts); - } - } - Ok(()) - } - - fn generate_import( - &mut self, - id: ImportId, - import: &AuxImport, - bindings: &NonstandardWebidlSection, - variadic: bool, - catch: bool, - assert_no_shim: bool, - ) -> Result<(), Error> { - let binding = &bindings.imports[&id]; - let webidl = bindings - .types - .get::(binding.webidl_ty) - .unwrap(); - match import { - AuxImport::Value(AuxValue::Bare(js)) - if !variadic && !catch && self.import_does_not_require_glue(binding, webidl) => - { - self.direct_import(id, js) - } - _ => { - if assert_no_shim { - panic!( - "imported function was annotated with `#[wasm_bindgen(assert_no_shim)]` \ - but we need to generate a JS shim for it:\n\n\ - \timport = {:?}\n\n\ - \tbinding = {:?}\n\n\ - \twebidl = {:?}", - import, binding, webidl, - ); - } - - let disable_log_error = self.import_never_log_error(import); - let mut builder = binding::Builder::new(self); - builder.catch(catch)?; - builder.disable_log_error(disable_log_error); - let js = builder.process( - &binding, - &webidl, - false, - &None, - &mut |cx, prelude, args| { - cx.invoke_import(&binding, import, bindings, args, variadic, prelude) - }, - )?; + Kind::Import(core) => { self.wasm_import_definitions - .insert(id, format!("function{}", js)); - Ok(()) + .insert(core, format!("function{}", js)); + } + Kind::Adapter => { + self.globals.push_str("function "); + self.globals.push_str(&self.adapter_name(id)); + self.globals.push_str(&js); + self.globals.push_str("\n\n"); } } + return Ok(()); } /// Returns whether we should disable the logic, in debug mode, to catch an @@ -2156,34 +2073,72 @@ impl<'a> Context<'a> { } } - fn import_does_not_require_glue( - &self, - binding: &Binding, - webidl: &ast::WebidlFunction, - ) -> bool { - if !self.config.anyref && binding.contains_anyref(self.module) { - return false; + /// Attempts to directly hook up the `id` import in the wasm module with + /// the `instrs` specified. + /// + /// If this succeeds it returns `Ok(true)`, otherwise if it cannot be + /// directly imported then `Ok(false)` is returned. + fn attempt_direct_import( + &mut self, + id: ImportId, + instrs: &[InstructionData], + ) -> Result { + // First up extract the ID of the single called adapter, if any. + let mut call = None; + for instr in instrs { + match instr.instr { + Instruction::CallAdapter(id) => { + if call.is_some() { + return Ok(false); + } else { + call = Some(id); + } + } + Instruction::CallExport(_) + | Instruction::CallTableElement(_) + | Instruction::Standard(wit_walrus::Instruction::CallCore(_)) + | Instruction::Standard(wit_walrus::Instruction::CallAdapter(_)) => { + return Ok(false) + } + _ => {} + } + } + let adapter = match call { + Some(id) => id, + None => return Ok(false), + }; + match &self.wit.adapters[&adapter].kind { + AdapterKind::Import { kind, .. } => match kind { + AdapterJsImportKind::Normal => {} + // method/constructors need glue because we either need to + // invoke them as `new` or we need to invoke them with + // method-call syntax to get the `this` parameter right. + AdapterJsImportKind::Method | AdapterJsImportKind::Constructor => return Ok(false), + }, + // This is an adapter-to-adapter call, so it needs a shim. + AdapterKind::Local { .. } => return Ok(false), } - let wasm_ty = self.module.types.get(binding.wasm_ty); - webidl.kind == ast::WebidlFunctionKind::Static - && webidl::outgoing_do_not_require_glue( - &binding.outgoing, - wasm_ty.params(), - &webidl.params, - self.config.wasm_interface_types, - ) - && webidl::incoming_do_not_require_glue( - &binding.incoming, - &webidl.result.into_iter().collect::>(), - wasm_ty.results(), - self.config.wasm_interface_types, - ) - } + // Next up check to make sure that this import is to a bare JS value + // itself, no extra fluff intended. + let js = match &self.aux.import_map[&adapter] { + AuxImport::Value(AuxValue::Bare(js)) => js, + _ => return Ok(false), + }; + + // Make sure this isn't variadic in any way which means we need some + // sort of adapter glue. + if self.aux.imports_with_variadic.contains(&adapter) { + return Ok(false); + } + + // Ensure that every single instruction can be represented without JS + // glue being generated, aka it's covered by the JS ECMAScript bindings + // for wasm. + if !self.representable_without_js_glue(instrs) { + return Ok(false); + } - /// Emit a direct import directive that hooks up the `js` value specified to - /// the wasm import `id`. - fn direct_import(&mut self, id: ImportId, js: &JsImport) -> Result<(), Error> { // If there's no field projection happening here and this is a direct // import from an ES-looking module, then we can actually just hook this // up directly in the wasm file itself. Note that this is covered in the @@ -2203,14 +2158,14 @@ impl<'a> Context<'a> { let import = self.module.imports.get_mut(id); import.module = module.clone(); import.name = name.clone(); - return Ok(()); + return Ok(true); } JsImportName::LocalModule { module, name } => { let module = self.config.local_module_name(module); let import = self.module.imports.get_mut(id); import.module = module; import.name = name.clone(); - return Ok(()); + return Ok(true); } JsImportName::InlineJs { unique_crate_identifier, @@ -2223,7 +2178,7 @@ impl<'a> Context<'a> { let import = self.module.imports.get_mut(id); import.module = module; import.name = name.clone(); - return Ok(()); + return Ok(true); } // Fall through below to requiring a JS shim to create an item @@ -2241,7 +2196,61 @@ impl<'a> Context<'a> { name = name, ); self.wasm_import_definitions.insert(id, js); - Ok(()) + Ok(true) + } + + fn representable_without_js_glue(&self, instrs: &[InstructionData]) -> bool { + use Instruction::*; + let standard_enabled = self.config.wasm_interface_types; + + let mut last_arg = None; + let mut saw_call = false; + for instr in instrs { + match instr.instr { + // Is an adapter section getting emitted? If so, then every + // standard operation is natively supported! + Standard(_) if standard_enabled => {} + + // Fetching arguments is just that, a fetch, so no need for + // glue. Note though that the arguments must be fetched in order + // for this to actually work, + Standard(wit_walrus::Instruction::ArgGet(i)) => { + if saw_call { + return false; + } + match (i, last_arg) { + (0, None) => last_arg = Some(0), + (n, Some(i)) if n == i + 1 => last_arg = Some(n), + _ => return false, + } + } + + // Similarly calling a function is the same as in JS, no glue + // needed. + CallAdapter(_) => saw_call = true, + + // Conversions to wasm integers are always supported since + // they're coerced into i32/f32/f64 appropriately. + Standard(wit_walrus::Instruction::IntToWasm { .. }) => {} + + // Converts from wasm to JS, however, only supports most + // integers. Converting into a u32 isn't supported because we + // need to generate glue to change the sign. + Standard(wit_walrus::Instruction::WasmToInt { + output: wit_walrus::ValType::U32, + .. + }) => return false, + Standard(wit_walrus::Instruction::WasmToInt { .. }) => {} + + // JS spec automatically coerces boolean values to i32 of 0 or 1 + // depending on true/false + I32FromBool => {} + + _ => return false, + } + } + + return true; } /// Generates a JS snippet appropriate for invoking `import`. @@ -2256,14 +2265,12 @@ impl<'a> Context<'a> { /// purpose of `AuxImport`, which depends on the kind of import. fn invoke_import( &mut self, - binding: &Binding, import: &AuxImport, - bindings: &NonstandardWebidlSection, + kind: AdapterJsImportKind, args: &[String], variadic: bool, prelude: &mut String, ) -> Result { - let webidl_ty: &ast::WebidlFunction = bindings.types.get(binding.webidl_ty).unwrap(); let variadic_args = |js_arguments: &[String]| { Ok(if !variadic { format!("{}", js_arguments.join(", ")) @@ -2280,15 +2287,15 @@ impl<'a> Context<'a> { }) }; match import { - AuxImport::Value(val) => match webidl_ty.kind { - ast::WebidlFunctionKind::Constructor => { + AuxImport::Value(val) => match kind { + AdapterJsImportKind::Constructor => { let js = match val { AuxValue::Bare(js) => self.import_name(js)?, _ => bail!("invalid import set for constructor"), }; Ok(format!("new {}({})", js, variadic_args(&args)?)) } - ast::WebidlFunctionKind::Method(_) => { + AdapterJsImportKind::Method => { let descriptor = |anchor: &str, extra: &str, field: &str, which: &str| { format!( "GetOwnOrInheritedPropertyDescriptor({}{}, '{}').{}", @@ -2320,7 +2327,7 @@ impl<'a> Context<'a> { }; Ok(format!("{}.call({})", js, variadic_args(&args)?)) } - ast::WebidlFunctionKind::Static => { + AdapterJsImportKind::Normal => { let js = match val { AuxValue::Bare(js) => self.import_name(js)?, _ => bail!("invalid import set for free function"), @@ -2335,7 +2342,7 @@ impl<'a> Context<'a> { } AuxImport::Instanceof(js) => { - assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static); + assert!(kind == AdapterJsImportKind::Normal); assert!(!variadic); assert_eq!(args.len(), 1); let js = self.import_name(js)?; @@ -2343,7 +2350,7 @@ impl<'a> Context<'a> { } AuxImport::Static(js) => { - assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static); + assert!(kind == AdapterJsImportKind::Normal); assert!(!variadic); assert_eq!(args.len(), 0); self.import_name(js) @@ -2352,10 +2359,10 @@ impl<'a> Context<'a> { AuxImport::Closure { dtor, mutable, - binding_idx, + adapter, nargs, } => { - assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static); + assert!(kind == AdapterJsImportKind::Normal); assert!(!variadic); assert_eq!(args.len(), 3); let arg_names = (0..*nargs) @@ -2370,7 +2377,7 @@ impl<'a> Context<'a> { self.export_function_table()?; let dtor = format!("wasm.__wbg_function_table.get({})", dtor); - let call = format!("__wbg_elem_binding{}", binding_idx); + let call = self.adapter_name(*adapter); if *mutable { // For mutable closures they can't be invoked recursively. @@ -2423,7 +2430,7 @@ impl<'a> Context<'a> { } AuxImport::StructuralMethod(name) => { - assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static); + assert!(kind == AdapterJsImportKind::Normal); let (receiver, args) = match args.split_first() { Some(pair) => pair, None => bail!("structural method calls must have at least one argument"), @@ -2432,14 +2439,14 @@ impl<'a> Context<'a> { } AuxImport::StructuralGetter(field) => { - assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static); + assert!(kind == AdapterJsImportKind::Normal); assert!(!variadic); assert_eq!(args.len(), 1); Ok(format!("{}.{}", args[0], field)) } AuxImport::StructuralClassGetter(class, field) => { - assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static); + assert!(kind == AdapterJsImportKind::Normal); assert!(!variadic); assert_eq!(args.len(), 0); let class = self.import_name(class)?; @@ -2447,14 +2454,14 @@ impl<'a> Context<'a> { } AuxImport::StructuralSetter(field) => { - assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static); + assert!(kind == AdapterJsImportKind::Normal); assert!(!variadic); assert_eq!(args.len(), 2); Ok(format!("{}.{} = {}", args[0], field, args[1])) } AuxImport::StructuralClassSetter(class, field) => { - assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static); + assert!(kind == AdapterJsImportKind::Normal); assert!(!variadic); assert_eq!(args.len(), 1); let class = self.import_name(class)?; @@ -2462,7 +2469,7 @@ impl<'a> Context<'a> { } AuxImport::IndexingGetterOfClass(class) => { - assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static); + assert!(kind == AdapterJsImportKind::Normal); assert!(!variadic); assert_eq!(args.len(), 1); let class = self.import_name(class)?; @@ -2470,14 +2477,14 @@ impl<'a> Context<'a> { } AuxImport::IndexingGetterOfObject => { - assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static); + assert!(kind == AdapterJsImportKind::Normal); assert!(!variadic); assert_eq!(args.len(), 2); Ok(format!("{}[{}]", args[0], args[1])) } AuxImport::IndexingSetterOfClass(class) => { - assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static); + assert!(kind == AdapterJsImportKind::Normal); assert!(!variadic); assert_eq!(args.len(), 2); let class = self.import_name(class)?; @@ -2485,14 +2492,14 @@ impl<'a> Context<'a> { } AuxImport::IndexingSetterOfObject => { - assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static); + assert!(kind == AdapterJsImportKind::Normal); assert!(!variadic); assert_eq!(args.len(), 3); Ok(format!("{}[{}] = {}", args[0], args[1], args[2])) } AuxImport::IndexingDeleterOfClass(class) => { - assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static); + assert!(kind == AdapterJsImportKind::Normal); assert!(!variadic); assert_eq!(args.len(), 1); let class = self.import_name(class)?; @@ -2500,14 +2507,14 @@ impl<'a> Context<'a> { } AuxImport::IndexingDeleterOfObject => { - assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static); + assert!(kind == AdapterJsImportKind::Normal); assert!(!variadic); assert_eq!(args.len(), 2); Ok(format!("delete {}[{}]", args[0], args[1])) } AuxImport::WrapInExportedClass(class) => { - assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static); + assert!(kind == AdapterJsImportKind::Normal); assert!(!variadic); assert_eq!(args.len(), 1); self.require_class_wrap(class); @@ -2515,7 +2522,7 @@ impl<'a> Context<'a> { } AuxImport::Intrinsic(intrinsic) => { - assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static); + assert!(kind == AdapterJsImportKind::Normal); assert!(!variadic); self.invoke_intrinsic(intrinsic, args, prelude) } @@ -2618,26 +2625,15 @@ impl<'a> Context<'a> { } Intrinsic::NumberGet => { - assert_eq!(args.len(), 2); - self.expose_uint8_memory(); + assert_eq!(args.len(), 1); prelude.push_str(&format!("const obj = {};\n", args[0])); - prelude.push_str("if (typeof(obj) === 'number') return obj;\n"); - prelude.push_str(&format!("getUint8Memory()[{}] = 1;\n", args[1])); - "0".to_string() + format!("typeof(obj) === 'number' ? obj : undefined") } Intrinsic::StringGet => { - self.expose_pass_string_to_wasm()?; - self.expose_uint32_memory(); - assert_eq!(args.len(), 2); + assert_eq!(args.len(), 1); prelude.push_str(&format!("const obj = {};\n", args[0])); - prelude.push_str("if (typeof(obj) !== 'string') return 0;\n"); - prelude.push_str("const ptr = passStringToWasm(obj);\n"); - prelude.push_str(&format!( - "getUint32Memory()[{} / 4] = WASM_VECTOR_LEN;\n", - args[1], - )); - "ptr".to_string() + format!("typeof(obj) === 'string' ? obj : undefined") } Intrinsic::BooleanGet => { @@ -2669,7 +2665,19 @@ impl<'a> Context<'a> { Intrinsic::Memory => { assert_eq!(args.len(), 0); - self.memory().to_string() + let mut memories = self.module.memories.iter(); + let memory = memories + .next() + .ok_or_else(|| anyhow!("no memory found to return in memory intrinsic"))? + .id(); + if memories.next().is_some() { + bail!( + "multiple memories found, unsure which to return \ + from memory intrinsic" + ); + } + drop(memories); + format!("wasm.{}", self.export_name_of(memory)) } Intrinsic::FunctionTable => { @@ -2732,16 +2740,22 @@ impl<'a> Context<'a> { } Intrinsic::InitAnyrefTable => { + let table = self + .aux + .anyref_table + .ok_or_else(|| anyhow!("must enable anyref to use anyref intrinsic"))?; + let name = self.export_name_of(table); // Grow the table to insert our initial values, and then also // set the 0th slot to `undefined` since that's what we've // historically used for our ABI which is that the index of 0 // returns `undefined` for types like `None` going out. let mut base = format!( " - const table = wasm.__wbg_anyref_table; + const table = wasm.{}; const offset = table.grow({}); table.set(0, undefined); ", + name, INITIAL_HEAP_VALUES.len(), ); for (i, value) in INITIAL_HEAP_VALUES.iter().enumerate() { @@ -2929,6 +2943,35 @@ impl<'a> Context<'a> { self.module.exports.add("__wbg_function_table", id); Ok(()) } + + fn export_name_of(&mut self, id: impl Into) -> String { + let id = id.into(); + let export = self.module.exports.iter().find(|e| { + use walrus::ExportItem::*; + + match (e.item, id) { + (Function(a), Function(b)) => a == b, + (Table(a), Table(b)) => a == b, + (Memory(a), Memory(b)) => a == b, + (Global(a), Global(b)) => a == b, + _ => false, + } + }); + if let Some(export) = export { + self.required_internal_exports + .insert(export.name.clone().into()); + return export.name.clone(); + } + let name = format!("__wbindgen_export_{}", self.next_export_idx); + self.next_export_idx += 1; + self.module.exports.add(&name, id); + self.required_internal_exports.insert(name.clone().into()); + return name; + } + + fn adapter_name(&self, id: AdapterId) -> String { + format!("__wbg_adapter_{}", id.0) + } } fn check_duplicated_getter_and_setter_names( @@ -3095,3 +3138,14 @@ fn test_generate_identifier() { "default2".to_string() ); } + +struct MemView { + name: &'static str, + num: usize, +} + +impl fmt::Display for MemView { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}{}", self.name, self.num) + } +} diff --git a/crates/cli-support/src/js/outgoing.rs b/crates/cli-support/src/js/outgoing.rs deleted file mode 100644 index eceece77..00000000 --- a/crates/cli-support/src/js/outgoing.rs +++ /dev/null @@ -1,451 +0,0 @@ -//! Implementation of translating a `NonstandardOutgoing` expression to an -//! actual JS shim and code snippet which ensures that bindings behave as we'd -//! expect. - -use crate::descriptor::VectorKind; -use crate::js::binding::JsBuilder; -use crate::js::Context; -use crate::webidl::NonstandardOutgoing; -use anyhow::{bail, Error}; -use wasm_webidl_bindings::ast; - -pub struct Outgoing<'a, 'b> { - cx: &'a mut Context<'b>, - js: &'a mut JsBuilder, -} - -impl<'a, 'b> Outgoing<'a, 'b> { - pub fn new(cx: &'a mut Context<'b>, js: &'a mut JsBuilder) -> Outgoing<'a, 'b> { - Outgoing { cx, js } - } - - pub fn process(&mut self, outgoing: &NonstandardOutgoing) -> Result { - let before = self.js.typescript_len(); - let ret = self.nonstandard(outgoing)?; - assert_eq!(before + 1, self.js.typescript_len()); - Ok(ret) - } - - fn nonstandard(&mut self, outgoing: &NonstandardOutgoing) -> Result { - match outgoing { - NonstandardOutgoing::Standard(expr) => self.standard(expr), - - // Converts the wasm argument, a single code unit, to a string. - NonstandardOutgoing::Char { idx } => { - self.js.typescript_required("string"); - Ok(format!("String.fromCodePoint({})", self.arg(*idx))) - } - - // Just need to wrap up the pointer we get from Rust into a JS type - // and then we can pass that along - NonstandardOutgoing::RustType { class, idx } => { - self.js.typescript_required(class); - self.cx.require_class_wrap(class); - Ok(format!("{}.__wrap({})", class, self.arg(*idx))) - } - - // Just a small wrapper around `getObject` - NonstandardOutgoing::BorrowedAnyref { idx } => { - self.js.typescript_required("any"); - self.cx.expose_get_object(); - Ok(format!("getObject({})", self.arg(*idx))) - } - - // given the low/high bits we get from Rust, store them into a - // temporary 64-bit conversion array and then load the BigInt out of - // it. - NonstandardOutgoing::Number64 { - lo_idx, - hi_idx, - signed, - } => { - self.js.typescript_required("BigInt"); - let f = if *signed { - self.cx.expose_int64_cvt_shim() - } else { - self.cx.expose_uint64_cvt_shim() - }; - let i = self.js.tmp(); - self.js.prelude(&format!( - "\ - u32CvtShim[0] = {low}; - u32CvtShim[1] = {high}; - const n{i} = {f}[0]; - ", - low = self.arg(*lo_idx), - high = self.arg(*hi_idx), - f = f, - i = i, - )); - Ok(format!("n{}", i)) - } - - // Similar to `View` below, except using 64-bit types which don't - // fit into webidl scalar types right now. - NonstandardOutgoing::View64 { - offset, - length, - signed, - } => { - let ptr = self.arg(*offset); - let len = self.arg(*length); - let kind = if *signed { - VectorKind::I64 - } else { - VectorKind::U64 - }; - self.js.typescript_required(kind.js_ty()); - let f = self.cx.expose_get_vector_from_wasm(kind)?; - Ok(format!("{}({}, {})", f, ptr, len)) - } - - // Similar to `View` below, except using anyref types which have - // fancy conversion functions on our end. - NonstandardOutgoing::ViewAnyref { offset, length } => { - let ptr = self.arg(*offset); - let len = self.arg(*length); - self.js.typescript_required(VectorKind::Anyref.js_ty()); - let f = self.cx.expose_get_vector_from_wasm(VectorKind::Anyref)?; - Ok(format!("{}({}, {})", f, ptr, len)) - } - - // Similar to `View` below, except we free the memory in JS right - // now. - // - // TODO: we should free the memory in Rust to allow using standard - // webidl bindings. - NonstandardOutgoing::Vector { - offset, - length, - kind, - } => { - let ptr = self.arg(*offset); - let len = self.arg(*length); - self.js.typescript_required(kind.js_ty()); - let f = self.cx.expose_get_vector_from_wasm(*kind)?; - let i = self.js.tmp(); - self.js - .prelude(&format!("const v{} = {}({}, {}).slice();", i, f, ptr, len)); - self.prelude_free_vector(*offset, *length, *kind)?; - Ok(format!("v{}", i)) - } - - NonstandardOutgoing::CachedString { - offset, - length, - owned, - optional, - } => { - let ptr = self.arg(*offset); - let len = self.arg(*length); - let tmp = self.js.tmp(); - - if *optional { - self.js.typescript_optional("string"); - } else { - self.js.typescript_required("string"); - } - - self.cx.expose_get_cached_string_from_wasm()?; - - self.js.prelude(&format!( - "const v{} = getCachedStringFromWasm({}, {});", - tmp, ptr, len - )); - - if *owned { - self.prelude_free_cached_string(&ptr, &len)?; - } - - Ok(format!("v{}", tmp)) - } - - NonstandardOutgoing::StackClosure { - a, - b, - binding_idx, - nargs, - mutable, - } => { - self.js.typescript_optional("any"); - let i = self.js.tmp(); - self.js.prelude(&format!( - "const state{} = {{a: {}, b: {}}};", - i, - self.arg(*a), - self.arg(*b), - )); - let args = (0..*nargs) - .map(|i| format!("arg{}", i)) - .collect::>() - .join(", "); - if *mutable { - // Mutable closures need protection against being called - // recursively, so ensure that we clear out one of the - // internal pointers while it's being invoked. - self.js.prelude(&format!( - "const cb{i} = ({args}) => {{ - const a = state{i}.a; - state{i}.a = 0; - try {{ - return __wbg_elem_binding{idx}(a, state{i}.b, {args}); - }} finally {{ - state{i}.a = a; - }} - }};", - i = i, - args = args, - idx = binding_idx, - )); - } else { - self.js.prelude(&format!( - "const cb{i} = ({args}) => __wbg_elem_binding{idx}(state{i}.a, state{i}.b, {args});", - i = i, - args = args, - idx = binding_idx, - )); - } - - // Make sure to null out our internal pointers when we return - // back to Rust to ensure that any lingering references to the - // closure will fail immediately due to null pointers passed in - // to Rust. - self.js.finally(&format!("state{}.a = state{0}.b = 0;", i)); - Ok(format!("cb{}", i)) - } - - NonstandardOutgoing::OptionBool { idx } => { - self.js.typescript_optional("boolean"); - Ok(format!( - "{0} === 0xFFFFFF ? undefined : {0} !== 0", - self.arg(*idx) - )) - } - - NonstandardOutgoing::OptionChar { idx } => { - self.js.typescript_optional("string"); - Ok(format!( - "{0} === 0xFFFFFF ? undefined : String.fromCodePoint({0})", - self.arg(*idx) - )) - } - - NonstandardOutgoing::OptionIntegerEnum { idx, hole } => { - self.js.typescript_optional("number"); - Ok(format!( - "{0} === {1} ? undefined : {0}", - self.arg(*idx), - hole - )) - } - - NonstandardOutgoing::OptionRustType { class, idx } => { - self.cx.require_class_wrap(class); - self.js.typescript_optional(class); - Ok(format!( - "{0} === 0 ? undefined : {1}.__wrap({0})", - self.arg(*idx), - class, - )) - } - - NonstandardOutgoing::OptionU32Sentinel { idx } => { - self.js.typescript_optional("number"); - Ok(format!( - "{0} === 0xFFFFFF ? undefined : {0}", - self.arg(*idx) - )) - } - - NonstandardOutgoing::OptionNative { - signed, - present, - val, - } => { - self.js.typescript_optional("number"); - Ok(format!( - "{} === 0 ? undefined : {}{}", - self.arg(*present), - self.arg(*val), - if *signed { "" } else { " >>> 0" }, - )) - } - - NonstandardOutgoing::OptionInt64 { - present, - _ignored, - lo, - hi, - signed, - } => { - self.js.typescript_optional("BigInt"); - let f = if *signed { - self.cx.expose_int64_cvt_shim() - } else { - self.cx.expose_uint64_cvt_shim() - }; - let i = self.js.tmp(); - self.js.prelude(&format!( - " - u32CvtShim[0] = {low}; - u32CvtShim[1] = {high}; - const n{i} = {present} === 0 ? undefined : {f}[0]; - ", - present = self.arg(*present), - low = self.arg(*lo), - high = self.arg(*hi), - f = f, - i = i, - )); - Ok(format!("n{}", i)) - } - - NonstandardOutgoing::OptionSlice { - kind, - offset, - length, - } => { - let ptr = self.arg(*offset); - let len = self.arg(*length); - self.js.typescript_optional(kind.js_ty()); - let f = self.cx.expose_get_vector_from_wasm(*kind)?; - Ok(format!( - "{ptr} === 0 ? undefined : {f}({ptr}, {len})", - ptr = ptr, - len = len, - f = f - )) - } - - NonstandardOutgoing::OptionVector { - offset, - length, - kind, - } => { - let ptr = self.arg(*offset); - let len = self.arg(*length); - self.js.typescript_optional(kind.js_ty()); - let f = self.cx.expose_get_vector_from_wasm(*kind)?; - let i = self.js.tmp(); - self.js.prelude(&format!("let v{};", i)); - self.js.prelude(&format!("if ({} !== 0) {{", ptr)); - self.js - .prelude(&format!("v{} = {}({}, {}).slice();", i, f, ptr, len)); - self.prelude_free_vector(*offset, *length, *kind)?; - self.js.prelude("}"); - Ok(format!("v{}", i)) - } - } - } - - /// Evaluates the `standard` binding expression, returning the JS expression - /// needed to evaluate the binding. - fn standard(&mut self, standard: &ast::OutgoingBindingExpression) -> Result { - match standard { - ast::OutgoingBindingExpression::As(expr) => match expr.ty { - ast::WebidlTypeRef::Scalar(ast::WebidlScalarType::Any) => { - self.js.typescript_required("any"); - if self.cx.config.anyref { - Ok(self.arg(expr.idx)) - } else { - self.cx.expose_take_object(); - Ok(format!("takeObject({})", self.arg(expr.idx))) - } - } - ast::WebidlTypeRef::Scalar(ast::WebidlScalarType::Boolean) => { - self.js.typescript_required("boolean"); - Ok(format!("{} !== 0", self.arg(expr.idx))) - } - ast::WebidlTypeRef::Scalar(ast::WebidlScalarType::UnsignedLong) => { - self.js.typescript_required("number"); - Ok(format!("{} >>> 0", self.arg(expr.idx))) - } - _ => { - self.js.typescript_required("number"); - Ok(self.arg(expr.idx)) - } - }, - ast::OutgoingBindingExpression::View(view) => { - // TODO: deduplicate with same match statement in incoming - // bindings - let scalar = match view.ty { - ast::WebidlTypeRef::Scalar(s) => s, - ast::WebidlTypeRef::Id(_) => { - bail!("unsupported type passed to `view` in webidl binding") - } - }; - let kind = match scalar { - ast::WebidlScalarType::Int8Array => VectorKind::I8, - ast::WebidlScalarType::Uint8Array => VectorKind::U8, - ast::WebidlScalarType::Uint8ClampedArray => VectorKind::ClampedU8, - ast::WebidlScalarType::Int16Array => VectorKind::I16, - ast::WebidlScalarType::Uint16Array => VectorKind::U16, - ast::WebidlScalarType::Int32Array => VectorKind::I32, - ast::WebidlScalarType::Uint32Array => VectorKind::U32, - ast::WebidlScalarType::Float32Array => VectorKind::F32, - ast::WebidlScalarType::Float64Array => VectorKind::F64, - _ => bail!("unsupported type passed to `view`: {:?}", scalar), - }; - self.js.typescript_required(kind.js_ty()); - let ptr = self.arg(view.offset); - let len = self.arg(view.length); - let f = self.cx.expose_get_vector_from_wasm(kind)?; - Ok(format!("{}({}, {})", f, ptr, len)) - } - - ast::OutgoingBindingExpression::Utf8Str(expr) => { - assert_eq!(expr.ty, ast::WebidlScalarType::DomString.into()); - self.js.typescript_required("string"); - let ptr = self.arg(expr.offset); - let len = self.arg(expr.length); - self.cx.expose_get_string_from_wasm()?; - Ok(format!("getStringFromWasm({}, {})", ptr, len)) - } - - ast::OutgoingBindingExpression::Utf8CStr(_) => { - bail!("unsupported `utf8-cstr` found in outgoing webidl bindings"); - } - ast::OutgoingBindingExpression::I32ToEnum(_) => { - bail!("unsupported `i32-to-enum` found in outgoing webidl bindings"); - } - ast::OutgoingBindingExpression::Copy(_) => { - bail!("unsupported `copy` found in outgoing webidl bindings"); - } - ast::OutgoingBindingExpression::Dict(_) => { - bail!("unsupported `dict` found in outgoing webidl bindings"); - } - ast::OutgoingBindingExpression::BindExport(_) => { - bail!("unsupported `bind-export` found in outgoing webidl bindings"); - } - } - } - - fn arg(&self, idx: u32) -> String { - self.js.arg(idx).to_string() - } - - fn prelude_free_vector( - &mut self, - offset: u32, - length: u32, - kind: VectorKind, - ) -> Result<(), Error> { - self.js.prelude(&format!( - "wasm.__wbindgen_free({0}, {1} * {size});", - self.arg(offset), - self.arg(length), - size = kind.size(), - )); - self.cx.require_internal_export("__wbindgen_free") - } - - fn prelude_free_cached_string(&mut self, ptr: &str, len: &str) -> Result<(), Error> { - self.js.prelude(&format!( - "if ({ptr} !== 0) {{ wasm.__wbindgen_free({ptr}, {len}); }}", - ptr = ptr, - len = len, - )); - - self.cx.require_internal_export("__wbindgen_free") - } -} diff --git a/crates/cli-support/src/lib.rs b/crates/cli-support/src/lib.rs index 20ee1e38..e3313998 100755 --- a/crates/cli-support/src/lib.rs +++ b/crates/cli-support/src/lib.rs @@ -16,8 +16,9 @@ mod descriptor; mod descriptors; mod intrinsic; mod js; +mod multivalue; pub mod wasm2es6js; -mod webidl; +mod wit; pub struct Bindgen { input: Input, @@ -268,7 +269,7 @@ impl Bindgen { .generate_dwarf(self.keep_debug) .generate_name_section(!self.remove_name_section) .generate_producers_section(!self.remove_producers_section) - .on_parse(wasm_webidl_bindings::binary::on_parse) + .on_parse(wit_walrus::on_parse) .parse(&contents) .context("failed to parse input file as wasm")?; let stem = match &self.out_name { @@ -329,11 +330,11 @@ impl Bindgen { // Process and remove our raw custom sections emitted by the // #[wasm_bindgen] macro and the compiler. In their stead insert a - // forward-compatible WebIDL bindings section (forward-compatible with - // the webidl bindings proposal) as well as an auxiliary section for all - // sorts of miscellaneous information and features #[wasm_bindgen] - // supports that aren't covered by WebIDL bindings. - webidl::process( + // forward-compatible wasm interface types section as well as an + // auxiliary section for all sorts of miscellaneous information and + // features #[wasm_bindgen] supports that aren't covered by wasm + // interface types. + wit::process( &mut module, self.anyref, self.wasm_interface_types, @@ -346,34 +347,32 @@ impl Bindgen { // currently off-by-default since `anyref` is still in development in // engines. if self.anyref { - anyref::process(&mut module, self.wasm_interface_types)?; + anyref::process(&mut module)?; } let aux = module .customs - .delete_typed::() + .delete_typed::() .expect("aux section should be present"); - let mut bindings = module + let mut adapters = module .customs - .delete_typed::() + .delete_typed::() .unwrap(); // Now that our module is massaged and good to go, feed it into the JS // shim generation which will actually generate JS for all this. let (npm_dependencies, (js, ts)) = { - let mut cx = js::Context::new(&mut module, self)?; - cx.generate(&aux, &bindings)?; + let mut cx = js::Context::new(&mut module, self, &adapters, &aux)?; + cx.generate()?; let npm_dependencies = cx.npm_dependencies.clone(); (npm_dependencies, cx.finalize(stem)?) }; if self.wasm_interface_types { - if self.multi_value { - webidl::standard::add_multi_value(&mut module, &mut bindings) - .context("failed to transform return pointers into multi-value Wasm")?; - } - webidl::standard::add_section(&mut module, &aux, &bindings) - .with_context(|| "failed to generate a standard wasm bindings custom section")?; + multivalue::run(&mut module, &mut adapters) + .context("failed to transform return pointers into multi-value Wasm")?; + wit::section::add(&mut module, &aux, &adapters) + .context("failed to generate a standard wasm bindings custom section")?; } else { if self.multi_value { anyhow::bail!( diff --git a/crates/cli-support/src/multivalue.rs b/crates/cli-support/src/multivalue.rs new file mode 100644 index 00000000..6e2f866c --- /dev/null +++ b/crates/cli-support/src/multivalue.rs @@ -0,0 +1,77 @@ +use crate::wit::{Adapter, NonstandardWitSection}; +use crate::wit::{AdapterKind, Instruction}; +use anyhow::Error; +use walrus::Module; +use wasm_bindgen_multi_value_xform as multi_value_xform; +use wasm_bindgen_wasm_conventions as wasm_conventions; + +pub fn run(module: &mut Module, adapters: &mut NonstandardWitSection) -> Result<(), Error> { + let mut to_xform = Vec::new(); + let mut slots = Vec::new(); + + for (_, adapter) in adapters.adapters.iter_mut() { + extract_xform(adapter, &mut to_xform, &mut slots); + } + if to_xform.is_empty() { + // Early exit to avoid failing if we don't have a memory or shadow stack + // pointer because this is a minimal module that doesn't use linear + // memory. + return Ok(()); + } + + let shadow_stack_pointer = wasm_conventions::get_shadow_stack_pointer(module)?; + let memory = wasm_conventions::get_memory(module)?; + let wrappers = multi_value_xform::run(module, memory, shadow_stack_pointer, &to_xform)?; + + for (slot, id) in slots.into_iter().zip(wrappers) { + *slot = id; + } + + Ok(()) +} + +fn extract_xform<'a>( + adapter: &'a mut Adapter, + to_xform: &mut Vec<(walrus::FunctionId, usize, Vec)>, + slots: &mut Vec<&'a mut walrus::FunctionId>, +) { + let instructions = match &mut adapter.kind { + AdapterKind::Local { instructions } => instructions, + AdapterKind::Import { .. } => return, + }; + + // If the first instruction is a `Retptr`, then this must be an exported + // adapter which calls a wasm-defined function. Something we'd like to + // adapt to multi-value! + if let Some(Instruction::Retptr) = instructions.first().map(|e| &e.instr) { + instructions.remove(0); + let mut types = Vec::new(); + instructions.retain(|instruction| match instruction.instr { + Instruction::LoadRetptr { ty, .. } => { + types.push(ty.to_wasm().unwrap()); + false + } + _ => true, + }); + let id = instructions + .iter_mut() + .filter_map(|i| match &mut i.instr { + Instruction::Standard(wit_walrus::Instruction::CallCore(f)) => Some(f), + _ => None, + }) + .next() + .expect("should have found call-core"); + + // LLVM currently always uses the first parameter for the return + // pointer. We hard code that here, since we have no better option. + to_xform.push((*id, 0, types)); + slots.push(id); + return; + } + + // If the last instruction is a `StoreRetptr`, then this must be an adapter + // which calls an imported function. + // + // FIXME(#1872) handle this + // if let Some(Instruction::StoreRetptr { .. }) = instructions.last() {} +} diff --git a/crates/cli-support/src/webidl/bindings.rs b/crates/cli-support/src/webidl/bindings.rs deleted file mode 100644 index cc6db92f..00000000 --- a/crates/cli-support/src/webidl/bindings.rs +++ /dev/null @@ -1,250 +0,0 @@ -//! Location where `Binding` structures are actually created. -//! -//! This module is tasked with converting `Descriptor::Function` instances to -//! `Binding`s. It uses the incoming/outgoing modules/builders to do most of the -//! heavy lifting, and then this is the glue around the edges to make sure -//! everything is processed, hooked up in the second, and then inserted into the -//! right map. -//! -//! This module is called from `src/webidl/mod.rs` exclusively to populate the -//! imports/exports/elements of the bindings section. Most of this module is -//! largely just connecting the dots! - -use crate::descriptor::Function; -use crate::webidl::incoming::IncomingBuilder; -use crate::webidl::outgoing::OutgoingBuilder; -use crate::webidl::{Binding, NonstandardWebidlSection}; -use anyhow::{format_err, Error}; -use walrus::{FunctionId, Module, ValType}; -use wasm_webidl_bindings::ast; - -/// Adds an element to the `bindings.imports` map for the `import` specified -/// that is supposed to have the signature specified in `binding`. This also -/// expects that the imported item is called as `kind`. -pub fn register_import( - module: &mut Module, - bindings: &mut NonstandardWebidlSection, - import: walrus::ImportId, - binding: Function, - kind: ast::WebidlFunctionKind, -) -> Result<(), Error> { - let import = module.imports.get(import); - let id = match import.kind { - walrus::ImportKind::Function(f) => f, - _ => unreachable!(), - }; - let import_id = import.id(); - - // Process the return value first to determine if we need a return pointer - // since that is always the first argument. - let mut incoming = IncomingBuilder::default(); - incoming.process(&binding.ret)?; - - // Next process all outgoing arguments, and configure the module/bindings - // section to be available to the builder so we can recursively register - // stack closures. - let mut outgoing = OutgoingBuilder::default(); - outgoing.module = Some(module); - outgoing.bindings_section = Some(bindings); - if incoming.wasm.len() > 1 { - outgoing.process_retptr(); - } - for arg in binding.arguments.iter() { - outgoing.process(arg)?; - } - - // A bit of destructuring to kill the borrow that the outgoing builder has - // on the module/bindings. - let OutgoingBuilder { - wasm: outgoing_wasm, - webidl: outgoing_webidl, - bindings: outgoing_bindings, - .. - } = outgoing; - - // Boilerplate to assemble the `webidl_ty` and `wasm_ty` values. - let webidl_ty = webidl_ty( - &mut bindings.types, - kind, - &outgoing_webidl, - &incoming.webidl, - ); - let (wasm_ty, return_via_outptr) = - assert_signature_match(module, id, &outgoing_wasm, &incoming.wasm); - - // ... and finally insert it into our map! - bindings.imports.insert( - import_id, - Binding { - return_via_outptr, - wasm_ty, - incoming: incoming.bindings, - outgoing: outgoing_bindings, - webidl_ty, - }, - ); - Ok(()) -} - -/// Adds an element to `bindings.exports` for the `export` specified to have the -/// `binding` given. -pub fn register_export( - module: &mut Module, - bindings: &mut NonstandardWebidlSection, - export: walrus::ExportId, - binding: Function, -) -> Result<(), Error> { - let export = module.exports.get(export); - let id = match export.item { - walrus::ExportItem::Function(f) => f, - _ => unreachable!(), - }; - let export_id = export.id(); - // Do the actual heavy lifting elsewhere to generate the `binding`. - let binding = register_wasm_export(module, bindings, id, binding)?; - bindings.exports.insert(export_id, binding); - Ok(()) -} - -/// Like `register_export` except registers a binding for a table element. In -/// this case ensures that the table element `idx` is specified to have the -/// `binding` signature specified, eventually updating `bindings.elems` list. -/// -/// Returns the index of the item added in the `bindings.elems` list. -pub fn register_table_element( - module: &mut Module, - bindings: &mut NonstandardWebidlSection, - idx: u32, - binding: Function, -) -> Result { - let table = module - .tables - .main_function_table()? - .ok_or_else(|| format_err!("no function table found"))?; - let table = module.tables.get(table); - let functions = match &table.kind { - walrus::TableKind::Function(f) => f, - _ => unreachable!(), - }; - let id = functions.elements[idx as usize].unwrap(); - let ret = bindings.elems.len() as u32; - // like above, largely just defer the work elsewhere - let binding = register_wasm_export(module, bindings, id, binding)?; - bindings.elems.push((idx, binding)); - Ok(ret) -} - -/// Common routine to create a `Binding` for an exported wasm function, using -/// incoming arguments and an outgoing return value. -fn register_wasm_export( - module: &mut Module, - bindings: &mut NonstandardWebidlSection, - id: walrus::FunctionId, - binding: Function, -) -> Result { - // Like imports, process the return value first to determine if we need a - // return pointer - let mut outgoing = OutgoingBuilder::default(); - outgoing.process(&binding.ret)?; - - // Afterwards process all arguments... - let mut incoming = IncomingBuilder::default(); - if outgoing.wasm.len() > 1 { - incoming.process_retptr(); - } - for arg in binding.arguments.iter() { - incoming.process(arg)?; - } - - // ... do similar boilerplate to imports (but with incoming/outgoing - // swapped) to produce some types ... - let webidl_ty = webidl_ty( - &mut bindings.types, - ast::WebidlFunctionKind::Static, - &incoming.webidl, - &outgoing.webidl, - ); - let (wasm_ty, return_via_outptr) = - assert_signature_match(module, id, &incoming.wasm, &outgoing.wasm); - - // ... and there's our `Binding`! - Ok(Binding { - wasm_ty, - incoming: incoming.bindings, - outgoing: outgoing.bindings, - webidl_ty, - return_via_outptr, - }) -} - -/// Asserts that the `params` and `results` we've determined from an -/// incoming/outgoing builder actually matches the signature of `id` in the -/// `module` provided. This is a somewhat loose comparison since `anyref` in the -/// expected lists will be present as `i32` in the actual module due to rustc -/// limitations. -/// -/// This at the end manufactures an actual `walrus::Type` that will be used to -/// describe a WebIDL value. This manufactured value actually has `anyref` types -/// in it and also respects the out ptr ABI that we currently use to handle -/// multiple-value returns. -fn assert_signature_match( - module: &mut Module, - id: FunctionId, - params: &[ValType], - mut results: &[ValType], -) -> (walrus::TypeId, Option>) { - let ty = module.funcs.get(id).ty(); - let ty = module.types.get(ty); - - fn assert_eq(expected: ValType, actual: ValType) { - match expected { - ValType::Anyref => assert_eq!(actual, ValType::I32), - _ => assert_eq!(expected, actual), - } - } - let mut ret_outptr = None; - - match results.len() { - 0 => assert_eq!(ty.results().len(), 0), - 1 => assert_eq(results[0], ty.results()[0]), - - // multi value isn't supported yet so all aggregate returns are done - // through an outptr as the first argument. This means that our - // signature should have no results. The new signature we create will - // also have no results. - _ => { - assert_eq!(ty.results().len(), 0); - ret_outptr = Some(results.to_vec()); - results = &[]; - } - } - - let mut iter = params.iter(); - for actual in ty.params().iter() { - let expected = iter.next().unwrap(); - assert_eq(*expected, *actual); - } - assert!(iter.next().is_none()); - - (module.types.add(params, results), ret_outptr) -} - -// boilerplate to convert arguments to a `WebidlFunctionId`. -fn webidl_ty( - types: &mut ast::WebidlTypes, - kind: ast::WebidlFunctionKind, - params: &[ast::WebidlScalarType], - results: &[ast::WebidlScalarType], -) -> ast::WebidlFunctionId { - let result = match results.len() { - 0 => None, - 1 => Some(results[0].into()), - _ => panic!("too many results in a webidl return value"), - }; - let func = ast::WebidlFunction { - kind, - params: params.iter().cloned().map(|x| x.into()).collect(), - result, - }; - types.insert(func) -} diff --git a/crates/cli-support/src/webidl/incoming.rs b/crates/cli-support/src/webidl/incoming.rs deleted file mode 100644 index 24791914..00000000 --- a/crates/cli-support/src/webidl/incoming.rs +++ /dev/null @@ -1,492 +0,0 @@ -//! Nonstandard and wasm-bindgen specific definition of incoming bindings to a -//! wasm module. -//! -//! This module provides a builder which is used to translate Rust types (aka a -//! `Descriptor`) to a `NonstandardIncoming` definition which describes how the -//! JS type is converted into a Rust type. We try to use standard webidl -//! bindings as much as possible, but we have quite a few other bindings which -//! require custom code and shims currently still. -//! -//! Note that the mirror operation, going from WebAssembly to JS, is found in -//! the `outgoing.rs` module. - -use crate::descriptor::{Descriptor, VectorKind}; -use anyhow::{bail, format_err, Error}; -use walrus::ValType; -use wasm_webidl_bindings::ast; - -/// A list of all incoming bindings from JS to WebAssembly that wasm-bindgen -/// will take advantage of. -#[derive(Debug, Clone)] -pub enum NonstandardIncoming { - /// This is a standard vanilla incoming binding. When WebIDL bindings are - /// implemented, this can be used as-is. - Standard(ast::IncomingBindingExpression), - - /// JS is passing a `BigInt` to Rust. - Int64 { - val: ast::IncomingBindingExpression, - /// Whether it's a `u64` or `i64` in Rust. - signed: bool, - }, - - /// JS is passing a `BigInt64Array` or `BigUint64Array` to Rust - /// - /// A copy of the array needs to be made into the Rust address space. - AllocCopyInt64 { - alloc_func_name: String, - expr: Box, - /// Whether or not this is for &[u64] or &[i64] - signed: bool, - }, - - /// JS is passing an array of anyref values into Rust, and all the values - /// need to be copied in. - AllocCopyAnyrefArray { - alloc_func_name: String, - expr: Box, - }, - - /// A mutable slice of values going from JS to Rust, and after Rust finishes - /// the JS slice is updated with the current value of the slice. - MutableSlice { - kind: VectorKind, - val: ast::IncomingBindingExpression, - }, - - /// This is either a slice or `undefined` being passed into Rust. - OptionSlice { - kind: VectorKind, - val: ast::IncomingBindingExpression, - mutable: bool, - }, - - /// This is either a vector or `undefined` being passed into Rust. - OptionVector { - kind: VectorKind, - val: ast::IncomingBindingExpression, - }, - - /// Not actually used for `JsValue` but used for imported types, this is - /// either `undefined` or the imported type getting passed into Rust. - OptionAnyref { val: ast::IncomingBindingExpression }, - - /// An optional "native type" which includes i32/u32/f32/f64, all of which - /// require a discriminant. - OptionNative { val: ast::IncomingBindingExpression }, - - /// An optional integer type which uses an 0xffffff sentinel value for - /// "none" - OptionU32Sentinel { val: ast::IncomingBindingExpression }, - - /// An optional boolean using a special ABI for communicating `undefined` - OptionBool { val: ast::IncomingBindingExpression }, - - /// An optional `char` which uses an ABI where `undefined` is a hole in the - /// range of valid values for a `char` in Rust. Note that in JS a string is - /// passed in. - OptionChar { val: ast::IncomingBindingExpression }, - - /// An optional integral enum where `undefined` is the hole specified. - OptionIntegerEnum { - val: ast::IncomingBindingExpression, - hole: u32, - }, - - /// An optional `BigInt`. - OptionInt64 { - val: ast::IncomingBindingExpression, - signed: bool, - }, - - /// An optional Rust-based type which internally has a pointer that's - /// wrapped up in a JS class. This transfers ownership from JS to Rust. - RustType { - class: String, - val: ast::IncomingBindingExpression, - }, - - /// A reference to a Rust-based type where Rust won't take ownership of the - /// value, it just has a temporary borrow on the input. - RustTypeRef { - class: String, - val: ast::IncomingBindingExpression, - }, - - /// An optional owned Rust type being transferred from JS to Rust. - OptionRustType { - class: String, - val: ast::IncomingBindingExpression, - }, - - /// A string from JS where the first character goes through to Rust. - Char { val: ast::IncomingBindingExpression }, - - /// An arbitrary `anyref` being passed into Rust, but explicitly one that's - /// borrowed and doesn't need to be persisted in a heap table. - BorrowedAnyref { val: ast::IncomingBindingExpression }, -} - -/// Builder used to create a incomig binding from a `Descriptor`. -#[derive(Default)] -pub struct IncomingBuilder { - /// The wasm types that needs to be used to represent all the descriptors in - /// Rust. - pub wasm: Vec, - /// The WebIDL scalar types which match what JS will be providing. - pub webidl: Vec, - /// The list of bindings necessary to connect `wasm` to `webidl` above. - pub bindings: Vec, -} - -impl IncomingBuilder { - /// Adds an initial argument which is passed through verbatim, currently - /// used to handle return pointers in Rust. - pub fn process_retptr(&mut self) { - self.number(ValType::I32, ast::WebidlScalarType::Long); - } - - /// Process a `Descriptor` as if it's being passed from JS to Rust. This - /// will skip `Unit` and otherwise internally add a `NonstandardIncoming` - /// binding necessary for the descriptor. - pub fn process(&mut self, arg: &Descriptor) -> Result<(), Error> { - if let Descriptor::Unit = arg { - return Ok(()); - } - // This is a wrapper around `_process` to have a number of sanity checks - // that we don't forget things. We should always produce at least one - // wasm arge and exactly one webidl arg. Additionally the number of - // bindings should always match the number of webidl types for now. - assert_eq!(self.webidl.len(), self.bindings.len()); - let wasm_before = self.wasm.len(); - let webidl_before = self.webidl.len(); - self._process(arg)?; - assert_eq!(self.webidl.len(), self.bindings.len()); - assert_eq!(webidl_before + 1, self.webidl.len()); - assert!(wasm_before < self.wasm.len()); - Ok(()) - } - - fn _process(&mut self, arg: &Descriptor) -> Result<(), Error> { - match arg { - Descriptor::Boolean => { - let binding = self.expr_as(ValType::I32); - self.wasm.push(ValType::I32); - self.webidl.push(ast::WebidlScalarType::Boolean); - self.bindings.push(NonstandardIncoming::Standard(binding)); - } - Descriptor::Char => { - let expr = self.expr_get(); - self.wasm.push(ValType::I32); - self.webidl.push(ast::WebidlScalarType::DomString); - self.bindings.push(NonstandardIncoming::Char { val: expr }); - } - Descriptor::Anyref => { - let expr = self.expr_as(ValType::Anyref); - self.wasm.push(ValType::Anyref); - self.webidl.push(ast::WebidlScalarType::Any); - self.bindings.push(NonstandardIncoming::Standard(expr)); - } - Descriptor::RustStruct(class) => { - let expr = self.expr_get(); - self.wasm.push(ValType::I32); - self.webidl.push(ast::WebidlScalarType::Any); - self.bindings.push(NonstandardIncoming::RustType { - val: expr, - class: class.to_string(), - }); - } - Descriptor::I8 => self.number(ValType::I32, ast::WebidlScalarType::Byte), - Descriptor::U8 => self.number(ValType::I32, ast::WebidlScalarType::Octet), - Descriptor::I16 => self.number(ValType::I32, ast::WebidlScalarType::Short), - Descriptor::U16 => self.number(ValType::I32, ast::WebidlScalarType::UnsignedShort), - Descriptor::I32 => self.number(ValType::I32, ast::WebidlScalarType::Long), - Descriptor::U32 => self.number(ValType::I32, ast::WebidlScalarType::UnsignedLong), - Descriptor::I64 => self.number64(true), - Descriptor::U64 => self.number64(false), - Descriptor::F32 => self.number(ValType::F32, ast::WebidlScalarType::Float), - Descriptor::F64 => self.number(ValType::F64, ast::WebidlScalarType::Double), - Descriptor::Enum { .. } => self.number(ValType::I32, ast::WebidlScalarType::Long), - Descriptor::Ref(d) => self.process_ref(false, d)?, - Descriptor::RefMut(d) => self.process_ref(true, d)?, - Descriptor::Option(d) => self.process_option(d)?, - - Descriptor::String | Descriptor::CachedString | Descriptor::Vector(_) => { - let kind = arg.vector_kind().ok_or_else(|| { - format_err!("unsupported argument type for calling Rust function from JS {:?}", arg) - })? ; - self.wasm.extend(&[ValType::I32; 2]); - self.alloc_copy_kind(kind) - } - - // Can't be passed from JS to Rust yet - Descriptor::Function(_) | - Descriptor::Closure(_) | - - // Always behind a `Ref` - Descriptor::Slice(_) => bail!( - "unsupported argument type for calling Rust function from JS: {:?}", - arg - ), - - // nothing to do - Descriptor::Unit => {} - - // Largely synthetic and can't show up - Descriptor::ClampedU8 => unreachable!(), - } - Ok(()) - } - - fn process_ref(&mut self, mutable: bool, arg: &Descriptor) -> Result<(), Error> { - match arg { - Descriptor::RustStruct(class) => { - let expr = self.expr_get(); - self.wasm.push(ValType::I32); - self.webidl.push(ast::WebidlScalarType::Any); - self.bindings.push(NonstandardIncoming::RustTypeRef { - val: expr, - class: class.to_string(), - }); - } - Descriptor::Anyref => { - let expr = self.expr_get(); - self.wasm.push(ValType::Anyref); - self.webidl.push(ast::WebidlScalarType::Any); - self.bindings - .push(NonstandardIncoming::BorrowedAnyref { val: expr }); - } - Descriptor::String | Descriptor::CachedString | Descriptor::Slice(_) => { - let kind = arg.vector_kind().ok_or_else(|| { - format_err!( - "unsupported slice type for calling Rust function from JS {:?}", - arg - ) - })?; - self.wasm.extend(&[ValType::I32; 2]); - if mutable { - self.bindings.push(NonstandardIncoming::MutableSlice { - kind, - val: self.expr_get(), - }); - self.webidl.push(ast::WebidlScalarType::Any); - } else { - self.alloc_copy_kind(kind) - } - } - _ => bail!( - "unsupported reference argument type for calling Rust function from JS: {:?}", - arg - ), - } - Ok(()) - } - - fn process_option(&mut self, arg: &Descriptor) -> Result<(), Error> { - match arg { - Descriptor::Anyref => { - self.wasm.push(ValType::I32); - self.bindings.push(NonstandardIncoming::OptionAnyref { - val: self.expr_get(), - }); - self.webidl.push(ast::WebidlScalarType::Any); - } - Descriptor::I8 => self.option_sentinel(), - Descriptor::U8 => self.option_sentinel(), - Descriptor::I16 => self.option_sentinel(), - Descriptor::U16 => self.option_sentinel(), - Descriptor::I32 => self.option_native(ValType::I32), - Descriptor::U32 => self.option_native(ValType::I32), - Descriptor::F32 => self.option_native(ValType::F32), - Descriptor::F64 => self.option_native(ValType::F64), - Descriptor::I64 | Descriptor::U64 => { - let expr = self.expr_get(); - let signed = match arg { - Descriptor::I64 => true, - _ => false, - }; - self.wasm.extend(&[walrus::ValType::I32; 4]); - self.webidl.push(ast::WebidlScalarType::Any); - self.bindings - .push(NonstandardIncoming::OptionInt64 { val: expr, signed }); - } - Descriptor::Boolean => { - let expr = self.expr_get(); - self.wasm.push(walrus::ValType::I32); - self.webidl.push(ast::WebidlScalarType::Any); - self.bindings - .push(NonstandardIncoming::OptionBool { val: expr }); - } - Descriptor::Char => { - let expr = self.expr_get(); - self.wasm.push(walrus::ValType::I32); - self.webidl.push(ast::WebidlScalarType::Any); - self.bindings - .push(NonstandardIncoming::OptionChar { val: expr }); - } - Descriptor::Enum { hole } => { - let expr = self.expr_get(); - self.wasm.push(walrus::ValType::I32); - self.webidl.push(ast::WebidlScalarType::Any); - self.bindings.push(NonstandardIncoming::OptionIntegerEnum { - val: expr, - hole: *hole, - }); - } - Descriptor::RustStruct(name) => { - let expr = self.expr_get(); - self.wasm.push(walrus::ValType::I32); - self.webidl.push(ast::WebidlScalarType::Any); - self.bindings.push(NonstandardIncoming::OptionRustType { - val: expr, - class: name.to_string(), - }); - } - - Descriptor::Ref(_) | Descriptor::RefMut(_) => { - let mutable = match arg { - Descriptor::Ref(_) => false, - _ => true, - }; - let kind = arg.vector_kind().ok_or_else(|| { - format_err!( - "unsupported optional slice type for calling Rust function from JS {:?}", - arg - ) - })?; - self.bindings.push(NonstandardIncoming::OptionSlice { - kind, - val: self.expr_get(), - mutable, - }); - self.wasm.extend(&[ValType::I32; 2]); - self.webidl.push(ast::WebidlScalarType::Any); - } - - Descriptor::String | Descriptor::CachedString | Descriptor::Vector(_) => { - let kind = arg.vector_kind().ok_or_else(|| { - format_err!( - "unsupported optional slice type for calling Rust function from JS {:?}", - arg - ) - })?; - self.bindings.push(NonstandardIncoming::OptionVector { - kind, - val: self.expr_get(), - }); - self.wasm.extend(&[ValType::I32; 2]); - self.webidl.push(ast::WebidlScalarType::Any); - } - - _ => bail!( - "unsupported optional argument type for calling Rust function from JS: {:?}", - arg - ), - } - Ok(()) - } - - fn expr_get(&self) -> ast::IncomingBindingExpression { - let idx = self.webidl.len() as u32; - ast::IncomingBindingExpressionGet { idx }.into() - } - - fn expr_as(&self, ty: ValType) -> ast::IncomingBindingExpression { - ast::IncomingBindingExpressionAs { - ty, - expr: Box::new(self.expr_get()), - } - .into() - } - - fn alloc_func_name(&self) -> String { - "__wbindgen_malloc".to_string() - } - - fn alloc_copy_kind(&mut self, kind: VectorKind) { - use wasm_webidl_bindings::ast::WebidlScalarType::*; - - match kind { - VectorKind::I8 => self.alloc_copy(Int8Array), - VectorKind::U8 => self.alloc_copy(Uint8Array), - VectorKind::ClampedU8 => self.alloc_copy(Uint8ClampedArray), - VectorKind::I16 => self.alloc_copy(Int16Array), - VectorKind::U16 => self.alloc_copy(Uint16Array), - VectorKind::I32 => self.alloc_copy(Int32Array), - VectorKind::U32 => self.alloc_copy(Uint32Array), - VectorKind::F32 => self.alloc_copy(Float32Array), - VectorKind::F64 => self.alloc_copy(Float64Array), - VectorKind::String => { - let expr = ast::IncomingBindingExpressionAllocUtf8Str { - alloc_func_name: self.alloc_func_name(), - expr: Box::new(self.expr_get()), - }; - self.webidl.push(DomString); - self.bindings - .push(NonstandardIncoming::Standard(expr.into())); - } - VectorKind::I64 | VectorKind::U64 => { - let signed = match kind { - VectorKind::I64 => true, - _ => false, - }; - self.bindings.push(NonstandardIncoming::AllocCopyInt64 { - alloc_func_name: self.alloc_func_name(), - expr: Box::new(self.expr_get()), - signed, - }); - self.webidl.push(Any); - } - VectorKind::Anyref => { - self.bindings - .push(NonstandardIncoming::AllocCopyAnyrefArray { - alloc_func_name: self.alloc_func_name(), - expr: Box::new(self.expr_get()), - }); - self.webidl.push(Any); - } - } - } - - fn alloc_copy(&mut self, webidl: ast::WebidlScalarType) { - let expr = ast::IncomingBindingExpressionAllocCopy { - alloc_func_name: self.alloc_func_name(), - expr: Box::new(self.expr_get()), - }; - self.webidl.push(webidl); - self.bindings - .push(NonstandardIncoming::Standard(expr.into())); - } - - fn number(&mut self, wasm: ValType, webidl: ast::WebidlScalarType) { - let binding = self.expr_as(wasm); - self.wasm.push(wasm); - self.webidl.push(webidl); - self.bindings.push(NonstandardIncoming::Standard(binding)); - } - - fn number64(&mut self, signed: bool) { - let expr = self.expr_get(); - self.wasm.extend(&[ValType::I32; 2]); - self.webidl.push(ast::WebidlScalarType::Any); - self.bindings - .push(NonstandardIncoming::Int64 { val: expr, signed }); - } - - fn option_native(&mut self, wasm: ValType) { - let expr = self.expr_get(); - self.wasm.push(ValType::I32); - self.wasm.push(wasm); - self.webidl.push(ast::WebidlScalarType::Any); - self.bindings - .push(NonstandardIncoming::OptionNative { val: expr }); - } - - fn option_sentinel(&mut self) { - let expr = self.expr_get(); - self.wasm.push(ValType::I32); - self.webidl.push(ast::WebidlScalarType::Any); - self.bindings - .push(NonstandardIncoming::OptionU32Sentinel { val: expr }); - } -} diff --git a/crates/cli-support/src/webidl/outgoing.rs b/crates/cli-support/src/webidl/outgoing.rs deleted file mode 100644 index ba57ab54..00000000 --- a/crates/cli-support/src/webidl/outgoing.rs +++ /dev/null @@ -1,556 +0,0 @@ -//! This module is used to define `NonstandardOutgoing`, a list of possible ways -//! values in Rust can be passed to JS. -//! -//! Like the `NonstandardIncoming` list we attempt to use a standard -//! `OutgoingBindingExpression` wherever possible but we naturally have a lot of -//! features in `wasm-bindgen` which haven't been upstreamed into the WebIDL -//! bindings standard yet (nor which are likely to ever get standardized). We -//! attempt to use standard bindings aggressively and wherever possible, but -//! sometimes we need to resort to our own custom bindings with our own custom -//! JS shims for now. -//! -//! This module also houses the definition of converting a `Descriptor` to a -//! `NonstandardOutgoing` binding, effectively defining how to translate from a -//! Rust type to an outgoing binding. - -use crate::descriptor::{Descriptor, VectorKind}; -use crate::webidl::NonstandardWebidlSection; -use anyhow::{bail, format_err, Error}; -use walrus::{Module, ValType}; -use wasm_webidl_bindings::ast; - -/// A list of all possible outgoing bindings which can be used when converting -/// Rust types to JS. This is predominantly used when calling an imported JS -/// function. -#[derive(Debug, Clone)] -pub enum NonstandardOutgoing { - /// This is a standard upstream WebIDL outgoing binding expression. Where - /// possible we can actually leave this in the wasm file and generate even - /// less JS shim code. - Standard(ast::OutgoingBindingExpression), - - /// We're returning a pointer from Rust to JS to get wrapped in a JS class - /// which has memory management around it. - RustType { class: String, idx: u32 }, - - /// A single rust `char` value which is converted to a `string` in JS. - Char { idx: u32 }, - - /// An `i64` or `u64` in Rust converted to a `BigInt` in JS - Number64 { - lo_idx: u32, - hi_idx: u32, - signed: bool, - }, - - /// A *borrowed* anyref value which has special meanings about ownership, - /// namely Rust is still using the underlying value after the call returns. - BorrowedAnyref { idx: u32 }, - - /// An owned vector is passed from Rust to JS. Note that this is currently a - /// special binding because it requires memory management via deallocation - /// in the JS shim. - /// - /// TODO: we should strive to not have this nonstandard binding and instead - /// do all the memory management in Rust. Ideally we'd use `AllocCopy` in - /// place of this. - Vector { - offset: u32, - length: u32, - kind: VectorKind, - }, - - /// A Rust String (or &str) which might be cached, or might be `None`. - /// - /// If `offset` is 0 then it is cached, and the cached JsValue's index is in `length`. - /// - /// If `offset` and `length` are both 0, then it is `None`. - CachedString { - offset: u32, - length: u32, - owned: bool, - optional: bool, - }, - - /// A `&[u64]` or `&[i64]` is being passed to JS, and the 64-bit sizes here - /// aren't supported by WebIDL bindings yet. - View64 { - offset: u32, - length: u32, - signed: bool, - }, - - /// A list of `anyref` is being passed to JS, and it's got a somewhat - /// magical representation with indics which doesn't map to WebIDL bindings. - ViewAnyref { offset: u32, length: u32 }, - - /// An optional owned vector of data is being passed to JS. - /// - /// TODO: with some cleverness this could probably use `AllocCopy`. - OptionVector { - offset: u32, - length: u32, - kind: VectorKind, - }, - - /// An optional slice of data is being passed into JS. - /// - /// TODO: with some cleverness this could probably use `AllocCopy`. - OptionSlice { - kind: VectorKind, - offset: u32, - length: u32, - }, - - /// An optional "native type" like i32/u32/f32/f64 is being passed to JS, - /// and this requires a discriminant in the ABI. - OptionNative { - present: u32, - val: u32, - signed: bool, - }, - - /// An optional number is being passed to JS where the number uses a - /// sentinel value to represent `None` - OptionU32Sentinel { idx: u32 }, - - /// An optional boolean with a special value for `None` - OptionBool { idx: u32 }, - - /// An optional character with a special value for `None` - OptionChar { idx: u32 }, - - /// An optional integral enum value with the specified `hole` being used for - /// `None`. - OptionIntegerEnum { idx: u32, hole: u32 }, - - /// An optional 64-bit integer being used. - OptionInt64 { - present: u32, - _ignored: u32, - lo: u32, - hi: u32, - signed: bool, - }, - - /// An optional owned Rust type being transferred from Rust to JS. - OptionRustType { class: String, idx: u32 }, - - /// A temporary stack closure being passed from Rust to JS. A JS function is - /// manufactured and then neutered just before the call returns. - StackClosure { - /// Argument index of the first data pointer Rust needs - a: u32, - /// Argument index of the second data pointer Rust needs - b: u32, - /// The index of the shim in the element bindings section that we're - /// going to be invoking. - binding_idx: u32, - /// Number of arguments to the closure - nargs: usize, - /// Whether or not this is a mutable closure (affects codegen and how - /// it's called recursively) - mutable: bool, - }, -} - -/// A definition of building `NonstandardOutgoing` expressions from a -/// `Descriptor`. -/// -/// This will internally keep track of wasm/webidl types generated as we visit -/// `Descriptor` arguments and add more for a function signature. -#[derive(Default)] -pub struct OutgoingBuilder<'a> { - /// All wasm types used so far to produce the resulting JS values. - pub wasm: Vec, - /// The WebIDL types that we're passing along out of wasm. - pub webidl: Vec, - /// The list of bindings we've created, currently 1:1 with the webidl above. - pub bindings: Vec, - - // These two arguments are optional and, if set, will enable creating - // `StackClosure` bindings. They're not present for return values from - // exported Rust functions, but they are available for the arguments of - // calling imported functions. - pub module: Option<&'a mut Module>, - pub bindings_section: Option<&'a mut NonstandardWebidlSection>, -} - -impl OutgoingBuilder<'_> { - /// Adds a dummy first argument which is passed through as an integer - /// representing the return pointer. - pub fn process_retptr(&mut self) { - self.standard_as(ValType::I32, ast::WebidlScalarType::Long); - } - - /// Processes one more `Descriptor` as an argument to a JS function that - /// wasm is calling. - /// - /// This will internally skip `Unit` and otherwise build up the `bindings` - /// map and ensure that it's correctly mapped from wasm to JS. - pub fn process(&mut self, arg: &Descriptor) -> Result<(), Error> { - if let Descriptor::Unit = arg { - return Ok(()); - } - assert_eq!(self.webidl.len(), self.bindings.len()); - let wasm_before = self.wasm.len(); - let webidl_before = self.webidl.len(); - self._process(arg)?; - assert_eq!(self.webidl.len(), self.bindings.len()); - assert_eq!(webidl_before + 1, self.webidl.len()); - assert!(wasm_before < self.wasm.len()); - Ok(()) - } - - fn _process(&mut self, arg: &Descriptor) -> Result<(), Error> { - match arg { - Descriptor::Boolean => self.standard_as(ValType::I32, ast::WebidlScalarType::Boolean), - Descriptor::Anyref => self.standard_as(ValType::Anyref, ast::WebidlScalarType::Any), - Descriptor::I8 => self.standard_as(ValType::I32, ast::WebidlScalarType::Byte), - Descriptor::U8 => self.standard_as(ValType::I32, ast::WebidlScalarType::Octet), - Descriptor::I16 => self.standard_as(ValType::I32, ast::WebidlScalarType::Short), - Descriptor::U16 => self.standard_as(ValType::I32, ast::WebidlScalarType::UnsignedShort), - Descriptor::I32 => self.standard_as(ValType::I32, ast::WebidlScalarType::Long), - Descriptor::U32 => self.standard_as(ValType::I32, ast::WebidlScalarType::UnsignedLong), - Descriptor::F32 => { - self.standard_as(ValType::F32, ast::WebidlScalarType::UnrestrictedFloat) - } - Descriptor::F64 => { - self.standard_as(ValType::F64, ast::WebidlScalarType::UnrestrictedDouble) - } - Descriptor::Enum { .. } => self.standard_as(ValType::I32, ast::WebidlScalarType::Long), - - Descriptor::Char => { - let idx = self.push_wasm(ValType::I32); - self.webidl.push(ast::WebidlScalarType::DomString); - self.bindings.push(NonstandardOutgoing::Char { idx }); - } - - Descriptor::I64 | Descriptor::U64 => { - let signed = match arg { - Descriptor::I64 => true, - _ => false, - }; - let lo_idx = self.push_wasm(ValType::I32); - let hi_idx = self.push_wasm(ValType::I32); - self.webidl.push(ast::WebidlScalarType::Any); - self.bindings.push(NonstandardOutgoing::Number64 { - lo_idx, - hi_idx, - signed, - }); - } - - Descriptor::RustStruct(class) => { - let idx = self.push_wasm(ValType::I32); - self.webidl.push(ast::WebidlScalarType::Any); - self.bindings.push(NonstandardOutgoing::RustType { - idx, - class: class.to_string(), - }); - } - Descriptor::Ref(d) => self.process_ref(false, d)?, - Descriptor::RefMut(d) => self.process_ref(true, d)?, - - Descriptor::CachedString => self.cached_string(false, true), - - Descriptor::Vector(_) | Descriptor::String => { - let kind = arg.vector_kind().ok_or_else(|| { - format_err!( - "unsupported argument type for calling JS function from Rust {:?}", - arg - ) - })?; - let offset = self.push_wasm(ValType::I32); - let length = self.push_wasm(ValType::I32); - self.webidl.push(ast::WebidlScalarType::Any); - self.bindings.push(NonstandardOutgoing::Vector { - offset, - kind, - length, - }) - } - - Descriptor::Option(d) => self.process_option(d)?, - - Descriptor::Function(_) | Descriptor::Closure(_) | Descriptor::Slice(_) => bail!( - "unsupported argument type for calling JS function from Rust: {:?}", - arg - ), - - // nothing to do - Descriptor::Unit => {} - - // Largely synthetic and can't show up - Descriptor::ClampedU8 => unreachable!(), - } - Ok(()) - } - - fn process_ref(&mut self, mutable: bool, arg: &Descriptor) -> Result<(), Error> { - match arg { - Descriptor::Anyref => { - let idx = self.push_wasm(ValType::Anyref); - self.webidl.push(ast::WebidlScalarType::Any); - self.bindings - .push(NonstandardOutgoing::BorrowedAnyref { idx }); - } - Descriptor::CachedString => self.cached_string(false, false), - Descriptor::Slice(_) | Descriptor::String => { - use wasm_webidl_bindings::ast::WebidlScalarType::*; - - let kind = arg.vector_kind().ok_or_else(|| { - format_err!( - "unsupported argument type for calling JS function from Rust {:?}", - arg - ) - })?; - let offset = self.push_wasm(ValType::I32); - let length = self.push_wasm(ValType::I32); - match kind { - VectorKind::I8 => self.standard_view(offset, length, Int8Array), - VectorKind::U8 => self.standard_view(offset, length, Uint8Array), - VectorKind::ClampedU8 => self.standard_view(offset, length, Uint8ClampedArray), - VectorKind::I16 => self.standard_view(offset, length, Int16Array), - VectorKind::U16 => self.standard_view(offset, length, Uint16Array), - VectorKind::I32 => self.standard_view(offset, length, Int32Array), - VectorKind::U32 => self.standard_view(offset, length, Uint32Array), - VectorKind::F32 => self.standard_view(offset, length, Float32Array), - VectorKind::F64 => self.standard_view(offset, length, Float64Array), - VectorKind::String => { - self.webidl.push(DomString); - let binding = ast::OutgoingBindingExpressionUtf8Str { - ty: ast::WebidlScalarType::DomString.into(), - offset, - length, - }; - self.bindings - .push(NonstandardOutgoing::Standard(binding.into())); - } - VectorKind::I64 | VectorKind::U64 => { - let signed = match kind { - VectorKind::I64 => true, - _ => false, - }; - self.webidl.push(Any); - self.bindings.push(NonstandardOutgoing::View64 { - offset, - length, - signed, - }); - } - VectorKind::Anyref => { - self.webidl.push(Any); - self.bindings - .push(NonstandardOutgoing::ViewAnyref { offset, length }); - } - } - } - - Descriptor::Function(descriptor) => { - let module = self - .module - .as_mut() - .ok_or_else(|| format_err!("cannot return a closure from Rust"))?; - let section = self.bindings_section.as_mut().unwrap(); - // synthesize the a/b arguments that aren't present in the - // signature from wasm-bindgen but are present in the wasm file. - let mut descriptor = (**descriptor).clone(); - let nargs = descriptor.arguments.len(); - descriptor.arguments.insert(0, Descriptor::I32); - descriptor.arguments.insert(0, Descriptor::I32); - let binding_idx = super::bindings::register_table_element( - module, - section, - descriptor.shim_idx, - descriptor, - )?; - let a = self.push_wasm(ValType::I32); - let b = self.push_wasm(ValType::I32); - self.webidl.push(ast::WebidlScalarType::Any); - self.bindings.push(NonstandardOutgoing::StackClosure { - a, - b, - binding_idx, - nargs, - mutable, - }); - } - - _ => bail!( - "unsupported reference argument type for calling JS function from Rust: {:?}", - arg - ), - } - Ok(()) - } - - fn process_option(&mut self, arg: &Descriptor) -> Result<(), Error> { - match arg { - Descriptor::Anyref => self.standard_as(ValType::Anyref, ast::WebidlScalarType::Any), - Descriptor::I8 => self.option_sentinel(), - Descriptor::U8 => self.option_sentinel(), - Descriptor::I16 => self.option_sentinel(), - Descriptor::U16 => self.option_sentinel(), - Descriptor::I32 => self.option_native(true, ValType::I32), - Descriptor::U32 => self.option_native(false, ValType::I32), - Descriptor::F32 => self.option_native(true, ValType::F32), - Descriptor::F64 => self.option_native(true, ValType::F64), - Descriptor::I64 | Descriptor::U64 => { - let signed = match arg { - Descriptor::I64 => true, - _ => false, - }; - let binding = NonstandardOutgoing::OptionInt64 { - present: self.push_wasm(ValType::I32), - _ignored: self.push_wasm(ValType::I32), - lo: self.push_wasm(ValType::I32), - hi: self.push_wasm(ValType::I32), - signed, - }; - self.webidl.push(ast::WebidlScalarType::Any); - self.bindings.push(binding); - } - Descriptor::Boolean => { - let idx = self.push_wasm(ValType::I32); - self.webidl.push(ast::WebidlScalarType::Any); - self.bindings.push(NonstandardOutgoing::OptionBool { idx }); - } - Descriptor::Char => { - let idx = self.push_wasm(ValType::I32); - self.webidl.push(ast::WebidlScalarType::Any); - self.bindings.push(NonstandardOutgoing::OptionChar { idx }); - } - Descriptor::Enum { hole } => { - let idx = self.push_wasm(ValType::I32); - self.webidl.push(ast::WebidlScalarType::Long); - self.bindings - .push(NonstandardOutgoing::OptionIntegerEnum { idx, hole: *hole }); - } - Descriptor::RustStruct(name) => { - let idx = self.push_wasm(ValType::I32); - self.webidl.push(ast::WebidlScalarType::Any); - self.bindings.push(NonstandardOutgoing::OptionRustType { - idx, - class: name.to_string(), - }); - } - Descriptor::Ref(d) => self.process_option_ref(false, d)?, - Descriptor::RefMut(d) => self.process_option_ref(true, d)?, - - Descriptor::CachedString => self.cached_string(true, true), - - Descriptor::String | Descriptor::Vector(_) => { - let kind = arg.vector_kind().ok_or_else(|| { - format_err!( - "unsupported optional slice type for calling JS function from Rust {:?}", - arg - ) - })?; - let offset = self.push_wasm(ValType::I32); - let length = self.push_wasm(ValType::I32); - self.webidl.push(ast::WebidlScalarType::Any); - self.bindings.push(NonstandardOutgoing::OptionVector { - kind, - offset, - length, - }) - } - - _ => bail!( - "unsupported optional argument type for calling JS function from Rust: {:?}", - arg - ), - } - Ok(()) - } - - fn process_option_ref(&mut self, _mutable: bool, arg: &Descriptor) -> Result<(), Error> { - match arg { - Descriptor::Anyref => { - let idx = self.push_wasm(ValType::Anyref); - self.webidl.push(ast::WebidlScalarType::Any); - self.bindings - .push(NonstandardOutgoing::BorrowedAnyref { idx }); - } - Descriptor::CachedString => self.cached_string(true, false), - Descriptor::String | Descriptor::Slice(_) => { - let kind = arg.vector_kind().ok_or_else(|| { - format_err!( - "unsupported optional slice type for calling JS function from Rust {:?}", - arg - ) - })?; - let offset = self.push_wasm(ValType::I32); - let length = self.push_wasm(ValType::I32); - self.webidl.push(ast::WebidlScalarType::Any); - self.bindings.push(NonstandardOutgoing::OptionSlice { - kind, - offset, - length, - }); - } - _ => bail!( - "unsupported optional ref argument type for calling JS function from Rust: {:?}", - arg - ), - } - Ok(()) - } - - fn push_wasm(&mut self, ty: ValType) -> u32 { - self.wasm.push(ty); - self.wasm.len() as u32 - 1 - } - - fn standard_as(&mut self, wasm: ValType, webidl: ast::WebidlScalarType) { - let binding = ast::OutgoingBindingExpressionAs { - ty: webidl.into(), - idx: self.push_wasm(wasm), - }; - self.webidl.push(webidl); - self.bindings - .push(NonstandardOutgoing::Standard(binding.into())); - } - - fn standard_view(&mut self, offset: u32, length: u32, ty: ast::WebidlScalarType) { - let binding = ast::OutgoingBindingExpressionView { - ty: ty.into(), - offset, - length, - }; - self.webidl.push(ty); - self.bindings - .push(NonstandardOutgoing::Standard(binding.into())); - } - - fn cached_string(&mut self, optional: bool, owned: bool) { - let offset = self.push_wasm(ValType::I32); - let length = self.push_wasm(ValType::I32); - self.webidl.push(ast::WebidlScalarType::DomString); - self.bindings.push(NonstandardOutgoing::CachedString { - offset, - length, - owned, - optional, - }) - } - - fn option_native(&mut self, signed: bool, ty: ValType) { - let present = self.push_wasm(ValType::I32); - let val = self.push_wasm(ty); - self.webidl.push(ast::WebidlScalarType::Any); - self.bindings.push(NonstandardOutgoing::OptionNative { - signed, - present, - val, - }); - } - - fn option_sentinel(&mut self) { - let idx = self.push_wasm(ValType::I32); - self.webidl.push(ast::WebidlScalarType::Any); - self.bindings - .push(NonstandardOutgoing::OptionU32Sentinel { idx }); - } -} diff --git a/crates/cli-support/src/webidl/standard.rs b/crates/cli-support/src/webidl/standard.rs deleted file mode 100644 index 228977ee..00000000 --- a/crates/cli-support/src/webidl/standard.rs +++ /dev/null @@ -1,562 +0,0 @@ -//! Support for generating a standard WebIDL custom section -//! -//! This module has all the necessary support for generating a full-fledged -//! standard WebIDL custom section as defined by the `wasm-webidl-bindings` -//! crate. This module also critically assumes that the WebAssembly module -//! being generated **must be standalone**. In this mode all sorts of features -//! supported by `#[wasm_bindgen]` aren't actually supported, such as closures, -//! imports of global js names, js getters/setters, exporting structs, etc. -//! These features may all eventually come to the standard bindings proposal, -//! but it will likely take some time. In the meantime this module simply focuses -//! on taking what's already a valid wasm module and letting it through with a -//! standard WebIDL custom section. All other modules generate an error during -//! this binding process. -//! -//! Note that when this function is called and used we're also not actually -//! generating any JS glue. Any JS glue currently generated is also invalid if -//! the module contains the wasm bindings section and it's actually respected. - -use crate::descriptor::VectorKind; -use crate::webidl::{AuxExportKind, AuxImport, AuxValue, JsImport, JsImportName}; -use crate::webidl::{NonstandardIncoming, NonstandardOutgoing}; -use crate::webidl::{NonstandardWebidlSection, WasmBindgenAux}; -use anyhow::{bail, Context, Error}; -use walrus::Module; -use wasm_bindgen_multi_value_xform as multi_value_xform; -use wasm_bindgen_wasm_conventions as wasm_conventions; -use wasm_webidl_bindings::ast; - -pub fn add_multi_value( - module: &mut Module, - bindings: &mut NonstandardWebidlSection, -) -> Result<(), Error> { - let mut to_xform = vec![]; - for (id, binding) in &bindings.exports { - if let Some(ref results) = binding.return_via_outptr { - // LLVM currently always uses the first parameter for the return - // pointer. We hard code that here, since we have no better option. - let return_pointer_index = 0; - to_xform.push((*id, return_pointer_index, &results[..])); - } - } - - if to_xform.is_empty() { - // Early exit to avoid failing if we don't have a memory or shadow stack - // pointer because this is a minimal module that doesn't use linear - // memory. - return Ok(()); - } - - let shadow_stack_pointer = wasm_conventions::get_shadow_stack_pointer(module)?; - let memory = wasm_conventions::get_memory(module)?; - multi_value_xform::run(module, memory, shadow_stack_pointer, &to_xform)?; - - // Finally, unset `return_via_outptr`, fix up its incoming bindings' - // argument numberings, and update its function type. - for (id, binding) in &mut bindings.exports { - if binding.return_via_outptr.take().is_none() { - continue; - } - if binding.incoming.is_empty() { - bail!("missing incoming binding expression for return pointer parameter"); - } - if !is_ret_ptr_bindings(binding.incoming.remove(0)) { - bail!("unexpected incoming binding expression for return pointer parameter"); - } - - fixup_binding_argument_gets(&mut binding.incoming)?; - - let func = match module.exports.get(*id).item { - walrus::ExportItem::Function(f) => f, - _ => unreachable!(), - }; - binding.wasm_ty = module.funcs.get(func).ty(); - - // Be sure to delete the out-param pointer from the WebIDL type as well. - let webidl_ty = bindings - .types - .get::(binding.webidl_ty) - .unwrap(); - let mut new_ty = webidl_ty.clone(); - new_ty.params.remove(0); - binding.webidl_ty = bindings.types.insert(new_ty); - } - - Ok(()) -} - -fn is_ret_ptr_bindings(b: NonstandardIncoming) -> bool { - match b { - NonstandardIncoming::Standard(ast::IncomingBindingExpression::As( - ast::IncomingBindingExpressionAs { - ty: walrus::ValType::I32, - expr, - }, - )) => match *expr { - ast::IncomingBindingExpression::Get(ast::IncomingBindingExpressionGet { idx: 0 }) => { - true - } - _ => false, - }, - _ => false, - } -} - -// Since we removed the first parameter (which was the return pointer) now all -// of the `Get` binding expression's are off by one. This function fixes these -// `Get`s. -fn fixup_binding_argument_gets(incoming: &mut [NonstandardIncoming]) -> Result<(), Error> { - for inc in incoming { - fixup_nonstandard_incoming(inc)?; - } - return Ok(()); - - fn fixup_nonstandard_incoming(inc: &mut NonstandardIncoming) -> Result<(), Error> { - match inc { - NonstandardIncoming::Standard(s) => fixup_standard_incoming(s), - _ => bail!("found usage of non-standard bindings when in standard-bindings-only mode"), - } - } - - fn fixup_standard_incoming(s: &mut ast::IncomingBindingExpression) -> Result<(), Error> { - match s { - ast::IncomingBindingExpression::Get(e) => { - if e.idx == 0 { - bail!( - "found usage of removed return pointer parameter in \ - non-return pointer bindings" - ); - } else { - e.idx -= 1; - Ok(()) - } - } - ast::IncomingBindingExpression::As(e) => fixup_standard_incoming(&mut e.expr), - ast::IncomingBindingExpression::AllocUtf8Str(e) => fixup_standard_incoming(&mut e.expr), - ast::IncomingBindingExpression::AllocCopy(e) => fixup_standard_incoming(&mut e.expr), - ast::IncomingBindingExpression::EnumToI32(e) => fixup_standard_incoming(&mut e.expr), - ast::IncomingBindingExpression::Field(e) => fixup_standard_incoming(&mut e.expr), - ast::IncomingBindingExpression::BindImport(e) => fixup_standard_incoming(&mut e.expr), - } - } -} - -pub fn add_section( - module: &mut Module, - aux: &WasmBindgenAux, - nonstandard: &NonstandardWebidlSection, -) -> Result<(), Error> { - let mut section = ast::WebidlBindings::default(); - let WasmBindgenAux { - extra_typescript: _, // ignore this even if it's specified - local_modules, - snippets, - package_jsons, - export_map, - import_map, - imports_with_catch, - imports_with_variadic, - imports_with_assert_no_shim: _, // not relevant for this purpose - enums, - structs, - } = aux; - - for (export, binding) in nonstandard.exports.iter() { - // First up make sure this is something that's actually valid to export - // form a vanilla WebAssembly module with WebIDL bindings. - match &export_map[export].kind { - AuxExportKind::Function(_) => {} - AuxExportKind::Constructor(name) => { - bail!( - "cannot export `{}` constructor function when generating \ - a standalone WebAssembly module with no JS glue", - name, - ); - } - AuxExportKind::Getter { class, field } => { - bail!( - "cannot export `{}::{}` getter function when generating \ - a standalone WebAssembly module with no JS glue", - class, - field, - ); - } - AuxExportKind::Setter { class, field } => { - bail!( - "cannot export `{}::{}` setter function when generating \ - a standalone WebAssembly module with no JS glue", - class, - field, - ); - } - AuxExportKind::StaticFunction { class, name } => { - bail!( - "cannot export `{}::{}` static function when \ - generating a standalone WebAssembly module with no \ - JS glue", - class, - name - ); - } - AuxExportKind::Method { class, name, .. } => { - bail!( - "cannot export `{}::{}` method when \ - generating a standalone WebAssembly module with no \ - JS glue", - class, - name - ); - } - } - - let name = &module.exports.get(*export).name; - let params = extract_incoming(&binding.incoming).with_context(|| { - format!( - "failed to map arguments for export `{}` to standard \ - binding expressions", - name - ) - })?; - let result = extract_outgoing(&binding.outgoing).with_context(|| { - format!( - "failed to map return value for export `{}` to standard \ - binding expressions", - name - ) - })?; - - assert!(binding.return_via_outptr.is_none()); - let binding = section.bindings.insert(ast::ExportBinding { - wasm_ty: binding.wasm_ty, - webidl_ty: copy_ty( - &mut section.types, - binding.webidl_ty.into(), - &nonstandard.types, - ), - params: ast::IncomingBindingMap { bindings: params }, - result: ast::OutgoingBindingMap { bindings: result }, - }); - let func = match module.exports.get(*export).item { - walrus::ExportItem::Function(f) => f, - _ => unreachable!(), - }; - section.binds.insert(ast::Bind { - func, - binding: binding.into(), - }); - } - - for (import, binding) in nonstandard.imports.iter() { - check_standard_import(&import_map[import])?; - let (module_name, name) = { - let import = module.imports.get(*import); - (&import.module, &import.name) - }; - let params = extract_outgoing(&binding.outgoing).with_context(|| { - format!( - "failed to map arguments of import `{}::{}` to standard \ - binding expressions", - module_name, name, - ) - })?; - let result = extract_incoming(&binding.incoming).with_context(|| { - format!( - "failed to map return value of import `{}::{}` to standard \ - binding expressions", - module_name, name, - ) - })?; - assert!(binding.return_via_outptr.is_none()); - let binding = section.bindings.insert(ast::ImportBinding { - wasm_ty: binding.wasm_ty, - webidl_ty: copy_ty( - &mut section.types, - binding.webidl_ty.into(), - &nonstandard.types, - ), - params: ast::OutgoingBindingMap { bindings: params }, - result: ast::IncomingBindingMap { bindings: result }, - }); - let func = match module.imports.get(*import).kind { - walrus::ImportKind::Function(f) => f, - _ => unreachable!(), - }; - section.binds.insert(ast::Bind { - func, - binding: binding.into(), - }); - } - - if let Some((name, _)) = local_modules.iter().next() { - bail!( - "generating a bindings section is currently incompatible with \ - local JS modules being specified as well, `{}` cannot be used \ - since a standalone wasm file is being generated", - name, - ); - } - - if let Some((name, _)) = snippets.iter().filter(|(_, v)| !v.is_empty()).next() { - bail!( - "generating a bindings section is currently incompatible with \ - local JS snippets being specified as well, `{}` cannot be used \ - since a standalone wasm file is being generated", - name, - ); - } - - if let Some(path) = package_jsons.iter().next() { - bail!( - "generating a bindings section is currently incompatible with \ - package.json being consumed as well, `{}` cannot be used \ - since a standalone wasm file is being generated", - path.display(), - ); - } - - if let Some(import) = imports_with_catch.iter().next() { - let import = module.imports.get(*import); - bail!( - "generating a bindings section is currently incompatible with \ - `#[wasm_bindgen(catch)]` on the `{}::{}` import because a \ - a standalone wasm file is being generated", - import.module, - import.name, - ); - } - - if let Some(import) = imports_with_variadic.iter().next() { - let import = module.imports.get(*import); - bail!( - "generating a bindings section is currently incompatible with \ - `#[wasm_bindgen(variadic)]` on the `{}::{}` import because a \ - a standalone wasm file is being generated", - import.module, - import.name, - ); - } - - if let Some(enum_) = enums.iter().next() { - bail!( - "generating a bindings section is currently incompatible with \ - exporting an `enum` from the wasm file, cannot export `{}`", - enum_.name, - ); - } - - if let Some(struct_) = structs.iter().next() { - bail!( - "generating a bindings section is currently incompatible with \ - exporting a `struct` from the wasm file, cannot export `{}`", - struct_.name, - ); - } - - if nonstandard.elems.len() > 0 { - // Note that this is a pretty cryptic error message, but we in theory - // shouldn't ever hit this since closures always show up as some form - // of nonstandard binding which was previously checked. - bail!("generating a standalone wasm file requires no table element bindings"); - } - - module.customs.add(section); - Ok(()) -} - -fn extract_incoming( - nonstandard: &[NonstandardIncoming], -) -> Result, Error> { - let mut exprs = Vec::new(); - for expr in nonstandard { - let desc = match expr { - NonstandardIncoming::Standard(e) => { - exprs.push(e.clone()); - continue; - } - NonstandardIncoming::Int64 { .. } => "64-bit integer", - NonstandardIncoming::AllocCopyInt64 { .. } => "64-bit integer array", - NonstandardIncoming::AllocCopyAnyrefArray { .. } => "array of JsValue", - NonstandardIncoming::MutableSlice { .. } => "mutable slice", - NonstandardIncoming::OptionSlice { .. } => "optional slice", - NonstandardIncoming::OptionVector { .. } => "optional vector", - NonstandardIncoming::OptionAnyref { .. } => "optional anyref", - NonstandardIncoming::OptionNative { .. } => "optional integer", - NonstandardIncoming::OptionU32Sentinel { .. } => "optional integer", - NonstandardIncoming::OptionBool { .. } => "optional bool", - NonstandardIncoming::OptionChar { .. } => "optional char", - NonstandardIncoming::OptionIntegerEnum { .. } => "optional enum", - NonstandardIncoming::OptionInt64 { .. } => "optional integer", - NonstandardIncoming::RustType { .. } => "native Rust type", - NonstandardIncoming::RustTypeRef { .. } => "reference to Rust type", - NonstandardIncoming::OptionRustType { .. } => "optional Rust type", - NonstandardIncoming::Char { .. } => "character", - NonstandardIncoming::BorrowedAnyref { .. } => "borrowed anyref", - }; - bail!( - "cannot represent {} with a standard bindings expression", - desc - ); - } - Ok(exprs) -} - -fn extract_outgoing( - nonstandard: &[NonstandardOutgoing], -) -> Result, Error> { - let mut exprs = Vec::new(); - for expr in nonstandard { - let desc = match expr { - NonstandardOutgoing::Standard(e) => { - exprs.push(e.clone()); - continue; - } - // ... yeah ... let's just leak strings - // see comment at top of this module about returning strings for - // what this is doing and why it's weird - NonstandardOutgoing::Vector { - offset, - length, - kind: VectorKind::String, - } => { - exprs.push( - ast::OutgoingBindingExpressionUtf8Str { - offset: *offset, - length: *length, - ty: ast::WebidlScalarType::DomString.into(), - } - .into(), - ); - continue; - } - - NonstandardOutgoing::RustType { .. } => "rust type", - NonstandardOutgoing::Char { .. } => "character", - NonstandardOutgoing::Number64 { .. } => "64-bit integer", - NonstandardOutgoing::BorrowedAnyref { .. } => "borrowed anyref", - NonstandardOutgoing::Vector { .. } => "vector", - NonstandardOutgoing::CachedString { .. } => "cached string", - NonstandardOutgoing::View64 { .. } => "64-bit slice", - NonstandardOutgoing::ViewAnyref { .. } => "anyref slice", - NonstandardOutgoing::OptionVector { .. } => "optional vector", - NonstandardOutgoing::OptionSlice { .. } => "optional slice", - NonstandardOutgoing::OptionNative { .. } => "optional integer", - NonstandardOutgoing::OptionU32Sentinel { .. } => "optional integer", - NonstandardOutgoing::OptionBool { .. } => "optional boolean", - NonstandardOutgoing::OptionChar { .. } => "optional character", - NonstandardOutgoing::OptionIntegerEnum { .. } => "optional enum", - NonstandardOutgoing::OptionInt64 { .. } => "optional 64-bit integer", - NonstandardOutgoing::OptionRustType { .. } => "optional rust type", - NonstandardOutgoing::StackClosure { .. } => "closures", - }; - bail!( - "cannot represent {} with a standard bindings expression", - desc - ); - } - Ok(exprs) -} - -/// Recursively clones `ty` into` dst` where it originally indexes values in -/// `src`, returning a new type ref which indexes inside of `dst`. -pub fn copy_ty( - dst: &mut ast::WebidlTypes, - ty: ast::WebidlTypeRef, - src: &ast::WebidlTypes, -) -> ast::WebidlTypeRef { - let id = match ty { - ast::WebidlTypeRef::Id(id) => id, - ast::WebidlTypeRef::Scalar(_) => return ty, - }; - let ty: &ast::WebidlCompoundType = src.get(id).unwrap(); - match ty { - ast::WebidlCompoundType::Function(f) => { - let params = f - .params - .iter() - .map(|param| copy_ty(dst, *param, src)) - .collect(); - let result = f.result.map(|ty| copy_ty(dst, ty, src)); - dst.insert(ast::WebidlFunction { - kind: f.kind.clone(), - params, - result, - }) - .into() - } - _ => unimplemented!(), - } -} - -fn check_standard_import(import: &AuxImport) -> Result<(), Error> { - let desc_js = |js: &JsImport| { - let mut extra = String::new(); - for field in js.fields.iter() { - extra.push_str("."); - extra.push_str(field); - } - match &js.name { - JsImportName::Global { name } | JsImportName::VendorPrefixed { name, .. } => { - format!("global `{}{}`", name, extra) - } - JsImportName::Module { module, name } => { - format!("`{}{}` from '{}'", name, extra, module) - } - JsImportName::LocalModule { module, name } => { - format!("`{}{}` from local module '{}'", name, extra, module) - } - JsImportName::InlineJs { - unique_crate_identifier, - name, - .. - } => format!( - "`{}{}` from inline js in '{}'", - name, extra, unique_crate_identifier - ), - } - }; - - let item = match import { - AuxImport::Value(AuxValue::Bare(js)) => { - if js.fields.len() == 0 { - if let JsImportName::Module { .. } = js.name { - return Ok(()); - } - } - desc_js(js) - } - AuxImport::Value(AuxValue::Getter(js, name)) - | AuxImport::Value(AuxValue::Setter(js, name)) - | AuxImport::Value(AuxValue::ClassGetter(js, name)) - | AuxImport::Value(AuxValue::ClassSetter(js, name)) => { - format!("field access of `{}` for {}", name, desc_js(js)) - } - AuxImport::ValueWithThis(js, method) => format!("method `{}.{}`", desc_js(js), method), - AuxImport::Instanceof(js) => format!("instance of check of {}", desc_js(js)), - AuxImport::Static(js) => format!("static js value {}", desc_js(js)), - AuxImport::StructuralMethod(name) => format!("structural method `{}`", name), - AuxImport::StructuralGetter(name) - | AuxImport::StructuralSetter(name) - | AuxImport::StructuralClassGetter(_, name) - | AuxImport::StructuralClassSetter(_, name) => { - format!("structural field access of `{}`", name) - } - AuxImport::IndexingDeleterOfClass(_) - | AuxImport::IndexingDeleterOfObject - | AuxImport::IndexingGetterOfClass(_) - | AuxImport::IndexingGetterOfObject - | AuxImport::IndexingSetterOfClass(_) - | AuxImport::IndexingSetterOfObject => format!("indexing getters/setters/deleters"), - AuxImport::WrapInExportedClass(name) => { - format!("wrapping a pointer in a `{}` js class wrapper", name) - } - AuxImport::Intrinsic(intrinsic) => { - format!("wasm-bindgen specific intrinsic `{}`", intrinsic.name()) - } - AuxImport::Closure { .. } => format!("creating a `Closure` wrapper"), - }; - bail!( - "cannot generate a standalone WebAssembly module which \ - contains an import of {} since it requires JS glue", - item - ); -} diff --git a/crates/cli-support/src/wit/incoming.rs b/crates/cli-support/src/wit/incoming.rs new file mode 100644 index 00000000..dc00b90c --- /dev/null +++ b/crates/cli-support/src/wit/incoming.rs @@ -0,0 +1,385 @@ +//! Definition of how to convert Rust types (`Description`) into wasm types +//! through adapter functions. +//! +//! Note that many Rust types use "nonstandard" instructions which only work in +//! the JS output, not for the "pure wasm interface types" output. +//! +//! Note that the mirror operation, going from WebAssembly to JS, is found in +//! the `outgoing.rs` module. + +use crate::descriptor::Descriptor; +use crate::wit::InstructionData; +use crate::wit::{AdapterType, Instruction, InstructionBuilder, StackChange}; +use anyhow::{bail, format_err, Error}; +use walrus::ValType; + +impl InstructionBuilder<'_, '_> { + /// Process a `Descriptor` as if it's being passed from JS to Rust. This + /// will skip `Unit` and otherwise internally add instructions necessary to + /// convert the foreign type into the Rust bits. + pub fn incoming(&mut self, arg: &Descriptor) -> Result<(), Error> { + if let Descriptor::Unit = arg { + return Ok(()); + } + // This is a wrapper around `_incoming` to have a number of sanity checks + // that we don't forget things. We should always produce at least one + // wasm arge and exactly one webidl arg. Additionally the number of + // bindings should always match the number of webidl types for now. + let input_before = self.input.len(); + let output_before = self.output.len(); + self._incoming(arg)?; + assert_eq!( + input_before + 1, + self.input.len(), + "didn't push an input {:?}", + arg + ); + assert!( + output_before < self.output.len(), + "didn't push more outputs {:?}", + arg + ); + Ok(()) + } + + fn _incoming(&mut self, arg: &Descriptor) -> Result<(), Error> { + use walrus::ValType as WasmVT; + use wit_walrus::ValType as WitVT; + match arg { + Descriptor::Boolean => { + self.instruction( + &[AdapterType::Bool], + Instruction::I32FromBool, + &[AdapterType::I32], + ); + } + Descriptor::Char => { + self.instruction( + &[AdapterType::String], + Instruction::I32FromStringFirstChar, + &[AdapterType::I32], + ); + } + Descriptor::Anyref => { + self.instruction( + &[AdapterType::Anyref], + Instruction::I32FromAnyrefOwned, + &[AdapterType::I32], + ); + } + Descriptor::RustStruct(class) => { + self.instruction( + &[AdapterType::Anyref], + Instruction::I32FromAnyrefRustOwned { + class: class.clone(), + }, + &[AdapterType::I32], + ); + } + Descriptor::I8 => self.number(WitVT::S8, WasmVT::I32), + Descriptor::U8 => self.number(WitVT::U8, WasmVT::I32), + Descriptor::I16 => self.number(WitVT::S16, WasmVT::I32), + Descriptor::U16 => self.number(WitVT::U16, WasmVT::I32), + Descriptor::I32 => self.number(WitVT::S32, WasmVT::I32), + Descriptor::U32 => self.number(WitVT::U32, WasmVT::I32), + Descriptor::I64 => self.number64(true), + Descriptor::U64 => self.number64(false), + Descriptor::F32 => { + self.get(AdapterType::F32); + self.output.push(AdapterType::F32); + } + Descriptor::F64 => { + self.get(AdapterType::F64); + self.output.push(AdapterType::F64); + } + Descriptor::Enum { .. } => self.number(WitVT::U32, WasmVT::I32), + Descriptor::Ref(d) => self.incoming_ref(false, d)?, + Descriptor::RefMut(d) => self.incoming_ref(true, d)?, + Descriptor::Option(d) => self.incoming_option(d)?, + + Descriptor::String | Descriptor::CachedString => { + let std = wit_walrus::Instruction::StringToMemory { + malloc: self.cx.malloc()?, + mem: self.cx.memory()?, + }; + self.instruction( + &[AdapterType::String], + Instruction::Standard(std), + &[AdapterType::I32, AdapterType::I32], + ); + } + + Descriptor::Vector(_) => { + let kind = arg.vector_kind().ok_or_else(|| { + format_err!("unsupported argument type for calling Rust function from JS {:?}", arg) + })?; + self.instruction( + &[AdapterType::Vector(kind)], + Instruction::VectorToMemory { + kind, + malloc: self.cx.malloc()?, + mem: self.cx.memory()?, + }, + &[AdapterType::I32, AdapterType::I32], + ); + } + + // Can't be passed from JS to Rust yet + Descriptor::Function(_) | + Descriptor::Closure(_) | + + // Always behind a `Ref` + Descriptor::Slice(_) => bail!( + "unsupported argument type for calling Rust function from JS: {:?}", + arg + ), + + // nothing to do + Descriptor::Unit => {} + + // Largely synthetic and can't show up + Descriptor::ClampedU8 => unreachable!(), + } + Ok(()) + } + + fn incoming_ref(&mut self, mutable: bool, arg: &Descriptor) -> Result<(), Error> { + match arg { + Descriptor::RustStruct(class) => { + self.instruction( + &[AdapterType::Anyref], + Instruction::I32FromAnyrefRustBorrow { + class: class.clone(), + }, + &[AdapterType::I32], + ); + } + Descriptor::Anyref => { + self.instruction( + &[AdapterType::Anyref], + Instruction::I32FromAnyrefBorrow, + &[AdapterType::I32], + ); + } + Descriptor::String | Descriptor::CachedString => { + // This allocation is cleaned up once it's received in Rust. + let std = wit_walrus::Instruction::StringToMemory { + malloc: self.cx.malloc()?, + mem: self.cx.memory()?, + }; + self.instruction( + &[AdapterType::String], + Instruction::Standard(std), + &[AdapterType::I32, AdapterType::I32], + ); + } + Descriptor::Slice(_) => { + // like strings, this allocation is cleaned up after being + // received in Rust. + let kind = arg.vector_kind().ok_or_else(|| { + format_err!( + "unsupported argument type for calling Rust function from JS {:?}", + arg + ) + })?; + if mutable { + self.instruction( + &[AdapterType::Vector(kind)], + Instruction::MutableSliceToMemory { + kind, + malloc: self.cx.malloc()?, + mem: self.cx.memory()?, + free: self.cx.free()?, + }, + &[AdapterType::I32, AdapterType::I32], + ); + } else { + self.instruction( + &[AdapterType::Vector(kind)], + Instruction::VectorToMemory { + kind, + malloc: self.cx.malloc()?, + mem: self.cx.memory()?, + }, + &[AdapterType::I32, AdapterType::I32], + ); + } + } + _ => bail!( + "unsupported reference argument type for calling Rust function from JS: {:?}", + arg + ), + } + Ok(()) + } + + fn incoming_option(&mut self, arg: &Descriptor) -> Result<(), Error> { + match arg { + Descriptor::Anyref => { + self.instruction( + &[AdapterType::Anyref], + Instruction::I32FromOptionAnyref, + &[AdapterType::I32], + ); + } + Descriptor::I8 => self.in_option_sentinel(), + Descriptor::U8 => self.in_option_sentinel(), + Descriptor::I16 => self.in_option_sentinel(), + Descriptor::U16 => self.in_option_sentinel(), + Descriptor::I32 => self.in_option_native(ValType::I32), + Descriptor::U32 => self.in_option_native(ValType::I32), + Descriptor::F32 => self.in_option_native(ValType::F32), + Descriptor::F64 => self.in_option_native(ValType::F64), + Descriptor::I64 | Descriptor::U64 => { + let signed = match arg { + Descriptor::I64 => true, + _ => false, + }; + self.instruction( + &[AdapterType::Anyref], + Instruction::I32SplitOption64 { signed }, + &[AdapterType::I32; 3], + ); + } + Descriptor::Boolean => { + self.instruction( + &[AdapterType::Anyref], + Instruction::I32FromOptionBool, + &[AdapterType::I32], + ); + } + Descriptor::Char => { + self.instruction( + &[AdapterType::Anyref], + Instruction::I32FromOptionChar, + &[AdapterType::I32], + ); + } + Descriptor::Enum { hole } => { + self.instruction( + &[AdapterType::Anyref], + Instruction::I32FromOptionEnum { hole: *hole }, + &[AdapterType::I32], + ); + } + Descriptor::RustStruct(name) => { + self.instruction( + &[AdapterType::Anyref], + Instruction::I32FromOptionRust { + class: name.to_string(), + }, + &[AdapterType::I32], + ); + } + + Descriptor::String | Descriptor::CachedString | Descriptor::Vector(_) => { + let kind = arg.vector_kind().ok_or_else(|| { + format_err!( + "unsupported optional slice type for calling Rust function from JS {:?}", + arg + ) + })?; + let malloc = self.cx.malloc()?; + let mem = self.cx.memory()?; + self.instruction( + &[AdapterType::Anyref], + Instruction::OptionVector { kind, malloc, mem }, + &[AdapterType::I32; 2], + ); + } + + _ => bail!( + "unsupported optional argument type for calling Rust function from JS: {:?}", + arg + ), + } + Ok(()) + } + + pub fn get(&mut self, ty: AdapterType) { + self.input.push(ty); + + // If we're generating instructions in the return position then the + // arguments are already on the stack to consume, otherwise we need to + // fetch them from the parameters. + if !self.return_position { + let idx = self.input.len() as u32 - 1; + let std = wit_walrus::Instruction::ArgGet(idx); + self.instructions.push(InstructionData { + instr: Instruction::Standard(std), + stack_change: StackChange::Modified { + pushed: 1, + popped: 0, + }, + }); + } + } + + pub fn instruction( + &mut self, + inputs: &[AdapterType], + instr: Instruction, + outputs: &[AdapterType], + ) { + // If we're generating instructions in the return position then the + // arguments are already on the stack to consume, otherwise we need to + // fetch them from the parameters. + if !self.return_position { + for input in inputs { + self.get(*input); + } + } else { + self.input.extend_from_slice(inputs); + } + + self.instructions.push(InstructionData { + instr, + stack_change: StackChange::Modified { + popped: inputs.len(), + pushed: outputs.len(), + }, + }); + self.output.extend_from_slice(outputs); + } + + fn number(&mut self, input: wit_walrus::ValType, output: walrus::ValType) { + let std = wit_walrus::Instruction::IntToWasm { + input, + output, + trap: false, + }; + self.instruction( + &[AdapterType::from_wit(input)], + Instruction::Standard(std), + &[AdapterType::from_wasm(output).unwrap()], + ); + } + + fn number64(&mut self, signed: bool) { + self.instruction( + &[if signed { + AdapterType::S64 + } else { + AdapterType::U64 + }], + Instruction::I32Split64 { signed }, + &[AdapterType::I32, AdapterType::I32], + ); + } + + fn in_option_native(&mut self, wasm: ValType) { + self.instruction( + &[AdapterType::Anyref], + Instruction::FromOptionNative { ty: wasm }, + &[AdapterType::I32, AdapterType::from_wasm(wasm).unwrap()], + ); + } + + fn in_option_sentinel(&mut self) { + self.instruction( + &[AdapterType::Anyref], + Instruction::I32FromOptionU32Sentinel, + &[AdapterType::I32], + ); + } +} diff --git a/crates/cli-support/src/webidl/mod.rs b/crates/cli-support/src/wit/mod.rs similarity index 51% rename from crates/cli-support/src/webidl/mod.rs rename to crates/cli-support/src/wit/mod.rs index da0ae730..2368d9e8 100644 --- a/crates/cli-support/src/webidl/mod.rs +++ b/crates/cli-support/src/wit/mod.rs @@ -1,482 +1,32 @@ -//! The polyfill for the WebIDL bindings proposal in wasm-bindgen. -//! -//! This module contains the polyfill (or at least the current state of and as -//! closely as we can match) the WebIDL bindings proposal. The module exports -//! one main function, `process`, which takes a `walrus::Module`. This module is -//! expected to have two items: -//! -//! * First it contains all of the raw wasm-bindgen modules emitted by the Rust -//! compiler. These raw custom sections are extracted, removed, decoded, and -//! handled here. They contain information such as what's exported where, -//! what's imported, comments, etc. -//! * Second, the `descriptors.rs` pass must have run previously to execute all -//! the descriptor functions in the wasm module. Through the synthesized -//! custom section there we learn the type information of all -//! functions/imports/exports in the module. -//! -//! The output of this function is then a new `walrus::Module` with the previous -//! custom sections all removed and two new ones inserted. One is the webidl -//! bindings custom section (or at least a close approximate) and the second is -//! an auxiliary section for wasm-bindgen itself. The goal is for this auxiliary -//! section to eventually be empty or inconsequential, allowing us to emit -//! something that doesn't even need a JS shim one day. For now we're still -//! pretty far away from that, so we'll settle for using webidl bindings as -//! aggressively as possible! - use crate::decode; use crate::descriptor::{Descriptor, Function}; use crate::descriptors::WasmBindgenDescriptorsSection; use crate::intrinsic::Intrinsic; use anyhow::{anyhow, bail, Error}; -use std::borrow::Cow; -use std::collections::{HashMap, HashSet}; -use std::path::PathBuf; +use std::collections::HashMap; use std::str; -use walrus::{ExportId, FunctionId, ImportId, Module, TypedCustomSectionId}; +use walrus::MemoryId; +use walrus::{ExportId, FunctionId, ImportId, Module}; use wasm_bindgen_shared::struct_function_export_name; -use wasm_webidl_bindings::ast; const PLACEHOLDER_MODULE: &str = "__wbindgen_placeholder__"; -mod bindings; mod incoming; +mod nonstandard; mod outgoing; -pub mod standard; - -pub use self::incoming::NonstandardIncoming; -pub use self::outgoing::NonstandardOutgoing; - -/// A nonstandard wasm-bindgen-specific WebIDL custom section. -/// -/// This nonstandard section is intended to convey all information that -/// wasm-bindgen itself needs to know about binding functions. This means that -/// it basically uses `NonstandardIncoming` instead of -/// `IncomingBindingExpression` and such. It's also in a bit easier to work with -/// format than the official WebIDL bindings custom section. -/// -/// Note that this is intended to be consumed during generation of JS shims and -/// bindings. There it can be transformed, however, into an actual WebIDL -/// binding section using all of the values it has internally. -#[derive(Default, Debug)] -pub struct NonstandardWebidlSection { - /// Store of all WebIDL types. Used currently to store all function types - /// specified in `Bindings`. This is intended to be passed through verbatim - /// to a final WebIDL bindings section. - pub types: ast::WebidlTypes, - - /// A mapping from all bound exported functions to the binding that we have - /// listed for them. This is the master list of every binding that will be - /// bound and have a shim generated for it in the wasm module. - pub exports: HashMap, - - /// Similar to `exports` above, except for imports. This will describe all - /// imports from the wasm module to indicate what the JS shim is expected to - /// do. - pub imports: HashMap, - - /// For closures and such we'll be calling entries in the function table - /// with rich arguments (just like we call exports) so to do that we - /// describe all the elem indices that we need to modify here as well. - /// - /// This is a list of pairs where the first element in the list is the - /// element index in the function table being described and the `Binding` - /// describes the signature that it's supposed to have. - /// - /// The index within this table itself is then used to call actually - /// transformed functions. - pub elems: Vec<(u32, Binding)>, -} - -pub type NonstandardWebidlSectionId = TypedCustomSectionId; - -/// A non-standard wasm-bindgen-specifi WebIDL binding. This is meant to vaguely -/// resemble a `FuctionBinding` in the official WebIDL bindings proposal, or at -/// least make it very easy to manufacture an official value from this one. -#[derive(Debug, Clone)] -pub struct Binding { - /// The WebAssembly type that the function is expected to have. Note that - /// this may not match the actual bound function's type! That's because this - /// type includes `anyref` but the Rust compiler never emits anyref. This - /// is, however, used for the `anyref` pass to know what to transform to - /// `anyref`. - pub wasm_ty: walrus::TypeId, - - /// The WebIDL type of this binding, which is an index into the webidl - /// binding section's `types` field. - pub webidl_ty: ast::WebidlFunctionId, - - /// A list of incoming bindings. For exports this is the list of arguments, - /// and for imports this is the return value. - pub incoming: Vec, - - /// A list of outgoing bindings. For exports this is the return value and - /// for imports this is the list of arguments. - pub outgoing: Vec, - - /// An unfortunate necessity of today's implementation. Ideally WebIDL - /// bindings are used with multi-value support in wasm everywhere, but today - /// few engines support multi-value and LLVM certainly doesn't. Aggregates - /// are then always returned through an out-ptr, so this indicates that if - /// an out-ptr is present what wasm types are being transmitted through it. - pub return_via_outptr: Option>, -} - -impl Binding { - /// Does this binding's wasm function signature have any `anyref`s? - pub fn contains_anyref(&self, module: &walrus::Module) -> bool { - let ty = module.types.get(self.wasm_ty); - ty.params() - .iter() - .chain(ty.results()) - .any(|ty| *ty == walrus::ValType::Anyref) - } -} - -/// A synthetic custom section which is not standardized, never will be, and -/// cannot be serialized or parsed. This is synthesized from all of the -/// compiler-emitted wasm-bindgen sections and then immediately removed to be -/// processed in the JS generation pass. -#[derive(Default, Debug)] -pub struct WasmBindgenAux { - /// Extra typescript annotations that should be appended to the generated - /// TypeScript file. This is provided via a custom attribute in Rust code. - pub extra_typescript: String, - - /// A map from identifier to the contents of each local module defined via - /// the `#[wasm_bindgen(module = "/foo.js")]` import options. - pub local_modules: HashMap, - - /// A map from unique crate identifier to the list of inline JS snippets for - /// that crate identifier. - pub snippets: HashMap>, - - /// A list of all `package.json` files that are intended to be included in - /// the final build. - pub package_jsons: HashSet, - - /// A map from exported function id to where it's expected to be exported - /// to. - pub export_map: HashMap, - - /// A map from imported function id to what it's expected to import. - pub import_map: HashMap, - - /// Small bits of metadata about imports. - pub imports_with_catch: HashSet, - pub imports_with_variadic: HashSet, - pub imports_with_assert_no_shim: HashSet, - - /// Auxiliary information to go into JS/TypeScript bindings describing the - /// exported enums from Rust. - pub enums: Vec, - - /// Auxiliary information to go into JS/TypeScript bindings describing the - /// exported structs from Rust and their fields they've got exported. - pub structs: Vec, -} - -pub type WasmBindgenAuxId = TypedCustomSectionId; - -#[derive(Debug)] -pub struct AuxExport { - /// When generating errors about this export, a helpful name to remember it - /// by. - pub debug_name: String, - /// Comments parsed in Rust and forwarded here to show up in JS bindings. - pub comments: String, - /// Argument names in Rust forwarded here to configure the names that show - /// up in TypeScript bindings. - pub arg_names: Option>, - /// What kind of function this is and where it shows up - pub kind: AuxExportKind, -} - -/// All possible kinds of exports from a wasm module. -/// -/// This `enum` says where to place an exported wasm function. For example it -/// may want to get hooked up to a JS class, or it may want to be exported as a -/// free function (etc). -/// -/// TODO: it feels like this should not really be here per se. We probably want -/// to either construct the JS object itself from within wasm or somehow move -/// more of this information into some other section. Really what this is is -/// sort of an "export map" saying how to wire up all the free functions from -/// the wasm module into the output expected JS module. All our functions here -/// currently take integer parameters and require a JS wrapper, but ideally -/// we'd change them one day to taking/receiving `anyref` which then use some -/// sort of webidl import to customize behavior or something like that. In any -/// case this doesn't feel quite right in terms of priviledge separation, so -/// we'll want to work on this. For now though it works. -#[derive(Debug)] -pub enum AuxExportKind { - /// A free function that's just listed on the exported module - Function(String), - - /// A function that's used to create an instane of a class. The function - /// actually return just an integer which is put on an JS object currently. - Constructor(String), - - /// This function is intended to be a getter for a field on a class. The - /// first argument is the internal pointer and the returned value is - /// expected to be the field. - Getter { class: String, field: String }, - - /// This function is intended to be a setter for a field on a class. The - /// first argument is the internal pointer and the second argument is - /// expected to be the field's new value. - Setter { class: String, field: String }, - - /// This is a free function (ish) but scoped inside of a class name. - StaticFunction { class: String, name: String }, - - /// This is a member function of a class where the first parameter is the - /// implicit integer stored in the class instance. - Method { - class: String, - name: String, - /// Whether or not this is calling a by-value method in Rust and should - /// clear the internal pointer in JS automatically. - consumed: bool, - }, -} - -#[derive(Debug)] -pub struct AuxEnum { - /// The name of this enum - pub name: String, - /// The copied Rust comments to forward to JS - pub comments: String, - /// A list of variants with their name and value - pub variants: Vec<(String, u32)>, -} - -#[derive(Debug)] -pub struct AuxStruct { - /// The name of this struct - pub name: String, - /// The copied Rust comments to forward to JS - pub comments: String, - /// Whether to generate helper methods for inspecting the class - pub is_inspectable: bool, -} - -/// All possible types of imports that can be imported by a wasm module. -/// -/// This `enum` is intended to map out what an imported value is. For example -/// this contains a ton of shims and various ways you can call a function. The -/// base variant here is `Value` which simply means "hook this up to the import" -/// and the signatures will match up. -/// -/// Note that this is *not* the same as the webidl bindings section. This is -/// intended to be coupled with that to map out what actually gets hooked up to -/// an import in the wasm module. The two work in tandem. -/// -/// Some of these items here are native to JS (like `Value`, indexing -/// operations, etc). Others are shims generated by wasm-bindgen (like `Closure` -/// or `Instanceof`). -#[derive(Debug)] -pub enum AuxImport { - /// This import is expected to simply be whatever is the value that's - /// imported - Value(AuxValue), - - /// A static method on a class is being imported, and the `this` of the - /// function call is expected to always be the class. - ValueWithThis(JsImport, String), - - /// This import is expected to be a function that takes an `anyref` and - /// returns a `bool`. It's expected that it tests if the argument is an - /// instance of (using `instanceof`) the name specified. - /// - /// TODO: can we use `Reflect` or something like that to avoid an extra kind - /// of import here? - Instanceof(JsImport), - - /// This import is expected to be a shim that returns the JS value named by - /// `JsImport`. - Static(JsImport), - - /// This import is intended to manufacture a JS closure with the given - /// signature and then return that back to Rust. - Closure { - mutable: bool, // whether or not this was a `FnMut` closure - dtor: u32, // table element index of the destructor function - binding_idx: u32, - nargs: usize, - }, - - /// This import is expected to be a shim that simply calls the `foo` method - /// on the first object, passing along all other parameters and returning - /// the resulting value. - StructuralMethod(String), - - /// This import is a "structural getter" which simply returns the `.field` - /// value of the first argument as an object. - /// - /// e.g. `function(x) { return x.foo; }` - StructuralGetter(String), - - /// This import is a "structural getter" which simply returns the `.field` - /// value of the specified class - /// - /// e.g. `function() { return TheClass.foo; }` - StructuralClassGetter(JsImport, String), - - /// This import is a "structural setter" which simply sets the `.field` - /// value of the first argument to the second argument. - /// - /// e.g. `function(x, y) { x.foo = y; }` - StructuralSetter(String), - - /// This import is a "structural setter" which simply sets the `.field` - /// value of the specified class to the first argument of the function. - /// - /// e.g. `function(x) { TheClass.foo = x; }` - StructuralClassSetter(JsImport, String), - - /// This import is expected to be a shim that is an indexing getter of the - /// JS class here, where the first argument of the function is the field to - /// look up. The return value is the value of the field. - /// - /// e.g. `function(x) { return TheClass[x]; }` - /// - /// TODO: can we use `Reflect` or something like that to avoid an extra kind - /// of import here? - IndexingGetterOfClass(JsImport), - - /// This import is expected to be a shim that is an indexing getter of the - /// first argument interpreted as an object where the field to look up is - /// the second argument. - /// - /// e.g. `function(x, y) { return x[y]; }` - /// - /// TODO: can we use `Reflect` or something like that to avoid an extra kind - /// of import here? - IndexingGetterOfObject, - - /// This import is expected to be a shim that is an indexing setter of the - /// JS class here, where the first argument of the function is the field to - /// set and the second is the value to set it to. - /// - /// e.g. `function(x, y) { TheClass[x] = y; }` - /// - /// TODO: can we use `Reflect` or something like that to avoid an extra kind - /// of import here? - IndexingSetterOfClass(JsImport), - - /// This import is expected to be a shim that is an indexing setter of the - /// first argument interpreted as an object where the next two fields are - /// the field to set and the value to set it to. - /// - /// e.g. `function(x, y, z) { x[y] = z; }` - /// - /// TODO: can we use `Reflect` or something like that to avoid an extra kind - /// of import here? - IndexingSetterOfObject, - - /// This import is expected to be a shim that is an indexing deleter of the - /// JS class here, where the first argument of the function is the field to - /// delete. - /// - /// e.g. `function(x) { delete TheClass[x]; }` - /// - /// TODO: can we use `Reflect` or something like that to avoid an extra kind - /// of import here? - IndexingDeleterOfClass(JsImport), - - /// This import is expected to be a shim that is an indexing deleter of the - /// first argument interpreted as an object where the second argument is - /// the field to delete. - /// - /// e.g. `function(x, y) { delete x[y]; }` - /// - /// TODO: can we use `Reflect` or something like that to avoid an extra kind - /// of import here? - IndexingDeleterOfObject, - - /// This import is a generated shim which will wrap the provided pointer in - /// a JS object corresponding to the Class name given here. The class name - /// is one that is exported from the Rust/wasm. - /// - /// TODO: sort of like the export map below we should ideally create the - /// `anyref` from within Rust itself and then return it directly rather than - /// requiring an intrinsic here to do so. - WrapInExportedClass(String), - - /// This is an intrinsic function expected to be implemented with a JS glue - /// shim. Each intrinsic has its own expected signature and implementation. - Intrinsic(Intrinsic), -} - -/// Values that can be imported verbatim to hook up to an import. -#[derive(Debug)] -pub enum AuxValue { - /// A bare JS value, no transformations, just put it in the slot. - Bare(JsImport), - - /// A getter function for the class listed for the field, acquired using - /// `getOwnPropertyDescriptor`. - Getter(JsImport, String), - - /// Like `Getter`, but accesses a field of a class instead of an instance - /// of the class. - ClassGetter(JsImport, String), - - /// Like `Getter`, except the `set` property. - Setter(JsImport, String), - - /// Like `Setter`, but for class fields instead of instance fields. - ClassSetter(JsImport, String), -} - -/// What can actually be imported and typically a value in each of the variants -/// above of `AuxImport` -/// -/// A `JsImport` is intended to indicate what exactly is being imported for a -/// particular operation. -#[derive(Debug, Hash, Eq, PartialEq, Clone)] -pub struct JsImport { - /// The base of whatever is being imported, either from a module, the global - /// namespace, or similar. - pub name: JsImportName, - /// Various field accesses (like `.foo.bar.baz`) to hang off the `name` - /// above. - pub fields: Vec, -} - -/// Return value of `determine_import` which is where we look at an imported -/// function AST and figure out where it's actually being imported from -/// (performing some validation checks and whatnot). -#[derive(Debug, Hash, Eq, PartialEq, Clone)] -pub enum JsImportName { - /// An item is imported from the global scope. The `name` is what's - /// imported. - Global { name: String }, - /// Same as `Global`, except the `name` is imported via an ESM import from - /// the specified `module` path. - Module { module: String, name: String }, - /// Same as `Module`, except we're importing from a local module defined in - /// a local JS snippet. - LocalModule { module: String, name: String }, - /// Same as `Module`, except we're importing from an `inline_js` attribute - InlineJs { - unique_crate_identifier: String, - snippet_idx_in_crate: usize, - name: String, - }, - /// A global import which may have a number of vendor prefixes associated - /// with it, like `webkitAudioPrefix`. The `name` is the name to test - /// whether it's prefixed. - VendorPrefixed { name: String, prefixes: Vec }, -} +pub mod section; +mod standard; +pub use self::nonstandard::*; +pub use self::standard::*; struct Context<'a> { start_found: bool, module: &'a mut Module, - bindings: NonstandardWebidlSection, + adapters: NonstandardWitSection, aux: WasmBindgenAux, function_exports: HashMap, function_imports: HashMap, + memory: Option, vendor_prefixes: HashMap>, unique_crate_identifier: &'a str, descriptors: HashMap, @@ -485,23 +35,32 @@ struct Context<'a> { support_start: bool, } +struct InstructionBuilder<'a, 'b> { + input: Vec, + output: Vec, + instructions: Vec, + cx: &'a mut Context<'b>, + return_position: bool, +} + pub fn process( module: &mut Module, anyref_enabled: bool, wasm_interface_types: bool, support_start: bool, -) -> Result<(NonstandardWebidlSectionId, WasmBindgenAuxId), Error> { +) -> Result<(NonstandardWitSectionId, WasmBindgenAuxId), Error> { let mut storage = Vec::new(); let programs = extract_programs(module, &mut storage)?; let mut cx = Context { - bindings: Default::default(), + adapters: Default::default(), aux: Default::default(), function_exports: Default::default(), function_imports: Default::default(), vendor_prefixes: Default::default(), descriptors: Default::default(), unique_crate_identifier: "", + memory: wasm_bindgen_wasm_conventions::get_memory(module).ok(), module, start_found: false, anyref_enabled, @@ -518,15 +77,15 @@ pub fn process( cx.discover_main()?; } - if let Some(standard) = cx.module.customs.delete_typed::() { + if let Some(standard) = cx.module.customs.delete_typed() { cx.standard(&standard)?; } cx.verify()?; - let bindings = cx.module.customs.add(cx.bindings); + let adapters = cx.module.customs.add(cx.adapters); let aux = cx.module.customs.add(cx.aux); - Ok((bindings, aux)) + Ok((adapters, aux)) } impl<'a> Context<'a> { @@ -578,29 +137,23 @@ impl<'a> Context<'a> { // to manufacture a particular type of closure. // // First we register the imported function shim which returns a - // `JsValue` for the closure. We manufacture this signature's - // binding since it's not listed anywhere. + // `JsValue` for the closure. We manufacture this signature + // since it's not listed anywhere. // - // Next we register the corresponding table element's binding in - // the webidl bindings section. This binding will later be used to + // Next we register the corresponding table element's signature in + // the interface types section. This adapter will later be used to // generate a shim (if necessary) for the table element. // // Finally we store all this metadata in the import map which we've // learned so when a binding for the import is generated we can // generate all the appropriate shims. for (id, descriptor) in closure_imports { - let binding = Function { + let signature = Function { shim_idx: 0, arguments: vec![Descriptor::I32; 3], ret: Descriptor::Anyref, }; - bindings::register_import( - self.module, - &mut self.bindings, - id, - binding, - ast::WebidlFunctionKind::Static, - )?; + let id = self.import_adapter(id, signature, AdapterJsImportKind::Normal)?; // Synthesize the two integer pointers we pass through which // aren't present in the signature but are present in the wasm // signature. @@ -608,19 +161,14 @@ impl<'a> Context<'a> { let nargs = function.arguments.len(); function.arguments.insert(0, Descriptor::I32); function.arguments.insert(0, Descriptor::I32); - let binding_idx = bindings::register_table_element( - self.module, - &mut self.bindings, - descriptor.shim_idx, - function, - )?; + let adapter = self.table_element_adapter(descriptor.shim_idx, function)?; self.aux.import_map.insert( id, AuxImport::Closure { dtor: descriptor.dtor_idx, mutable: descriptor.mutable, - binding_idx, nargs, + adapter, }, ); } @@ -699,13 +247,7 @@ impl<'a> Context<'a> { } fn bind_intrinsic(&mut self, id: ImportId, intrinsic: Intrinsic) -> Result<(), Error> { - bindings::register_import( - self.module, - &mut self.bindings, - id, - intrinsic.binding(), - ast::WebidlFunctionKind::Static, - )?; + let id = self.import_adapter(id, intrinsic.signature(), AdapterJsImportKind::Normal)?; self.aux .import_map .insert(id, AuxImport::Intrinsic(intrinsic)); @@ -833,8 +375,9 @@ impl<'a> Context<'a> { None => AuxExportKind::Function(export.function.name.to_string()), }; + let id = self.export_adapter(export_id, descriptor)?; self.aux.export_map.insert( - export_id, + id, AuxExport { debug_name: wasm_name, comments: concatenate_comments(&export.comments), @@ -842,7 +385,6 @@ impl<'a> Context<'a> { kind, }, ); - bindings::register_export(self.module, &mut self.bindings, export_id, descriptor)?; Ok(()) } @@ -908,60 +450,35 @@ impl<'a> Context<'a> { Some(d) => d.unwrap_function(), }; - // Record this for later as it affects JS binding generation, but note - // that this doesn't affect the WebIDL interface at all. - if *variadic { - self.aux.imports_with_variadic.insert(import_id); - } - if *catch { - self.aux.imports_with_catch.insert(import_id); - } - if *assert_no_shim { - self.aux.imports_with_assert_no_shim.insert(import_id); - } - - // Perform two functions here. First we're saving off our WebIDL - // bindings signature, indicating what we think our import is going to - // be. Next we're saving off other metadata indicating where this item - // is going to be imported from. The `import_map` table will record, for - // each import, what is getting hooked up to that slot of the import - // table to the WebAssembly instance. - let import = match method { + // Perform two functions here. First we're saving off our adapter + // signature, indicating what we think our import is going to be. Next + // we're saving off other metadata indicating where this item is going + // to be imported from. The `import_map` table will record, for each + // import, what is getting hooked up to that slot of the import table + // to the WebAssembly instance. + let (id, import) = match method { Some(data) => { let class = self.determine_import(import, &data.class)?; match &data.kind { // NB: `structural` is ignored for constructors since the // js type isn't expected to change anyway. decode::MethodKind::Constructor => { - bindings::register_import( - self.module, - &mut self.bindings, + let id = self.import_adapter( import_id, descriptor, - ast::WebidlFunctionKind::Constructor, + AdapterJsImportKind::Constructor, )?; - AuxImport::Value(AuxValue::Bare(class)) + (id, AuxImport::Value(AuxValue::Bare(class))) } decode::MethodKind::Operation(op) => { let (import, method) = self.determine_import_op(class, function, *structural, op)?; let kind = if method { - let kind = ast::WebidlFunctionKindMethod { - // TODO: what should this actually be? - ty: ast::WebidlScalarType::Any.into(), - }; - ast::WebidlFunctionKind::Method(kind) + AdapterJsImportKind::Method } else { - ast::WebidlFunctionKind::Static + AdapterJsImportKind::Normal }; - bindings::register_import( - self.module, - &mut self.bindings, - import_id, - descriptor, - kind, - )?; - import + (self.import_adapter(import_id, descriptor, kind)?, import) } } } @@ -969,19 +486,31 @@ impl<'a> Context<'a> { // NB: `structural` is ignored for free functions since it's // expected that the binding isn't changing anyway. None => { - bindings::register_import( - self.module, - &mut self.bindings, - import_id, - descriptor, - ast::WebidlFunctionKind::Static, - )?; + let id = self.import_adapter(import_id, descriptor, AdapterJsImportKind::Normal)?; let name = self.determine_import(import, function.name)?; - AuxImport::Value(AuxValue::Bare(name)) + (id, AuxImport::Value(AuxValue::Bare(name))) } }; - self.aux.import_map.insert(import_id, import); + // Record this for later as it affects JS binding generation, but note + // that this doesn't affect the WebIDL interface at all. + if *variadic { + self.aux.imports_with_variadic.insert(id); + } + + // Note that `catch`/`assert_no_shim` is applied not to the import + // itself but to the adapter shim we generated, so fetch that shim id + // and flag it as catch here. This basically just needs to be kept in + // sync with `js/mod.rs`. + let adapter = self.adapters.implements.last().unwrap().1; + if *catch { + self.aux.imports_with_catch.insert(adapter); + } + if *assert_no_shim { + self.aux.imports_with_assert_no_shim.insert(adapter); + } + + self.aux.import_map.insert(id, import); Ok(()) } @@ -1105,24 +634,20 @@ impl<'a> Context<'a> { }; // Register the signature of this imported shim - bindings::register_import( - self.module, - &mut self.bindings, + let id = self.import_adapter( import_id, Function { arguments: Vec::new(), shim_idx: 0, ret: descriptor, }, - ast::WebidlFunctionKind::Static, + AdapterJsImportKind::Normal, )?; // And then save off that this function is is an instanceof shim for an // imported item. let import = self.determine_import(import, &static_.name)?; - self.aux - .import_map - .insert(import_id, AuxImport::Static(import)); + self.aux.import_map.insert(id, AuxImport::Static(import)); Ok(()) } @@ -1137,16 +662,14 @@ impl<'a> Context<'a> { }; // Register the signature of this imported shim - bindings::register_import( - self.module, - &mut self.bindings, + let id = self.import_adapter( import_id, Function { arguments: vec![Descriptor::Ref(Box::new(Descriptor::Anyref))], shim_idx: 0, ret: Descriptor::Boolean, }, - ast::WebidlFunctionKind::Static, + AdapterJsImportKind::Normal, )?; // And then save off that this function is is an instanceof shim for an @@ -1154,7 +677,7 @@ impl<'a> Context<'a> { let import = self.determine_import(import, &type_.name)?; self.aux .import_map - .insert(import_id, AuxImport::Instanceof(import)); + .insert(id, AuxImport::Instanceof(import)); Ok(()) } @@ -1188,12 +711,7 @@ impl<'a> Context<'a> { shim_idx: 0, ret: descriptor.clone(), }; - bindings::register_export( - self.module, - &mut self.bindings, - getter_id, - getter_descriptor, - )?; + let getter_id = self.export_adapter(getter_id, getter_descriptor)?; self.aux.export_map.insert( getter_id, AuxExport { @@ -1218,12 +736,7 @@ impl<'a> Context<'a> { shim_idx: 0, ret: Descriptor::Unit, }; - bindings::register_export( - self.module, - &mut self.bindings, - setter_id, - setter_descriptor, - )?; + let setter_id = self.export_adapter(setter_id, setter_descriptor)?; self.aux.export_map.insert( setter_id, AuxExport { @@ -1245,23 +758,16 @@ impl<'a> Context<'a> { self.aux.structs.push(aux); let wrap_constructor = wasm_bindgen_shared::new_function(struct_.name); - if let Some((import_id, _id)) = self.function_imports.get(&wrap_constructor) { - self.aux.import_map.insert( - *import_id, - AuxImport::WrapInExportedClass(struct_.name.to_string()), - ); - let binding = Function { + if let Some((import_id, _id)) = self.function_imports.get(&wrap_constructor).cloned() { + let signature = Function { shim_idx: 0, arguments: vec![Descriptor::I32], ret: Descriptor::Anyref, }; - bindings::register_import( - self.module, - &mut self.bindings, - *import_id, - binding, - ast::WebidlFunctionKind::Static, - )?; + let id = self.import_adapter(import_id, signature, AdapterJsImportKind::Normal)?; + self.aux + .import_map + .insert(id, AuxImport::WrapInExportedClass(struct_.name.to_string())); } Ok(()) @@ -1352,144 +858,146 @@ impl<'a> Context<'a> { Ok(JsImport { name, fields }) } - /// Processes bindings from a standard WebIDL bindings custom section. - /// - /// No module coming out of the Rust compiler will have one of these, but - /// eventually there's going to be other producers of the WebIDL bindings - /// custom section as well. This functionality is intended to allow - /// `wasm-bindgen`-the-CLI-tool to act as a polyfill for those modules as - /// well as Rust modules. - /// - /// Here a standard `WebidlBindings` custom section is taken and we process - /// that into our own internal data structures to ensure that we have a - /// binding listed for all the described bindings. - /// - /// In other words, this is a glorified conversion from the "official" - /// WebIDL bindings custom section into the wasm-bindgen internal - /// representation. - fn standard(&mut self, std: &ast::WebidlBindings) -> Result<(), Error> { - for (_id, bind) in std.binds.iter() { - let binding = self.standard_binding(std, bind)?; - let func = self.module.funcs.get(bind.func); - match &func.kind { - walrus::FunctionKind::Import(i) => { - let id = i.import; - self.standard_import(binding, id)?; - } - walrus::FunctionKind::Local(_) => { - let export = self - .module - .exports - .iter() - .find(|e| match e.item { - walrus::ExportItem::Function(f) => f == bind.func, - _ => false, - }) - .ok_or_else(|| anyhow!("missing export function for webidl binding"))?; - let id = export.id(); - self.standard_export(binding, id)?; - } - walrus::FunctionKind::Uninitialized(_) => unreachable!(), + fn standard(&mut self, std: &wit_walrus::WasmInterfaceTypes) -> Result<(), Error> { + let mut walrus2us = HashMap::new(); + let params_and_results = |id: wit_walrus::TypeId| -> (Vec<_>, Vec<_>) { + let ty = std.types.get(id); + let params = ty + .params() + .iter() + .cloned() + .map(AdapterType::from_wit) + .collect(); + let results = ty + .results() + .iter() + .cloned() + .map(AdapterType::from_wit) + .collect(); + (params, results) + }; + + // Register all imports, allocating our own id for them and configuring + // where the JS value for the import is coming from. + for import in std.imports.iter() { + let func = std.funcs.get(import.func); + let (params, results) = params_and_results(func.ty); + let id = self.adapters.append( + params, + results, + AdapterKind::Import { + module: import.module.clone(), + name: import.name.clone(), + kind: AdapterJsImportKind::Normal, + }, + ); + walrus2us.insert(import.func, id); + let js = JsImport { + name: JsImportName::Module { + module: import.module.clone(), + name: import.name.clone(), + }, + fields: Vec::new(), + }; + let value = AuxValue::Bare(js); + assert!(self + .aux + .import_map + .insert(id, AuxImport::Value(value)) + .is_none()); + } + + // Register all functions, allocating our own id system for each of the + // functions. + for func in std.funcs.iter() { + if let wit_walrus::FuncKind::Import(_) = func.kind { + continue; + } + let (params, results) = params_and_results(func.ty); + walrus2us.insert( + func.id(), + self.adapters.append( + params, + results, + AdapterKind::Local { + instructions: Vec::new(), + }, + ), + ); + } + + // .. and then actually translate all functions using our id mapping, + // now that we're able to remap all the `CallAdapter` instructions. + for func in std.funcs.iter() { + let instrs = match &func.kind { + wit_walrus::FuncKind::Local(instrs) => instrs, + wit_walrus::FuncKind::Import(_) => continue, + }; + let instrs = instrs + .iter() + .map(|i| match i { + wit_walrus::Instruction::CallAdapter(f) => { + Instruction::CallAdapter(walrus2us[&f]) + } + other => Instruction::Standard(other.clone()), + }) + .map(|instr| InstructionData { + instr, + stack_change: StackChange::Unknown, + }) + .collect::>(); + + // Store the instrs into the adapter function directly. + let adapter = self + .adapters + .adapters + .get_mut(&walrus2us[&func.id()]) + .unwrap(); + match &mut adapter.kind { + AdapterKind::Local { instructions } => *instructions = instrs, + _ => unreachable!(), } } - Ok(()) - } - /// Creates a wasm-bindgen-internal `Binding` from an official `Bind` - /// structure specified in the upstream binary format. - /// - /// This will largely just copy some things into our own arenas but also - /// processes the list of binding expressions into our own representations. - fn standard_binding( - &mut self, - std: &ast::WebidlBindings, - bind: &ast::Bind, - ) -> Result { - let binding: &ast::FunctionBinding = std - .bindings - .get(bind.binding) - .ok_or_else(|| anyhow!("bad binding id"))?; - let (wasm_ty, webidl_ty, incoming, outgoing) = match binding { - ast::FunctionBinding::Export(e) => ( - e.wasm_ty, - e.webidl_ty, - e.params.bindings.as_slice(), - &e.result.bindings[..], - ), - ast::FunctionBinding::Import(e) => ( - e.wasm_ty, - e.webidl_ty, - &e.result.bindings[..], - e.params.bindings.as_slice(), - ), - }; - let webidl_ty = standard::copy_ty(&mut self.bindings.types, webidl_ty, &std.types); - let webidl_ty = match webidl_ty { - ast::WebidlTypeRef::Id(id) => ::wrap(id), - _ => bail!("invalid webidl type listed"), - }; + // next up register all exports, ensuring that our export map says + // what's happening as well for JS + for export in std.exports.iter() { + let id = walrus2us[&export.func]; + self.adapters.exports.push((export.name.clone(), id)); - Ok(Binding { - wasm_ty, - webidl_ty, - incoming: incoming - .iter() - .cloned() - .map(NonstandardIncoming::Standard) - .collect(), - outgoing: outgoing - .iter() - .cloned() - .map(NonstandardOutgoing::Standard) - .collect(), - return_via_outptr: None, - }) - } + let kind = AuxExportKind::Function(export.name.clone()); + let export = AuxExport { + debug_name: format!("standard export {:?}", id), + comments: String::new(), + arg_names: None, + kind, + }; + assert!(self.aux.export_map.insert(id, export).is_none()); + } - /// Registers that `id` has a `binding` which was read from a standard - /// webidl bindings section, so the source of `id` is its actual module/name - /// listed in the wasm module. - fn standard_import(&mut self, binding: Binding, id: walrus::ImportId) -> Result<(), Error> { - let import = self.module.imports.get(id); - let js = JsImport { - name: JsImportName::Module { - module: import.module.clone(), - name: import.name.clone(), - }, - fields: Vec::new(), - }; - let value = AuxValue::Bare(js); - assert!(self - .aux - .import_map - .insert(id, AuxImport::Value(value)) - .is_none()); - assert!(self.bindings.imports.insert(id, binding).is_none()); - - Ok(()) - } - - /// Registers that `id` has a `binding` and comes from a standard webidl - /// bindings section so it doesn't have any documentation or debug names we - /// can work with. - fn standard_export(&mut self, binding: Binding, id: walrus::ExportId) -> Result<(), Error> { - let export = self.module.exports.get(id); - let kind = AuxExportKind::Function(export.name.clone()); - let export = AuxExport { - debug_name: format!("standard export {:?}", id), - comments: String::new(), - arg_names: None, - kind, - }; - assert!(self.aux.export_map.insert(id, export).is_none()); - assert!(self.bindings.exports.insert(id, binding).is_none()); + // ... and finally the `implements` section + for i in std.implements.iter() { + let import_id = match &self.module.funcs.get(i.core_func).kind { + walrus::FunctionKind::Import(i) => i.import, + _ => panic!("malformed wasm interface typess section"), + }; + self.adapters + .implements + .push((import_id, walrus2us[&i.adapter_func])); + } Ok(()) } /// Perform a small verification pass over the module to perform some /// internal sanity checks. fn verify(&self) -> Result<(), Error> { - let mut imports_counted = 0; + // First up verify that all imports in the wasm module from our + // `$PLACEHOLDER_MODULE` are connected to an adapter via the + // `implements` section. + let mut implemented = HashMap::new(); + for (core, adapter) in self.adapters.implements.iter() { + implemented.insert(core, adapter); + } for import in self.module.imports.iter() { if import.module != PLACEHOLDER_MODULE { continue; @@ -1498,66 +1006,244 @@ impl<'a> Context<'a> { walrus::ImportKind::Function(_) => {} _ => bail!("import from `{}` was not a function", PLACEHOLDER_MODULE), } + if implemented.remove(&import.id()).is_none() { + bail!("import of `{}` doesn't have an adapter listed", import.name); + } + } + if implemented.len() != 0 { + bail!("more implementations listed than imports"); + } - // Ensure that everything imported from the `__wbindgen_placeholder__` - // module has a location listed as to where it's expected to be - // imported from. - if !self.aux.import_map.contains_key(&import.id()) { + // Next up verify that all imported adapter functions have a listing of + // where they're imported from. + let mut imports_counted = 0; + for (id, adapter) in self.adapters.adapters.iter() { + let name = match &adapter.kind { + AdapterKind::Import { name, .. } => name, + AdapterKind::Local { .. } => continue, + }; + if !self.aux.import_map.contains_key(id) { bail!( "import of `{}` doesn't have an import map item listed", - import.name + name ); } - // Also make sure there's a binding listed for it. - if !self.bindings.imports.contains_key(&import.id()) { - bail!("import of `{}` doesn't have a binding listed", import.name); - } imports_counted += 1; } - - // Make sure there's no extraneous bindings that weren't actually + // Make sure there's no extraneous adapters that weren't actually // imported in the module. if self.aux.import_map.len() != imports_counted { bail!("import map is larger than the number of imports"); } - if self.bindings.imports.len() != imports_counted { - bail!("import binding map is larger than the number of imports"); - } - // Make sure the export map and export bindings map contain the same + // Make sure the export map and export adapters map contain the same // number of entries. - for id in self.bindings.exports.keys() { + for (_, id) in self.adapters.exports.iter() { if !self.aux.export_map.contains_key(id) { - bail!("bindings map has an entry that the export map does not"); + bail!("adapters map has an entry that the export map does not"); } } - if self.bindings.exports.len() != self.aux.export_map.len() { - bail!("export map and export bindings map have different sizes"); + if self.adapters.exports.len() != self.aux.export_map.len() { + bail!("export map and export adapters map have different sizes"); } Ok(()) } -} -impl walrus::CustomSection for NonstandardWebidlSection { - fn name(&self) -> &str { - "webidl custom section" + /// Creates an import adapter for the `import` which will have the given + /// `signature`. + /// + /// Note that the JS function imported will be invoked as `kind`. + fn import_adapter( + &mut self, + import: ImportId, + signature: Function, + kind: AdapterJsImportKind, + ) -> Result { + let import = self.module.imports.get(import); + let (import_module, import_name) = (import.module.clone(), import.name.clone()); + let import_id = import.id(); + + // Process the returned type first to see if it needs an out-pointer. This + // happens if the results of the incoming arguments translated to wasm take + // up more than one type. + let mut ret = self.instruction_builder(true); + ret.incoming(&signature.ret)?; + let uses_retptr = ret.output.len() > 1; + + // Process the argument next, allocating space of the return value if one + // was present. Additionally configure the `module` and `adapters` to allow + // usage of closures going out to the import. + let mut args = ret.cx.instruction_builder(false); + if uses_retptr { + args.input.push(AdapterType::I32); + } + for arg in signature.arguments.iter() { + args.outgoing(arg)?; + } + + // Build up the list of instructions for our adapter function. We start out + // with all the outgoing instructions which convert all wasm params to the + // desired types to call our import... + let mut instructions = args.instructions; + + // ... and then we actually call our import. We synthesize an adapter + // definition for it with the appropriate types here on the fly. + let f = args.cx.adapters.append( + args.output, + ret.input, + AdapterKind::Import { + module: import_module, + name: import_name, + kind, + }, + ); + instructions.push(InstructionData { + instr: Instruction::CallAdapter(f), + stack_change: StackChange::Unknown, + }); + + // ... and then we follow up with a conversion of the incoming type + // back to wasm. + instructions.extend(ret.instructions); + + // ... and if a return pointer is in use then we need to store the types on + // the stack into the wasm return pointer. Note that we iterate in reverse + // here because the last result is the top value on the stack. + let results = if uses_retptr { + let mem = args.cx.memory()?; + for (i, ty) in ret.output.into_iter().enumerate().rev() { + instructions.push(InstructionData { + instr: Instruction::StoreRetptr { offset: i, ty, mem }, + stack_change: StackChange::Modified { + pushed: 0, + popped: 1, + }, + }); + } + Vec::new() + } else { + ret.output + }; + let id = args + .cx + .adapters + .append(args.input, results, AdapterKind::Local { instructions }); + args.cx.adapters.implements.push((import_id, id)); + Ok(f) } - fn data(&self, _: &walrus::IdsToIndices) -> Cow<[u8]> { - panic!("shouldn't emit custom sections just yet"); - } -} - -impl walrus::CustomSection for WasmBindgenAux { - fn name(&self) -> &str { - "wasm-bindgen custom section" + /// Creates an adapter function for the `export` given to have the + /// `signature` specified. + fn export_adapter( + &mut self, + export: ExportId, + signature: Function, + ) -> Result { + let export = self.module.exports.get(export); + let name = export.name.clone(); + // Do the actual heavy lifting elsewhere to generate the `binding`. + let call = Instruction::CallExport(export.id()); + let id = self.register_export_adapter(call, signature)?; + self.adapters.exports.push((name, id)); + Ok(id) } - fn data(&self, _: &walrus::IdsToIndices) -> Cow<[u8]> { - panic!("shouldn't emit custom sections just yet"); + fn table_element_adapter(&mut self, idx: u32, signature: Function) -> Result { + let call = Instruction::CallTableElement(idx); + // like above, largely just defer the work elsewhere + Ok(self.register_export_adapter(call, signature)?) + } + + fn register_export_adapter( + &mut self, + call: Instruction, + signature: Function, + ) -> Result { + // Figure out how to translate all the incoming arguments ... + let mut args = self.instruction_builder(false); + for arg in signature.arguments.iter() { + args.incoming(arg)?; + } + + // ... then the returned value being translated back + let mut ret = args.cx.instruction_builder(true); + ret.outgoing(&signature.ret)?; + let uses_retptr = ret.input.len() > 1; + + // Our instruction stream starts out with the return pointer as the first + // argument to the wasm function, if one is in use. Then we convert + // everything to wasm types. + // + // After calling the core wasm function we need to load all the return + // pointer arguments if there were any, otherwise we simply convert + // everything into the outgoing arguments. + let mut instructions = Vec::new(); + if uses_retptr { + instructions.push(InstructionData { + instr: Instruction::Retptr, + stack_change: StackChange::Modified { + pushed: 1, + popped: 0, + }, + }); + } + instructions.extend(args.instructions); + instructions.push(InstructionData { + instr: call, + stack_change: StackChange::Unknown, + }); + if uses_retptr { + let mem = ret.cx.memory()?; + for (i, ty) in ret.input.into_iter().enumerate() { + instructions.push(InstructionData { + instr: Instruction::LoadRetptr { offset: i, ty, mem }, + stack_change: StackChange::Modified { + pushed: 1, + popped: 0, + }, + }); + } + } + instructions.extend(ret.instructions); + + Ok(ret + .cx + .adapters + .append(args.input, ret.output, AdapterKind::Local { instructions })) + } + + fn instruction_builder<'b>(&'b mut self, return_position: bool) -> InstructionBuilder<'b, 'a> { + InstructionBuilder { + cx: self, + input: Vec::new(), + output: Vec::new(), + instructions: Vec::new(), + return_position, + } + } + + fn malloc(&self) -> Result { + self.function_exports + .get("__wbindgen_malloc") + .cloned() + .map(|p| p.1) + .ok_or_else(|| anyhow!("failed to find declaration of `__wbindgen_malloc` in module")) + } + + fn free(&self) -> Result { + self.function_exports + .get("__wbindgen_free") + .cloned() + .map(|p| p.1) + .ok_or_else(|| anyhow!("failed to find declaration of `__wbindgen_free` in module")) + } + + fn memory(&self) -> Result { + self.memory + .ok_or_else(|| anyhow!("failed to find memory declaration in module")) } } @@ -1689,77 +1375,3 @@ fn concatenate_comments(comments: &[&str]) -> String { .collect::>() .join("\n") } - -/// Do we need to generate JS glue shims for these incoming bindings? -pub fn incoming_do_not_require_glue( - exprs: &[NonstandardIncoming], - from_webidl_tys: &[ast::WebidlTypeRef], - to_wasm_tys: &[walrus::ValType], - standard_webidl_enabled: bool, -) -> bool { - // If anything is nonstandard, then we're unconditionally going to need a JS - // shim because, well, it's not standard. - if exprs.iter().any(|e| match e { - NonstandardIncoming::Standard(_) => false, - _ => true, - }) { - return false; - } - - // If everything is `Standard` and we've actually got WebIDL bindings fully - // enabled, then we don't require any glue at all! - if standard_webidl_enabled { - return true; - } - - exprs.len() == from_webidl_tys.len() - && exprs.len() == to_wasm_tys.len() - && exprs - .iter() - .zip(from_webidl_tys) - .zip(to_wasm_tys) - .enumerate() - .all(|(i, ((expr, from_webidl_ty), to_wasm_ty))| match expr { - NonstandardIncoming::Standard(e) => e.is_expressible_in_js_without_webidl_bindings( - *from_webidl_ty, - *to_wasm_ty, - i as u32, - ), - _ => false, - }) -} - -/// Do we need to generate JS glue shims for these outgoing bindings? -pub fn outgoing_do_not_require_glue( - exprs: &[NonstandardOutgoing], - from_wasm_tys: &[walrus::ValType], - to_webidl_tys: &[ast::WebidlTypeRef], - standard_webidl_enabled: bool, -) -> bool { - // Same short-circuits as above. - if exprs.iter().any(|e| match e { - NonstandardOutgoing::Standard(_) => false, - _ => true, - }) { - return false; - } - if standard_webidl_enabled { - return true; - } - - exprs.len() == from_wasm_tys.len() - && exprs.len() == to_webidl_tys.len() - && exprs - .iter() - .zip(from_wasm_tys) - .zip(to_webidl_tys) - .enumerate() - .all(|(i, ((expr, from_wasm_ty), to_webidl_ty))| match expr { - NonstandardOutgoing::Standard(e) => e.is_expressible_in_js_without_webidl_bindings( - *from_wasm_ty, - *to_webidl_ty, - i as u32, - ), - _ => false, - }) -} diff --git a/crates/cli-support/src/wit/nonstandard.rs b/crates/cli-support/src/wit/nonstandard.rs new file mode 100644 index 00000000..20f03cf5 --- /dev/null +++ b/crates/cli-support/src/wit/nonstandard.rs @@ -0,0 +1,360 @@ +use crate::intrinsic::Intrinsic; +use crate::wit::AdapterId; +use std::borrow::Cow; +use std::collections::{HashMap, HashSet}; +use std::path::PathBuf; +use walrus::TypedCustomSectionId; + +/// A synthetic custom section which is not standardized, never will be, and +/// cannot be serialized or parsed. This is synthesized from all of the +/// compiler-emitted wasm-bindgen sections and then immediately removed to be +/// processed in the JS generation pass. +#[derive(Default, Debug)] +pub struct WasmBindgenAux { + /// Extra typescript annotations that should be appended to the generated + /// TypeScript file. This is provided via a custom attribute in Rust code. + pub extra_typescript: String, + + /// A map from identifier to the contents of each local module defined via + /// the `#[wasm_bindgen(module = "/foo.js")]` import options. + pub local_modules: HashMap, + + /// A map from unique crate identifier to the list of inline JS snippets for + /// that crate identifier. + pub snippets: HashMap>, + + /// A list of all `package.json` files that are intended to be included in + /// the final build. + pub package_jsons: HashSet, + + /// A map from exported function id to where it's expected to be exported + /// to. + pub export_map: HashMap, + + /// A map from imported function id to what it's expected to import. + pub import_map: HashMap, + + /// Small bits of metadata about imports. + pub imports_with_catch: HashSet, + pub imports_with_variadic: HashSet, + pub imports_with_assert_no_shim: HashSet, + + /// Auxiliary information to go into JS/TypeScript bindings describing the + /// exported enums from Rust. + pub enums: Vec, + + /// Auxiliary information to go into JS/TypeScript bindings describing the + /// exported structs from Rust and their fields they've got exported. + pub structs: Vec, + + /// Information about various internal functions used to manage the `anyref` + /// table, later used to process JS bindings. + pub anyref_table: Option, + pub anyref_alloc: Option, + pub anyref_drop_slice: Option, +} + +pub type WasmBindgenAuxId = TypedCustomSectionId; + +#[derive(Debug)] +pub struct AuxExport { + /// When generating errors about this export, a helpful name to remember it + /// by. + pub debug_name: String, + /// Comments parsed in Rust and forwarded here to show up in JS bindings. + pub comments: String, + /// Argument names in Rust forwarded here to configure the names that show + /// up in TypeScript bindings. + pub arg_names: Option>, + /// What kind of function this is and where it shows up + pub kind: AuxExportKind, +} + +/// All possible kinds of exports from a wasm module. +/// +/// This `enum` says where to place an exported wasm function. For example it +/// may want to get hooked up to a JS class, or it may want to be exported as a +/// free function (etc). +/// +/// TODO: it feels like this should not really be here per se. We probably want +/// to either construct the JS object itself from within wasm or somehow move +/// more of this information into some other section. Really what this is is +/// sort of an "export map" saying how to wire up all the free functions from +/// the wasm module into the output expected JS module. All our functions here +/// currently take integer parameters and require a JS wrapper, but ideally +/// we'd change them one day to taking/receiving `anyref` which then use some +/// sort of webidl import to customize behavior or something like that. In any +/// case this doesn't feel quite right in terms of priviledge separation, so +/// we'll want to work on this. For now though it works. +#[derive(Debug)] +pub enum AuxExportKind { + /// A free function that's just listed on the exported module + Function(String), + + /// A function that's used to create an instane of a class. The function + /// actually return just an integer which is put on an JS object currently. + Constructor(String), + + /// This function is intended to be a getter for a field on a class. The + /// first argument is the internal pointer and the returned value is + /// expected to be the field. + Getter { class: String, field: String }, + + /// This function is intended to be a setter for a field on a class. The + /// first argument is the internal pointer and the second argument is + /// expected to be the field's new value. + Setter { class: String, field: String }, + + /// This is a free function (ish) but scoped inside of a class name. + StaticFunction { class: String, name: String }, + + /// This is a member function of a class where the first parameter is the + /// implicit integer stored in the class instance. + Method { + class: String, + name: String, + /// Whether or not this is calling a by-value method in Rust and should + /// clear the internal pointer in JS automatically. + consumed: bool, + }, +} + +#[derive(Debug)] +pub struct AuxEnum { + /// The name of this enum + pub name: String, + /// The copied Rust comments to forward to JS + pub comments: String, + /// A list of variants with their name and value + pub variants: Vec<(String, u32)>, +} + +#[derive(Debug)] +pub struct AuxStruct { + /// The name of this struct + pub name: String, + /// The copied Rust comments to forward to JS + pub comments: String, + /// Whether to generate helper methods for inspecting the class + pub is_inspectable: bool, +} + +/// All possible types of imports that can be imported by a wasm module. +/// +/// This `enum` is intended to map out what an imported value is. For example +/// this contains a ton of shims and various ways you can call a function. The +/// base variant here is `Value` which simply means "hook this up to the import" +/// and the signatures will match up. +/// +/// Note that this is *not* the same as the webidl bindings section. This is +/// intended to be coupled with that to map out what actually gets hooked up to +/// an import in the wasm module. The two work in tandem. +/// +/// Some of these items here are native to JS (like `Value`, indexing +/// operations, etc). Others are shims generated by wasm-bindgen (like `Closure` +/// or `Instanceof`). +#[derive(Debug)] +pub enum AuxImport { + /// This import is expected to simply be whatever is the value that's + /// imported + Value(AuxValue), + + /// A static method on a class is being imported, and the `this` of the + /// function call is expected to always be the class. + ValueWithThis(JsImport, String), + + /// This import is expected to be a function that takes an `anyref` and + /// returns a `bool`. It's expected that it tests if the argument is an + /// instance of (using `instanceof`) the name specified. + /// + /// TODO: can we use `Reflect` or something like that to avoid an extra kind + /// of import here? + Instanceof(JsImport), + + /// This import is expected to be a shim that returns the JS value named by + /// `JsImport`. + Static(JsImport), + + /// This import is intended to manufacture a JS closure with the given + /// signature and then return that back to Rust. + Closure { + mutable: bool, // whether or not this was a `FnMut` closure + dtor: u32, // table element index of the destructor function + adapter: AdapterId, // the adapter which translates the types for this closure + nargs: usize, + }, + + /// This import is expected to be a shim that simply calls the `foo` method + /// on the first object, passing along all other parameters and returning + /// the resulting value. + StructuralMethod(String), + + /// This import is a "structural getter" which simply returns the `.field` + /// value of the first argument as an object. + /// + /// e.g. `function(x) { return x.foo; }` + StructuralGetter(String), + + /// This import is a "structural getter" which simply returns the `.field` + /// value of the specified class + /// + /// e.g. `function() { return TheClass.foo; }` + StructuralClassGetter(JsImport, String), + + /// This import is a "structural setter" which simply sets the `.field` + /// value of the first argument to the second argument. + /// + /// e.g. `function(x, y) { x.foo = y; }` + StructuralSetter(String), + + /// This import is a "structural setter" which simply sets the `.field` + /// value of the specified class to the first argument of the function. + /// + /// e.g. `function(x) { TheClass.foo = x; }` + StructuralClassSetter(JsImport, String), + + /// This import is expected to be a shim that is an indexing getter of the + /// JS class here, where the first argument of the function is the field to + /// look up. The return value is the value of the field. + /// + /// e.g. `function(x) { return TheClass[x]; }` + /// + /// TODO: can we use `Reflect` or something like that to avoid an extra kind + /// of import here? + IndexingGetterOfClass(JsImport), + + /// This import is expected to be a shim that is an indexing getter of the + /// first argument interpreted as an object where the field to look up is + /// the second argument. + /// + /// e.g. `function(x, y) { return x[y]; }` + /// + /// TODO: can we use `Reflect` or something like that to avoid an extra kind + /// of import here? + IndexingGetterOfObject, + + /// This import is expected to be a shim that is an indexing setter of the + /// JS class here, where the first argument of the function is the field to + /// set and the second is the value to set it to. + /// + /// e.g. `function(x, y) { TheClass[x] = y; }` + /// + /// TODO: can we use `Reflect` or something like that to avoid an extra kind + /// of import here? + IndexingSetterOfClass(JsImport), + + /// This import is expected to be a shim that is an indexing setter of the + /// first argument interpreted as an object where the next two fields are + /// the field to set and the value to set it to. + /// + /// e.g. `function(x, y, z) { x[y] = z; }` + /// + /// TODO: can we use `Reflect` or something like that to avoid an extra kind + /// of import here? + IndexingSetterOfObject, + + /// This import is expected to be a shim that is an indexing deleter of the + /// JS class here, where the first argument of the function is the field to + /// delete. + /// + /// e.g. `function(x) { delete TheClass[x]; }` + /// + /// TODO: can we use `Reflect` or something like that to avoid an extra kind + /// of import here? + IndexingDeleterOfClass(JsImport), + + /// This import is expected to be a shim that is an indexing deleter of the + /// first argument interpreted as an object where the second argument is + /// the field to delete. + /// + /// e.g. `function(x, y) { delete x[y]; }` + /// + /// TODO: can we use `Reflect` or something like that to avoid an extra kind + /// of import here? + IndexingDeleterOfObject, + + /// This import is a generated shim which will wrap the provided pointer in + /// a JS object corresponding to the Class name given here. The class name + /// is one that is exported from the Rust/wasm. + /// + /// TODO: sort of like the export map below we should ideally create the + /// `anyref` from within Rust itself and then return it directly rather than + /// requiring an intrinsic here to do so. + WrapInExportedClass(String), + + /// This is an intrinsic function expected to be implemented with a JS glue + /// shim. Each intrinsic has its own expected signature and implementation. + Intrinsic(Intrinsic), +} + +/// Values that can be imported verbatim to hook up to an import. +#[derive(Debug)] +pub enum AuxValue { + /// A bare JS value, no transformations, just put it in the slot. + Bare(JsImport), + + /// A getter function for the class listed for the field, acquired using + /// `getOwnPropertyDescriptor`. + Getter(JsImport, String), + + /// Like `Getter`, but accesses a field of a class instead of an instance + /// of the class. + ClassGetter(JsImport, String), + + /// Like `Getter`, except the `set` property. + Setter(JsImport, String), + + /// Like `Setter`, but for class fields instead of instance fields. + ClassSetter(JsImport, String), +} + +/// What can actually be imported and typically a value in each of the variants +/// above of `AuxImport` +/// +/// A `JsImport` is intended to indicate what exactly is being imported for a +/// particular operation. +#[derive(Debug, Hash, Eq, PartialEq, Clone)] +pub struct JsImport { + /// The base of whatever is being imported, either from a module, the global + /// namespace, or similar. + pub name: JsImportName, + /// Various field accesses (like `.foo.bar.baz`) to hang off the `name` + /// above. + pub fields: Vec, +} + +/// Return value of `determine_import` which is where we look at an imported +/// function AST and figure out where it's actually being imported from +/// (performing some validation checks and whatnot). +#[derive(Debug, Hash, Eq, PartialEq, Clone)] +pub enum JsImportName { + /// An item is imported from the global scope. The `name` is what's + /// imported. + Global { name: String }, + /// Same as `Global`, except the `name` is imported via an ESM import from + /// the specified `module` path. + Module { module: String, name: String }, + /// Same as `Module`, except we're importing from a local module defined in + /// a local JS snippet. + LocalModule { module: String, name: String }, + /// Same as `Module`, except we're importing from an `inline_js` attribute + InlineJs { + unique_crate_identifier: String, + snippet_idx_in_crate: usize, + name: String, + }, + /// A global import which may have a number of vendor prefixes associated + /// with it, like `webkitAudioPrefix`. The `name` is the name to test + /// whether it's prefixed. + VendorPrefixed { name: String, prefixes: Vec }, +} + +impl walrus::CustomSection for WasmBindgenAux { + fn name(&self) -> &str { + "wasm-bindgen custom section" + } + + fn data(&self, _: &walrus::IdsToIndices) -> Cow<[u8]> { + panic!("shouldn't emit custom sections just yet"); + } +} diff --git a/crates/cli-support/src/wit/outgoing.rs b/crates/cli-support/src/wit/outgoing.rs new file mode 100644 index 00000000..36473193 --- /dev/null +++ b/crates/cli-support/src/wit/outgoing.rs @@ -0,0 +1,382 @@ +use crate::descriptor::Descriptor; +use crate::wit::{AdapterType, Instruction, InstructionBuilder}; +use crate::wit::{InstructionData, StackChange}; +use anyhow::{bail, format_err, Error}; +use walrus::ValType; + +impl InstructionBuilder<'_, '_> { + /// Processes one more `Descriptor` as an argument to a JS function that + /// wasm is calling. + /// + /// This will internally skip `Unit` and otherwise build up the `bindings` + /// map and ensure that it's correctly mapped from wasm to JS. + pub fn outgoing(&mut self, arg: &Descriptor) -> Result<(), Error> { + if let Descriptor::Unit = arg { + return Ok(()); + } + // Similar rationale to `incoming.rs` around these sanity checks. + let input_before = self.input.len(); + let output_before = self.output.len(); + self._outgoing(arg)?; + assert_eq!(output_before + 1, self.output.len()); + assert!(input_before < self.input.len()); + Ok(()) + } + + fn _outgoing(&mut self, arg: &Descriptor) -> Result<(), Error> { + match arg { + Descriptor::Boolean => { + self.instruction( + &[AdapterType::I32], + Instruction::BoolFromI32, + &[AdapterType::Bool], + ); + } + Descriptor::Anyref => { + self.instruction( + &[AdapterType::I32], + Instruction::AnyrefLoadOwned, + &[AdapterType::Anyref], + ); + } + Descriptor::I8 => self.outgoing_i32(AdapterType::S8), + Descriptor::U8 => self.outgoing_i32(AdapterType::U8), + Descriptor::I16 => self.outgoing_i32(AdapterType::S16), + Descriptor::U16 => self.outgoing_i32(AdapterType::U16), + Descriptor::I32 => self.outgoing_i32(AdapterType::S32), + Descriptor::U32 => self.outgoing_i32(AdapterType::U32), + Descriptor::F32 => { + self.get(AdapterType::F32); + self.output.push(AdapterType::F32); + } + Descriptor::F64 => { + self.get(AdapterType::F64); + self.output.push(AdapterType::F64); + } + Descriptor::Enum { .. } => self.outgoing_i32(AdapterType::U32), + + Descriptor::Char => { + self.instruction( + &[AdapterType::I32], + Instruction::StringFromChar, + &[AdapterType::String], + ); + } + + Descriptor::I64 | Descriptor::U64 => { + let signed = match arg { + Descriptor::I64 => true, + _ => false, + }; + self.instruction( + &[AdapterType::I32; 2], + Instruction::I64FromLoHi { signed }, + &[if signed { + AdapterType::S64 + } else { + AdapterType::U64 + }], + ); + } + + Descriptor::RustStruct(class) => { + self.instruction( + &[AdapterType::I32], + Instruction::RustFromI32 { + class: class.to_string(), + }, + &[AdapterType::Anyref], + ); + } + Descriptor::Ref(d) => self.outgoing_ref(false, d)?, + Descriptor::RefMut(d) => self.outgoing_ref(true, d)?, + + Descriptor::CachedString => self.cached_string(false, true)?, + + Descriptor::String => { + // fetch the ptr/length ... + self.get(AdapterType::I32); + self.get(AdapterType::I32); + + // ... then defer a call to `free` to happen later + let free = self.cx.free()?; + let std = wit_walrus::Instruction::DeferCallCore(free); + self.instructions.push(InstructionData { + instr: Instruction::Standard(std), + stack_change: StackChange::Modified { + popped: 2, + pushed: 2, + }, + }); + + // ... and then convert it to a string type + let std = wit_walrus::Instruction::MemoryToString(self.cx.memory()?); + self.instructions.push(InstructionData { + instr: Instruction::Standard(std), + stack_change: StackChange::Modified { + popped: 2, + pushed: 1, + }, + }); + self.output.push(AdapterType::String); + } + + Descriptor::Vector(_) => { + let kind = arg.vector_kind().ok_or_else(|| { + format_err!( + "unsupported argument type for calling JS function from Rust {:?}", + arg + ) + })?; + let mem = self.cx.memory()?; + let free = self.cx.free()?; + self.instruction( + &[AdapterType::I32; 2], + Instruction::VectorLoad { kind, mem, free }, + &[AdapterType::Vector(kind)], + ); + } + + Descriptor::Option(d) => self.outgoing_option(d)?, + + Descriptor::Function(_) | Descriptor::Closure(_) | Descriptor::Slice(_) => bail!( + "unsupported argument type for calling JS function from Rust: {:?}", + arg + ), + + // nothing to do + Descriptor::Unit => {} + + // Largely synthetic and can't show up + Descriptor::ClampedU8 => unreachable!(), + } + Ok(()) + } + + fn outgoing_ref(&mut self, mutable: bool, arg: &Descriptor) -> Result<(), Error> { + match arg { + Descriptor::Anyref => { + self.instruction( + &[AdapterType::I32], + Instruction::TableGet, + &[AdapterType::Anyref], + ); + } + Descriptor::CachedString => self.cached_string(false, false)?, + + Descriptor::String => { + let std = wit_walrus::Instruction::MemoryToString(self.cx.memory()?); + self.instruction( + &[AdapterType::I32; 2], + Instruction::Standard(std), + &[AdapterType::String], + ); + } + Descriptor::Slice(_) => { + let kind = arg.vector_kind().ok_or_else(|| { + format_err!( + "unsupported argument type for calling JS function from Rust {:?}", + arg + ) + })?; + let mem = self.cx.memory()?; + self.instruction( + &[AdapterType::I32; 2], + Instruction::View { kind, mem }, + &[AdapterType::Vector(kind)], + ); + } + + Descriptor::Function(descriptor) => { + // synthesize the a/b arguments that aren't present in the + // signature from wasm-bindgen but are present in the wasm file. + let mut descriptor = (**descriptor).clone(); + let nargs = descriptor.arguments.len(); + descriptor.arguments.insert(0, Descriptor::I32); + descriptor.arguments.insert(0, Descriptor::I32); + let adapter = self + .cx + .table_element_adapter(descriptor.shim_idx, descriptor)?; + self.instruction( + &[AdapterType::I32; 2], + Instruction::StackClosure { + adapter, + nargs, + mutable, + }, + &[AdapterType::Anyref], + ); + } + + _ => bail!( + "unsupported reference argument type for calling JS function from Rust: {:?}", + arg + ), + } + Ok(()) + } + + fn outgoing_option(&mut self, arg: &Descriptor) -> Result<(), Error> { + match arg { + Descriptor::Anyref => { + // This is set to `undefined` in the `None` case and otherwise + // is the valid owned index. + self.instruction( + &[AdapterType::I32], + Instruction::AnyrefLoadOwned, + &[AdapterType::Anyref], + ); + } + Descriptor::I8 => self.out_option_sentinel(), + Descriptor::U8 => self.out_option_sentinel(), + Descriptor::I16 => self.out_option_sentinel(), + Descriptor::U16 => self.out_option_sentinel(), + Descriptor::I32 => self.option_native(true, ValType::I32), + Descriptor::U32 => self.option_native(false, ValType::I32), + Descriptor::F32 => self.option_native(true, ValType::F32), + Descriptor::F64 => self.option_native(true, ValType::F64), + Descriptor::I64 | Descriptor::U64 => { + let signed = match arg { + Descriptor::I64 => true, + _ => false, + }; + self.instruction( + &[AdapterType::I32; 3], + Instruction::Option64FromI32 { signed }, + &[AdapterType::Anyref], + ); + } + Descriptor::Boolean => { + self.instruction( + &[AdapterType::I32], + Instruction::OptionBoolFromI32, + &[AdapterType::Anyref], + ); + } + Descriptor::Char => { + self.instruction( + &[AdapterType::I32], + Instruction::OptionCharFromI32, + &[AdapterType::Anyref], + ); + } + Descriptor::Enum { hole } => { + self.instruction( + &[AdapterType::I32], + Instruction::OptionEnumFromI32 { hole: *hole }, + &[AdapterType::Anyref], + ); + } + Descriptor::RustStruct(name) => { + self.instruction( + &[AdapterType::I32], + Instruction::OptionRustFromI32 { + class: name.to_string(), + }, + &[AdapterType::Anyref], + ); + } + Descriptor::Ref(d) => self.outgoing_option_ref(false, d)?, + Descriptor::RefMut(d) => self.outgoing_option_ref(true, d)?, + + Descriptor::CachedString => self.cached_string(true, true)?, + + Descriptor::String | Descriptor::Vector(_) => { + let kind = arg.vector_kind().ok_or_else(|| { + format_err!( + "unsupported optional slice type for calling JS function from Rust {:?}", + arg + ) + })?; + let mem = self.cx.memory()?; + let free = self.cx.free()?; + self.instruction( + &[AdapterType::I32; 2], + Instruction::OptionVectorLoad { kind, mem, free }, + &[AdapterType::Anyref], + ); + } + + _ => bail!( + "unsupported optional argument type for calling JS function from Rust: {:?}", + arg + ), + } + Ok(()) + } + + fn outgoing_option_ref(&mut self, _mutable: bool, arg: &Descriptor) -> Result<(), Error> { + match arg { + Descriptor::Anyref => { + // If this is `Some` then it's the index, otherwise if it's + // `None` then it's the index pointing to undefined. + self.instruction( + &[AdapterType::I32], + Instruction::TableGet, + &[AdapterType::Anyref], + ); + } + Descriptor::CachedString => self.cached_string(true, false)?, + Descriptor::String | Descriptor::Slice(_) => { + let kind = arg.vector_kind().ok_or_else(|| { + format_err!( + "unsupported optional slice type for calling JS function from Rust {:?}", + arg + ) + })?; + let mem = self.cx.memory()?; + self.instruction( + &[AdapterType::I32; 2], + Instruction::OptionView { kind, mem }, + &[AdapterType::Anyref], + ); + } + _ => bail!( + "unsupported optional ref argument type for calling JS function from Rust: {:?}", + arg + ), + } + Ok(()) + } + + fn outgoing_i32(&mut self, output: AdapterType) { + let std = wit_walrus::Instruction::WasmToInt { + input: walrus::ValType::I32, + output: output.to_wit().unwrap(), + trap: false, + }; + self.instruction(&[AdapterType::I32], Instruction::Standard(std), &[output]); + } + + fn cached_string(&mut self, optional: bool, owned: bool) -> Result<(), Error> { + let mem = self.cx.memory()?; + let free = self.cx.free()?; + self.instruction( + &[AdapterType::I32; 2], + Instruction::CachedStringLoad { + owned, + optional, + mem, + free, + }, + &[AdapterType::String], + ); + Ok(()) + } + + fn option_native(&mut self, signed: bool, ty: ValType) { + self.instruction( + &[AdapterType::I32, AdapterType::from_wasm(ty).unwrap()], + Instruction::ToOptionNative { signed, ty }, + &[AdapterType::Anyref], + ); + } + + fn out_option_sentinel(&mut self) { + self.instruction( + &[AdapterType::I32], + Instruction::OptionU32Sentinel, + &[AdapterType::Anyref], + ); + } +} diff --git a/crates/cli-support/src/wit/section.rs b/crates/cli-support/src/wit/section.rs new file mode 100644 index 00000000..54c89e1a --- /dev/null +++ b/crates/cli-support/src/wit/section.rs @@ -0,0 +1,414 @@ +//! Support for generating a standard wasm interface types +//! +//! This module has all the necessary support for generating a full-fledged +//! standard wasm interface types section as defined by the `wit_walrus` +//! crate. This module also critically assumes that the WebAssembly module +//! being generated **must be standalone**. In this mode all sorts of features +//! supported by `#[wasm_bindgen]` aren't actually supported, such as closures, +//! imports of global js names, js getters/setters, exporting structs, etc. +//! These features may all eventually come to the standard bindings proposal, +//! but it will likely take some time. In the meantime this module simply focuses +//! on taking what's already a valid wasm module and letting it through with a +//! standard WebIDL custom section. All other modules generate an error during +//! this binding process. +//! +//! Note that when this function is called and used we're also not actually +//! generating any JS glue. Any JS glue currently generated is also invalid if +//! the module contains the wasm bindings section and it's actually respected. + +use crate::wit::{AdapterId, AdapterJsImportKind, AdapterType, Instruction}; +use crate::wit::{AdapterKind, NonstandardWitSection, WasmBindgenAux}; +use crate::wit::{AuxExport, InstructionData}; +use crate::wit::{AuxExportKind, AuxImport, AuxValue, JsImport, JsImportName}; +use anyhow::{anyhow, bail, Context, Error}; +use std::collections::HashMap; +use walrus::Module; + +pub fn add( + module: &mut Module, + aux: &WasmBindgenAux, + nonstandard: &NonstandardWitSection, +) -> Result<(), Error> { + let mut section = wit_walrus::WasmInterfaceTypes::default(); + let WasmBindgenAux { + extra_typescript: _, // ignore this even if it's specified + local_modules, + snippets, + package_jsons, + export_map, + import_map, + imports_with_catch, + imports_with_variadic, + imports_with_assert_no_shim: _, // not relevant for this purpose + enums, + structs, + anyref_table: _, // not relevant + anyref_alloc: _, // not relevant + anyref_drop_slice: _, // not relevant + } = aux; + + let adapter_context = |id: AdapterId| { + if let Some((name, _)) = nonstandard.exports.iter().find(|p| p.1 == id) { + return format!("in function export `{}`", name); + } + if let Some((core, _)) = nonstandard.implements.iter().find(|p| p.1 == id) { + let import = module.imports.get(*core); + return format!( + "in function import from `{}::{}`", + import.module, import.name + ); + } + unreachable!() + }; + + let mut us2walrus = HashMap::new(); + for (us, func) in nonstandard.adapters.iter() { + if let Some(export) = export_map.get(us) { + check_standard_export(export).context(adapter_context(*us))?; + } + if let Some(import) = import_map.get(us) { + check_standard_import(import).context(adapter_context(*us))?; + } + let params = translate_tys(&func.params).context(adapter_context(*us))?; + let results = translate_tys(&func.results).context(adapter_context(*us))?; + let ty = section.types.add(params, results); + let walrus = match &func.kind { + AdapterKind::Local { .. } => section.funcs.add_local(ty, Vec::new()), + AdapterKind::Import { + module, + name, + kind: AdapterJsImportKind::Normal, + } => section.add_import_func(module, name, ty).0, + AdapterKind::Import { + module, + name, + kind: AdapterJsImportKind::Constructor, + } => { + bail!( + "interfaces types doesn't support import of `{}::{}` \ + as a constructor", + module, + name + ); + } + AdapterKind::Import { + module, + name, + kind: AdapterJsImportKind::Method, + } => { + bail!( + "interfaces types doesn't support import of `{}::{}` \ + as a method", + module, + name + ); + } + }; + us2walrus.insert(*us, walrus); + } + + for (core, adapter) in nonstandard.implements.iter() { + let core = match module.imports.get(*core).kind { + walrus::ImportKind::Function(f) => f, + _ => bail!("cannot implement a non-function"), + }; + section.implements.add(us2walrus[adapter], core); + } + + for (name, adapter) in nonstandard.exports.iter() { + section.exports.add(name, us2walrus[adapter]); + } + + for (id, func) in nonstandard.adapters.iter() { + let instructions = match &func.kind { + AdapterKind::Local { instructions } => instructions, + AdapterKind::Import { .. } => continue, + }; + let result = match &mut section.funcs.get_mut(us2walrus[id]).kind { + wit_walrus::FuncKind::Local(i) => i, + _ => unreachable!(), + }; + + for instruction in instructions { + result.push( + translate_instruction(instruction, &us2walrus, module) + .with_context(|| adapter_context(*id))?, + ); + } + } + + if let Some((name, _)) = local_modules.iter().next() { + bail!( + "generating a bindings section is currently incompatible with \ + local JS modules being specified as well, `{}` cannot be used \ + since a standalone wasm file is being generated", + name, + ); + } + + if let Some((name, _)) = snippets.iter().filter(|(_, v)| !v.is_empty()).next() { + bail!( + "generating a bindings section is currently incompatible with \ + local JS snippets being specified as well, `{}` cannot be used \ + since a standalone wasm file is being generated", + name, + ); + } + + if let Some(path) = package_jsons.iter().next() { + bail!( + "generating a bindings section is currently incompatible with \ + package.json being consumed as well, `{}` cannot be used \ + since a standalone wasm file is being generated", + path.display(), + ); + } + + if let Some(id) = imports_with_catch.iter().next() { + bail!( + "{}\ngenerating a bindings section is currently incompatible with \ + `#[wasm_bindgen(catch)]`", + adapter_context(*id), + ); + } + + if let Some(id) = imports_with_variadic.iter().next() { + bail!( + "{}\ngenerating a bindings section is currently incompatible with \ + `#[wasm_bindgen(variadic)]`", + adapter_context(*id), + ); + } + + if let Some(enum_) = enums.iter().next() { + bail!( + "generating a bindings section is currently incompatible with \ + exporting an `enum` from the wasm file, cannot export `{}`", + enum_.name, + ); + } + + if let Some(struct_) = structs.iter().next() { + bail!( + "generating a bindings section is currently incompatible with \ + exporting a `struct` from the wasm file, cannot export `{}`", + struct_.name, + ); + } + + module.customs.add(section); + Ok(()) +} + +fn translate_instruction( + instr: &InstructionData, + us2walrus: &HashMap, + module: &Module, +) -> Result { + use Instruction::*; + + match &instr.instr { + Standard(s) => Ok(s.clone()), + CallAdapter(id) => { + let id = us2walrus[id]; + Ok(wit_walrus::Instruction::CallAdapter(id)) + } + CallExport(e) => match module.exports.get(*e).item { + walrus::ExportItem::Function(f) => Ok(wit_walrus::Instruction::CallCore(f)), + _ => bail!("can only call exported functions"), + }, + CallTableElement(e) => { + let table = module + .tables + .main_function_table()? + .ok_or_else(|| anyhow!("no function table found in module"))?; + let functions = match &module.tables.get(table).kind { + walrus::TableKind::Function(f) => f, + _ => unreachable!(), + }; + match functions.elements.get(*e as usize) { + Some(Some(f)) => Ok(wit_walrus::Instruction::CallCore(*f)), + _ => bail!("expected to find an element of the function table"), + } + } + StoreRetptr { .. } | LoadRetptr { .. } | Retptr => { + bail!("return pointers aren't supported in wasm interface types"); + } + I32FromBool | BoolFromI32 => { + bail!("booleans aren't supported in wasm interface types"); + } + I32FromStringFirstChar | StringFromChar => { + bail!("chars aren't supported in wasm interface types"); + } + I32FromAnyrefOwned | I32FromAnyrefBorrow | AnyrefLoadOwned | TableGet => { + bail!("anyref pass failed to sink into wasm module"); + } + I32FromAnyrefRustOwned { .. } | I32FromAnyrefRustBorrow { .. } | RustFromI32 { .. } => { + bail!("rust types aren't supported in wasm interface types"); + } + I32Split64 { .. } | I64FromLoHi { .. } => { + bail!("64-bit integers aren't supported in wasm-bindgen"); + } + I32SplitOption64 { .. } + | I32FromOptionAnyref + | I32FromOptionU32Sentinel + | I32FromOptionRust { .. } + | I32FromOptionBool + | I32FromOptionChar + | I32FromOptionEnum { .. } + | FromOptionNative { .. } + | OptionVector { .. } + | OptionRustFromI32 { .. } + | OptionVectorLoad { .. } + | OptionView { .. } + | OptionU32Sentinel + | ToOptionNative { .. } + | OptionBoolFromI32 + | OptionCharFromI32 + | OptionEnumFromI32 { .. } + | Option64FromI32 { .. } => { + bail!("optional types aren't supported in wasm bindgen"); + } + MutableSliceToMemory { .. } | VectorToMemory { .. } | VectorLoad { .. } | View { .. } => { + bail!("vector slices aren't supported in wasm interface types yet"); + } + CachedStringLoad { .. } => { + bail!("cached strings aren't supported in wasm interface types"); + } + StackClosure { .. } => { + bail!("closures aren't supported in wasm interface types"); + } + } +} + +fn check_standard_import(import: &AuxImport) -> Result<(), Error> { + let desc_js = |js: &JsImport| { + let mut extra = String::new(); + for field in js.fields.iter() { + extra.push_str("."); + extra.push_str(field); + } + match &js.name { + JsImportName::Global { name } | JsImportName::VendorPrefixed { name, .. } => { + format!("global `{}{}`", name, extra) + } + JsImportName::Module { module, name } => { + format!("`{}{}` from '{}'", name, extra, module) + } + JsImportName::LocalModule { module, name } => { + format!("`{}{}` from local module '{}'", name, extra, module) + } + JsImportName::InlineJs { + unique_crate_identifier, + name, + .. + } => format!( + "`{}{}` from inline js in '{}'", + name, extra, unique_crate_identifier + ), + } + }; + + let item = match import { + AuxImport::Value(AuxValue::Bare(js)) => { + if js.fields.len() == 0 { + if let JsImportName::Module { .. } = js.name { + return Ok(()); + } + } + desc_js(js) + } + AuxImport::Value(AuxValue::Getter(js, name)) + | AuxImport::Value(AuxValue::Setter(js, name)) + | AuxImport::Value(AuxValue::ClassGetter(js, name)) + | AuxImport::Value(AuxValue::ClassSetter(js, name)) => { + format!("field access of `{}` for {}", name, desc_js(js)) + } + AuxImport::ValueWithThis(js, method) => format!("method `{}.{}`", desc_js(js), method), + AuxImport::Instanceof(js) => format!("instance of check of {}", desc_js(js)), + AuxImport::Static(js) => format!("static js value {}", desc_js(js)), + AuxImport::StructuralMethod(name) => format!("structural method `{}`", name), + AuxImport::StructuralGetter(name) + | AuxImport::StructuralSetter(name) + | AuxImport::StructuralClassGetter(_, name) + | AuxImport::StructuralClassSetter(_, name) => { + format!("structural field access of `{}`", name) + } + AuxImport::IndexingDeleterOfClass(_) + | AuxImport::IndexingDeleterOfObject + | AuxImport::IndexingGetterOfClass(_) + | AuxImport::IndexingGetterOfObject + | AuxImport::IndexingSetterOfClass(_) + | AuxImport::IndexingSetterOfObject => format!("indexing getters/setters/deleters"), + AuxImport::WrapInExportedClass(name) => { + format!("wrapping a pointer in a `{}` js class wrapper", name) + } + AuxImport::Intrinsic(intrinsic) => { + format!("wasm-bindgen specific intrinsic `{}`", intrinsic.name()) + } + AuxImport::Closure { .. } => format!("creating a `Closure` wrapper"), + }; + bail!( + "cannot generate a standalone WebAssembly module which \ + contains an import of {} since it requires JS glue", + item + ); +} + +fn check_standard_export(export: &AuxExport) -> Result<(), Error> { + // First up make sure this is something that's actually valid to export + // form a vanilla WebAssembly module with WebIDL bindings. + match &export.kind { + AuxExportKind::Function(_) => Ok(()), + AuxExportKind::Constructor(name) => { + bail!( + "cannot export `{}` constructor function when generating \ + a standalone WebAssembly module with no JS glue", + name, + ); + } + AuxExportKind::Getter { class, field } => { + bail!( + "cannot export `{}::{}` getter function when generating \ + a standalone WebAssembly module with no JS glue", + class, + field, + ); + } + AuxExportKind::Setter { class, field } => { + bail!( + "cannot export `{}::{}` setter function when generating \ + a standalone WebAssembly module with no JS glue", + class, + field, + ); + } + AuxExportKind::StaticFunction { class, name } => { + bail!( + "cannot export `{}::{}` static function when \ + generating a standalone WebAssembly module with no \ + JS glue", + class, + name + ); + } + AuxExportKind::Method { class, name, .. } => { + bail!( + "cannot export `{}::{}` method when \ + generating a standalone WebAssembly module with no \ + JS glue", + class, + name + ); + } + } +} + +fn translate_tys(tys: &[AdapterType]) -> Result, Error> { + tys.iter() + .map(|ty| { + ty.to_wit() + .ok_or_else(|| anyhow!("type {:?} isn't supported in standard interface types", ty)) + }) + .collect() +} diff --git a/crates/cli-support/src/wit/standard.rs b/crates/cli-support/src/wit/standard.rs new file mode 100644 index 00000000..0388d9d1 --- /dev/null +++ b/crates/cli-support/src/wit/standard.rs @@ -0,0 +1,359 @@ +use crate::descriptor::VectorKind; +use std::borrow::Cow; +use std::collections::HashMap; +use walrus::{ImportId, TypedCustomSectionId}; + +#[derive(Default, Debug)] +pub struct NonstandardWitSection { + /// A list of adapter functions, keyed by their id. + pub adapters: HashMap, + + /// A list of pairs for adapter functions that implement core wasm imports. + pub implements: Vec<(ImportId, AdapterId)>, + + /// A list of adapter functions and the names they're exported under. + pub exports: Vec<(String, AdapterId)>, +} + +pub type NonstandardWitSectionId = TypedCustomSectionId; + +#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, PartialOrd, Ord)] +pub struct AdapterId(pub usize); + +#[derive(Debug, Clone)] +pub struct Adapter { + pub id: AdapterId, + pub params: Vec, + pub results: Vec, + pub kind: AdapterKind, +} + +#[derive(Debug, Clone)] +pub enum AdapterKind { + Local { + instructions: Vec, + }, + Import { + module: String, + name: String, + kind: AdapterJsImportKind, + }, +} + +#[derive(Debug, Clone)] +pub struct InstructionData { + pub instr: Instruction, + pub stack_change: StackChange, +} + +#[derive(Debug, Clone)] +pub enum StackChange { + Modified { pushed: usize, popped: usize }, + Unknown, +} + +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum AdapterJsImportKind { + /// The first argument is an `anyref` which is the `this` of the function + /// call + Method, + /// The value imported should be invoked as `new` + Constructor, + /// A bland function import + Normal, +} + +#[derive(Debug, Clone, Copy)] +pub enum AdapterType { + S8, + S16, + S32, + S64, + U8, + U16, + U32, + U64, + F32, + F64, + String, + Anyref, + Bool, + I32, + I64, + Vector(VectorKind), +} + +#[derive(Debug, Clone)] +pub enum Instruction { + /// A known instruction in the "standard" + Standard(wit_walrus::Instruction), + + /// A call to one of our own defined adapters, similar to the standard + /// call-adapter instruction + CallAdapter(AdapterId), + /// Call an exported function in the core module + CallExport(walrus::ExportId), + /// Call an element in the function table of the core module + CallTableElement(u32), + + /// An instruction to store `ty` at the `offset` index in the return pointer + StoreRetptr { + ty: AdapterType, + offset: usize, + mem: walrus::MemoryId, + }, + /// An instruction to load `ty` at the `offset` index from the return pointer + LoadRetptr { + ty: AdapterType, + offset: usize, + mem: walrus::MemoryId, + }, + /// An instruction which pushes the return pointer onto the stack. + Retptr, + + /// Pops a `bool` from the stack and pushes an `i32` equivalent + I32FromBool, + /// Pops a `string` from the stack and pushes the first character as `i32` + I32FromStringFirstChar, + /// Pops an `anyref` from the stack, allocates space in the anyref table, + /// returns the index it was stored at. + I32FromAnyrefOwned, + /// Pops an `anyref` from the stack, pushes it onto the anyref wasm table + /// stack, and returns the index it was stored at. + I32FromAnyrefBorrow, + /// Pops an `anyref` from the stack, assumes it's a Rust class given, and + /// deallocates the JS object and returns the i32 Rust pointer. + I32FromAnyrefRustOwned { + class: String, + }, + /// Pops an `anyref` from the stack, assumes it's a Rust class given, and + /// passes the pointer to Rust which will be borrowed for the duration of a + /// call + I32FromAnyrefRustBorrow { + class: String, + }, + /// Pops an `anyref` from the stack, pushes 0 if it's "none" or the + /// consumed pointer value if it's "some". + I32FromOptionRust { + class: String, + }, + /// Pops an `s64` or `u64` from the stack, pushing two `i32` values. + I32Split64 { + signed: bool, + }, + /// Pops an `s64` or `u64` from the stack, pushing three `i32` values. + /// First is the "some/none" bit, and the next is the low bits, and the + /// next is the high bits. + I32SplitOption64 { + signed: bool, + }, + /// Pops an `anyref` from the stack, pushes either 0 if it's "none" or and + /// index into the owned wasm table it was stored at if it's "some" + I32FromOptionAnyref, + /// Pops an `anyref` from the stack, pushes either a sentinel value if it's + /// "none" or the integer value of it if it's "some" + I32FromOptionU32Sentinel, + /// Pops an `anyref` from the stack, pushes 0 for "none", 1 for + /// "some(false)', and 2 for "some(true)" + I32FromOptionBool, + /// Pops an `anyref` from the stack, pushes a sentinel for "none" or the + /// value if it's "some" + I32FromOptionChar, + /// Pops an `anyref` from the stack, pushes `hole` for "none" or the + /// value if it's "some" + I32FromOptionEnum { + hole: u32, + }, + /// Pops any anyref from the stack and then pushes two values. First is a + /// 0/1 if it's none/some and second is `ty` value if it was there or 0 if + /// it wasn't there. + FromOptionNative { + ty: walrus::ValType, + }, + + /// Pops a vector value of `kind` from the stack, allocates memory with + /// `malloc`, and then copies all the data into `mem`. Pushes the pointer + /// and length as i32. + VectorToMemory { + kind: VectorKind, + malloc: walrus::FunctionId, + mem: walrus::MemoryId, + }, + MutableSliceToMemory { + kind: VectorKind, + malloc: walrus::FunctionId, + free: walrus::FunctionId, + mem: walrus::MemoryId, + }, + + /// Pops an anyref, pushes pointer/length or all zeros + OptionVector { + kind: VectorKind, + malloc: walrus::FunctionId, + mem: walrus::MemoryId, + }, + + /// pops a `i32`, pushes `bool` + BoolFromI32, + /// pops `i32`, loads anyref at that slot, dealloates anyref, pushes `anyref` + AnyrefLoadOwned, + /// pops `i32`, pushes string from that `char` + StringFromChar, + /// pops two `i32`, pushes a 64-bit number + I64FromLoHi { + signed: bool, + }, + /// pops `i32`, pushes an anyref for the wrapped rust class + RustFromI32 { + class: String, + }, + OptionRustFromI32 { + class: String, + }, + /// pops ptr/length i32, loads string from cache + CachedStringLoad { + owned: bool, + optional: bool, + mem: walrus::MemoryId, + free: walrus::FunctionId, + }, + /// pops ptr/length, pushes a vector, frees the original data + VectorLoad { + kind: VectorKind, + mem: walrus::MemoryId, + free: walrus::FunctionId, + }, + /// pops ptr/length, pushes a vector, frees the original data + OptionVectorLoad { + kind: VectorKind, + mem: walrus::MemoryId, + free: walrus::FunctionId, + }, + /// pops i32, loads anyref from anyref table + TableGet, + /// pops two i32 data pointers, pushes an anyref closure + StackClosure { + adapter: AdapterId, + nargs: usize, + mutable: bool, + }, + /// pops two i32 data pointers, pushes a vector view + View { + kind: VectorKind, + mem: walrus::MemoryId, + }, + /// pops two i32 data pointers, pushes a vector view + OptionView { + kind: VectorKind, + mem: walrus::MemoryId, + }, + /// pops i32, pushes it viewed as an optional value with a known sentinel + OptionU32Sentinel, + /// pops an i32, then `ty`, then pushes anyref + ToOptionNative { + ty: walrus::ValType, + signed: bool, + }, + OptionBoolFromI32, + OptionCharFromI32, + OptionEnumFromI32 { + hole: u32, + }, + Option64FromI32 { + signed: bool, + }, +} + +impl AdapterType { + pub fn from_wit(wit: wit_walrus::ValType) -> AdapterType { + match wit { + wit_walrus::ValType::S8 => AdapterType::S8, + wit_walrus::ValType::S16 => AdapterType::S16, + wit_walrus::ValType::S32 => AdapterType::S32, + wit_walrus::ValType::S64 => AdapterType::S64, + wit_walrus::ValType::U8 => AdapterType::U8, + wit_walrus::ValType::U16 => AdapterType::U16, + wit_walrus::ValType::U32 => AdapterType::U32, + wit_walrus::ValType::U64 => AdapterType::U64, + wit_walrus::ValType::F32 => AdapterType::F32, + wit_walrus::ValType::F64 => AdapterType::F64, + wit_walrus::ValType::String => AdapterType::String, + wit_walrus::ValType::Anyref => AdapterType::Anyref, + wit_walrus::ValType::I32 => AdapterType::I32, + wit_walrus::ValType::I64 => AdapterType::I64, + } + } + + pub fn from_wasm(wasm: walrus::ValType) -> Option { + Some(match wasm { + walrus::ValType::I32 => AdapterType::I32, + walrus::ValType::I64 => AdapterType::I64, + walrus::ValType::F32 => AdapterType::F32, + walrus::ValType::F64 => AdapterType::F64, + walrus::ValType::Anyref => AdapterType::Anyref, + walrus::ValType::V128 => return None, + }) + } + + pub fn to_wasm(&self) -> Option { + Some(match self { + AdapterType::I32 => walrus::ValType::I32, + AdapterType::I64 => walrus::ValType::I64, + AdapterType::F32 => walrus::ValType::F32, + AdapterType::F64 => walrus::ValType::F64, + AdapterType::Anyref => walrus::ValType::Anyref, + _ => return None, + }) + } + + pub fn to_wit(&self) -> Option { + Some(match self { + AdapterType::S8 => wit_walrus::ValType::S8, + AdapterType::S16 => wit_walrus::ValType::S16, + AdapterType::S32 => wit_walrus::ValType::S32, + AdapterType::S64 => wit_walrus::ValType::S64, + AdapterType::U8 => wit_walrus::ValType::U8, + AdapterType::U16 => wit_walrus::ValType::U16, + AdapterType::U32 => wit_walrus::ValType::U32, + AdapterType::U64 => wit_walrus::ValType::U64, + AdapterType::F32 => wit_walrus::ValType::F32, + AdapterType::F64 => wit_walrus::ValType::F64, + AdapterType::String => wit_walrus::ValType::String, + AdapterType::Anyref => wit_walrus::ValType::Anyref, + AdapterType::I32 => wit_walrus::ValType::I32, + AdapterType::I64 => wit_walrus::ValType::I64, + AdapterType::Bool | AdapterType::Vector(_) => return None, + }) + } +} + +impl NonstandardWitSection { + pub fn append( + &mut self, + params: Vec, + results: Vec, + kind: AdapterKind, + ) -> AdapterId { + let id = AdapterId(self.adapters.len()); + self.adapters.insert( + id, + Adapter { + id, + params, + results, + kind, + }, + ); + return id; + } +} + +impl walrus::CustomSection for NonstandardWitSection { + fn name(&self) -> &str { + "nonstandard wit section" + } + + fn data(&self, _: &walrus::IdsToIndices) -> Cow<[u8]> { + panic!("shouldn't emit custom sections just yet"); + } +} diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 0fb5c867..489e31b6 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -24,7 +24,7 @@ rouille = { version = "3.0.0", default-features = false } serde = { version = "1.0", features = ['derive'] } serde_derive = "1.0" serde_json = "1.0" -walrus = { version = "0.13.0", features = ['parallel'] } +walrus = { version = "0.14.0", features = ['parallel'] } wasm-bindgen-cli-support = { path = "../cli-support", version = "=0.2.55" } wasm-bindgen-shared = { path = "../shared", version = "=0.2.55" } diff --git a/crates/multi-value-xform/Cargo.toml b/crates/multi-value-xform/Cargo.toml index 18399d18..16a35edd 100644 --- a/crates/multi-value-xform/Cargo.toml +++ b/crates/multi-value-xform/Cargo.toml @@ -13,4 +13,14 @@ edition = "2018" [dependencies] anyhow = "1.0" -walrus = "0.13.0" +walrus = "0.14.0" + +[dev-dependencies] +rayon = "1.0" +wasmprinter = "0.2" +wast = "3.0" +wat = "1.0" + +[[test]] +name = "all" +harness = false diff --git a/crates/multi-value-xform/src/lib.rs b/crates/multi-value-xform/src/lib.rs index 2bcd974a..75e7c262 100644 --- a/crates/multi-value-xform/src/lib.rs +++ b/crates/multi-value-xform/src/lib.rs @@ -108,23 +108,27 @@ /// return pointer parameter that will be removed. The `Vec` /// is the new result type that will be returned directly instead of via the /// return pointer. +/// +/// Returns a list of wrappers which have multi value signatures and call the +/// corresponding element in the `to_xform` list. pub fn run( module: &mut walrus::Module, memory: walrus::MemoryId, shadow_stack_pointer: walrus::GlobalId, - to_xform: &[(walrus::ExportId, usize, &[walrus::ValType])], -) -> Result<(), anyhow::Error> { - for &(export, return_pointer_index, results) in to_xform { - xform_one( + to_xform: &[(walrus::FunctionId, usize, Vec)], +) -> Result, anyhow::Error> { + let mut wrappers = Vec::new(); + for (func, return_pointer_index, results) in to_xform { + wrappers.push(xform_one( module, memory, shadow_stack_pointer, - export, - return_pointer_index, + *func, + *return_pointer_index, results, - )?; + )?); } - Ok(()) + Ok(wrappers) } // Ensure that `n` is aligned to `align`, rounding up as necessary. @@ -137,19 +141,14 @@ fn xform_one( module: &mut walrus::Module, memory: walrus::MemoryId, shadow_stack_pointer: walrus::GlobalId, - export: walrus::ExportId, + func: walrus::FunctionId, return_pointer_index: usize, results: &[walrus::ValType], -) -> Result<(), anyhow::Error> { +) -> Result { if module.globals.get(shadow_stack_pointer).ty != walrus::ValType::I32 { anyhow::bail!("shadow stack pointer global does not have type `i32`"); } - let func = match module.exports.get(export).item { - walrus::ExportItem::Function(f) => f, - _ => anyhow::bail!("can only multi-value transform exported functions, found non-function"), - }; - // Compute the total size of all results, potentially with padding to ensure // that each result is aligned. let mut results_size = 0; @@ -300,13 +299,7 @@ fn xform_one( module.funcs.get_mut(wrapper).name = Some(format!("{} multivalue shim", name)); } - // Replace the old export with our new multi-value wrapper for it! - match module.exports.get_mut(export).item { - walrus::ExportItem::Function(ref mut f) => *f = wrapper, - _ => unreachable!(), - } - - Ok(()) + Ok(wrapper) } #[cfg(test)] diff --git a/crates/multi-value-xform/tests/align.wat b/crates/multi-value-xform/tests/align.wat new file mode 100644 index 00000000..384d31da --- /dev/null +++ b/crates/multi-value-xform/tests/align.wat @@ -0,0 +1,37 @@ +;; @xform export "foo" (f64 i32 i64) + +(module + (global (mut i32) (i32.const 0)) + (memory 1) + + (func $foo (export "foo") (param i32)) +) + +(; CHECK-ALL: +(module + (type (;0;) (func (result f64 i32 i64))) + (type (;1;) (func (param i32))) + (func $foo multivalue shim (type 0) (result f64 i32 i64) + (local i32) + global.get 0 + i32.const 32 + i32.sub + local.tee 0 + global.set 0 + local.get 0 + call $foo + local.get 0 + f64.load + local.get 0 + i32.load offset=8 + local.get 0 + i64.load offset=16 + local.get 0 + i32.const 32 + i32.add + global.set 0) + (func $foo (type 1) (param i32)) + (memory (;0;) 1) + (global (;0;) (mut i32) (i32.const 0)) + (export "foo" (func $foo multivalue shim))) +;) diff --git a/crates/multi-value-xform/tests/all.rs b/crates/multi-value-xform/tests/all.rs new file mode 100644 index 00000000..588abd26 --- /dev/null +++ b/crates/multi-value-xform/tests/all.rs @@ -0,0 +1,225 @@ +//! A small test framework to execute a test function over all files in a +//! directory. +//! +//! Each file in the directory has its own `CHECK-ALL` annotation indicating the +//! expected output of the test. That can be automatically updated with +//! `BLESS=1` in the environment. Otherwise the test are checked against the +//! listed expectation. + +use anyhow::{bail, Context, Result}; +use rayon::prelude::*; +use std::env; +use std::fs; +use std::path::{Path, PathBuf}; +use wast::parser::{Parse, Parser}; + +fn main() { + run("tests".as_ref(), runtest); +} + +fn runtest(test: &Test) -> Result { + let wasm = wat::parse_file(&test.file)?; + let mut walrus = walrus::Module::from_buffer(&wasm)?; + let mut exports = Vec::new(); + let mut xforms = Vec::new(); + for directive in test.directives.iter() { + let export = walrus + .exports + .iter() + .find(|e| e.name == directive.name) + .unwrap(); + let id = match export.item { + walrus::ExportItem::Function(id) => id, + _ => panic!("must be function export"), + }; + exports.push(export.id()); + xforms.push((id, 0, directive.tys.clone())); + } + let memory = walrus.memories.iter().next().unwrap().id(); + let stack_pointer = walrus.globals.iter().next().unwrap().id(); + let ret = wasm_bindgen_multi_value_xform::run(&mut walrus, memory, stack_pointer, &xforms)?; + for (export, id) in exports.into_iter().zip(ret) { + walrus.exports.get_mut(export).item = walrus::ExportItem::Function(id); + } + walrus::passes::gc::run(&mut walrus); + let printed = wasmprinter::print_bytes(&walrus.emit_wasm())?; + Ok(printed) +} + +fn run(dir: &Path, run: fn(&Test) -> Result) { + let mut tests = Vec::new(); + find_tests(dir, &mut tests); + let filter = std::env::args().nth(1); + + let bless = env::var("BLESS").is_ok(); + let tests = tests + .iter() + .filter(|test| { + if let Some(filter) = &filter { + if let Some(s) = test.file_name().and_then(|s| s.to_str()) { + if !s.contains(filter) { + return false; + } + } + } + true + }) + .collect::>(); + + println!("\nrunning {} tests\n", tests.len()); + + let errors = tests + .par_iter() + .filter_map(|test| run_test(test, bless, run).err()) + .collect::>(); + + if !errors.is_empty() { + for msg in errors.iter() { + eprintln!("error: {:?}", msg); + } + + panic!("{} tests failed", errors.len()) + } + + println!("test result: ok. {} passed\n", tests.len()); +} + +fn run_test(test: &Path, bless: bool, run: fn(&Test) -> anyhow::Result) -> Result<()> { + (|| -> Result<_> { + let expected = Test::from_file(test)?; + let actual = run(&expected)?; + expected.check(&actual, bless)?; + Ok(()) + })() + .context(format!("test failed - {}", test.display()))?; + Ok(()) +} + +fn find_tests(path: &Path, tests: &mut Vec) { + for f in path.read_dir().unwrap() { + let f = f.unwrap(); + if f.file_type().unwrap().is_dir() { + find_tests(&f.path(), tests); + continue; + } + match f.path().extension().and_then(|s| s.to_str()) { + Some("wat") => {} + _ => continue, + } + tests.push(f.path()); + } +} + +struct Test { + file: PathBuf, + directives: Vec, + assertion: Option, +} + +struct Directive { + name: String, + tys: Vec, +} + +impl Test { + fn from_file(path: &Path) -> Result { + let contents = fs::read_to_string(path)?; + let mut iter = contents.lines(); + let mut assertion = None; + let mut directives = Vec::new(); + while let Some(line) = iter.next() { + if line.starts_with("(; CHECK-ALL:") { + let mut pattern = String::new(); + while let Some(line) = iter.next() { + if line == ";)" { + break; + } + pattern.push_str(line); + pattern.push_str("\n"); + } + while pattern.ends_with("\n") { + pattern.pop(); + } + if iter.next().is_some() { + bail!("CHECK-ALL must be at the end of the file"); + } + assertion = Some(pattern); + continue; + } + + if !line.starts_with(";; @xform") { + continue; + } + let directive = &line[9..]; + let buf = wast::parser::ParseBuffer::new(directive)?; + directives.push(wast::parser::parse::(&buf)?); + } + Ok(Test { + file: path.to_path_buf(), + directives, + assertion, + }) + } + + fn check(&self, output: &str, bless: bool) -> Result<()> { + if bless { + update_output(&self.file, output) + } else if let Some(pattern) = &self.assertion { + if output == pattern { + return Ok(()); + } + bail!( + "expected\n {}\n\nactual\n {}", + pattern.replace("\n", "\n "), + output.replace("\n", "\n ") + ); + } else { + bail!( + "no test assertions were found in this file, but you can \ + rerun tests with `BLESS=1` to automatically add assertions \ + to this file" + ); + } + } +} + +fn update_output(path: &Path, output: &str) -> Result<()> { + let contents = fs::read_to_string(path)?; + let start = contents.find("(; CHECK-ALL:").unwrap_or(contents.len()); + + let mut new_output = String::new(); + for line in output.lines() { + new_output.push_str(line); + new_output.push_str("\n"); + } + let new = format!( + "{}\n\n(; CHECK-ALL:\n{}\n;)\n", + contents[..start].trim(), + new_output.trim_end() + ); + fs::write(path, new)?; + Ok(()) +} + +impl<'a> Parse<'a> for Directive { + fn parse(parser: Parser<'a>) -> wast::parser::Result { + use wast::{kw, ValType}; + + parser.parse::()?; + let name = parser.parse()?; + let mut tys = Vec::new(); + parser.parens(|p| { + while !p.is_empty() { + tys.push(match p.parse()? { + ValType::I32 => walrus::ValType::I32, + ValType::I64 => walrus::ValType::I64, + ValType::F32 => walrus::ValType::F32, + ValType::F64 => walrus::ValType::F64, + _ => panic!(), + }); + } + Ok(()) + })?; + Ok(Directive { name, tys }) + } +} diff --git a/crates/multi-value-xform/tests/many.wat b/crates/multi-value-xform/tests/many.wat new file mode 100644 index 00000000..ea48d8ae --- /dev/null +++ b/crates/multi-value-xform/tests/many.wat @@ -0,0 +1,39 @@ +;; @xform export "foo" (i32 f32 f64 i64) + +(module + (global (mut i32) (i32.const 0)) + (memory 1) + + (func $foo (export "foo") (param i32)) +) + +(; CHECK-ALL: +(module + (type (;0;) (func (result i32 f32 f64 i64))) + (type (;1;) (func (param i32))) + (func $foo multivalue shim (type 0) (result i32 f32 f64 i64) + (local i32) + global.get 0 + i32.const 32 + i32.sub + local.tee 0 + global.set 0 + local.get 0 + call $foo + local.get 0 + i32.load + local.get 0 + f32.load offset=4 + local.get 0 + f64.load offset=8 + local.get 0 + i64.load offset=16 + local.get 0 + i32.const 32 + i32.add + global.set 0) + (func $foo (type 1) (param i32)) + (memory (;0;) 1) + (global (;0;) (mut i32) (i32.const 0)) + (export "foo" (func $foo multivalue shim))) +;) diff --git a/crates/multi-value-xform/tests/simple.wat b/crates/multi-value-xform/tests/simple.wat new file mode 100644 index 00000000..c1ebc23d --- /dev/null +++ b/crates/multi-value-xform/tests/simple.wat @@ -0,0 +1,93 @@ +;; @xform export "i32" (i32) +;; @xform export "i64" (i64) +;; @xform export "f32" (f32) +;; @xform export "f64" (f64) + +(module + (global (mut i32) (i32.const 0)) + (memory 1) + + (func $i32 (export "i32") (param i32)) + (func $i64 (export "i64") (param i32)) + (func $f32 (export "f32") (param i32)) + (func $f64 (export "f64") (param i32)) +) + +(; CHECK-ALL: +(module + (type (;0;) (func (result i32))) + (type (;1;) (func (result i64))) + (type (;2;) (func (result f32))) + (type (;3;) (func (result f64))) + (type (;4;) (func (param i32))) + (func $i32 multivalue shim (type 0) (result i32) + (local i32) + global.get 0 + i32.const 16 + i32.sub + local.tee 0 + global.set 0 + local.get 0 + call $i32 + local.get 0 + i32.load + local.get 0 + i32.const 16 + i32.add + global.set 0) + (func $i64 multivalue shim (type 1) (result i64) + (local i32) + global.get 0 + i32.const 16 + i32.sub + local.tee 0 + global.set 0 + local.get 0 + call $i64 + local.get 0 + i64.load + local.get 0 + i32.const 16 + i32.add + global.set 0) + (func $f32 multivalue shim (type 2) (result f32) + (local i32) + global.get 0 + i32.const 16 + i32.sub + local.tee 0 + global.set 0 + local.get 0 + call $f32 + local.get 0 + f32.load + local.get 0 + i32.const 16 + i32.add + global.set 0) + (func $f64 multivalue shim (type 3) (result f64) + (local i32) + global.get 0 + i32.const 16 + i32.sub + local.tee 0 + global.set 0 + local.get 0 + call $f64 + local.get 0 + f64.load + local.get 0 + i32.const 16 + i32.add + global.set 0) + (func $i32 (type 4) (param i32)) + (func $i64 (type 4) (param i32)) + (func $f32 (type 4) (param i32)) + (func $f64 (type 4) (param i32)) + (memory (;0;) 1) + (global (;0;) (mut i32) (i32.const 0)) + (export "i32" (func $i32 multivalue shim)) + (export "i64" (func $i64 multivalue shim)) + (export "f32" (func $f32 multivalue shim)) + (export "f64" (func $f64 multivalue shim))) +;) diff --git a/crates/threads-xform/Cargo.toml b/crates/threads-xform/Cargo.toml index a104af45..e3132153 100644 --- a/crates/threads-xform/Cargo.toml +++ b/crates/threads-xform/Cargo.toml @@ -13,5 +13,5 @@ edition = "2018" [dependencies] anyhow = "1.0" -walrus = "0.13.0" +walrus = "0.14.0" wasm-bindgen-wasm-conventions = { path = "../wasm-conventions", version = "=0.2.55" } diff --git a/crates/wasm-conventions/Cargo.toml b/crates/wasm-conventions/Cargo.toml index f6d41564..3ece2d96 100644 --- a/crates/wasm-conventions/Cargo.toml +++ b/crates/wasm-conventions/Cargo.toml @@ -10,5 +10,5 @@ description = "Utilities for working with Wasm codegen conventions (usually esta edition = "2018" [dependencies] -walrus = "0.13.0" +walrus = "0.14.0" anyhow = "1.0" diff --git a/crates/wasm-interpreter/Cargo.toml b/crates/wasm-interpreter/Cargo.toml index 4fe0c6f1..f2ac7b8f 100644 --- a/crates/wasm-interpreter/Cargo.toml +++ b/crates/wasm-interpreter/Cargo.toml @@ -14,7 +14,7 @@ edition = '2018' [dependencies] anyhow = "1.0" log = "0.4" -walrus = "0.13.0" +walrus = "0.14.0" [dev-dependencies] tempfile = "3" diff --git a/src/convert/impls.rs b/src/convert/impls.rs index 91d08dec..89f74dbf 100644 --- a/src/convert/impls.rs +++ b/src/convert/impls.rs @@ -51,7 +51,6 @@ unsafe impl WasmAbi for Wasm64 {} #[repr(C)] pub struct WasmOptional64 { pub present: u32, - pub padding: u32, pub low: u32, pub high: u32, } @@ -177,13 +176,11 @@ macro_rules! type_64 { match self { None => WasmOptional64 { present: 0, - padding: 0, low: 0 as u32, high: 0 as u32, }, Some(me) => WasmOptional64 { present: 1, - padding: 0, low: me as u32, high: (me >> 32) as u32, }, diff --git a/src/convert/mod.rs b/src/convert/mod.rs index fc249948..ce2c0b2c 100644 --- a/src/convert/mod.rs +++ b/src/convert/mod.rs @@ -6,5 +6,6 @@ mod impls; mod slices; mod traits; +pub use self::impls::*; pub use self::slices::WasmSlice; pub use self::traits::*; diff --git a/src/lib.rs b/src/lib.rs index 2d662fd9..3b54fb47 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,7 +14,7 @@ use core::marker; use core::mem; use core::ops::{Deref, DerefMut}; -use crate::convert::FromWasmAbi; +use crate::convert::{FromWasmAbi, WasmOptionalF64, WasmSlice}; macro_rules! if_std { ($($i:item)*) => ($( @@ -226,10 +226,8 @@ impl JsValue { T: for<'a> serde::de::Deserialize<'a>, { unsafe { - let mut ret = [0usize; 2]; - __wbindgen_json_serialize(&mut ret, self.idx); - let s = Vec::from_raw_parts(ret[0] as *mut u8, ret[1], ret[1]); - let s = String::from_utf8_unchecked(s); + let ret = __wbindgen_json_serialize(self.idx); + let s = String::from_abi(ret); serde_json::from_str(&s) } } @@ -240,15 +238,7 @@ impl JsValue { /// If this JS value is not an instance of a number then this returns /// `None`. pub fn as_f64(&self) -> Option { - let mut invalid = 0; - unsafe { - let ret = __wbindgen_number_get(self.idx, &mut invalid); - if invalid == 1 { - None - } else { - Some(ret) - } - } + unsafe { FromWasmAbi::from_abi(__wbindgen_number_get(self.idx)) } } /// Tests whether this JS value is a JS string. @@ -278,16 +268,7 @@ impl JsValue { /// [caveats]: https://rustwasm.github.io/docs/wasm-bindgen/reference/types/str.html #[cfg(feature = "std")] pub fn as_string(&self) -> Option { - unsafe { - let mut len = 0; - let ptr = __wbindgen_string_get(self.idx, &mut len); - if ptr.is_null() { - None - } else { - let data = Vec::from_raw_parts(ptr, len, len); - Some(String::from_utf8_unchecked(data)) - } - } + unsafe { FromWasmAbi::from_abi(__wbindgen_string_get(self.idx)) } } /// Returns the `bool` value of this JS value if it's an instance of a @@ -523,9 +504,9 @@ externs! { fn __wbindgen_is_string(idx: u32) -> u32; fn __wbindgen_is_falsy(idx: u32) -> u32; - fn __wbindgen_number_get(idx: u32, invalid: *mut u8) -> f64; + fn __wbindgen_number_get(idx: u32) -> WasmOptionalF64; fn __wbindgen_boolean_get(idx: u32) -> u32; - fn __wbindgen_string_get(idx: u32, len: *mut usize) -> *mut u8; + fn __wbindgen_string_get(idx: u32) -> WasmSlice; fn __wbindgen_debug_string(ret: *mut [usize; 2], idx: u32) -> (); @@ -539,7 +520,7 @@ externs! { fn __wbindgen_describe_closure(a: u32, b: u32, c: u32) -> u32; fn __wbindgen_json_parse(ptr: *const u8, len: usize) -> u32; - fn __wbindgen_json_serialize(ret: *mut [usize; 2], idx: u32) -> (); + fn __wbindgen_json_serialize(idx: u32) -> WasmSlice; fn __wbindgen_jsval_eq(a: u32, b: u32) -> u32; fn __wbindgen_memory() -> u32; diff --git a/tests/wasm/imports.js b/tests/wasm/imports.js index 6d11ccb5..44182a38 100644 --- a/tests/wasm/imports.js +++ b/tests/wasm/imports.js @@ -115,3 +115,15 @@ class StaticMethodCheck { } exports.StaticMethodCheck = StaticMethodCheck; + +exports.receive_undefined = val => { + assert.strictEqual(val, undefined); +}; + +const VAL = {}; + +exports.receive_some = val => { + assert.strictEqual(val, VAL); +}; + +exports.get_some_val = () => VAL; diff --git a/tests/wasm/imports.rs b/tests/wasm/imports.rs index 4dc2b401..116c7aff 100644 --- a/tests/wasm/imports.rs +++ b/tests/wasm/imports.rs @@ -57,6 +57,18 @@ extern "C" { fn static_method_of_right_this(); static STATIC_STRING: String; + + #[derive(Clone)] + type PassOutOptionUndefined; + fn get_some_val() -> PassOutOptionUndefined; + #[wasm_bindgen(js_name = "receive_undefined")] + fn receive_undefined_ref(arg: Option<&PassOutOptionUndefined>); + #[wasm_bindgen(js_name = "receive_undefined")] + fn receive_undefined_owned(arg: Option); + #[wasm_bindgen(js_name = "receive_some")] + fn receive_some_ref(arg: Option<&PassOutOptionUndefined>); + #[wasm_bindgen(js_name = "receive_some")] + fn receive_some_owned(arg: Option); } #[wasm_bindgen] @@ -248,3 +260,17 @@ fn static_string_ok() { fn static_method_of_has_right_this() { StaticMethodCheck::static_method_of_right_this(); } + +#[wasm_bindgen_test] +fn pass_out_options_as_undefined() { + receive_undefined_ref(None); + receive_undefined_ref(None); + receive_undefined_owned(None); + receive_undefined_owned(None); + + let v = get_some_val(); + receive_some_ref(Some(&v)); + receive_some_ref(Some(&v)); + receive_some_owned(Some(v.clone())); + receive_some_owned(Some(v)); +} diff --git a/tests/wasm/slice.js b/tests/wasm/slice.js index 373e3859..c23535e9 100644 --- a/tests/wasm/slice.js +++ b/tests/wasm/slice.js @@ -41,63 +41,27 @@ exports.js_export = () => { assert.deepStrictEqual(wasm.export_f64(f64), f64); }; -exports.import_js_i8 = a => { +const test_import = (a, b, c) => { assert.strictEqual(a.length, 2); assert.strictEqual(a[0], 1); assert.strictEqual(a[1], 2); + assert.strictEqual(b.length, 2); + assert.strictEqual(b[0], 1); + assert.strictEqual(b[1], 2); + assert.strictEqual(c, undefined); return a; }; -exports.import_js_u8 = a => { - assert.strictEqual(a.length, 2); - assert.strictEqual(a[0], 1); - assert.strictEqual(a[1], 2); - return a; -}; - -exports.import_js_i16 = a => { - assert.strictEqual(a.length, 2); - assert.strictEqual(a[0], 1); - assert.strictEqual(a[1], 2); - return a; -}; - -exports.import_js_u16 = a => { - assert.strictEqual(a.length, 2); - assert.strictEqual(a[0], 1); - assert.strictEqual(a[1], 2); - return a; -}; - -exports.import_js_i32 = a => { - assert.strictEqual(a.length, 2); - assert.strictEqual(a[0], 1); - assert.strictEqual(a[1], 2); - return a; -}; -exports.import_js_isize = exports.import_js_i32; - -exports.import_js_u32 = a => { - assert.strictEqual(a.length, 2); - assert.strictEqual(a[0], 1); - assert.strictEqual(a[1], 2); - return a; -}; -exports.import_js_usize = exports.import_js_u32; - -exports.import_js_f32 = a => { - assert.strictEqual(a.length, 2); - assert.strictEqual(a[0], 1); - assert.strictEqual(a[1], 2); - return a; -}; - -exports.import_js_f64 = a => { - assert.strictEqual(a.length, 2); - assert.strictEqual(a[0], 1); - assert.strictEqual(a[1], 2); - return a; -}; +exports.import_js_i8 = test_import; +exports.import_js_u8 = test_import; +exports.import_js_i16 = test_import; +exports.import_js_u16 = test_import; +exports.import_js_i32 = test_import; +exports.import_js_isize = test_import; +exports.import_js_u32 = test_import; +exports.import_js_usize = test_import; +exports.import_js_f32 = test_import; +exports.import_js_f64 = test_import; exports.js_import = () => { const i8 = new Int8Array(2); @@ -152,12 +116,19 @@ exports.js_pass_array = () => { wasm.pass_array_rust_f64([1, 2]); }; -const import_mut_foo = a => { +const import_mut_foo = (a, b, c) => { assert.strictEqual(a.length, 3); assert.strictEqual(a[0], 1); assert.strictEqual(a[1], 2); a[0] = 4; a[1] = 5; + assert.strictEqual(b.length, 3); + assert.strictEqual(b[0], 4); + assert.strictEqual(b[1], 5); + assert.strictEqual(b[2], 6); + b[0] = 8; + b[1] = 7; + assert.strictEqual(c, undefined); }; exports.import_mut_js_i8 = import_mut_foo; diff --git a/tests/wasm/slice.rs b/tests/wasm/slice.rs index 8e299c0f..2f2457bc 100644 --- a/tests/wasm/slice.rs +++ b/tests/wasm/slice.rs @@ -55,7 +55,7 @@ macro_rules! import_macro { ($(($rust:ident, $js:ident, $i:ident))*) => ($( #[wasm_bindgen(module = "tests/wasm/slice.js")] extern "C" { - fn $js(a: &[$i]) -> Vec<$i>; + fn $js(a: &[$i], b: Option<&[$i]>, c: Option<&[$i]>) -> Vec<$i>; } #[wasm_bindgen] @@ -63,7 +63,7 @@ macro_rules! import_macro { assert_eq!(a.len(), 2); assert_eq!(a[0], 1 as $i); assert_eq!(a[1], 2 as $i); - $js(a) + $js(a, Some(a), None) } )*) } @@ -120,19 +120,27 @@ macro_rules! import_mut_macro { $( #[wasm_bindgen(module = "tests/wasm/slice.js")] extern "C" { - fn $js(a: &mut [$i]); + fn $js(a: &mut [$i], b: Option<&mut [$i]>, c: Option<&mut [$i]>); } fn $rust() { - let mut buf = [ + let mut buf1 = [ 1 as $i, 2 as $i, 3 as $i, ]; - $js(&mut buf); - assert_eq!(buf[0], 4 as $i); - assert_eq!(buf[1], 5 as $i); - assert_eq!(buf[2], 3 as $i); + let mut buf2 = [ + 4 as $i, + 5 as $i, + 6 as $i, + ]; + $js(&mut buf1, Some(&mut buf2), None); + assert_eq!(buf1[0], 4 as $i); + assert_eq!(buf1[1], 5 as $i); + assert_eq!(buf1[2], 3 as $i); + assert_eq!(buf2[0], 8 as $i); + assert_eq!(buf2[1], 7 as $i); + assert_eq!(buf2[2], 6 as $i); } )*