From 91d68a0012d643ede5a680e168d46f757dceae28 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 29 Oct 2018 15:15:03 -0700 Subject: [PATCH 1/3] Add tests for the wasm-gc crate This commit adds a test harness and the beginnings of a test suite for the crate that performs GC over a wasm module. This crate historically has had zero tests because it was thought that it would no longer be used once LLD landed with `--gc-sections`, but `wasm-bindgen` has come to rely more and more on `wasm-gc` for various purposes. The last release of `wasm-bindgen` was also released with a bug in the recently refactored support in the `wasm-gc` crate, providing a perfect time and motivation to start writing some tests! All tests added here are `*.wat` files which contain the expected output after the gc pass is executed. Tests are automatically updated with `BLESS_TESTS=1` in the environment, which is the expected way to generate the output for each test. --- .travis.yml | 10 ++ crates/gc/Cargo.toml | 11 ++ crates/gc/src/lib.rs | 35 +++-- crates/gc/tests/all.rs | 146 ++++++++++++++++++ crates/gc/tests/wat/dont-remove-func1.wat | 11 ++ crates/gc/tests/wat/dont-remove-func2.wat | 16 ++ crates/gc/tests/wat/import-and-type.wat | 21 +++ crates/gc/tests/wat/import-memory.wat | 8 + crates/gc/tests/wat/keep-data.wat | 10 ++ crates/gc/tests/wat/keep-global.wat | 11 ++ crates/gc/tests/wat/keep-memory.wat | 8 + crates/gc/tests/wat/keep-set-global.wat | 23 +++ crates/gc/tests/wat/keep-start.wat | 14 ++ crates/gc/tests/wat/keep-table.wat | 10 ++ crates/gc/tests/wat/keep-table2.wat | 17 ++ crates/gc/tests/wat/locals-compressed.wat | 39 +++++ crates/gc/tests/wat/preserve-local1.wat | 16 ++ crates/gc/tests/wat/preserve-local2.wat | 18 +++ crates/gc/tests/wat/preserve-local3.wat | 18 +++ crates/gc/tests/wat/remove-func.wat | 7 + crates/gc/tests/wat/remove-heap-base.wat | 9 ++ .../gc/tests/wat/remove-import-function.wat | 26 ++++ crates/gc/tests/wat/remove-import-global.wat | 7 + crates/gc/tests/wat/remove-import-globals.wat | 35 +++++ crates/gc/tests/wat/remove-import-table.wat | 11 ++ crates/gc/tests/wat/remove-local.wat | 13 ++ crates/gc/tests/wat/remove-table.wat | 7 + crates/gc/tests/wat/renumber-functions.wat | 16 ++ crates/gc/tests/wat/renumber-globals.wat | 16 ++ crates/gc/tests/wat/renumber-locals.wat | 16 ++ crates/gc/tests/wat/renumber-types.wat | 13 ++ crates/gc/tests/wat/smoke.wat | 5 + crates/gc/tests/wat/table-elem.wat | 15 ++ crates/gc/tests/wat/table-elem2.wat | 25 +++ 34 files changed, 650 insertions(+), 13 deletions(-) create mode 100644 crates/gc/tests/all.rs create mode 100644 crates/gc/tests/wat/dont-remove-func1.wat create mode 100644 crates/gc/tests/wat/dont-remove-func2.wat create mode 100644 crates/gc/tests/wat/import-and-type.wat create mode 100644 crates/gc/tests/wat/import-memory.wat create mode 100644 crates/gc/tests/wat/keep-data.wat create mode 100644 crates/gc/tests/wat/keep-global.wat create mode 100644 crates/gc/tests/wat/keep-memory.wat create mode 100644 crates/gc/tests/wat/keep-set-global.wat create mode 100644 crates/gc/tests/wat/keep-start.wat create mode 100644 crates/gc/tests/wat/keep-table.wat create mode 100644 crates/gc/tests/wat/keep-table2.wat create mode 100644 crates/gc/tests/wat/locals-compressed.wat create mode 100644 crates/gc/tests/wat/preserve-local1.wat create mode 100644 crates/gc/tests/wat/preserve-local2.wat create mode 100644 crates/gc/tests/wat/preserve-local3.wat create mode 100644 crates/gc/tests/wat/remove-func.wat create mode 100644 crates/gc/tests/wat/remove-heap-base.wat create mode 100644 crates/gc/tests/wat/remove-import-function.wat create mode 100644 crates/gc/tests/wat/remove-import-global.wat create mode 100644 crates/gc/tests/wat/remove-import-globals.wat create mode 100644 crates/gc/tests/wat/remove-import-table.wat create mode 100644 crates/gc/tests/wat/remove-local.wat create mode 100644 crates/gc/tests/wat/remove-table.wat create mode 100644 crates/gc/tests/wat/renumber-functions.wat create mode 100644 crates/gc/tests/wat/renumber-globals.wat create mode 100644 crates/gc/tests/wat/renumber-locals.wat create mode 100644 crates/gc/tests/wat/renumber-types.wat create mode 100644 crates/gc/tests/wat/smoke.wat create mode 100644 crates/gc/tests/wat/table-elem.wat create mode 100644 crates/gc/tests/wat/table-elem2.wat diff --git a/.travis.yml b/.travis.yml index 5e1d911a2..a6a8b655a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -167,6 +167,16 @@ matrix: script: cargo test -p ui-tests if: branch = master + # wasm-gc tests work alright + - name: "test wasm-bindgen-gc crate" + install: + - git clone https://github.com/WebAssembly/wabt + - mkdir -p wabt/build + - (cd wabt/wabt && cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=sccache -DCMAKE_CXX_COMPILER_ARG1=c++ && cmake --build . -- -j4) + - export PATH=$PATH:`pwd`/wabt/build + script: cargo test -p wasm-bindgen-gc + if: branch = master + # Dist linux binary - name: "dist: Linux (x86_64-unknown-linux-musl)" env: JOB=dist-linux TARGET=x86_64-unknown-linux-musl diff --git a/crates/gc/Cargo.toml b/crates/gc/Cargo.toml index 35345527f..639df8f15 100644 --- a/crates/gc/Cargo.toml +++ b/crates/gc/Cargo.toml @@ -14,3 +14,14 @@ Support for removing unused items from a wasm executable parity-wasm = "0.35.1" log = "0.4" rustc-demangle = "0.1.9" + +[dev-dependencies] +rayon = "1.0.2" +tempfile = "3.0.4" + +[lib] +doctest = false + +[[test]] +name = 'all' +harness = false diff --git a/crates/gc/src/lib.rs b/crates/gc/src/lib.rs index 19210b06c..f2ee52323 100644 --- a/crates/gc/src/lib.rs +++ b/crates/gc/src/lib.rs @@ -71,18 +71,20 @@ fn run(config: &mut Config, module: &mut Module) { } } - // Pessimistically assume all data and table segments are + // Pessimistically assume all passive data and table segments are // required - // - // TODO: this shouldn't assume this! if let Some(section) = module.data_section() { for entry in section.entries() { - cx.add_data_segment(entry); + if entry.passive() { + cx.add_data_segment(entry); + } } } if let Some(elements) = module.elements_section() { - for seg in elements.entries() { - cx.add_element_segment(seg); + for (i, seg) in elements.entries().iter().enumerate() { + if seg.passive() { + cx.add_element_segment(i as u32, seg); + } } } @@ -144,6 +146,7 @@ struct Analysis { imports: BitSet, exports: BitSet, functions: BitSet, + elements: BitSet, imported_functions: u32, imported_globals: u32, imported_memories: u32, @@ -235,9 +238,13 @@ impl<'a> LiveContext<'a> { // Add all element segments that initialize this table if let Some(elements) = self.element_section { - for entry in elements.entries().iter().filter(|d| !d.passive()) { + let iter = elements.entries() + .iter() + .enumerate() + .filter(|(_, d)| !d.passive()); + for (i, entry) in iter { if entry.index() == idx { - self.add_element_segment(entry); + self.add_element_segment(i as u32, entry); } } } @@ -438,7 +445,10 @@ impl<'a> LiveContext<'a> { } } - fn add_element_segment(&mut self, seg: &ElementSegment) { + fn add_element_segment(&mut self, idx: u32, seg: &ElementSegment) { + if !self.analysis.elements.insert(idx) { + return + } for member in seg.members() { self.add_function(*member); } @@ -530,8 +540,7 @@ impl<'a> RemapContext<'a> { } if let Some(s) = m.table_section() { for i in 0..(s.entries().len() as u32) { - // TODO: should remove `|| true` here when this is better tested - if analysis.tables.contains(&(i + analysis.imported_tables)) || true { + if analysis.tables.contains(&(i + analysis.imported_tables)) { tables.push(ntables); ntables += 1; } else { @@ -542,8 +551,7 @@ impl<'a> RemapContext<'a> { } if let Some(s) = m.memory_section() { for i in 0..(s.entries().len() as u32) { - // TODO: should remove `|| true` here when this is better tested - if analysis.memories.contains(&(i + analysis.imported_memories)) || true { + if analysis.memories.contains(&(i + analysis.imported_memories)) { memories.push(nmemories); nmemories += 1; } else { @@ -713,6 +721,7 @@ impl<'a> RemapContext<'a> { } fn remap_element_section(&self, s: &mut ElementSection) -> bool { + self.retain(&self.analysis.elements, s.entries_mut(), "element", 0); for s in s.entries_mut() { self.remap_element_segment(s); } diff --git a/crates/gc/tests/all.rs b/crates/gc/tests/all.rs new file mode 100644 index 000000000..f05dec1d2 --- /dev/null +++ b/crates/gc/tests/all.rs @@ -0,0 +1,146 @@ +extern crate parity_wasm; +extern crate rayon; +extern crate tempfile; +extern crate wasm_bindgen_gc; + +use std::env; +use std::error::Error; +use std::fs; +use std::io; +use std::path::{Path, PathBuf}; +use std::process::{Command, Stdio}; + +use rayon::prelude::*; +use parity_wasm::elements::Module; +use tempfile::NamedTempFile; + +struct Test { + input: PathBuf, +} + +fn main() { + let mut tests = Vec::new(); + find_tests(&mut tests, "tests/wat".as_ref()); + + run_tests(&tests); +} + +fn find_tests(tests: &mut Vec, path: &Path) { + for entry in path.read_dir().unwrap() { + let entry = entry.unwrap(); + let path = entry.path(); + if entry.file_type().unwrap().is_dir() { + find_tests(tests, &path); + continue + } + + if path.extension().and_then(|s| s.to_str()) == Some("wat") { + tests.push(Test { + input: path, + }); + } + } +} + +fn run_tests(tests: &[Test]) { + println!(""); + + let results = tests.par_iter() + .map(|test| { + run_test(test).map_err(|e| (test, e.to_string())) + }) + .collect::>(); + + let mut bad = false; + for result in results { + let (test, err) = match result { + Ok(()) => continue, + Err(p) => p, + }; + println!("fail: {} - {}", test.input.display(), err); + bad = true; + } + if bad { + std::process::exit(2); + } + + println!("\nall good!"); +} + +fn run_test(test: &Test) -> Result<(), Box> { + println!("test {}", test.input.display()); + + let f = NamedTempFile::new()?; + let input = fs::read_to_string(&test.input)?; + let expected = extract_expected(&input); + let status = Command::new("wat2wasm") + .arg("--debug-names") + .arg(&test.input) + .arg("-o") + .arg(f.path()) + .status()?; + if !status.success() { + return Err(io::Error::new(io::ErrorKind::Other, "failed to run wat2wasm").into()) + } + + let wasm = fs::read(f.path())?; + let mut module: Module = parity_wasm::deserialize_buffer(&wasm)?; + module = match module.parse_names() { + Ok(m) => m, + Err((_, m)) => m, + }; + wasm_bindgen_gc::Config::new().run(&mut module); + let wasm = parity_wasm::serialize(module)?; + fs::write(f.path(), wasm)?; + + let status = Command::new("wasm2wat") + .arg(&f.path()) + .stderr(Stdio::inherit()) + .output()?; + if !status.status.success() { + return Err(io::Error::new(io::ErrorKind::Other, "failed to run wasm2wat").into()) + } + let actual = String::from_utf8(status.stdout)?; + let actual = actual.trim(); + + if env::var("BLESS_TESTS").is_ok() { + fs::write(&test.input, generate_blesssed(&input, &actual))?; + } else { + if actual != expected { + println!("{:?} {:?}", actual, expected); + return Err(io::Error::new(io::ErrorKind::Other, + "test failed").into()) + } + } + + Ok(()) +} + +fn extract_expected(input: &str) -> String { + input.lines() + .filter(|l| l.starts_with(";; ")) + .skip_while(|l| !l.contains("STDOUT")) + .skip(1) + .take_while(|l| !l.contains("STDOUT")) + .map(|l| &l[3..]) + .collect::>() + .join("\n") +} + +fn generate_blesssed(input: &str, actual: &str) -> String { + let mut input = input.lines() + .filter(|l| !l.starts_with(";;")) + .collect::>() + .join("\n") + .trim() + .to_string(); + input.push_str("\n\n"); + input.push_str(";; STDOUT (update this section with `BLESS_TESTS=1` while running tests)\n"); + for line in actual.lines() { + input.push_str(";; "); + input.push_str(line); + input.push_str("\n"); + } + input.push_str(";; STDOUT\n"); + return input +} diff --git a/crates/gc/tests/wat/dont-remove-func1.wat b/crates/gc/tests/wat/dont-remove-func1.wat new file mode 100644 index 000000000..eb3bdf89d --- /dev/null +++ b/crates/gc/tests/wat/dont-remove-func1.wat @@ -0,0 +1,11 @@ +(module + (func $foo) + (export "foo" (func $foo)) + ) + +;; STDOUT (update this section with `BLESS_TESTS=1` while running tests) +;; (module +;; (type (;0;) (func)) +;; (func $foo (type 0)) +;; (export "foo" (func $foo))) +;; STDOUT diff --git a/crates/gc/tests/wat/dont-remove-func2.wat b/crates/gc/tests/wat/dont-remove-func2.wat new file mode 100644 index 000000000..b97067e2e --- /dev/null +++ b/crates/gc/tests/wat/dont-remove-func2.wat @@ -0,0 +1,16 @@ +(module + (func $foo + call $bar + ) + (func $bar) + (export "foo" (func $foo)) + ) + +;; STDOUT (update this section with `BLESS_TESTS=1` while running tests) +;; (module +;; (type (;0;) (func)) +;; (func $foo (type 0) +;; call $bar) +;; (func $bar (type 0)) +;; (export "foo" (func $foo))) +;; STDOUT diff --git a/crates/gc/tests/wat/import-and-type.wat b/crates/gc/tests/wat/import-and-type.wat new file mode 100644 index 000000000..313671a3a --- /dev/null +++ b/crates/gc/tests/wat/import-and-type.wat @@ -0,0 +1,21 @@ +(module + (import "" "" (func (param i32))) + + (func $foo + i32.const 0 + call 0 + ) + + (start $foo) + ) + +;; STDOUT (update this section with `BLESS_TESTS=1` while running tests) +;; (module +;; (type (;0;) (func (param i32))) +;; (type (;1;) (func)) +;; (import "" "" (func (;0;) (type 0))) +;; (func $foo (type 1) +;; i32.const 0 +;; call 0) +;; (start 1)) +;; STDOUT diff --git a/crates/gc/tests/wat/import-memory.wat b/crates/gc/tests/wat/import-memory.wat new file mode 100644 index 000000000..16e1bf077 --- /dev/null +++ b/crates/gc/tests/wat/import-memory.wat @@ -0,0 +1,8 @@ +(module + (import "" "a" (memory 0)) + ) + +;; STDOUT (update this section with `BLESS_TESTS=1` while running tests) +;; (module +;; (import "" "a" (memory (;0;) 0))) +;; STDOUT diff --git a/crates/gc/tests/wat/keep-data.wat b/crates/gc/tests/wat/keep-data.wat new file mode 100644 index 000000000..0310d9dd0 --- /dev/null +++ b/crates/gc/tests/wat/keep-data.wat @@ -0,0 +1,10 @@ +(module + (memory 0 1) + (data (i32.const 0) "foo") + ) + +;; STDOUT (update this section with `BLESS_TESTS=1` while running tests) +;; (module +;; (memory (;0;) 0 1) +;; (data (;0;) (i32.const 0) "foo")) +;; STDOUT diff --git a/crates/gc/tests/wat/keep-global.wat b/crates/gc/tests/wat/keep-global.wat new file mode 100644 index 000000000..7aa668676 --- /dev/null +++ b/crates/gc/tests/wat/keep-global.wat @@ -0,0 +1,11 @@ +(module + (global i32 (i32.const 0)) + + (export "foo" (global 0)) + ) + +;; STDOUT (update this section with `BLESS_TESTS=1` while running tests) +;; (module +;; (global (;0;) i32 (i32.const 0)) +;; (export "foo" (global 0))) +;; STDOUT diff --git a/crates/gc/tests/wat/keep-memory.wat b/crates/gc/tests/wat/keep-memory.wat new file mode 100644 index 000000000..0df7e52a7 --- /dev/null +++ b/crates/gc/tests/wat/keep-memory.wat @@ -0,0 +1,8 @@ +(module + (memory 0 17) + ) + +;; STDOUT (update this section with `BLESS_TESTS=1` while running tests) +;; (module +;; (memory (;0;) 0 17)) +;; STDOUT diff --git a/crates/gc/tests/wat/keep-set-global.wat b/crates/gc/tests/wat/keep-set-global.wat new file mode 100644 index 000000000..315c6d7f2 --- /dev/null +++ b/crates/gc/tests/wat/keep-set-global.wat @@ -0,0 +1,23 @@ +(module + + (global (mut i32) (i32.const 0)) + + (start $foo) + + (func $bar) + (func $foo + i32.const 1 + set_global 0 + ) + (func $baz) + ) + +;; STDOUT (update this section with `BLESS_TESTS=1` while running tests) +;; (module +;; (type (;0;) (func)) +;; (func $foo (type 0) +;; i32.const 1 +;; set_global 0) +;; (global (;0;) (mut i32) (i32.const 0)) +;; (start 0)) +;; STDOUT diff --git a/crates/gc/tests/wat/keep-start.wat b/crates/gc/tests/wat/keep-start.wat new file mode 100644 index 000000000..60795979e --- /dev/null +++ b/crates/gc/tests/wat/keep-start.wat @@ -0,0 +1,14 @@ +(module + (start $foo) + + (func $bar) + (func $foo) + (func $baz) + ) + +;; STDOUT (update this section with `BLESS_TESTS=1` while running tests) +;; (module +;; (type (;0;) (func)) +;; (func $foo (type 0)) +;; (start 0)) +;; STDOUT diff --git a/crates/gc/tests/wat/keep-table.wat b/crates/gc/tests/wat/keep-table.wat new file mode 100644 index 000000000..592bef768 --- /dev/null +++ b/crates/gc/tests/wat/keep-table.wat @@ -0,0 +1,10 @@ +(module + (table 0 17 anyfunc) + (export "foo" (table 0)) + ) + +;; STDOUT (update this section with `BLESS_TESTS=1` while running tests) +;; (module +;; (table (;0;) 0 17 anyfunc) +;; (export "foo" (table 0))) +;; STDOUT diff --git a/crates/gc/tests/wat/keep-table2.wat b/crates/gc/tests/wat/keep-table2.wat new file mode 100644 index 000000000..e264d9139 --- /dev/null +++ b/crates/gc/tests/wat/keep-table2.wat @@ -0,0 +1,17 @@ +(module + (table 0 17 anyfunc) + (func $foo + i32.const 0 + call_indirect) + (export "foo" (func $foo)) + ) + +;; STDOUT (update this section with `BLESS_TESTS=1` while running tests) +;; (module +;; (type (;0;) (func)) +;; (func $foo (type 0) +;; i32.const 0 +;; call_indirect (type 0)) +;; (table (;0;) 0 17 anyfunc) +;; (export "foo" (func $foo))) +;; STDOUT diff --git a/crates/gc/tests/wat/locals-compressed.wat b/crates/gc/tests/wat/locals-compressed.wat new file mode 100644 index 000000000..18fcf9243 --- /dev/null +++ b/crates/gc/tests/wat/locals-compressed.wat @@ -0,0 +1,39 @@ +(module + (func $foo + (local i32 f32 i32 f64 i64 i32 f32 i64 i32 f32 f64) + + get_local 0 + get_local 1 + get_local 2 + get_local 3 + get_local 4 + get_local 5 + get_local 6 + get_local 7 + get_local 8 + get_local 9 + get_local 10 + unreachable + ) + (export "foo" (func $foo)) + ) + +;; STDOUT (update this section with `BLESS_TESTS=1` while running tests) +;; (module +;; (type (;0;) (func)) +;; (func $foo (type 0) +;; (local i32 i32 i32 i32 f32 f32 f32 f64 f64 i64 i64) +;; get_local 0 +;; get_local 4 +;; get_local 1 +;; get_local 7 +;; get_local 9 +;; get_local 2 +;; get_local 5 +;; get_local 10 +;; get_local 3 +;; get_local 6 +;; get_local 8 +;; unreachable) +;; (export "foo" (func $foo))) +;; STDOUT diff --git a/crates/gc/tests/wat/preserve-local1.wat b/crates/gc/tests/wat/preserve-local1.wat new file mode 100644 index 000000000..782707d14 --- /dev/null +++ b/crates/gc/tests/wat/preserve-local1.wat @@ -0,0 +1,16 @@ +(module + (func $foo (result i32) + (local i32) + get_local 0 + ) + (export "foo" (func $foo)) + ) + +;; STDOUT (update this section with `BLESS_TESTS=1` while running tests) +;; (module +;; (type (;0;) (func (result i32))) +;; (func $foo (type 0) (result i32) +;; (local i32) +;; get_local 0) +;; (export "foo" (func $foo))) +;; STDOUT diff --git a/crates/gc/tests/wat/preserve-local2.wat b/crates/gc/tests/wat/preserve-local2.wat new file mode 100644 index 000000000..2373ad201 --- /dev/null +++ b/crates/gc/tests/wat/preserve-local2.wat @@ -0,0 +1,18 @@ +(module + (func $foo (param i32) + (local i32) + get_local 0 + set_local 1 + ) + (export "foo" (func $foo)) + ) + +;; STDOUT (update this section with `BLESS_TESTS=1` while running tests) +;; (module +;; (type (;0;) (func (param i32))) +;; (func $foo (type 0) (param i32) +;; (local i32) +;; get_local 0 +;; set_local 1) +;; (export "foo" (func $foo))) +;; STDOUT diff --git a/crates/gc/tests/wat/preserve-local3.wat b/crates/gc/tests/wat/preserve-local3.wat new file mode 100644 index 000000000..c46c19823 --- /dev/null +++ b/crates/gc/tests/wat/preserve-local3.wat @@ -0,0 +1,18 @@ +(module + (func $foo (param i32) (result i32) + (local i32) + get_local 0 + tee_local 1 + ) + (export "foo" (func $foo)) + ) + +;; STDOUT (update this section with `BLESS_TESTS=1` while running tests) +;; (module +;; (type (;0;) (func (param i32) (result i32))) +;; (func $foo (type 0) (param i32) (result i32) +;; (local i32) +;; get_local 0 +;; tee_local 1) +;; (export "foo" (func $foo))) +;; STDOUT diff --git a/crates/gc/tests/wat/remove-func.wat b/crates/gc/tests/wat/remove-func.wat new file mode 100644 index 000000000..e37c50177 --- /dev/null +++ b/crates/gc/tests/wat/remove-func.wat @@ -0,0 +1,7 @@ +(module + (func $foo) + ) + +;; STDOUT (update this section with `BLESS_TESTS=1` while running tests) +;; (module) +;; STDOUT diff --git a/crates/gc/tests/wat/remove-heap-base.wat b/crates/gc/tests/wat/remove-heap-base.wat new file mode 100644 index 000000000..406c85230 --- /dev/null +++ b/crates/gc/tests/wat/remove-heap-base.wat @@ -0,0 +1,9 @@ +(module + (global i32 (i32.const 0)) + + (export "__heap_base" (global 0)) + ) + +;; STDOUT (update this section with `BLESS_TESTS=1` while running tests) +;; (module) +;; STDOUT diff --git a/crates/gc/tests/wat/remove-import-function.wat b/crates/gc/tests/wat/remove-import-function.wat new file mode 100644 index 000000000..b0592b221 --- /dev/null +++ b/crates/gc/tests/wat/remove-import-function.wat @@ -0,0 +1,26 @@ +(module + (import "" "a" (func $i1)) + (import "" "b" (func $i2)) + (import "" "c" (func $i3)) + + (func $bar) + + (func $foo + call $i1 + call $i3) + + (func $baz) + + (export "foo" (func $foo)) + ) + +;; STDOUT (update this section with `BLESS_TESTS=1` while running tests) +;; (module +;; (type (;0;) (func)) +;; (import "" "a" (func $i1 (type 0))) +;; (import "" "c" (func $i3 (type 0))) +;; (func $foo (type 0) +;; call $i1 +;; call $i3) +;; (export "foo" (func $foo))) +;; STDOUT diff --git a/crates/gc/tests/wat/remove-import-global.wat b/crates/gc/tests/wat/remove-import-global.wat new file mode 100644 index 000000000..e0ccc861e --- /dev/null +++ b/crates/gc/tests/wat/remove-import-global.wat @@ -0,0 +1,7 @@ +(module + (import "" "" (global i32)) + ) + +;; STDOUT (update this section with `BLESS_TESTS=1` while running tests) +;; (module) +;; STDOUT diff --git a/crates/gc/tests/wat/remove-import-globals.wat b/crates/gc/tests/wat/remove-import-globals.wat new file mode 100644 index 000000000..b34d533b2 --- /dev/null +++ b/crates/gc/tests/wat/remove-import-globals.wat @@ -0,0 +1,35 @@ +(module + (import "" "a" (global i32)) + (import "" "b" (global i32)) + (import "" "c" (global i32)) + + (global i32 (i32.const 1)) + (global i32 (i32.const 2)) + + (func $foo + get_global 0 + drop + get_global 2 + drop + get_global 4 + drop + ) + + (export "foo" (func $foo)) + ) + +;; STDOUT (update this section with `BLESS_TESTS=1` while running tests) +;; (module +;; (type (;0;) (func)) +;; (import "" "a" (global (;0;) i32)) +;; (import "" "c" (global (;1;) i32)) +;; (func $foo (type 0) +;; get_global 0 +;; drop +;; get_global 1 +;; drop +;; get_global 2 +;; drop) +;; (global (;2;) i32 (i32.const 2)) +;; (export "foo" (func $foo))) +;; STDOUT diff --git a/crates/gc/tests/wat/remove-import-table.wat b/crates/gc/tests/wat/remove-import-table.wat new file mode 100644 index 000000000..561ac9ea0 --- /dev/null +++ b/crates/gc/tests/wat/remove-import-table.wat @@ -0,0 +1,11 @@ +(module + (import "" "" (table 0 1 anyfunc)) + + (func $foo) + + (elem (i32.const 1) $foo) + ) + +;; STDOUT (update this section with `BLESS_TESTS=1` while running tests) +;; (module) +;; STDOUT diff --git a/crates/gc/tests/wat/remove-local.wat b/crates/gc/tests/wat/remove-local.wat new file mode 100644 index 000000000..759b27824 --- /dev/null +++ b/crates/gc/tests/wat/remove-local.wat @@ -0,0 +1,13 @@ +(module + (func $foo + (local i32) + ) + (export "foo" (func $foo)) + ) + +;; STDOUT (update this section with `BLESS_TESTS=1` while running tests) +;; (module +;; (type (;0;) (func)) +;; (func $foo (type 0)) +;; (export "foo" (func $foo))) +;; STDOUT diff --git a/crates/gc/tests/wat/remove-table.wat b/crates/gc/tests/wat/remove-table.wat new file mode 100644 index 000000000..ca3d303dc --- /dev/null +++ b/crates/gc/tests/wat/remove-table.wat @@ -0,0 +1,7 @@ +(module + (table 0 17 anyfunc) + ) + +;; STDOUT (update this section with `BLESS_TESTS=1` while running tests) +;; (module) +;; STDOUT diff --git a/crates/gc/tests/wat/renumber-functions.wat b/crates/gc/tests/wat/renumber-functions.wat new file mode 100644 index 000000000..1d19197ab --- /dev/null +++ b/crates/gc/tests/wat/renumber-functions.wat @@ -0,0 +1,16 @@ +(module + (func + call 2) + (func) + (func) + (export "foo" (func 0)) + ) + +;; STDOUT (update this section with `BLESS_TESTS=1` while running tests) +;; (module +;; (type (;0;) (func)) +;; (func (;0;) (type 0) +;; call 1) +;; (func (;1;) (type 0)) +;; (export "foo" (func 0))) +;; STDOUT diff --git a/crates/gc/tests/wat/renumber-globals.wat b/crates/gc/tests/wat/renumber-globals.wat new file mode 100644 index 000000000..f34b08327 --- /dev/null +++ b/crates/gc/tests/wat/renumber-globals.wat @@ -0,0 +1,16 @@ +(module + (global i32 (i32.const 0)) + (global i32 (i32.const 0)) + (func (result i32) + get_global 1) + (export "foo" (func 0)) + ) + +;; STDOUT (update this section with `BLESS_TESTS=1` while running tests) +;; (module +;; (type (;0;) (func (result i32))) +;; (func (;0;) (type 0) (result i32) +;; get_global 0) +;; (global (;0;) i32 (i32.const 0)) +;; (export "foo" (func 0))) +;; STDOUT diff --git a/crates/gc/tests/wat/renumber-locals.wat b/crates/gc/tests/wat/renumber-locals.wat new file mode 100644 index 000000000..09f425e30 --- /dev/null +++ b/crates/gc/tests/wat/renumber-locals.wat @@ -0,0 +1,16 @@ +(module + (func $foo (result i32) + (local i32 i32) + get_local 1 + ) + (export "foo" (func $foo)) + ) + +;; STDOUT (update this section with `BLESS_TESTS=1` while running tests) +;; (module +;; (type (;0;) (func (result i32))) +;; (func $foo (type 0) (result i32) +;; (local i32) +;; get_local 0) +;; (export "foo" (func $foo))) +;; STDOUT diff --git a/crates/gc/tests/wat/renumber-types.wat b/crates/gc/tests/wat/renumber-types.wat new file mode 100644 index 000000000..001847db8 --- /dev/null +++ b/crates/gc/tests/wat/renumber-types.wat @@ -0,0 +1,13 @@ +(module + (type (func (result i32))) + (type (func)) + (func (type 1)) + (export "foo" (func 0)) + ) + +;; STDOUT (update this section with `BLESS_TESTS=1` while running tests) +;; (module +;; (type (;0;) (func)) +;; (func (;0;) (type 0)) +;; (export "foo" (func 0))) +;; STDOUT diff --git a/crates/gc/tests/wat/smoke.wat b/crates/gc/tests/wat/smoke.wat new file mode 100644 index 000000000..84e867624 --- /dev/null +++ b/crates/gc/tests/wat/smoke.wat @@ -0,0 +1,5 @@ +(module) + +;; STDOUT (update this section with `BLESS_TESTS=1` while running tests) +;; (module) +;; STDOUT diff --git a/crates/gc/tests/wat/table-elem.wat b/crates/gc/tests/wat/table-elem.wat new file mode 100644 index 000000000..6dd88a8ab --- /dev/null +++ b/crates/gc/tests/wat/table-elem.wat @@ -0,0 +1,15 @@ +(module + (func $foo + i32.const 0 + call_indirect + ) + + (func $bar) + + (table 0 10 anyfunc) + (elem (i32.const 0) $bar) + ) + +;; STDOUT (update this section with `BLESS_TESTS=1` while running tests) +;; (module) +;; STDOUT diff --git a/crates/gc/tests/wat/table-elem2.wat b/crates/gc/tests/wat/table-elem2.wat new file mode 100644 index 000000000..1ad663d78 --- /dev/null +++ b/crates/gc/tests/wat/table-elem2.wat @@ -0,0 +1,25 @@ +(module + (func $foo + i32.const 0 + call_indirect + ) + + (func $bar) + + (table 0 10 anyfunc) + (elem (i32.const 0) $bar) + + (export "foo" (func $foo)) + ) + +;; STDOUT (update this section with `BLESS_TESTS=1` while running tests) +;; (module +;; (type (;0;) (func)) +;; (func $foo (type 0) +;; i32.const 0 +;; call_indirect (type 0)) +;; (func $bar (type 0)) +;; (table (;0;) 0 10 anyfunc) +;; (export "foo" (func $foo)) +;; (elem (;0;) (i32.const 0) $bar)) +;; STDOUT From 31c11f078158c1b7cc9b9487fdb8068de04609ef Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 29 Oct 2018 15:54:32 -0700 Subject: [PATCH 2/3] GC passive segments We statically know which passive segments are actually used, so let's be sure to gc them! --- crates/gc/src/lib.rs | 133 ++++++++++++------ crates/gc/tests/all.rs | 2 + .../tests/wat/keep-passive-memory-segment.wat | 27 ++++ crates/gc/tests/wat/keep-passive-segment.wat | 30 ++++ .../wat/remove-passive-memory-segment.wat | 18 +++ .../wat/remove-unused-passive-segment.wat | 11 ++ crates/gc/tests/wat/renumber-data-segment.wat | 29 ++++ .../gc/tests/wat/renumber-passive-segment.wat | 32 +++++ 8 files changed, 242 insertions(+), 40 deletions(-) create mode 100644 crates/gc/tests/wat/keep-passive-memory-segment.wat create mode 100644 crates/gc/tests/wat/keep-passive-segment.wat create mode 100644 crates/gc/tests/wat/remove-passive-memory-segment.wat create mode 100644 crates/gc/tests/wat/remove-unused-passive-segment.wat create mode 100644 crates/gc/tests/wat/renumber-data-segment.wat create mode 100644 crates/gc/tests/wat/renumber-passive-segment.wat diff --git a/crates/gc/src/lib.rs b/crates/gc/src/lib.rs index f2ee52323..7f200906b 100644 --- a/crates/gc/src/lib.rs +++ b/crates/gc/src/lib.rs @@ -58,10 +58,11 @@ fn run(config: &mut Config, module: &mut Module) { cx.blacklist.insert("__heap_base"); cx.blacklist.insert("__data_end"); - // always treat memory as a root + // Always treat memory as a root. In theory we could actually gc this + // away in some circumstances, but it's probably not worth the effort. cx.add_memory(0); - // All exports are a root + // All non-blacklisted exports are roots if let Some(section) = module.export_section() { for (i, entry) in section.entries().iter().enumerate() { if cx.blacklist.contains(entry.field()) { @@ -71,24 +72,7 @@ fn run(config: &mut Config, module: &mut Module) { } } - // Pessimistically assume all passive data and table segments are - // required - if let Some(section) = module.data_section() { - for entry in section.entries() { - if entry.passive() { - cx.add_data_segment(entry); - } - } - } - if let Some(elements) = module.elements_section() { - for (i, seg) in elements.entries().iter().enumerate() { - if seg.passive() { - cx.add_element_segment(i as u32, seg); - } - } - } - - // The start function is also a root + // ... and finally, the start function if let Some(i) = module.start_section() { cx.add_function(i); } @@ -147,6 +131,7 @@ struct Analysis { exports: BitSet, functions: BitSet, elements: BitSet, + data_segments: BitSet, imported_functions: u32, imported_globals: u32, imported_memories: u32, @@ -241,11 +226,9 @@ impl<'a> LiveContext<'a> { let iter = elements.entries() .iter() .enumerate() - .filter(|(_, d)| !d.passive()); - for (i, entry) in iter { - if entry.index() == idx { - self.add_element_segment(i as u32, entry); - } + .filter(|(_, d)| !d.passive() && d.index() == idx); + for (i, _) in iter { + self.add_element_segment(i as u32); } } @@ -280,10 +263,12 @@ impl<'a> LiveContext<'a> { // Add all data segments that initialize this memory if let Some(data) = self.data_section { - for entry in data.entries().iter().filter(|d| !d.passive()) { - if entry.index() == idx { - self.add_data_segment(entry); - } + let iter = data.entries() + .iter() + .enumerate() + .filter(|(_, d)| !d.passive() && d.index() == idx); + for (i, _) in iter { + self.add_data_segment(i as u32); } } @@ -402,6 +387,16 @@ impl<'a> LiveContext<'a> { } Instruction::GetGlobal(i) | Instruction::SetGlobal(i) => self.add_global(i), + Instruction::MemoryInit(i) | + Instruction::MemoryDrop(i) => { + self.add_memory(0); + self.add_data_segment(i); + } + Instruction::TableInit(i) | + Instruction::TableDrop(i) => { + self.add_table(0); + self.add_element_segment(i); + } _ => {} } } @@ -438,23 +433,32 @@ impl<'a> LiveContext<'a> { } } - fn add_data_segment(&mut self, data: &DataSegment) { - if let Some(offset) = data.offset() { - self.add_memory(data.index()); - self.add_init_expr(offset); + fn add_data_segment(&mut self, idx: u32) { + if !self.analysis.data_segments.insert(idx) { + return + } + let data = &self.data_section.unwrap().entries()[idx as usize]; + if !data.passive() { + if let Some(offset) = data.offset() { + self.add_memory(data.index()); + self.add_init_expr(offset); + } } } - fn add_element_segment(&mut self, idx: u32, seg: &ElementSegment) { + fn add_element_segment(&mut self, idx: u32) { if !self.analysis.elements.insert(idx) { return } + let seg = &self.element_section.unwrap().entries()[idx as usize]; for member in seg.members() { self.add_function(*member); } - if let Some(offset) = seg.offset() { - self.add_table(seg.index()); - self.add_init_expr(offset); + if !seg.passive() { + if let Some(offset) = seg.offset() { + self.add_table(seg.index()); + self.add_init_expr(offset); + } } } } @@ -467,6 +471,8 @@ struct RemapContext<'a> { types: Vec, tables: Vec, memories: Vec, + elements: Vec, + data_segments: Vec, } impl<'a> RemapContext<'a> { @@ -561,6 +567,34 @@ impl<'a> RemapContext<'a> { } } + let mut elements = Vec::new(); + if let Some(s) = m.elements_section() { + let mut nelements = 0; + for i in 0..(s.entries().len() as u32) { + if analysis.elements.contains(&i) { + elements.push(nelements); + nelements += 1; + } else { + debug!("gc element segment {}", i); + elements.push(u32::max_value()); + } + } + } + + let mut data_segments = Vec::new(); + if let Some(s) = m.data_section() { + let mut ndata_segments = 0; + for i in 0..(s.entries().len() as u32) { + if analysis.data_segments.contains(&i) { + data_segments.push(ndata_segments); + ndata_segments += 1; + } else { + debug!("gc data segment {}", i); + data_segments.push(u32::max_value()); + } + } + } + RemapContext { analysis, functions, @@ -569,6 +603,8 @@ impl<'a> RemapContext<'a> { tables, types, config, + elements, + data_segments, } } @@ -729,9 +765,11 @@ impl<'a> RemapContext<'a> { } fn remap_element_segment(&self, s: &mut ElementSegment) { - let mut i = s.index(); - self.remap_table_idx(&mut i); - assert_eq!(s.index(), i); + if !s.passive() { + let mut i = s.index(); + self.remap_table_idx(&mut i); + assert_eq!(s.index(), i); + } for m in s.members_mut() { self.remap_function_idx(m); } @@ -772,6 +810,10 @@ impl<'a> RemapContext<'a> { Instruction::CallIndirect(ref mut t, _) => self.remap_type_idx(t), Instruction::GetGlobal(ref mut i) | Instruction::SetGlobal(ref mut i) => self.remap_global_idx(i), + Instruction::TableInit(ref mut i) | + Instruction::TableDrop(ref mut i) => self.remap_element_idx(i), + Instruction::MemoryInit(ref mut i) | + Instruction::MemoryDrop(ref mut i) => self.remap_data_idx(i), _ => {} } } @@ -784,6 +826,7 @@ impl<'a> RemapContext<'a> { } fn remap_data_section(&self, s: &mut DataSection) -> bool { + self.retain(&self.analysis.data_segments, s.entries_mut(), "data", 0); for data in s.entries_mut() { self.remap_data_segment(data); } @@ -825,6 +868,16 @@ impl<'a> RemapContext<'a> { assert!(*i != u32::max_value()); } + fn remap_element_idx(&self, i: &mut u32) { + *i = self.elements[*i as usize]; + assert!(*i != u32::max_value()); + } + + fn remap_data_idx(&self, i: &mut u32) { + *i = self.data_segments[*i as usize]; + assert!(*i != u32::max_value()); + } + fn remap_name_section(&self, s: &mut NameSection) { match *s { NameSection::Module(_) => {} diff --git a/crates/gc/tests/all.rs b/crates/gc/tests/all.rs index f05dec1d2..b7ed038b0 100644 --- a/crates/gc/tests/all.rs +++ b/crates/gc/tests/all.rs @@ -75,6 +75,7 @@ fn run_test(test: &Test) -> Result<(), Box> { let expected = extract_expected(&input); let status = Command::new("wat2wasm") .arg("--debug-names") + .arg("--enable-bulk-memory") .arg(&test.input) .arg("-o") .arg(f.path()) @@ -94,6 +95,7 @@ fn run_test(test: &Test) -> Result<(), Box> { fs::write(f.path(), wasm)?; let status = Command::new("wasm2wat") + .arg("--enable-bulk-memory") .arg(&f.path()) .stderr(Stdio::inherit()) .output()?; diff --git a/crates/gc/tests/wat/keep-passive-memory-segment.wat b/crates/gc/tests/wat/keep-passive-memory-segment.wat new file mode 100644 index 000000000..d3ae85f3a --- /dev/null +++ b/crates/gc/tests/wat/keep-passive-memory-segment.wat @@ -0,0 +1,27 @@ +(module + (memory 0 10) + + (func $foo + i32.const 0 + i32.const 0 + i32.const 0 + memory.init 0 + ) + + (data passive "wut") + + (start $foo) + ) + +;; STDOUT (update this section with `BLESS_TESTS=1` while running tests) +;; (module +;; (type (;0;) (func)) +;; (func $foo (type 0) +;; i32.const 0 +;; i32.const 0 +;; i32.const 0 +;; memory.init 0) +;; (memory (;0;) 0 10) +;; (start 0) +;; (data (;0;) passive "wut")) +;; STDOUT diff --git a/crates/gc/tests/wat/keep-passive-segment.wat b/crates/gc/tests/wat/keep-passive-segment.wat new file mode 100644 index 000000000..b37d5d05c --- /dev/null +++ b/crates/gc/tests/wat/keep-passive-segment.wat @@ -0,0 +1,30 @@ +(module + (import "" "" (table 0 1 anyfunc)) + + (func $foo + i32.const 0 + i32.const 0 + i32.const 0 + table.init 0 + ) + + (func $bar) + + (elem passive $bar) + + (start $foo) + ) + +;; STDOUT (update this section with `BLESS_TESTS=1` while running tests) +;; (module +;; (type (;0;) (func)) +;; (import "" "" (table (;0;) 0 1 anyfunc)) +;; (func $foo (type 0) +;; i32.const 0 +;; i32.const 0 +;; i32.const 0 +;; table.init 0) +;; (func $bar (type 0)) +;; (start 0) +;; (elem (;0;) passive $bar)) +;; STDOUT diff --git a/crates/gc/tests/wat/remove-passive-memory-segment.wat b/crates/gc/tests/wat/remove-passive-memory-segment.wat new file mode 100644 index 000000000..5c8d92d5e --- /dev/null +++ b/crates/gc/tests/wat/remove-passive-memory-segment.wat @@ -0,0 +1,18 @@ +(module + (memory 0 10) + + (func $foo + i32.const 0 + i32.const 0 + i32.const 0 + memory.init 0 + ) + + (data passive "wut") + + ) + +;; STDOUT (update this section with `BLESS_TESTS=1` while running tests) +;; (module +;; (memory (;0;) 0 10)) +;; STDOUT diff --git a/crates/gc/tests/wat/remove-unused-passive-segment.wat b/crates/gc/tests/wat/remove-unused-passive-segment.wat new file mode 100644 index 000000000..782adb3c9 --- /dev/null +++ b/crates/gc/tests/wat/remove-unused-passive-segment.wat @@ -0,0 +1,11 @@ +(module + (import "" "" (table 0 1 anyfunc)) + + (func $foo) + + (elem passive $foo) + ) + +;; STDOUT (update this section with `BLESS_TESTS=1` while running tests) +;; (module) +;; STDOUT diff --git a/crates/gc/tests/wat/renumber-data-segment.wat b/crates/gc/tests/wat/renumber-data-segment.wat new file mode 100644 index 000000000..199086eab --- /dev/null +++ b/crates/gc/tests/wat/renumber-data-segment.wat @@ -0,0 +1,29 @@ +(module + (memory 0 10) + + (func $foo + i32.const 0 + i32.const 0 + i32.const 0 + memory.init 1 + ) + + (data passive "wut") + (data passive "wut2") + (data passive "wut3") + + (start $foo) + ) + +;; STDOUT (update this section with `BLESS_TESTS=1` while running tests) +;; (module +;; (type (;0;) (func)) +;; (func $foo (type 0) +;; i32.const 0 +;; i32.const 0 +;; i32.const 0 +;; memory.init 0) +;; (memory (;0;) 0 10) +;; (start 0) +;; (data (;0;) passive "wut2")) +;; STDOUT diff --git a/crates/gc/tests/wat/renumber-passive-segment.wat b/crates/gc/tests/wat/renumber-passive-segment.wat new file mode 100644 index 000000000..f459ae39c --- /dev/null +++ b/crates/gc/tests/wat/renumber-passive-segment.wat @@ -0,0 +1,32 @@ +(module + (import "" "" (table 0 1 anyfunc)) + + (func $foo + i32.const 0 + i32.const 0 + i32.const 0 + table.init 1 + ) + + (func $bar) + (func $bar2) + + (elem passive $bar) + (elem passive $bar2) + + (start $foo) + ) + +;; STDOUT (update this section with `BLESS_TESTS=1` while running tests) +;; (module +;; (type (;0;) (func)) +;; (import "" "" (table (;0;) 0 1 anyfunc)) +;; (func $foo (type 0) +;; i32.const 0 +;; i32.const 0 +;; i32.const 0 +;; table.init 0) +;; (func $bar2 (type 0)) +;; (start 0) +;; (elem (;0;) passive $bar2)) +;; STDOUT From 40b68c66d9af15663bffc161cc6912b7a1920b05 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 29 Oct 2018 17:20:38 -0700 Subject: [PATCH 3/3] Fix a bug in coalescing types with GC When a duplicate type is found is should no longer be considered used! --- .travis.yml | 2 +- crates/gc/src/bitvec.rs | 9 ++++++ crates/gc/src/lib.rs | 7 +++-- crates/gc/tests/wat/remove-unused-type.wat | 34 ++++++++++++++++++++++ 4 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 crates/gc/tests/wat/remove-unused-type.wat diff --git a/.travis.yml b/.travis.yml index a6a8b655a..de257e3fe 100644 --- a/.travis.yml +++ b/.travis.yml @@ -172,7 +172,7 @@ matrix: install: - git clone https://github.com/WebAssembly/wabt - mkdir -p wabt/build - - (cd wabt/wabt && cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=sccache -DCMAKE_CXX_COMPILER_ARG1=c++ && cmake --build . -- -j4) + - (cd wabt/build && cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=sccache -DCMAKE_CXX_COMPILER_ARG1=c++ -DBUILD_TESTS=OFF && cmake --build . -- -j4) - export PATH=$PATH:`pwd`/wabt/build script: cargo test -p wasm-bindgen-gc if: branch = master diff --git a/crates/gc/src/bitvec.rs b/crates/gc/src/bitvec.rs index faa8b65b3..98cdee833 100644 --- a/crates/gc/src/bitvec.rs +++ b/crates/gc/src/bitvec.rs @@ -29,6 +29,15 @@ impl BitSet { } } + pub fn remove(&mut self, i: &u32) { + let i = *i as usize; + let idx = i / BITS; + let bit = 1 << (i % BITS); + if let Some(slot) = self.bits.get_mut(idx) { + *slot &= !bit; + } + } + pub fn contains(&self, i: &u32) -> bool { let i = *i as usize; let idx = i / BITS; diff --git a/crates/gc/src/lib.rs b/crates/gc/src/lib.rs index 7f200906b..4ce1fb7f5 100644 --- a/crates/gc/src/lib.rs +++ b/crates/gc/src/lib.rs @@ -51,7 +51,7 @@ impl Config { } fn run(config: &mut Config, module: &mut Module) { - let analysis = { + let mut analysis = { let mut cx = LiveContext::new(&module); cx.blacklist.insert("rust_eh_personality"); cx.blacklist.insert("__indirect_function_table"); @@ -80,7 +80,7 @@ fn run(config: &mut Config, module: &mut Module) { cx.analysis }; - let cx = RemapContext::new(&module, &analysis, config); + let cx = RemapContext::new(&module, &mut analysis, config); for i in (0..module.sections().len()).rev() { let retain = match module.sections_mut()[i] { Section::Unparsed { .. } => { @@ -476,7 +476,7 @@ struct RemapContext<'a> { } impl<'a> RemapContext<'a> { - fn new(m: &Module, analysis: &'a Analysis, config: &'a Config) -> RemapContext<'a> { + fn new(m: &Module, analysis: &'a mut Analysis, config: &'a Config) -> RemapContext<'a> { let mut nfunctions = 0; let mut functions = Vec::new(); let mut nglobals = 0; @@ -494,6 +494,7 @@ impl<'a> RemapContext<'a> { if analysis.types.contains(&(i as u32)) { if let Some(prev) = map.get(&ty) { types.push(*prev); + analysis.types.remove(&(i as u32)); continue } map.insert(ty, ntypes); diff --git a/crates/gc/tests/wat/remove-unused-type.wat b/crates/gc/tests/wat/remove-unused-type.wat new file mode 100644 index 000000000..b80d55d9b --- /dev/null +++ b/crates/gc/tests/wat/remove-unused-type.wat @@ -0,0 +1,34 @@ +(module + (type (func)) + (type (func (param i32))) + (type (func (param i32))) + (type (func (result i32))) + + (func $f1 (type 0)) + (func $f2 (type 1)) + (func $f3 (type 2)) + (func $f4 (type 3) + i32.const 0 + ) + + (export "a" (func $f1)) + (export "b" (func $f2)) + (export "c" (func $f3)) + (export "d" (func $f4)) + ) + +;; STDOUT (update this section with `BLESS_TESTS=1` while running tests) +;; (module +;; (type (;0;) (func)) +;; (type (;1;) (func (param i32))) +;; (type (;2;) (func (result i32))) +;; (func $f1 (type 0)) +;; (func $f2 (type 1) (param i32)) +;; (func $f3 (type 1) (param i32)) +;; (func $f4 (type 2) (result i32) +;; i32.const 0) +;; (export "a" (func $f1)) +;; (export "b" (func $f2)) +;; (export "c" (func $f3)) +;; (export "d" (func $f4))) +;; STDOUT