From d7a4a772cff1728f55ec680f47e61df07e7d3341 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 4 Dec 2019 12:01:39 -0600 Subject: [PATCH] Add reference output tests for JS operations (#1894) * Add reference output tests for JS operations This commit starts adding a test suite which checks in, to the repository, test assertions for both the JS and wasm file outputs of a Rust crate compiled with `#[wasm_bindgen]`. These aren't intended to be exhaustive or large scale tests, but rather micro-tests to help observe the changes in `wasm-bindgen`'s output over time. The motivation for this commit is basically overhauling how all the GC passes work in `wasm-bindgen` today. The reorganization is also included in this commit as well. Previously `wasm-bindgen` would, in an ad-hoc fashion, run the GC passes of `walrus` in a bunch of places to ensure that less "garbage" was seen by future passes. This not only was a source of slowdown but it also was pretty brittle since `wasm-bindgen` kept breaking if extra iteams leaked through. The strategy taken in this commit is to have one precise location for a GC pass, and everything goes through there. This is achieved by: * All internal exports are removed immediately when generating the nonstandard wasm interface types section. Internal exports, intrinsics, and runtime support are all referenced by the various instructions and/or sections that use them. This means that we now have precise tracking of what an adapter uses. * This in turn enables us to implement the `add_gc_roots` function for `walrus` custom sections, which in turn allows walrus GC passes to do what `unexport_unused_intrinsics` did before. That function is now no longer necessary, but effectively works the same way. All intrinsics are unexported at the beginning and then they're selectively re-imported and re-exported through the JS glue generation pass as necessary and defined by the bindings. * Passes like the `anyref` pass are now much more precise about the intrinsics that they work with. The `anyref` pass also deletes any internal intrinsics found and also does some rewriting of the adapters aftewards now to hook up calls to the heap count import to the heap count intrinsic in the wasm module. * Fix handling of __wbindgen_realloc The final user of the `require_internal_export` function was `__wbindgen_realloc`. This usage has now been removed by updating how we handle usage of the `realloc` function. The wasm interface types standard doesn't have a `realloc` function slot, nor do I think it ever will. This means that as a polyfill for wasm interface types we'll always have to support the lack of `realloc`. For direct Rust to JS, however, we can still optionally handle `realloc`. This is all handled with a few internal changes. * Custom `StringToMemory` instructions now exist. These have an extra `realloc` slot to store an intrinsic, if found. * Our custom instructions are lowered to the standard instructions when generating an interface types section. * The `realloc` function, if present, is passed as an argument like the malloc function when passing strings to wasm. If it's not present we use a slower fallback, but if it's present we use the faster implementation. This should mean that there's little-to-no impact on existing users of `wasm-bindgen`, but this should continue to still work for wasm interface types polyfills and such. Additionally the GC passes now work in that they don't delete `__wbindgen_realloc` which we later try to reference. * Add an empty test for the anyref pass * Precisely track I32FromOptionAnyref's dependencies This depends on the anyref table and a function to allocate an index if the anyref pass is running, so be sure to track that in the instruction itself for GC rooting. * Trim extraneous exports from nop anyref module Or if you're otherwise not using anyref slices, don't force some intrinsics to exist. * Remove globals from reference tests Looks like these values adjust in slight but insignificant ways over time * Update the anyref xform tests --- crates/anyref-xform/src/lib.rs | 15 +- .../anyref-xform/tests/anyref-param-owned.wat | 9 +- crates/anyref-xform/tests/anyref-param.wat | 20 +- .../tests/clone-ref-intrinsic.wat | 8 +- .../anyref-xform/tests/drop-ref-intrinsic.wat | 8 +- .../tests/import-anyref-owned.wat | 21 +- .../anyref-xform/tests/import-anyref-ret.wat | 10 +- crates/anyref-xform/tests/import-anyref.wat | 20 +- crates/anyref-xform/tests/mixed-export.wat | 18 +- crates/anyref-xform/tests/mixed.wat | 23 +- crates/anyref-xform/tests/ret-anyref.wat | 10 +- .../tests/table-grow-intrinsic.wat | 9 +- .../tests/table-set-null-intrinsic.wat | 9 +- crates/anyref-xform/tests/table.wat | 7 +- crates/cli-support/src/anyref.rs | 125 +++++++++- crates/cli-support/src/descriptors.rs | 21 -- crates/cli-support/src/js/binding.rs | 98 ++++++-- crates/cli-support/src/js/mod.rs | 171 +++++-------- crates/cli-support/src/lib.rs | 81 +++++-- crates/cli-support/src/wit/incoming.rs | 41 +++- crates/cli-support/src/wit/mod.rs | 75 +++++- crates/cli-support/src/wit/nonstandard.rs | 18 ++ crates/cli-support/src/wit/section.rs | 22 +- crates/cli-support/src/wit/standard.rs | 91 ++++++- crates/cli/Cargo.toml | 9 + crates/cli/tests/reference.rs | 225 ++++++++++++++++++ crates/cli/tests/reference/add.js | 22 ++ crates/cli/tests/reference/add.rs | 11 + crates/cli/tests/reference/add.wat | 8 + crates/cli/tests/reference/anyref-empty.js | 15 ++ crates/cli/tests/reference/anyref-empty.rs | 1 + crates/cli/tests/reference/anyref-empty.wat | 8 + .../tests/reference/anyref-import-catch.js | 65 +++++ .../tests/reference/anyref-import-catch.rs | 14 ++ .../tests/reference/anyref-import-catch.wat | 16 ++ crates/cli/tests/reference/anyref-nop.js | 21 ++ crates/cli/tests/reference/anyref-nop.rs | 6 + crates/cli/tests/reference/anyref-nop.wat | 10 + crates/cli/tests/reference/empty.js | 2 + crates/cli/tests/reference/empty.rs | 0 crates/cli/tests/reference/empty.wat | 3 + crates/cli/tests/reference/import-catch.js | 58 +++++ crates/cli/tests/reference/import-catch.rs | 12 + crates/cli/tests/reference/import-catch.wat | 9 + crates/cli/tests/reference/nop.js | 8 + crates/cli/tests/reference/nop.rs | 4 + crates/cli/tests/reference/nop.wat | 6 + crates/cli/tests/reference/string-arg.js | 89 +++++++ crates/cli/tests/reference/string-arg.rs | 6 + crates/cli/tests/reference/string-arg.wat | 12 + crates/wasm-conventions/src/lib.rs | 40 +--- src/anyref.rs | 10 +- 52 files changed, 1263 insertions(+), 357 deletions(-) create mode 100644 crates/cli/tests/reference.rs create mode 100644 crates/cli/tests/reference/add.js create mode 100644 crates/cli/tests/reference/add.rs create mode 100644 crates/cli/tests/reference/add.wat create mode 100644 crates/cli/tests/reference/anyref-empty.js create mode 100644 crates/cli/tests/reference/anyref-empty.rs create mode 100644 crates/cli/tests/reference/anyref-empty.wat create mode 100644 crates/cli/tests/reference/anyref-import-catch.js create mode 100644 crates/cli/tests/reference/anyref-import-catch.rs create mode 100644 crates/cli/tests/reference/anyref-import-catch.wat create mode 100644 crates/cli/tests/reference/anyref-nop.js create mode 100644 crates/cli/tests/reference/anyref-nop.rs create mode 100644 crates/cli/tests/reference/anyref-nop.wat create mode 100644 crates/cli/tests/reference/empty.js create mode 100644 crates/cli/tests/reference/empty.rs create mode 100644 crates/cli/tests/reference/empty.wat create mode 100644 crates/cli/tests/reference/import-catch.js create mode 100644 crates/cli/tests/reference/import-catch.rs create mode 100644 crates/cli/tests/reference/import-catch.wat create mode 100644 crates/cli/tests/reference/nop.js create mode 100644 crates/cli/tests/reference/nop.rs create mode 100644 crates/cli/tests/reference/nop.wat create mode 100644 crates/cli/tests/reference/string-arg.js create mode 100644 crates/cli/tests/reference/string-arg.rs create mode 100644 crates/cli/tests/reference/string-arg.wat diff --git a/crates/anyref-xform/src/lib.rs b/crates/anyref-xform/src/lib.rs index 90d63872..dfb77f1e 100644 --- a/crates/anyref-xform/src/lib.rs +++ b/crates/anyref-xform/src/lib.rs @@ -48,6 +48,7 @@ pub struct Meta { pub table: TableId, pub alloc: Option, pub drop_slice: Option, + pub live_count: Option, } struct Transform<'a> { @@ -178,20 +179,27 @@ impl Context { let mut heap_alloc = None; let mut heap_dealloc = None; let mut drop_slice = None; + let mut live_count = None; // Find exports of some intrinsics which we only need for a runtime // implementation. + let mut to_delete = Vec::new(); for export in module.exports.iter() { let f = match export.item { walrus::ExportItem::Function(f) => f, _ => continue, }; 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), + "__anyref_table_alloc" => heap_alloc = Some(f), + "__anyref_table_dealloc" => heap_dealloc = Some(f), + "__anyref_drop_slice" => drop_slice = Some(f), + "__anyref_heap_live_count" => live_count = Some(f), _ => continue, } + to_delete.push(export.id()); + } + for id in to_delete { + module.exports.delete(id); } let mut clone_ref = None; if let Some(heap_alloc) = heap_alloc { @@ -240,6 +248,7 @@ impl Context { table, alloc: heap_alloc, drop_slice, + live_count, }) } } diff --git a/crates/anyref-xform/tests/anyref-param-owned.wat b/crates/anyref-xform/tests/anyref-param-owned.wat index fa3148b9..a767c056 100644 --- a/crates/anyref-xform/tests/anyref-param-owned.wat +++ b/crates/anyref-xform/tests/anyref-param-owned.wat @@ -2,9 +2,9 @@ (module (func $foo (export "foo") (param i32)) - (func $alloc (export "__wbindgen_anyref_table_alloc") (result i32) + (func $alloc (export "__anyref_table_alloc") (result i32) i32.const 0) - (func $dealloc (export "__wbindgen_anyref_table_dealloc") (param i32)) + (func $dealloc (export "__anyref_table_dealloc") (param i32)) ) (; CHECK-ALL: @@ -23,9 +23,6 @@ (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))) + (export "foo" (func $foo anyref shim))) ;) diff --git a/crates/anyref-xform/tests/anyref-param.wat b/crates/anyref-xform/tests/anyref-param.wat index a9058858..d59fec1d 100644 --- a/crates/anyref-xform/tests/anyref-param.wat +++ b/crates/anyref-xform/tests/anyref-param.wat @@ -2,17 +2,16 @@ (module (func $foo (export "foo") (param i32)) - (func $alloc (export "__wbindgen_anyref_table_alloc") (result i32) + (func $alloc (export "__anyref_table_alloc") (result i32) i32.const 0) - (func $dealloc (export "__wbindgen_anyref_table_dealloc") (param i32)) + (func $dealloc (export "__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) + (type (;0;) (func (param i32))) + (type (;1;) (func (param anyref))) + (func $foo anyref shim (type 1) (param anyref) (local i32) global.get 0 i32.const 1 @@ -31,13 +30,8 @@ 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)) + (func $foo (type 0) (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))) + (export "foo" (func $foo anyref shim))) ;) diff --git a/crates/anyref-xform/tests/clone-ref-intrinsic.wat b/crates/anyref-xform/tests/clone-ref-intrinsic.wat index 47d384df..f5befbf5 100644 --- a/crates/anyref-xform/tests/clone-ref-intrinsic.wat +++ b/crates/anyref-xform/tests/clone-ref-intrinsic.wat @@ -6,9 +6,9 @@ (func $foo (export "foo") (param i32) (result i32) local.get 0 call $clone) - (func $alloc (export "__wbindgen_anyref_table_alloc") (result i32) + (func $alloc (export "__anyref_table_alloc") (result i32) i32.const 0) - (func $dealloc (export "__wbindgen_anyref_table_dealloc") (param i32)) + (func $dealloc (export "__anyref_table_dealloc") (param i32)) ) (; CHECK-ALL: @@ -44,7 +44,5 @@ 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))) + (export "foo" (func $foo anyref shim))) ;) diff --git a/crates/anyref-xform/tests/drop-ref-intrinsic.wat b/crates/anyref-xform/tests/drop-ref-intrinsic.wat index f1d2a2e6..48a2a567 100644 --- a/crates/anyref-xform/tests/drop-ref-intrinsic.wat +++ b/crates/anyref-xform/tests/drop-ref-intrinsic.wat @@ -6,9 +6,9 @@ (func $foo (export "foo") (param i32) local.get 0 call $drop) - (func $alloc (export "__wbindgen_anyref_table_alloc") (result i32) + (func $alloc (export "__anyref_table_alloc") (result i32) i32.const 0) - (func $dealloc (export "__wbindgen_anyref_table_dealloc") (param i32)) + (func $dealloc (export "__anyref_table_dealloc") (param i32)) ) (; CHECK-ALL: @@ -31,7 +31,5 @@ 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))) + (export "foo" (func $foo anyref shim))) ;) diff --git a/crates/anyref-xform/tests/import-anyref-owned.wat b/crates/anyref-xform/tests/import-anyref-owned.wat index 36060be8..506e7982 100644 --- a/crates/anyref-xform/tests/import-anyref-owned.wat +++ b/crates/anyref-xform/tests/import-anyref-owned.wat @@ -5,19 +5,18 @@ (func (export "foo") i32.const 0 call $a) - (func $alloc (export "__wbindgen_anyref_table_alloc") (result i32) + (func $alloc (export "__anyref_table_alloc") (result i32) i32.const 0) - (func $dealloc (export "__wbindgen_anyref_table_dealloc") (param i32)) + (func $dealloc (export "__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) + (type (;1;) (func (param i32))) + (type (;2;) (func (param anyref))) + (import "" "a" (func $a (type 2))) + (func $a anyref shim (type 1) (param i32) local.get 0 table.get 0 local.get 0 @@ -26,11 +25,7 @@ (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)) + (func $dealloc (type 1) (param i32)) (table (;0;) 32 anyref) - (export "foo" (func 2)) - (export "__wbindgen_anyref_table_alloc" (func $alloc)) - (export "__wbindgen_anyref_table_dealloc" (func $dealloc))) + (export "foo" (func 2))) ;) diff --git a/crates/anyref-xform/tests/import-anyref-ret.wat b/crates/anyref-xform/tests/import-anyref-ret.wat index 4f692b88..638e6149 100644 --- a/crates/anyref-xform/tests/import-anyref-ret.wat +++ b/crates/anyref-xform/tests/import-anyref-ret.wat @@ -4,16 +4,15 @@ (import "" "a" (func $a (result i32))) (func (export "foo") (result i32) call $a) - (func $alloc (export "__wbindgen_anyref_table_alloc") (result i32) + (func $alloc (export "__anyref_table_alloc") (result i32) i32.const 0) - (func $dealloc (export "__wbindgen_anyref_table_dealloc") (param i32)) + (func $dealloc (export "__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) @@ -28,9 +27,6 @@ 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))) + (export "foo" (func 2))) ;) diff --git a/crates/anyref-xform/tests/import-anyref.wat b/crates/anyref-xform/tests/import-anyref.wat index 0a70f01d..e2ea26ae 100644 --- a/crates/anyref-xform/tests/import-anyref.wat +++ b/crates/anyref-xform/tests/import-anyref.wat @@ -5,30 +5,24 @@ (func (export "foo") i32.const 0 call $a) - (func $alloc (export "__wbindgen_anyref_table_alloc") (result i32) + (func $alloc (export "__anyref_table_alloc") (result i32) i32.const 0) - (func $dealloc (export "__wbindgen_anyref_table_dealloc") (param i32)) + (func $dealloc (export "__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) + (type (;1;) (func (param i32))) + (type (;2;) (func (param anyref))) + (import "" "a" (func $a (type 2))) + (func $a anyref shim (type 1) (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))) + (export "foo" (func 2))) ;) diff --git a/crates/anyref-xform/tests/mixed-export.wat b/crates/anyref-xform/tests/mixed-export.wat index 85439757..d6e1ca37 100644 --- a/crates/anyref-xform/tests/mixed-export.wat +++ b/crates/anyref-xform/tests/mixed-export.wat @@ -2,18 +2,17 @@ (module (func $a (export "a") (param f32 i32 i64 i32 i32)) - (func $alloc (export "__wbindgen_anyref_table_alloc") (result i32) + (func $alloc (export "__anyref_table_alloc") (result i32) i32.const 0) - (func $dealloc (export "__wbindgen_anyref_table_dealloc") (param i32)) + (func $dealloc (export "__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) + (type (;1;) (func (param f32 i32 i64 i32 i32))) + (type (;2;) (func (param f32 anyref i64 anyref i32))) + (func $a anyref shim (type 2) (param f32 anyref i64 anyref i32) (local i32 i32) global.get 0 i32.const 1 @@ -42,11 +41,8 @@ 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)) + (func $a (type 1) (param f32 i32 i64 i32 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))) + (export "a" (func $a anyref shim))) ;) diff --git a/crates/anyref-xform/tests/mixed.wat b/crates/anyref-xform/tests/mixed.wat index d220a861..259ac41e 100644 --- a/crates/anyref-xform/tests/mixed.wat +++ b/crates/anyref-xform/tests/mixed.wat @@ -9,20 +9,19 @@ i32.const 4 i32.const 5 call $a) - (func $alloc (export "__wbindgen_anyref_table_alloc") (result i32) + (func $alloc (export "__anyref_table_alloc") (result i32) i32.const 0) - (func $dealloc (export "__wbindgen_anyref_table_dealloc") (param i32)) + (func $dealloc (export "__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) + (type (;1;) (func (param i32))) + (type (;2;) (func (param f32 i32 i64 i32 i32))) + (type (;3;) (func (param f32 anyref i64 anyref i32))) + (import "" "a" (func $a (type 3))) + (func $a anyref shim (type 2) (param f32 i32 i64 i32 i32) local.get 0 local.get 1 table.get 0 @@ -40,11 +39,7 @@ 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)) + (func $dealloc (type 1) (param i32)) (table (;0;) 32 anyref) - (export "foo" (func 2)) - (export "__wbindgen_anyref_table_alloc" (func $alloc)) - (export "__wbindgen_anyref_table_dealloc" (func $dealloc))) + (export "foo" (func 2))) ;) diff --git a/crates/anyref-xform/tests/ret-anyref.wat b/crates/anyref-xform/tests/ret-anyref.wat index 77df8ab6..275e51d9 100644 --- a/crates/anyref-xform/tests/ret-anyref.wat +++ b/crates/anyref-xform/tests/ret-anyref.wat @@ -4,9 +4,9 @@ (func $foo (export "foo") (result i32) i32.const 0) - (func $alloc (export "__wbindgen_anyref_table_alloc") (result i32) + (func $alloc (export "__anyref_table_alloc") (result i32) i32.const 0) - (func $dealloc (export "__wbindgen_anyref_table_dealloc") (param i32)) + (func $dealloc (export "__anyref_table_dealloc") (param i32)) ) (; CHECK-ALL: @@ -23,11 +23,7 @@ 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))) + (export "foo" (func $foo anyref shim))) ;) diff --git a/crates/anyref-xform/tests/table-grow-intrinsic.wat b/crates/anyref-xform/tests/table-grow-intrinsic.wat index 363165b1..b8d08f47 100644 --- a/crates/anyref-xform/tests/table-grow-intrinsic.wat +++ b/crates/anyref-xform/tests/table-grow-intrinsic.wat @@ -7,9 +7,9 @@ i32.const 0 call $grow drop) - (func $alloc (export "__wbindgen_anyref_table_alloc") (result i32) + (func $alloc (export "__anyref_table_alloc") (result i32) i32.const 0) - (func $dealloc (export "__wbindgen_anyref_table_dealloc") (param i32)) + (func $dealloc (export "__anyref_table_dealloc") (param i32)) ) (; CHECK-ALL: @@ -32,9 +32,6 @@ 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))) + (export "foo" (func $foo anyref shim))) ;) diff --git a/crates/anyref-xform/tests/table-set-null-intrinsic.wat b/crates/anyref-xform/tests/table-set-null-intrinsic.wat index 17988bff..3185489c 100644 --- a/crates/anyref-xform/tests/table-set-null-intrinsic.wat +++ b/crates/anyref-xform/tests/table-set-null-intrinsic.wat @@ -6,9 +6,9 @@ (func $foo (export "foo") (param i32) local.get 0 call $set-null) - (func $alloc (export "__wbindgen_anyref_table_alloc") (result i32) + (func $alloc (export "__anyref_table_alloc") (result i32) i32.const 0) - (func $dealloc (export "__wbindgen_anyref_table_dealloc") (param i32)) + (func $dealloc (export "__anyref_table_dealloc") (param i32)) ) (; CHECK-ALL: @@ -30,9 +30,6 @@ 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))) + (export "foo" (func $foo anyref shim))) ;) diff --git a/crates/anyref-xform/tests/table.wat b/crates/anyref-xform/tests/table.wat index a19b7cca..200869c1 100644 --- a/crates/anyref-xform/tests/table.wat +++ b/crates/anyref-xform/tests/table.wat @@ -4,9 +4,9 @@ (func $foo (param i32)) (table (export "func") 0 funcref) (elem (i32.const 0) 0) - (func $alloc (export "__wbindgen_anyref_table_alloc") (result i32) + (func $alloc (export "__anyref_table_alloc") (result i32) i32.const 0) - (func $dealloc (export "__wbindgen_anyref_table_dealloc") (param i32)) + (func $dealloc (export "__anyref_table_dealloc") (param i32)) ) (; CHECK-ALL: @@ -25,11 +25,8 @@ (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/cli-support/src/anyref.rs b/crates/cli-support/src/anyref.rs index 19d85c2c..b3db95ea 100644 --- a/crates/cli-support/src/anyref.rs +++ b/crates/cli-support/src/anyref.rs @@ -1,3 +1,6 @@ +use crate::descriptor::VectorKind; +use crate::intrinsic::Intrinsic; +use crate::wit::AuxImport; use crate::wit::{AdapterKind, Instruction, NonstandardWitSection}; use crate::wit::{AdapterType, InstructionData, StackChange, WasmBindgenAux}; use anyhow::Error; @@ -17,7 +20,7 @@ pub fn process(module: &mut Module) -> Result<(), Error> { .implements .iter() .cloned() - .map(|(core, adapter)| (adapter, core)) + .map(|(core, _, adapter)| (adapter, core)) .collect::>(); // Transform all exported functions in the module, using the bindings listed @@ -45,13 +48,73 @@ pub fn process(module: &mut Module) -> Result<(), Error> { let meta = cfg.run(module)?; + let mut aux = module + .customs + .delete_typed::() + .expect("wit custom section should exist"); let section = module .customs - .get_typed_mut::() + .get_typed_mut::() .expect("wit custom section should exist"); - section.anyref_table = Some(meta.table); - section.anyref_alloc = meta.alloc; - section.anyref_drop_slice = meta.drop_slice; + + // If the module looks like it's going to use some of these exports, store + // them in the aux section to get used. + // + // FIXME: this is not great, we should ideally have precise tracking of what + // requires what. These are used by catch clauses and anyref slices going + // in/out of wasm. The catch clauses are a bit weird but anyref slices + // should ideally track in their own instructions what table/functions + // they're referencing. This doesn't fit well in today's model of + // slice-related instructions, though, so let's just cop out and only enable + // these coarsely. + aux.anyref_table = Some(meta.table); + if module_needs_anyref_metadata(&aux, section) { + aux.anyref_alloc = meta.alloc; + aux.anyref_drop_slice = meta.drop_slice; + } + + // Additonally we may need to update some adapter instructions other than + // those found for the anyref pass. These are some general "fringe support" + // things necessary to get absolutely everything working. + for (_, adapter) in section.adapters.iter_mut() { + let instrs = match &mut adapter.kind { + AdapterKind::Local { instructions } => instructions, + AdapterKind::Import { .. } => continue, + }; + for instr in instrs { + match instr.instr { + // Calls to the heap live count intrinsic are now routed to the + // actual wasm function which keeps track of this. + Instruction::CallAdapter(adapter) => { + let id = match meta.live_count { + Some(id) => id, + None => continue, + }; + let import = match aux.import_map.get(&adapter) { + Some(import) => import, + None => continue, + }; + match import { + AuxImport::Intrinsic(Intrinsic::AnyrefHeapLiveCount) => {} + _ => continue, + } + instr.instr = Instruction::Standard(wit_walrus::Instruction::CallCore(id)); + } + + // Optional anyref values are now managed in the wasm module, so + // we need to store where they're managed. + Instruction::I32FromOptionAnyref { + ref mut table_and_alloc, + } => { + *table_and_alloc = meta.alloc.map(|id| (meta.table, id)); + } + _ => continue, + }; + } + } + + module.customs.add(*aux); + Ok(()) } @@ -267,3 +330,55 @@ fn export_xform(cx: &mut Context, export: Export, instrs: &mut Vec bool { + use Instruction::*; + + // our `handleError` intrinsic uses a few pieces of metadata to store + // indices directly into the wasm module. + if aux.imports_with_catch.len() > 0 { + return true; + } + + // Look for any instructions which may use `VectorKind::Anyref`. If there + // are any then we'll need our intrinsics/tables/etc, otherwise we shouldn't + // ever need them. + section.adapters.iter().any(|(_, adapter)| { + let instructions = match &adapter.kind { + AdapterKind::Local { instructions } => instructions, + AdapterKind::Import { .. } => return false, + }; + instructions.iter().any(|instr| match instr.instr { + VectorToMemory { + kind: VectorKind::Anyref, + .. + } + | MutableSliceToMemory { + kind: VectorKind::Anyref, + .. + } + | OptionVector { + kind: VectorKind::Anyref, + .. + } + | VectorLoad { + kind: VectorKind::Anyref, + .. + } + | OptionVectorLoad { + kind: VectorKind::Anyref, + .. + } + | View { + kind: VectorKind::Anyref, + .. + } + | OptionView { + kind: VectorKind::Anyref, + .. + } => true, + _ => false, + }) + }) +} diff --git a/crates/cli-support/src/descriptors.rs b/crates/cli-support/src/descriptors.rs index b693b4f4..74a79380 100644 --- a/crates/cli-support/src/descriptors.rs +++ b/crates/cli-support/src/descriptors.rs @@ -37,27 +37,6 @@ pub fn execute(module: &mut Module) -> Result Builder<'a, 'b> { instructions: &[InstructionData], explicit_arg_names: &Option>, ) -> Result { - if self.cx.aux.imports_with_assert_no_shim.contains(&adapter.id) { + if self + .cx + .aux + .imports_with_assert_no_shim + .contains(&adapter.id) + { bail!("generating a shim for something asserted to have no shim"); } @@ -414,6 +419,35 @@ impl<'a, 'b> JsBuilder<'a, 'b> { arg, )); } + + fn string_to_memory( + &mut self, + mem: walrus::MemoryId, + malloc: walrus::FunctionId, + realloc: Option, + ) -> Result<(), Error> { + self.typescript_required("string"); + let pass = self.cx.expose_pass_string_to_wasm(mem)?; + let val = self.pop(); + let malloc = self.cx.export_name_of(malloc); + let i = self.tmp(); + let realloc = match realloc { + Some(f) => format!(", wasm.{}", self.cx.export_name_of(f)), + None => String::new(), + }; + self.prelude(&format!( + "var ptr{i} = {f}({0}, wasm.{malloc}{realloc});", + val, + i = i, + f = pass, + malloc = malloc, + realloc = realloc, + )); + self.prelude(&format!("var len{} = WASM_VECTOR_LEN;", i)); + self.push(format!("ptr{}", i)); + self.push(format!("len{}", i)); + Ok(()) + } } fn instruction(js: &mut JsBuilder, instr: &Instruction, log_error: &mut bool) -> Result<(), Error> { @@ -512,21 +546,15 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction, log_error: &mut bool) -> } 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)); + js.string_to_memory(*mem, *malloc, None)?; + } + + Instruction::StringToMemory { + mem, + malloc, + realloc, + } => { + js.string_to_memory(*mem, *malloc, *realloc)?; } Instruction::Retptr => js.stack.push(retptr_val.to_string()), @@ -679,16 +707,16 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction, log_error: &mut bool) -> js.push(format!("high{}", i)); } - Instruction::I32FromOptionAnyref => { + Instruction::I32FromOptionAnyref { table_and_alloc } => { 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)?; + match table_and_alloc { + Some((table, alloc)) => { + let alloc = js.cx.expose_add_to_anyref_table(*table, *alloc)?; js.push(format!("isLikeNone({0}) ? 0 : {1}({0})", val, alloc)); } - _ => { + None => { js.cx.expose_add_heap_object(); js.push(format!("isLikeNone({0}) ? 0 : addHeapObject({0})", val)); } @@ -756,6 +784,34 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction, log_error: &mut bool) -> js.push(format!("len{}", i)); } + Instruction::OptionString { + mem, + malloc, + realloc, + } => { + js.typescript_optional("string"); + let func = js.cx.expose_pass_string_to_wasm(*mem)?; + js.cx.expose_is_like_none(); + let i = js.tmp(); + let malloc = js.cx.export_name_of(*malloc); + let val = js.pop(); + let realloc = match realloc { + Some(f) => format!(", wasm.{}", js.cx.export_name_of(*f)), + None => String::new(), + }; + js.prelude(&format!( + "var ptr{i} = isLikeNone({0}) ? 0 : {f}({0}, wasm.{malloc}{realloc});", + val, + i = i, + f = func, + malloc = malloc, + realloc = realloc, + )); + 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)?; diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index a0d35a40..1d536ddf 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -20,7 +20,6 @@ pub struct Context<'a> { imports_post: String, typescript: String, exposed_globals: Option>>, - required_internal_exports: HashSet>, next_export_idx: usize, config: &'a Bindgen, pub module: &'a mut Module, @@ -87,7 +86,6 @@ impl<'a> Context<'a> { imports_post: String::new(), typescript: "/* tslint:disable */\n".to_string(), exposed_globals: Some(Default::default()), - required_internal_exports: Default::default(), imported_names: Default::default(), js_imports: Default::default(), defined_identifiers: Default::default(), @@ -169,33 +167,12 @@ impl<'a> Context<'a> { Ok(()) } - fn require_internal_export(&mut self, name: &'static str) -> Result<(), Error> { - if !self.required_internal_exports.insert(name.into()) { - return Ok(()); - } - - if self.module.exports.iter().any(|e| e.name == name) { - return Ok(()); - } - - bail!( - "the exported function `{}` is required to generate bindings \ - but it was not found in the wasm file, perhaps the `std` feature \ - of the `wasm-bindgen` crate needs to be enabled?", - name - ); - } - pub fn finalize(&mut self, module_name: &str) -> Result<(String, String), Error> { // Finalize all bindings for JS classes. This is where we'll generate JS // glue for all classes as well as finish up a few final imports like // `__wrap` and such. self.write_classes()?; - // We're almost done here, so we can delete any internal exports (like - // `__wbindgen_malloc`) if none of our JS glue actually needed it. - self.unexport_unused_internal_exports(); - // Initialization is just flat out tricky and not something we // understand super well. To try to handle various issues that have come // up we always remove the `start` function if one is present. The JS @@ -203,22 +180,6 @@ impl<'a> Context<'a> { // previously present). let needs_manual_start = self.unstart_start_function(); - // After all we've done, especially - // `unexport_unused_internal_exports()`, we probably have a bunch of - // garbage in the module that's no longer necessary, so delete - // everything that we don't actually need. Afterwards make sure we don't - // try to emit bindings for now-nonexistent imports by pruning our - // `wasm_import_definitions` set. - walrus::passes::gc::run(self.module); - let remaining_imports = self - .module - .imports - .iter() - .map(|i| i.id()) - .collect::>(); - self.wasm_import_definitions - .retain(|id, _| remaining_imports.contains(id)); - // Cause any future calls to `should_write_global` to panic, making sure // we don't ask for items which we can no longer emit. drop(self.exposed_globals.take().unwrap()); @@ -739,25 +700,6 @@ impl<'a> Context<'a> { Ok(()) } - fn unexport_unused_internal_exports(&mut self) { - let mut to_remove = Vec::new(); - for export in self.module.exports.iter() { - match export.name.as_str() { - // Otherwise only consider our special exports, which all start - // with the same prefix which hopefully only we're using. - n if n.starts_with("__wbindgen") => { - if !self.required_internal_exports.contains(n) { - to_remove.push(export.id()); - } - } - _ => {} - } - } - for id in to_remove { - self.module.exports.delete(id); - } - } - fn expose_drop_ref(&mut self) { if !self.should_write_global("drop_ref") { return; @@ -962,8 +904,6 @@ impl<'a> Context<'a> { } } - self.require_internal_export("__wbindgen_realloc")?; - // A fast path that directly writes char codes into WASM memory as long // as it finds only ASCII characters. // @@ -975,10 +915,18 @@ impl<'a> Context<'a> { // charCodeAt on ASCII strings is usually optimised to raw bytes. let encode_as_ascii = format!( "\ + if (realloc === undefined) {{ + const buf = cachedTextEncoder.encode(arg); + const ptr = malloc(buf.length); + {mem}().subarray(ptr, ptr + buf.length).set(buf); + WASM_VECTOR_LEN = buf.length; + return ptr; + }} + let len = arg.length; let ptr = malloc(len); - const mem = {}(); + const mem = {mem}(); let offset = 0; @@ -988,7 +936,7 @@ impl<'a> Context<'a> { mem[ptr + offset] = code; }} ", - mem + mem = mem, ); // TODO: @@ -997,14 +945,14 @@ 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 {name}(arg, malloc) {{ + "function {name}(arg, malloc, realloc) {{ {debug} {ascii} if (offset !== len) {{ if (offset !== 0) {{ arg = arg.slice(offset); }} - ptr = wasm.__wbindgen_realloc(ptr, len, len = offset + arg.length * 3); + ptr = realloc(ptr, len, len = offset + arg.length * 3); const view = {mem}().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); {debug_end} @@ -1585,7 +1533,11 @@ impl<'a> Context<'a> { if !self.should_write_global("handle_error") { return Ok(()); } - self.require_internal_export("__wbindgen_exn_store")?; + let store = self + .aux + .exn_store + .ok_or_else(|| anyhow!("failed to find `__wbindgen_exn_store` intrinsic"))?; + let store = self.export_name_of(store); match (self.aux.anyref_table, self.aux.anyref_alloc) { (Some(table), Some(alloc)) => { let add = self.expose_add_to_anyref_table(table, alloc)?; @@ -1593,21 +1545,22 @@ impl<'a> Context<'a> { " function handleError(e) {{ const idx = {}(e); - wasm.__wbindgen_exn_store(idx); + wasm.{}(idx); }} ", - add, + add, store, )); } _ => { self.expose_add_heap_object(); - self.global( + self.global(&format!( " - function handleError(e) { - wasm.__wbindgen_exn_store(addHeapObject(e)); - } + function handleError(e) {{ + wasm.{}(addHeapObject(e)); + }} ", - ); + store, + )); } } Ok(()) @@ -1943,9 +1896,9 @@ impl<'a> Context<'a> { 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); + let core = self.wit.implements.iter().find(|pair| pair.2 == id); match core { - Some((core, _)) => Kind::Import(*core), + Some((core, _, _)) => Kind::Import(*core), None => Kind::Adapter, } } @@ -2708,35 +2661,22 @@ impl<'a> Context<'a> { Intrinsic::AnyrefHeapLiveCount => { assert_eq!(args.len(), 0); - if self.config.anyref { - // Eventually we should add support to the anyref-xform to - // re-write calls to the imported - // `__wbindgen_anyref_heap_live_count` function into calls to - // the exported `__wbindgen_anyref_heap_live_count_impl` - // function, and to un-export that function. - // - // But for now, we just bounce wasm -> js -> wasm because it is - // easy. - self.require_internal_export("__wbindgen_anyref_heap_live_count_impl")?; - "wasm.__wbindgen_anyref_heap_live_count_impl()".into() - } else { - self.expose_global_heap(); - prelude.push_str( - " - let free_count = 0; - let next = heap_next; - while (next < heap.length) { - free_count += 1; - next = heap[next]; - } - ", - ); - format!( - "heap.length - free_count - {} - {}", - INITIAL_HEAP_OFFSET, - INITIAL_HEAP_VALUES.len(), - ) - } + self.expose_global_heap(); + prelude.push_str( + " + let free_count = 0; + let next = heap_next; + while (next < heap.length) { + free_count += 1; + next = heap[next]; + } + ", + ); + format!( + "heap.length - free_count - {} - {}", + INITIAL_HEAP_OFFSET, + INITIAL_HEAP_VALUES.len(), + ) } Intrinsic::InitAnyrefTable => { @@ -2953,15 +2893,32 @@ impl<'a> Context<'a> { } }); 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); + let default_name = format!("__wbindgen_export_{}", self.next_export_idx); self.next_export_idx += 1; + let name = match id { + walrus::ExportItem::Function(f) => match &self.module.funcs.get(f).name { + Some(s) => to_js_identifier(s), + None => default_name, + }, + _ => default_name, + }; self.module.exports.add(&name, id); - self.required_internal_exports.insert(name.clone().into()); return name; + + // Not really an exhaustive list, but works for our purposes. + fn to_js_identifier(name: &str) -> String { + name.chars() + .map(|c| { + if c.is_ascii() && (c.is_alphabetic() || c.is_numeric()) { + c + } else { + '_' + } + }) + .collect() + } } fn adapter_name(&self, id: AdapterId) -> String { diff --git a/crates/cli-support/src/lib.rs b/crates/cli-support/src/lib.rs index e3313998..f7a69116 100755 --- a/crates/cli-support/src/lib.rs +++ b/crates/cli-support/src/lib.rs @@ -1,14 +1,13 @@ #![doc(html_root_url = "https://docs.rs/wasm-bindgen-cli-support/0.2")] use anyhow::{bail, Context, Error}; -use std::collections::{BTreeMap, BTreeSet, HashMap}; +use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::env; use std::fs; use std::mem; use std::path::{Path, PathBuf}; use std::str; use walrus::Module; -use wasm_bindgen_wasm_conventions as wasm_conventions; mod anyref; mod decode; @@ -280,15 +279,6 @@ impl Bindgen { } }; - // Our threads and multi-value xforms rely on the presence of the stack - // pointer, so temporarily export it so that our many GC's don't remove - // it before the xform runs. - let mut exported_shadow_stack_pointer = false; - if self.multi_value || self.threads.is_enabled(&module) { - wasm_conventions::export_shadow_stack_pointer(&mut module)?; - exported_shadow_stack_pointer = true; - } - // This isn't the hardest thing in the world too support but we // basically don't know how to rationalize #[wasm_bindgen(start)] and // the actual `start` function if present. Figure this out later if it @@ -346,10 +336,29 @@ impl Bindgen { // This is only done if the anyref pass is enabled, which it's // currently off-by-default since `anyref` is still in development in // engines. + // + // If the anyref pass isn't necessary, then we blanket delete the + // export of all our anyref intrinsics which will get cleaned up in the + // GC pass before JS generation. if self.anyref { anyref::process(&mut module)?; + } else { + let ids = module + .exports + .iter() + .filter(|e| e.name.starts_with("__anyref")) + .map(|e| e.id()) + .collect::>(); + for id in ids { + module.exports.delete(id); + } } + // We've done a whole bunch of transformations to the wasm module, many + // of which leave "garbage" lying around, so let's prune out all our + // unnecessary things here. + gc_module_and_adapters(&mut module); + let aux = module .customs .delete_typed::() @@ -382,17 +391,6 @@ impl Bindgen { } } - // If we exported the shadow stack pointer earlier, remove it from the - // export set now. - if exported_shadow_stack_pointer { - wasm_conventions::unexport_shadow_stack_pointer(&mut module)?; - // The shadow stack pointer is potentially unused now, but since it - // most likely _is_ in use, we don't pay the cost of a full GC here - // just to remove one potentially unnecessary global. - // - // walrus::passes::gc::run(&mut module); - } - Ok(Output { module, stem: stem.to_string(), @@ -720,3 +718,42 @@ impl Output { reset_indentation(&shim) } } + +fn gc_module_and_adapters(module: &mut Module) { + // First up we execute walrus's own gc passes, and this may enable us to + // delete entries in the `implements` section of the nonstandard wasm + // interface types section. (if the import is GC'd, then the implements + // annotation is no longer needed). + // + // By deleting adapter functions that may enable us to further delete more + // functions, so we run this in a loop until we don't actually delete any + // adapter functions. + loop { + walrus::passes::gc::run(module); + + let imports_remaining = module + .imports + .iter() + .map(|i| i.id()) + .collect::>(); + let section = module + .customs + .get_typed_mut::() + .unwrap(); + let mut deleted_implements = Vec::new(); + section.implements.retain(|pair| { + if imports_remaining.contains(&pair.0) { + true + } else { + deleted_implements.push(pair.2); + false + } + }); + if deleted_implements.len() == 0 { + break; + } + for id in deleted_implements { + section.adapters.remove(&id); + } + } +} diff --git a/crates/cli-support/src/wit/incoming.rs b/crates/cli-support/src/wit/incoming.rs index dc00b90c..a93f1ce2 100644 --- a/crates/cli-support/src/wit/incoming.rs +++ b/crates/cli-support/src/wit/incoming.rs @@ -98,13 +98,13 @@ impl InstructionBuilder<'_, '_> { 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), + Instruction::StringToMemory { + malloc: self.cx.malloc()?, + realloc: self.cx.realloc(), + mem: self.cx.memory()?, + }, &[AdapterType::I32, AdapterType::I32], ); } @@ -163,13 +163,13 @@ impl InstructionBuilder<'_, '_> { } 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), + Instruction::StringToMemory { + malloc: self.cx.malloc()?, + realloc: self.cx.realloc(), + mem: self.cx.memory()?, + }, &[AdapterType::I32, AdapterType::I32], ); } @@ -218,7 +218,9 @@ impl InstructionBuilder<'_, '_> { Descriptor::Anyref => { self.instruction( &[AdapterType::Anyref], - Instruction::I32FromOptionAnyref, + Instruction::I32FromOptionAnyref { + table_and_alloc: None, + }, &[AdapterType::I32], ); } @@ -272,7 +274,22 @@ impl InstructionBuilder<'_, '_> { ); } - Descriptor::String | Descriptor::CachedString | Descriptor::Vector(_) => { + Descriptor::String | Descriptor::CachedString => { + let malloc = self.cx.malloc()?; + let mem = self.cx.memory()?; + let realloc = self.cx.realloc(); + self.instruction( + &[AdapterType::Anyref], + Instruction::OptionString { + malloc, + mem, + realloc, + }, + &[AdapterType::I32; 2], + ); + } + + Descriptor::Vector(_) => { let kind = arg.vector_kind().ok_or_else(|| { format_err!( "unsupported optional slice type for calling Rust function from JS {:?}", diff --git a/crates/cli-support/src/wit/mod.rs b/crates/cli-support/src/wit/mod.rs index 2368d9e8..4308ba64 100644 --- a/crates/cli-support/src/wit/mod.rs +++ b/crates/cli-support/src/wit/mod.rs @@ -83,6 +83,8 @@ pub fn process( cx.verify()?; + cx.unexport_intrinsics(); + let adapters = cx.module.customs.add(cx.adapters); let aux = cx.module.customs.add(cx.aux); Ok((adapters, aux)) @@ -502,9 +504,15 @@ impl<'a> Context<'a> { // 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; + // + // For `catch` once we see that we'll need an internal intrinsic later + // for JS glue generation, so be sure to find that here. + let adapter = self.adapters.implements.last().unwrap().2; if *catch { self.aux.imports_with_catch.insert(adapter); + if self.aux.exn_store.is_none() { + self.find_exn_store(); + } } if *assert_no_shim { self.aux.imports_with_assert_no_shim.insert(adapter); @@ -983,7 +991,7 @@ impl<'a> Context<'a> { }; self.adapters .implements - .push((import_id, walrus2us[&i.adapter_func])); + .push((import_id, i.core_func, walrus2us[&i.adapter_func])); } Ok(()) } @@ -995,7 +1003,7 @@ impl<'a> Context<'a> { // `$PLACEHOLDER_MODULE` are connected to an adapter via the // `implements` section. let mut implemented = HashMap::new(); - for (core, adapter) in self.adapters.implements.iter() { + for (core, _, adapter) in self.adapters.implements.iter() { implemented.insert(core, adapter); } for import in self.module.imports.iter() { @@ -1006,6 +1014,15 @@ impl<'a> Context<'a> { walrus::ImportKind::Function(_) => {} _ => bail!("import from `{}` was not a function", PLACEHOLDER_MODULE), } + + // These are special intrinsics which were handled in the descriptor + // phase, but we don't have an implementation for them. We don't + // need to error about them in this verification pass though, + // having them lingering in the module is normal. + if import.name == "__wbindgen_describe" || import.name == "__wbindgen_describe_closure" + { + continue; + } if implemented.remove(&import.id()).is_none() { bail!("import of `{}` doesn't have an adapter listed", import.name); } @@ -1065,6 +1082,10 @@ impl<'a> Context<'a> { let import = self.module.imports.get(import); let (import_module, import_name) = (import.module.clone(), import.name.clone()); let import_id = import.id(); + let core_id = match import.kind { + walrus::ImportKind::Function(f) => f, + _ => bail!("bound import must be assigned to function"), + }; // 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 @@ -1131,7 +1152,7 @@ impl<'a> Context<'a> { .cx .adapters .append(args.input, results, AdapterKind::Local { instructions }); - args.cx.adapters.implements.push((import_id, id)); + args.cx.adapters.implements.push((import_id, core_id, id)); Ok(f) } @@ -1233,6 +1254,13 @@ impl<'a> Context<'a> { .ok_or_else(|| anyhow!("failed to find declaration of `__wbindgen_malloc` in module")) } + fn realloc(&self) -> Option { + self.function_exports + .get("__wbindgen_realloc") + .cloned() + .map(|p| p.1) + } + fn free(&self) -> Result { self.function_exports .get("__wbindgen_free") @@ -1245,6 +1273,45 @@ impl<'a> Context<'a> { self.memory .ok_or_else(|| anyhow!("failed to find memory declaration in module")) } + + /// Removes the export item for all `__wbindgen` intrinsics which are + /// generally only purely internal helpers. + /// + /// References to these functions are preserved through adapter instructions + /// if necessary, otherwise they can all be gc'd out. By the time this + /// function is called our discovery of these intrinsics has completed and + /// there's no need to keep around these exports. + fn unexport_intrinsics(&mut self) { + let mut to_remove = Vec::new(); + for export in self.module.exports.iter() { + match export.name.as_str() { + n if n.starts_with("__wbindgen") => { + to_remove.push(export.id()); + } + _ => {} + } + } + for id in to_remove { + self.module.exports.delete(id); + } + } + + /// Attempts to locate the `__wbindgen_exn_store` intrinsic and stores it in + /// our auxiliary information. + /// + /// This is only invoked if the intrinsic will actually be needed for JS + /// glue generation somewhere. + fn find_exn_store(&mut self) { + self.aux.exn_store = self + .module + .exports + .iter() + .find(|e| e.name == "__wbindgen_exn_store") + .and_then(|e| match e.item { + walrus::ExportItem::Function(f) => Some(f), + _ => None, + }); + } } fn extract_programs<'a>( diff --git a/crates/cli-support/src/wit/nonstandard.rs b/crates/cli-support/src/wit/nonstandard.rs index 20f03cf5..30920146 100644 --- a/crates/cli-support/src/wit/nonstandard.rs +++ b/crates/cli-support/src/wit/nonstandard.rs @@ -52,6 +52,9 @@ pub struct WasmBindgenAux { pub anyref_table: Option, pub anyref_alloc: Option, pub anyref_drop_slice: Option, + + /// Various intrinsics used for JS glue generation + pub exn_store: Option, } pub type WasmBindgenAuxId = TypedCustomSectionId; @@ -357,4 +360,19 @@ impl walrus::CustomSection for WasmBindgenAux { fn data(&self, _: &walrus::IdsToIndices) -> Cow<[u8]> { panic!("shouldn't emit custom sections just yet"); } + + fn add_gc_roots(&self, roots: &mut walrus::passes::Roots) { + if let Some(id) = self.anyref_table { + roots.push_table(id); + } + if let Some(id) = self.anyref_alloc { + roots.push_func(id); + } + if let Some(id) = self.anyref_drop_slice { + roots.push_func(id); + } + if let Some(id) = self.exn_store { + roots.push_func(id); + } + } } diff --git a/crates/cli-support/src/wit/section.rs b/crates/cli-support/src/wit/section.rs index 54c89e1a..adea17a3 100644 --- a/crates/cli-support/src/wit/section.rs +++ b/crates/cli-support/src/wit/section.rs @@ -45,13 +45,14 @@ pub fn add( anyref_table: _, // not relevant anyref_alloc: _, // not relevant anyref_drop_slice: _, // not relevant + exn_store: _, // 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) { + if let Some((core, _, _)) = nonstandard.implements.iter().find(|p| p.2 == id) { let import = module.imports.get(*core); return format!( "in function import from `{}::{}`", @@ -107,12 +108,8 @@ pub fn add( 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 (_, core, adapter) in nonstandard.implements.iter() { + section.implements.add(us2walrus[adapter], *core); } for (name, adapter) in nonstandard.exports.iter() { @@ -231,6 +228,14 @@ fn translate_instruction( _ => bail!("expected to find an element of the function table"), } } + StringToMemory { + mem, + malloc, + realloc: _, + } => Ok(wit_walrus::Instruction::StringToMemory { + mem: *mem, + malloc: *malloc, + }), StoreRetptr { .. } | LoadRetptr { .. } | Retptr => { bail!("return pointers aren't supported in wasm interface types"); } @@ -250,7 +255,7 @@ fn translate_instruction( bail!("64-bit integers aren't supported in wasm-bindgen"); } I32SplitOption64 { .. } - | I32FromOptionAnyref + | I32FromOptionAnyref { .. } | I32FromOptionU32Sentinel | I32FromOptionRust { .. } | I32FromOptionBool @@ -258,6 +263,7 @@ fn translate_instruction( | I32FromOptionEnum { .. } | FromOptionNative { .. } | OptionVector { .. } + | OptionString { .. } | OptionRustFromI32 { .. } | OptionVectorLoad { .. } | OptionView { .. } diff --git a/crates/cli-support/src/wit/standard.rs b/crates/cli-support/src/wit/standard.rs index 0388d9d1..bfc9bb93 100644 --- a/crates/cli-support/src/wit/standard.rs +++ b/crates/cli-support/src/wit/standard.rs @@ -1,7 +1,7 @@ use crate::descriptor::VectorKind; use std::borrow::Cow; use std::collections::HashMap; -use walrus::{ImportId, TypedCustomSectionId}; +use walrus::{FunctionId, ImportId, TypedCustomSectionId}; #[derive(Default, Debug)] pub struct NonstandardWitSection { @@ -9,7 +9,7 @@ pub struct NonstandardWitSection { pub adapters: HashMap, /// A list of pairs for adapter functions that implement core wasm imports. - pub implements: Vec<(ImportId, AdapterId)>, + pub implements: Vec<(ImportId, FunctionId, AdapterId)>, /// A list of adapter functions and the names they're exported under. pub exports: Vec<(String, AdapterId)>, @@ -149,7 +149,11 @@ pub enum Instruction { }, /// 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, + I32FromOptionAnyref { + /// Set to `Some` by the anyref pass of where to put it in the wasm + /// module, otherwise it's shoved into the JS shim. + table_and_alloc: Option<(walrus::TableId, walrus::FunctionId)>, + }, /// 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, @@ -186,6 +190,19 @@ pub enum Instruction { mem: walrus::MemoryId, }, + /// Pops a string, pushes pointer/length or all zeros + OptionString { + malloc: walrus::FunctionId, + mem: walrus::MemoryId, + realloc: Option, + }, + /// Pops a string, pushes pointer/length + StringToMemory { + malloc: walrus::FunctionId, + mem: walrus::MemoryId, + realloc: Option, + }, + /// Pops an anyref, pushes pointer/length or all zeros OptionVector { kind: VectorKind, @@ -356,4 +373,72 @@ impl walrus::CustomSection for NonstandardWitSection { fn data(&self, _: &walrus::IdsToIndices) -> Cow<[u8]> { panic!("shouldn't emit custom sections just yet"); } + + fn add_gc_roots(&self, roots: &mut walrus::passes::Roots) { + use Instruction::*; + + for (_, adapter) in self.adapters.iter() { + let instrs = match &adapter.kind { + AdapterKind::Local { instructions } => instructions, + AdapterKind::Import { .. } => continue, + }; + for instr in instrs { + match instr.instr { + Standard(wit_walrus::Instruction::DeferCallCore(f)) + | Standard(wit_walrus::Instruction::CallCore(f)) => { + roots.push_func(f); + } + StoreRetptr { mem, .. } + | LoadRetptr { mem, .. } + | View { mem, .. } + | OptionView { mem, .. } + | Standard(wit_walrus::Instruction::MemoryToString(mem)) => { + roots.push_memory(mem); + } + VectorToMemory { malloc, mem, .. } + | OptionVector { malloc, mem, .. } + | Standard(wit_walrus::Instruction::StringToMemory { mem, malloc }) => { + roots.push_memory(mem); + roots.push_func(malloc); + } + MutableSliceToMemory { + free, malloc, mem, .. + } => { + roots.push_memory(mem); + roots.push_func(malloc); + roots.push_func(free); + } + VectorLoad { free, mem, .. } + | OptionVectorLoad { free, mem, .. } + | CachedStringLoad { free, mem, .. } => { + roots.push_memory(mem); + roots.push_func(free); + } + OptionString { + mem, + malloc, + realloc, + } + | StringToMemory { + mem, + malloc, + realloc, + } => { + roots.push_memory(mem); + roots.push_func(malloc); + if let Some(id) = realloc { + roots.push_func(id); + } + } + I32FromOptionAnyref { table_and_alloc } => { + if let Some((table, alloc)) = table_and_alloc { + roots.push_table(table); + roots.push_func(alloc); + } + } + _ => {} + } + } + } + } } diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 489e31b6..32b17797 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -30,7 +30,16 @@ wasm-bindgen-shared = { path = "../shared", version = "=0.2.55" } [dev-dependencies] assert_cmd = "0.11" +diff = "0.1" predicates = "1.0.0" +rayon = "1.0" +tempfile = "3.0" +walrus = "0.14" +wasmprinter = "0.2" + +[[test]] +name = "reference" +harness = false [features] vendored-openssl = ['openssl/vendored'] diff --git a/crates/cli/tests/reference.rs b/crates/cli/tests/reference.rs new file mode 100644 index 00000000..6bcda84e --- /dev/null +++ b/crates/cli/tests/reference.rs @@ -0,0 +1,225 @@ +//! A test suite to check the reference JS and wasm output of the `wasm-bindgen` +//! library. +//! +//! This is intended as an end-to-end integration test where we can track +//! changes to the JS and wasm output. +//! +//! Tests are located in `reference/*.rs` files and are accompanied with sibling +//! `*.js` files and `*.wat` files with the expected output of the `*.rs` +//! compilation. Use `BLESS=1` in the environment to automatically update all +//! tests. + +use anyhow::{bail, Result}; +use assert_cmd::prelude::*; +use rayon::prelude::*; +use std::env; +use std::fs; +use std::path::{Path, PathBuf}; +use std::process::Command; + +fn main() -> Result<()> { + let filter = env::args().nth(1); + + let mut tests = Vec::new(); + let dir = env::current_dir()?.join("tests/reference"); + for entry in dir.read_dir()? { + let path = entry?.path(); + if path.extension().and_then(|s| s.to_str()) != Some("rs") { + continue; + } + if let Some(filter) = &filter { + if !path.display().to_string().contains(filter) { + continue; + } + } + tests.push(path); + } + tests.sort(); + + let errs = tests + .par_iter() + .filter_map(|t| runtest(t).err().map(|e| (t, e))) + .collect::>(); + + if errs.len() == 0 { + println!("{} tests passed", tests.len()); + return Ok(()); + } + eprintln!("failed tests:\n"); + for (test, err) in errs { + eprintln!("{} failure\n{}", test.display(), tab(&format!("{:?}", err))); + } + bail!("tests failed"); +} + +fn runtest(test: &Path) -> Result<()> { + let contents = fs::read_to_string(test)?; + let td = tempfile::TempDir::new()?; + + let manifest = format!( + " + [package] + name = \"reference-test\" + authors = [] + version = \"1.0.0\" + edition = '2018' + + [dependencies] + wasm-bindgen = {{ path = '{}' }} + + [lib] + crate-type = ['cdylib'] + path = '{}' + ", + repo_root().display(), + test.display(), + ); + + fs::write(td.path().join("Cargo.toml"), manifest)?; + let target_dir = target_dir(); + exec( + Command::new("cargo") + .current_dir(td.path()) + .arg("build") + .arg("--target") + .arg("wasm32-unknown-unknown") + .env("CARGO_TARGET_DIR", &target_dir), + )?; + + let wasm = target_dir + .join("wasm32-unknown-unknown") + .join("debug") + .join("reference_test.wasm"); + + let mut bindgen = Command::cargo_bin("wasm-bindgen")?; + bindgen + .arg("--out-dir") + .arg(td.path()) + .arg(&wasm) + .arg("--no-typescript"); + if contents.contains("// enable-anyref") { + bindgen.env("WASM_BINDGEN_ANYREF", "1"); + } + exec(&mut bindgen)?; + + let js = fs::read_to_string(td.path().join("reference_test.js"))?; + let wat = sanitize_wasm(&td.path().join("reference_test_bg.wasm"))?; + + let js_assertion = test.with_extension("js"); + let wat_assertion = test.with_extension("wat"); + + if env::var("BLESS").is_ok() { + fs::write(js_assertion, js)?; + fs::write(wat_assertion, wat)?; + return Ok(()); + } + + let js_expected = fs::read_to_string(&js_assertion)?; + let wat_expected = fs::read_to_string(&wat_assertion)?; + + diff(&js_expected, &js)?; + diff(&wat_expected, &wat)?; + + Ok(()) +} + +fn sanitize_wasm(wasm: &Path) -> Result { + // Clean up the wasm module by removing all function + // implementations/instructions, data sections, etc. This'll help us largely + // only deal with exports/imports which is all we're really interested in. + let mut module = walrus::Module::from_file(wasm)?; + for func in module.funcs.iter_mut() { + let local = match &mut func.kind { + walrus::FunctionKind::Local(l) => l, + _ => continue, + }; + local.block_mut(local.entry_block()).instrs.truncate(0); + } + let ids = module.data.iter().map(|d| d.id()).collect::>(); + for id in ids { + module.data.delete(id); + } + for mem in module.memories.iter_mut() { + mem.data_segments.drain(); + } + let ids = module + .exports + .iter() + .filter(|e| match e.item { + walrus::ExportItem::Global(_) => true, + _ => false, + }) + .map(|d| d.id()) + .collect::>(); + for id in ids { + module.exports.delete(id); + } + walrus::passes::gc::run(&mut module); + let mut wat = wasmprinter::print_bytes(&module.emit_wasm())?; + wat.push_str("\n"); + Ok(wat) +} + +fn diff(a: &str, b: &str) -> Result<()> { + if a == b { + return Ok(()); + } + let mut s = String::new(); + for result in diff::lines(a, b) { + match result { + diff::Result::Both(l, _) => { + s.push_str(" "); + s.push_str(l); + } + diff::Result::Left(l) => { + s.push_str("-"); + s.push_str(l); + } + diff::Result::Right(l) => { + s.push_str("+"); + s.push_str(l); + } + } + s.push_str("\n"); + } + bail!("found a difference:\n\n{}", s); +} + +fn target_dir() -> PathBuf { + let mut dir = PathBuf::from(env::current_exe().unwrap()); + dir.pop(); // current exe + if dir.ends_with("deps") { + dir.pop(); + } + dir.pop(); // debug and/or release + return dir; +} + +fn repo_root() -> PathBuf { + let mut repo_root = env::current_dir().unwrap(); + repo_root.pop(); // remove 'cli' + repo_root.pop(); // remove 'crates' + repo_root +} + +fn exec(cmd: &mut Command) -> Result<()> { + let output = cmd.output()?; + if output.status.success() { + return Ok(()); + } + let mut err = format!("command failed {:?}", cmd); + err.push_str(&format!("\nstatus: {}", output.status)); + err.push_str(&format!( + "\nstderr:\n{}", + tab(&String::from_utf8_lossy(&output.stderr)) + )); + err.push_str(&format!( + "\nstdout:\n{}", + tab(&String::from_utf8_lossy(&output.stdout)) + )); + bail!("{}", err); +} + +fn tab(s: &str) -> String { + format!(" {}", s.replace("\n", "\n ")) +} diff --git a/crates/cli/tests/reference/add.js b/crates/cli/tests/reference/add.js new file mode 100644 index 00000000..0d52b782 --- /dev/null +++ b/crates/cli/tests/reference/add.js @@ -0,0 +1,22 @@ +import * as wasm from './reference_test_bg.wasm'; + +/** +* @param {number} a +* @param {number} b +* @returns {number} +*/ +export function add_u32(a, b) { + var ret = wasm.add_u32(a, b); + return ret >>> 0; +} + +/** +* @param {number} a +* @param {number} b +* @returns {number} +*/ +export function add_i32(a, b) { + var ret = wasm.add_i32(a, b); + return ret; +} + diff --git a/crates/cli/tests/reference/add.rs b/crates/cli/tests/reference/add.rs new file mode 100644 index 00000000..f2cc3e7a --- /dev/null +++ b/crates/cli/tests/reference/add.rs @@ -0,0 +1,11 @@ +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +pub fn add_u32(a: u32, b: u32) -> u32 { + a + b +} + +#[wasm_bindgen] +pub fn add_i32(a: i32, b: i32) -> i32 { + a + b +} diff --git a/crates/cli/tests/reference/add.wat b/crates/cli/tests/reference/add.wat new file mode 100644 index 00000000..990d0158 --- /dev/null +++ b/crates/cli/tests/reference/add.wat @@ -0,0 +1,8 @@ +(module + (type (;0;) (func (param i32 i32) (result i32))) + (func $add_u32 (type 0) (param i32 i32) (result i32)) + (func $add_i32 (type 0) (param i32 i32) (result i32)) + (memory (;0;) 17) + (export "memory" (memory 0)) + (export "add_u32" (func $add_u32)) + (export "add_i32" (func $add_i32))) diff --git a/crates/cli/tests/reference/anyref-empty.js b/crates/cli/tests/reference/anyref-empty.js new file mode 100644 index 00000000..adc1a1ce --- /dev/null +++ b/crates/cli/tests/reference/anyref-empty.js @@ -0,0 +1,15 @@ +import * as wasm from './reference_test_bg.wasm'; + +export const __wbindgen_init_anyref_table = function() { + const table = wasm.__wbindgen_export_0; + const offset = table.grow(4); + table.set(0, undefined); + table.set(offset + 0, undefined); + table.set(offset + 1, null); + table.set(offset + 2, true); + table.set(offset + 3, false); + ; +}; + +wasm.__wbindgen_start(); + diff --git a/crates/cli/tests/reference/anyref-empty.rs b/crates/cli/tests/reference/anyref-empty.rs new file mode 100644 index 00000000..afcbb924 --- /dev/null +++ b/crates/cli/tests/reference/anyref-empty.rs @@ -0,0 +1 @@ +// enable-anyref diff --git a/crates/cli/tests/reference/anyref-empty.wat b/crates/cli/tests/reference/anyref-empty.wat new file mode 100644 index 00000000..e2372055 --- /dev/null +++ b/crates/cli/tests/reference/anyref-empty.wat @@ -0,0 +1,8 @@ +(module + (type (;0;) (func)) + (import "./reference_test.js" "__wbindgen_init_anyref_table" (func (;0;) (type 0))) + (table (;0;) 32 anyref) + (memory (;0;) 17) + (export "memory" (memory 0)) + (export "__wbindgen_export_0" (table 0)) + (export "__wbindgen_start" (func 0))) diff --git a/crates/cli/tests/reference/anyref-import-catch.js b/crates/cli/tests/reference/anyref-import-catch.js new file mode 100644 index 00000000..5e7c481b --- /dev/null +++ b/crates/cli/tests/reference/anyref-import-catch.js @@ -0,0 +1,65 @@ +import * as wasm from './reference_test_bg.wasm'; + +const lTextDecoder = typeof TextDecoder === 'undefined' ? require('util').TextDecoder : TextDecoder; + +let cachedTextDecoder = new lTextDecoder('utf-8', { ignoreBOM: true, fatal: true }); + +cachedTextDecoder.decode(); + +let cachegetUint8Memory0 = null; +function getUint8Memory0() { + if (cachegetUint8Memory0 === null || cachegetUint8Memory0.buffer !== wasm.memory.buffer) { + cachegetUint8Memory0 = new Uint8Array(wasm.memory.buffer); + } + return cachegetUint8Memory0; +} + +function getStringFromWasm0(ptr, len) { + return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)); +} + +function addToAnyrefTable0(obj) { + const idx = wasm.__anyref_table_alloc(); + wasm.__wbindgen_export_0.set(idx, obj); + return idx; +} + +function handleError(e) { + const idx = addToAnyrefTable0(e); + wasm.__wbindgen_exn_store(idx); +} +/** +*/ +export function exported() { + wasm.exported(); +} + +export const __wbg_foo_8d66ddef0ff279d6 = function() { + try { + foo(); + } catch (e) { + handleError(e) + } +}; + +export const __wbindgen_throw = function(arg0, arg1) { + throw new Error(getStringFromWasm0(arg0, arg1)); +}; + +export const __wbindgen_rethrow = function(arg0) { + throw arg0; +}; + +export const __wbindgen_init_anyref_table = function() { + const table = wasm.__wbindgen_export_0; + const offset = table.grow(4); + table.set(0, undefined); + table.set(offset + 0, undefined); + table.set(offset + 1, null); + table.set(offset + 2, true); + table.set(offset + 3, false); + ; +}; + +wasm.__wbindgen_start(); + diff --git a/crates/cli/tests/reference/anyref-import-catch.rs b/crates/cli/tests/reference/anyref-import-catch.rs new file mode 100644 index 00000000..f51161fe --- /dev/null +++ b/crates/cli/tests/reference/anyref-import-catch.rs @@ -0,0 +1,14 @@ +// enable-anyref + +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(catch)] + fn foo() -> Result<(), JsValue>; +} + +#[wasm_bindgen] +pub fn exported() -> Result<(), JsValue> { + foo() +} diff --git a/crates/cli/tests/reference/anyref-import-catch.wat b/crates/cli/tests/reference/anyref-import-catch.wat new file mode 100644 index 00000000..9e2ac1c8 --- /dev/null +++ b/crates/cli/tests/reference/anyref-import-catch.wat @@ -0,0 +1,16 @@ +(module + (type (;0;) (func)) + (type (;1;) (func (result i32))) + (type (;2;) (func (param i32))) + (import "./reference_test.js" "__wbindgen_init_anyref_table" (func (;0;) (type 0))) + (func $__wbindgen_exn_store (type 2) (param i32)) + (func $exported (type 0)) + (func $__anyref_table_alloc (type 1) (result i32)) + (table (;0;) 32 anyref) + (memory (;0;) 17) + (export "memory" (memory 0)) + (export "exported" (func $exported)) + (export "__wbindgen_export_0" (table 0)) + (export "__wbindgen_exn_store" (func $__wbindgen_exn_store)) + (export "__anyref_table_alloc" (func $__anyref_table_alloc)) + (export "__wbindgen_start" (func 0))) diff --git a/crates/cli/tests/reference/anyref-nop.js b/crates/cli/tests/reference/anyref-nop.js new file mode 100644 index 00000000..e3f060a3 --- /dev/null +++ b/crates/cli/tests/reference/anyref-nop.js @@ -0,0 +1,21 @@ +import * as wasm from './reference_test_bg.wasm'; + +/** +*/ +export function foo() { + wasm.foo(); +} + +export const __wbindgen_init_anyref_table = function() { + const table = wasm.__wbindgen_export_0; + const offset = table.grow(4); + table.set(0, undefined); + table.set(offset + 0, undefined); + table.set(offset + 1, null); + table.set(offset + 2, true); + table.set(offset + 3, false); + ; +}; + +wasm.__wbindgen_start(); + diff --git a/crates/cli/tests/reference/anyref-nop.rs b/crates/cli/tests/reference/anyref-nop.rs new file mode 100644 index 00000000..38359372 --- /dev/null +++ b/crates/cli/tests/reference/anyref-nop.rs @@ -0,0 +1,6 @@ +// enable-anyref + +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +pub fn foo() {} diff --git a/crates/cli/tests/reference/anyref-nop.wat b/crates/cli/tests/reference/anyref-nop.wat new file mode 100644 index 00000000..44fad1e4 --- /dev/null +++ b/crates/cli/tests/reference/anyref-nop.wat @@ -0,0 +1,10 @@ +(module + (type (;0;) (func)) + (import "./reference_test.js" "__wbindgen_init_anyref_table" (func (;0;) (type 0))) + (func $foo (type 0)) + (table (;0;) 32 anyref) + (memory (;0;) 17) + (export "memory" (memory 0)) + (export "foo" (func $foo)) + (export "__wbindgen_export_0" (table 0)) + (export "__wbindgen_start" (func 0))) diff --git a/crates/cli/tests/reference/empty.js b/crates/cli/tests/reference/empty.js new file mode 100644 index 00000000..275ace8a --- /dev/null +++ b/crates/cli/tests/reference/empty.js @@ -0,0 +1,2 @@ +import * as wasm from './reference_test_bg.wasm'; + diff --git a/crates/cli/tests/reference/empty.rs b/crates/cli/tests/reference/empty.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/cli/tests/reference/empty.wat b/crates/cli/tests/reference/empty.wat new file mode 100644 index 00000000..d0ef2bd8 --- /dev/null +++ b/crates/cli/tests/reference/empty.wat @@ -0,0 +1,3 @@ +(module + (memory (;0;) 17) + (export "memory" (memory 0))) diff --git a/crates/cli/tests/reference/import-catch.js b/crates/cli/tests/reference/import-catch.js new file mode 100644 index 00000000..d17f15e0 --- /dev/null +++ b/crates/cli/tests/reference/import-catch.js @@ -0,0 +1,58 @@ +import * as wasm from './reference_test_bg.wasm'; + +const heap = new Array(32); + +heap.fill(undefined); + +heap.push(undefined, null, true, false); + +function getObject(idx) { return heap[idx]; } + +let heap_next = heap.length; + +function dropObject(idx) { + if (idx < 36) return; + heap[idx] = heap_next; + heap_next = idx; +} + +function takeObject(idx) { + const ret = getObject(idx); + dropObject(idx); + return ret; +} + +function addHeapObject(obj) { + if (heap_next === heap.length) heap.push(heap.length + 1); + const idx = heap_next; + heap_next = heap[idx]; + + heap[idx] = obj; + return idx; +} + +function handleError(e) { + wasm.__wbindgen_exn_store(addHeapObject(e)); +} +/** +*/ +export function exported() { + wasm.exported(); +} + +export const __wbindgen_object_drop_ref = function(arg0) { + takeObject(arg0); +}; + +export const __wbg_foo_8d66ddef0ff279d6 = function() { + try { + foo(); + } catch (e) { + handleError(e) + } +}; + +export const __wbindgen_rethrow = function(arg0) { + throw takeObject(arg0); +}; + diff --git a/crates/cli/tests/reference/import-catch.rs b/crates/cli/tests/reference/import-catch.rs new file mode 100644 index 00000000..b6a6e58f --- /dev/null +++ b/crates/cli/tests/reference/import-catch.rs @@ -0,0 +1,12 @@ +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(catch)] + fn foo() -> Result<(), JsValue>; +} + +#[wasm_bindgen] +pub fn exported() -> Result<(), JsValue> { + foo() +} diff --git a/crates/cli/tests/reference/import-catch.wat b/crates/cli/tests/reference/import-catch.wat new file mode 100644 index 00000000..702b6ae0 --- /dev/null +++ b/crates/cli/tests/reference/import-catch.wat @@ -0,0 +1,9 @@ +(module + (type (;0;) (func)) + (type (;1;) (func (param i32))) + (func $__wbindgen_exn_store (type 1) (param i32)) + (func $exported (type 0)) + (memory (;0;) 17) + (export "memory" (memory 0)) + (export "exported" (func $exported)) + (export "__wbindgen_exn_store" (func $__wbindgen_exn_store))) diff --git a/crates/cli/tests/reference/nop.js b/crates/cli/tests/reference/nop.js new file mode 100644 index 00000000..edac6e41 --- /dev/null +++ b/crates/cli/tests/reference/nop.js @@ -0,0 +1,8 @@ +import * as wasm from './reference_test_bg.wasm'; + +/** +*/ +export function nop() { + wasm.nop(); +} + diff --git a/crates/cli/tests/reference/nop.rs b/crates/cli/tests/reference/nop.rs new file mode 100644 index 00000000..ac905ec2 --- /dev/null +++ b/crates/cli/tests/reference/nop.rs @@ -0,0 +1,4 @@ +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +pub fn nop() {} diff --git a/crates/cli/tests/reference/nop.wat b/crates/cli/tests/reference/nop.wat new file mode 100644 index 00000000..4ec16481 --- /dev/null +++ b/crates/cli/tests/reference/nop.wat @@ -0,0 +1,6 @@ +(module + (type (;0;) (func)) + (func $nop (type 0)) + (memory (;0;) 17) + (export "memory" (memory 0)) + (export "nop" (func $nop))) diff --git a/crates/cli/tests/reference/string-arg.js b/crates/cli/tests/reference/string-arg.js new file mode 100644 index 00000000..c830c61c --- /dev/null +++ b/crates/cli/tests/reference/string-arg.js @@ -0,0 +1,89 @@ +import * as wasm from './reference_test_bg.wasm'; + +const lTextDecoder = typeof TextDecoder === 'undefined' ? require('util').TextDecoder : TextDecoder; + +let cachedTextDecoder = new lTextDecoder('utf-8', { ignoreBOM: true, fatal: true }); + +cachedTextDecoder.decode(); + +let cachegetUint8Memory0 = null; +function getUint8Memory0() { + if (cachegetUint8Memory0 === null || cachegetUint8Memory0.buffer !== wasm.memory.buffer) { + cachegetUint8Memory0 = new Uint8Array(wasm.memory.buffer); + } + return cachegetUint8Memory0; +} + +function getStringFromWasm0(ptr, len) { + return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)); +} + +let WASM_VECTOR_LEN = 0; + +const lTextEncoder = typeof TextEncoder === 'undefined' ? require('util').TextEncoder : TextEncoder; + +let cachedTextEncoder = new lTextEncoder('utf-8'); + +const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' + ? function (arg, view) { + return cachedTextEncoder.encodeInto(arg, view); +} + : function (arg, view) { + const buf = cachedTextEncoder.encode(arg); + view.set(buf); + return { + read: arg.length, + written: buf.length + }; +}); + +function passStringToWasm0(arg, malloc, realloc) { + + if (realloc === undefined) { + const buf = cachedTextEncoder.encode(arg); + const ptr = malloc(buf.length); + getUint8Memory0().subarray(ptr, ptr + buf.length).set(buf); + WASM_VECTOR_LEN = buf.length; + return ptr; + } + + let len = arg.length; + let ptr = malloc(len); + + const mem = getUint8Memory0(); + + let offset = 0; + + for (; offset < len; offset++) { + const code = arg.charCodeAt(offset); + if (code > 0x7F) break; + mem[ptr + offset] = code; + } + + if (offset !== len) { + if (offset !== 0) { + arg = arg.slice(offset); + } + ptr = realloc(ptr, len, len = offset + arg.length * 3); + const view = getUint8Memory0().subarray(ptr + offset, ptr + len); + const ret = encodeString(arg, view); + + offset += ret.written; + } + + WASM_VECTOR_LEN = offset; + return ptr; +} +/** +* @param {string} a +*/ +export function foo(a) { + var ptr0 = passStringToWasm0(a, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len0 = WASM_VECTOR_LEN; + wasm.foo(ptr0, len0); +} + +export const __wbindgen_throw = function(arg0, arg1) { + throw new Error(getStringFromWasm0(arg0, arg1)); +}; + diff --git a/crates/cli/tests/reference/string-arg.rs b/crates/cli/tests/reference/string-arg.rs new file mode 100644 index 00000000..64b4b417 --- /dev/null +++ b/crates/cli/tests/reference/string-arg.rs @@ -0,0 +1,6 @@ +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +pub fn foo(a: &str) { + drop(a); +} diff --git a/crates/cli/tests/reference/string-arg.wat b/crates/cli/tests/reference/string-arg.wat new file mode 100644 index 00000000..bf24ad91 --- /dev/null +++ b/crates/cli/tests/reference/string-arg.wat @@ -0,0 +1,12 @@ +(module + (type (;0;) (func (param i32) (result i32))) + (type (;1;) (func (param i32 i32))) + (type (;2;) (func (param i32 i32 i32) (result i32))) + (func $__wbindgen_realloc (type 2) (param i32 i32 i32) (result i32)) + (func $__wbindgen_malloc (type 0) (param i32) (result i32)) + (func $foo (type 1) (param i32 i32)) + (memory (;0;) 17) + (export "memory" (memory 0)) + (export "foo" (func $foo)) + (export "__wbindgen_malloc" (func $__wbindgen_malloc)) + (export "__wbindgen_realloc" (func $__wbindgen_realloc))) diff --git a/crates/wasm-conventions/src/lib.rs b/crates/wasm-conventions/src/lib.rs index 705815f8..11ba8889 100755 --- a/crates/wasm-conventions/src/lib.rs +++ b/crates/wasm-conventions/src/lib.rs @@ -29,11 +29,11 @@ pub fn get_memory(module: &Module) -> Result { }) } -/// Discover the shadow stack pointer and add it to the module's exports as -/// `__shadow_stack_pointer`. +/// Get the `__shadow_stack_pointer`. /// -/// Adding it to the exports is useful for making sure it doesn't get GC'd. -pub fn export_shadow_stack_pointer(module: &mut Module) -> Result<(), Error> { +/// It must have been previously added to the module's exports via +/// `export_shadow_stack_pointer`. +pub fn get_shadow_stack_pointer(module: &Module) -> Result { let candidates = module .globals .iter() @@ -57,35 +57,5 @@ pub fn export_shadow_stack_pointer(module: &mut Module) -> Result<(), Error> { _ => bail!("too many mutable globals to infer which is the shadow stack pointer"), }; - module.exports.add("__shadow_stack_pointer", ssp); - Ok(()) -} - -/// Unexport the shadow stack pointer that was previously added to the module's -/// exports as `__shadow_stack_pointer`. -pub fn unexport_shadow_stack_pointer(module: &mut Module) -> Result<(), Error> { - let e = module - .exports - .iter() - .find(|e| e.name == "__shadow_stack_pointer") - .map(|e| e.id()) - .ok_or_else(|| anyhow!("did not find the `__shadow_stack_pointer` export in the module"))?; - module.exports.delete(e); - Ok(()) -} - -/// Get the `__shadow_stack_pointer`. -/// -/// It must have been previously added to the module's exports via -/// `export_shadow_stack_pointer`. -pub fn get_shadow_stack_pointer(module: &Module) -> Result { - module - .exports - .iter() - .find(|e| e.name == "__shadow_stack_pointer") - .ok_or_else(|| anyhow!("did not find the `__shadow_stack_pointer` export in the module")) - .and_then(|e| match e.item { - walrus::ExportItem::Global(g) => Ok(g), - _ => bail!("`__shadow_stack_pointer` export is wrong kind"), - }) + Ok(ssp) } diff --git a/src/anyref.rs b/src/anyref.rs index 7edff7db..9ec69897 100644 --- a/src/anyref.rs +++ b/src/anyref.rs @@ -126,7 +126,7 @@ fn internal_error(msg: &str) -> ! { std::thread_local!(pub static HEAP_SLAB: Cell = Cell::new(Slab::new())); #[no_mangle] -pub extern "C" fn __wbindgen_anyref_table_alloc() -> usize { +pub extern "C" fn __anyref_table_alloc() -> usize { HEAP_SLAB .try_with(|slot| { let mut slab = slot.replace(Slab::new()); @@ -138,7 +138,7 @@ pub extern "C" fn __wbindgen_anyref_table_alloc() -> usize { } #[no_mangle] -pub extern "C" fn __wbindgen_anyref_table_dealloc(idx: usize) { +pub extern "C" fn __anyref_table_dealloc(idx: usize) { if idx < super::JSIDX_RESERVED as usize { return; } @@ -157,16 +157,16 @@ pub extern "C" fn __wbindgen_anyref_table_dealloc(idx: usize) { } #[no_mangle] -pub unsafe extern "C" fn __wbindgen_drop_anyref_slice(ptr: *mut JsValue, len: usize) { +pub unsafe extern "C" fn __anyref_drop_slice(ptr: *mut JsValue, len: usize) { for slot in slice::from_raw_parts_mut(ptr, len) { - __wbindgen_anyref_table_dealloc(slot.idx as usize); + __anyref_table_dealloc(slot.idx as usize); } } // Implementation of `__wbindgen_anyref_heap_live_count` for when we are using // `anyref` instead of the JS `heap`. #[no_mangle] -pub unsafe extern "C" fn __wbindgen_anyref_heap_live_count_impl() -> u32 { +pub unsafe extern "C" fn __anyref_heap_live_count() -> u32 { HEAP_SLAB .try_with(|slot| { let slab = slot.replace(Slab::new());