From c783d52484490cdd26fe5c900bdcfebbabc71bfe Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 31 Jan 2019 09:54:23 -0800 Subject: [PATCH] Migrate `wasm-bindgen` to using `walrus` This commit moves `wasm-bindgen` the CLI tool from internally using `parity-wasm` for wasm parsing/serialization to instead use `walrus`. The `walrus` crate is something we've been working on recently with an aim to replace the usage of `parity-wasm` in `wasm-bindgen` to make the current CLI tool more maintainable as well as more future-proof. The `walrus` crate provides a much nicer AST to work with as well as a structured `Module`, whereas `parity-wasm` provides a very raw interface to the wasm module which isn't really appropriate for our use case. The many transformations and tweaks that wasm-bindgen does have a huge amount of ad-hoc index management to carefully craft a final wasm binary, but this is all entirely taken care for us with the `walrus` crate. Additionally, `wasm-bindgen` will ingest and rewrite the wasm file, often changing the binary offsets of functions. Eventually with DWARF debug information we'll need to be sure to preserve the debug information throughout the transformations that `wasm-bindgen` does today. This is practically impossible to do with the `parity-wasm` architecture, but `walrus` was designed from the get-go to solve this problem transparently in the `walrus` crate itself. (it doesn't today, but this is planned work) It is the intention that this does not end up regressing any `wasm-bindgen` use cases, neither in functionality or in speed. As a large change and refactoring, however, it's likely that at least something will arise! We'll want to continue to remain vigilant to any issues that come up with this commit. Note that the `gc` crate has been deleted as part of this change, as the `gc` crate is no longer necessary since `walrus` does it automatically. Additionally the `gc` crate was one of the main problems with preserving debug information as it often deletes wasm items! Finally, this also starts moving crates to the 2018 edition where necessary since `walrus` requires the 2018 edition, and in general it's more pleasant to work within the 2018 edition! --- .travis.yml | 14 +- Cargo.toml | 1 + crates/cli-support/Cargo.toml | 5 +- crates/cli-support/src/decode.rs | 2 +- crates/cli-support/src/js/closures.rs | 277 ++--- crates/cli-support/src/js/js2rust.rs | 14 +- crates/cli-support/src/js/mod.rs | 458 ++----- crates/cli-support/src/js/rust2js.rs | 14 +- crates/cli-support/src/lib.rs | 102 +- crates/cli-support/src/wasm2es6js.rs | 185 ++- crates/cli-support/src/wasm_utils.rs | 107 -- crates/cli/Cargo.toml | 7 +- .../bin/wasm-bindgen-test-runner/headless.rs | 13 +- .../src/bin/wasm-bindgen-test-runner/main.rs | 42 +- .../bin/wasm-bindgen-test-runner/server.rs | 6 +- crates/cli/src/bin/wasm-bindgen.rs | 15 +- crates/cli/src/bin/wasm2es6js.rs | 14 +- crates/futures/src/lib.rs | 4 +- crates/gc/Cargo.toml | 27 - crates/gc/src/bitvec.rs | 73 -- crates/gc/src/lib.rs | 1047 ----------------- 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 - .../tests/wat/keep-passive-memory-segment.wat | 27 - crates/gc/tests/wat/keep-passive-segment.wat | 30 - 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 - .../wat/remove-unused-passive-segment.wat | 11 - crates/gc/tests/wat/remove-unused-type.wat | 34 - 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 - crates/macro-support/src/lib.rs | 27 +- crates/macro-support/src/parser.rs | 33 +- crates/threads-xform/Cargo.toml | 3 +- crates/threads-xform/src/lib.rs | 718 +++++------ crates/wasm-interpreter/Cargo.toml | 4 +- crates/wasm-interpreter/src/lib.rs | 500 ++++---- crates/wasm-interpreter/tests/smoke.rs | 6 +- crates/web-sys/tests/wasm/main.rs | 2 +- .../wasm/whitelisted_immutable_slices.rs | 4 +- crates/webidl/src/first_pass.rs | 2 +- crates/webidl/src/idl_type.rs | 6 +- crates/webidl/src/lib.rs | 32 +- crates/webidl/src/util.rs | 14 +- examples/fetch/src/lib.rs | 2 +- examples/raytrace-parallel/src/lib.rs | 2 +- examples/todomvc/src/template.rs | 2 +- tests/headless.rs | 2 +- tests/wasm/api.rs | 6 +- tests/wasm/classes.rs | 3 +- tests/wasm/closures.rs | 17 +- 76 files changed, 990 insertions(+), 3531 deletions(-) delete mode 100644 crates/cli-support/src/wasm_utils.rs delete mode 100644 crates/gc/Cargo.toml delete mode 100644 crates/gc/src/bitvec.rs delete mode 100644 crates/gc/src/lib.rs delete mode 100644 crates/gc/tests/all.rs delete mode 100644 crates/gc/tests/wat/dont-remove-func1.wat delete mode 100644 crates/gc/tests/wat/dont-remove-func2.wat delete mode 100644 crates/gc/tests/wat/import-and-type.wat delete mode 100644 crates/gc/tests/wat/import-memory.wat delete mode 100644 crates/gc/tests/wat/keep-data.wat delete mode 100644 crates/gc/tests/wat/keep-global.wat delete mode 100644 crates/gc/tests/wat/keep-memory.wat delete mode 100644 crates/gc/tests/wat/keep-passive-memory-segment.wat delete mode 100644 crates/gc/tests/wat/keep-passive-segment.wat delete mode 100644 crates/gc/tests/wat/keep-set-global.wat delete mode 100644 crates/gc/tests/wat/keep-start.wat delete mode 100644 crates/gc/tests/wat/keep-table.wat delete mode 100644 crates/gc/tests/wat/keep-table2.wat delete mode 100644 crates/gc/tests/wat/locals-compressed.wat delete mode 100644 crates/gc/tests/wat/preserve-local1.wat delete mode 100644 crates/gc/tests/wat/preserve-local2.wat delete mode 100644 crates/gc/tests/wat/preserve-local3.wat delete mode 100644 crates/gc/tests/wat/remove-func.wat delete mode 100644 crates/gc/tests/wat/remove-heap-base.wat delete mode 100644 crates/gc/tests/wat/remove-import-function.wat delete mode 100644 crates/gc/tests/wat/remove-import-global.wat delete mode 100644 crates/gc/tests/wat/remove-import-globals.wat delete mode 100644 crates/gc/tests/wat/remove-import-table.wat delete mode 100644 crates/gc/tests/wat/remove-local.wat delete mode 100644 crates/gc/tests/wat/remove-table.wat delete mode 100644 crates/gc/tests/wat/remove-unused-passive-segment.wat delete mode 100644 crates/gc/tests/wat/remove-unused-type.wat delete mode 100644 crates/gc/tests/wat/renumber-functions.wat delete mode 100644 crates/gc/tests/wat/renumber-globals.wat delete mode 100644 crates/gc/tests/wat/renumber-locals.wat delete mode 100644 crates/gc/tests/wat/renumber-types.wat delete mode 100644 crates/gc/tests/wat/smoke.wat delete mode 100644 crates/gc/tests/wat/table-elem.wat delete mode 100644 crates/gc/tests/wat/table-elem2.wat diff --git a/.travis.yml b/.travis.yml index 8ef712e68dae..6a4c7f8c5135 100644 --- a/.travis.yml +++ b/.travis.yml @@ -173,17 +173,9 @@ 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/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 - # Interpreter tests should quickly pass - - cargo test -p wasm-bindgen-wasm-interpreter + # wasm-interpreter tests work alright + - name: "test wasm-bindgen-wasm-interpreter crate" + script: cargo test -p wasm-bindgen-wasm-interpreter if: branch = master # Dist linux binary diff --git a/Cargo.toml b/Cargo.toml index 064ea634f442..e032c6630e7f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -91,3 +91,4 @@ wasm-bindgen = { path = '.' } wasm-bindgen-futures = { path = 'crates/futures' } js-sys = { path = 'crates/js-sys' } web-sys = { path = 'crates/web-sys' } +walrus = { git = 'https://github.com/alexcrichton/walrus', branch = 'stack-neutral' } diff --git a/crates/cli-support/Cargo.toml b/crates/cli-support/Cargo.toml index 62a6bb7f88c4..8faf282fe2eb 100644 --- a/crates/cli-support/Cargo.toml +++ b/crates/cli-support/Cargo.toml @@ -9,13 +9,14 @@ documentation = "https://docs.rs/wasm-bindgen-cli-support" description = """ Shared support for the wasm-bindgen-cli package, an internal dependency """ +edition = '2018' [dependencies] base64 = "0.9" failure = "0.1.2" -parity-wasm = "0.36" +walrus = "0.1" tempfile = "3.0" -wasm-bindgen-gc = { path = '../gc', version = '=0.2.33' } wasm-bindgen-shared = { path = "../shared", version = '=0.2.33' } wasm-bindgen-threads-xform = { path = '../threads-xform', version = '=0.2.33' } wasm-bindgen-wasm-interpreter = { path = "../wasm-interpreter", version = '=0.2.33' } +rustc-demangle = "0.1.13" diff --git a/crates/cli-support/src/decode.rs b/crates/cli-support/src/decode.rs index d6dfb6b2cf50..e5283139af20 100644 --- a/crates/cli-support/src/decode.rs +++ b/crates/cli-support/src/decode.rs @@ -144,4 +144,4 @@ macro_rules! decode_api { ); } -shared_api!(decode_api); +wasm_bindgen_shared::shared_api!(decode_api); diff --git a/crates/cli-support/src/js/closures.rs b/crates/cli-support/src/js/closures.rs index 8f88b7387042..8cc8c13866f3 100644 --- a/crates/cli-support/src/js/closures.rs +++ b/crates/cli-support/src/js/closures.rs @@ -9,16 +9,14 @@ //! through values into the final `Closure` object. More details about how all //! this works can be found in the code below. -use std::collections::{BTreeMap, HashMap, HashSet}; -use std::mem; - +use crate::descriptor::Descriptor; +use crate::js::js2rust::Js2Rust; +use crate::js::Context; use failure::Error; -use parity_wasm::elements::*; - -use descriptor::Descriptor; -use js::js2rust::Js2Rust; -use js::Context; -use wasm_utils::Remap; +use std::collections::BTreeMap; +use std::mem; +use walrus::ir::{Expr, ExprId}; +use walrus::{FunctionId, LocalFunction}; pub fn rewrite(input: &mut Context) -> Result<(), Error> { let info = ClosureDescriptors::new(input); @@ -27,38 +25,14 @@ pub fn rewrite(input: &mut Context) -> Result<(), Error> { // there's not calls to `Closure::new`. assert_eq!( info.element_removal_list.len(), - info.code_idx_to_descriptor.len(), + info.func_to_descriptor.len(), ); if info.element_removal_list.len() == 0 { return Ok(()); } - // Make sure the names section is available in the wasm module because we'll - // want to remap those function indices, and then actually remap all - // function indices. We're going to be injecting a few imported functions - // below which will shift the index space for all defined functions. - input.parse_wasm_names(); - let old_num_imports = input - .module - .import_section() - .map(|s| s.functions()) - .unwrap_or(0) as u32; - Remap(|idx| { - // If this was an imported function we didn't reorder those, so nothing - // to do. - if idx < old_num_imports { - idx - } else { - // ... otherwise we're injecting a number of new imports, so offset - // everything. - idx + info.code_idx_to_descriptor.len() as u32 - } - }) - .remap_module(input.module); - info.delete_function_table_entries(input); info.inject_imports(input)?; - info.rewrite_calls(input); Ok(()) } @@ -67,20 +41,19 @@ struct ClosureDescriptors { /// A list of elements to remove from the function table. The first element /// of the pair is the index of the entry in the element section, and the /// second element of the pair is the index within that entry to remove. - element_removal_list: Vec<(usize, usize)>, + element_removal_list: Vec, - /// A map from indexes in the code section which contain calls to - /// `__wbindgen_describe_closure` to the new function the whole function is - /// replaced with as well as the descriptor that the function describes. + /// A map from local functions which contain calls to + /// `__wbindgen_describe_closure` to the information about the closure + /// descriptor it contains. /// /// This map is later used to replace all calls to the keys of this map with /// calls to the value of the map. - code_idx_to_descriptor: BTreeMap, + func_to_descriptor: BTreeMap, } struct DescribeInstruction { - new_idx: u32, - instr_idx: usize, + call: ExprId, descriptor: Descriptor, } @@ -97,49 +70,66 @@ impl ClosureDescriptors { /// All this information is then returned in the `ClosureDescriptors` return /// value. fn new(input: &mut Context) -> ClosureDescriptors { - let wbindgen_describe_closure = match input.interpreter.describe_closure_idx() { + use walrus::ir::*; + + let wbindgen_describe_closure = match input.interpreter.describe_closure_id() { Some(i) => i, None => return Default::default(), }; - let imports = input - .module - .import_section() - .map(|s| s.functions()) - .unwrap_or(0); let mut ret = ClosureDescriptors::default(); - let code = match input.module.code_section() { - Some(code) => code, - None => return Default::default(), - }; - for (i, function) in code.bodies().iter().enumerate() { - let call_pos = function.code().elements().iter().position(|i| match i { - Instruction::Call(i) => *i == wbindgen_describe_closure, - _ => false, - }); - let call_pos = match call_pos { - Some(i) => i, - None => continue, + for (id, local) in input.module.funcs.iter_local() { + let entry = local.entry_block(); + let mut find = FindDescribeClosure { + func: local, + wbindgen_describe_closure, + cur: entry.into(), + call: None, }; - let descriptor = input - .interpreter - .interpret_closure_descriptor(i, input.module, &mut ret.element_removal_list) - .unwrap(); - // `new_idx` is the function-space index of the function that we'll - // be injecting. Calls to the code function `i` will instead be - // rewritten to calls to `new_idx`, which is an import that we'll - // inject based on `descriptor`. - let new_idx = (ret.code_idx_to_descriptor.len() + imports) as u32; - ret.code_idx_to_descriptor.insert( - i as u32, - DescribeInstruction { - new_idx, - instr_idx: call_pos, - descriptor: Descriptor::decode(descriptor), - }, - ); + find.visit_block_id(&entry); + if let Some(call) = find.call { + let descriptor = input + .interpreter + .interpret_closure_descriptor(id, input.module, &mut ret.element_removal_list) + .unwrap(); + ret.func_to_descriptor.insert( + id, + DescribeInstruction { + call, + descriptor: Descriptor::decode(descriptor), + }, + ); + } } + return ret; + + struct FindDescribeClosure<'a> { + func: &'a LocalFunction, + wbindgen_describe_closure: FunctionId, + cur: ExprId, + call: Option, + } + + impl<'a> Visitor<'a> for FindDescribeClosure<'a> { + fn local_function(&self) -> &'a LocalFunction { + self.func + } + + fn visit_expr_id(&mut self, id: &ExprId) { + let prev = mem::replace(&mut self.cur, *id); + id.visit(self); + self.cur = prev; + } + + fn visit_call(&mut self, call: &Call) { + call.visit(self); + if call.func == self.wbindgen_describe_closure { + assert!(self.call.is_none()); + self.call = Some(self.cur); + } + } + } } /// Here we remove elements from the function table. All our descriptor @@ -151,49 +141,17 @@ impl ClosureDescriptors { /// altogether by splitting the section and having multiple `elem` sections /// with holes in them. fn delete_function_table_entries(&self, input: &mut Context) { - let elements = input.module.elements_section_mut().unwrap(); - let mut remove = HashMap::new(); - for (entry, idx) in self.element_removal_list.iter().cloned() { - remove.entry(entry).or_insert(HashSet::new()).insert(idx); - } - - let entries = mem::replace(elements.entries_mut(), Vec::new()); - let empty = HashSet::new(); - for (i, entry) in entries.into_iter().enumerate() { - let to_remove = remove.get(&i).unwrap_or(&empty); - - let mut current = Vec::new(); - let offset = entry.offset().as_ref().unwrap(); - assert_eq!(offset.code().len(), 2); - let mut offset = match offset.code()[0] { - Instruction::I32Const(x) => x, - _ => unreachable!(), - }; - for (j, idx) in entry.members().iter().enumerate() { - // If we keep this entry, then keep going - if !to_remove.contains(&j) { - current.push(*idx); - continue; - } - - // If we have members of `current` then we save off a section - // of the function table, then update `offset` and keep going. - let next_offset = offset + (current.len() as i32) + 1; - if current.len() > 0 { - let members = mem::replace(&mut current, Vec::new()); - let offset = - InitExpr::new(vec![Instruction::I32Const(offset), Instruction::End]); - let new_entry = ElementSegment::new(0, Some(offset), members, false); - elements.entries_mut().push(new_entry); - } - offset = next_offset; - } - // Any remaining function table entries get pushed at the end. - if current.len() > 0 { - let offset = InitExpr::new(vec![Instruction::I32Const(offset), Instruction::End]); - let new_entry = ElementSegment::new(0, Some(offset), current, false); - elements.entries_mut().push(new_entry); - } + let table_id = match input.interpreter.function_table_id() { + Some(id) => id, + None => return, + }; + let table = input.module.tables.get_mut(table_id); + let table = match &mut table.kind { + walrus::TableKind::Function(f) => f, + }; + for idx in self.element_removal_list.iter().cloned() { + assert!(table.elements[idx].is_some()); + table.elements[idx] = None; } } @@ -203,35 +161,24 @@ impl ClosureDescriptors { /// described by the fields internally. These new imports will be closure /// factories and are freshly generated shim in JS. fn inject_imports(&self, input: &mut Context) -> Result<(), Error> { - let wbindgen_describe_closure = match input.interpreter.describe_closure_idx() { + let wbindgen_describe_closure = match input.interpreter.describe_closure_id() { Some(i) => i, None => return Ok(()), }; // We'll be injecting new imports and we'll need to give them all a - // type. The signature is all `(i32, i32) -> i32` currently and we know - // that this signature already exists in the module as it's the - // signature of our `#[inline(never)]` functions. Find the type - // signature index so we can assign it below. - let type_idx = { - let kind = input.module.import_section().unwrap().entries() - [wbindgen_describe_closure as usize] - .external(); - match kind { - External::Function(i) => *i, - _ => unreachable!(), - } - }; + // type. The signature is all `(i32, i32, i32) -> i32` currently + let ty = input.module.funcs.get(wbindgen_describe_closure).ty(); - // The last piece of the magic. For all our descriptors we found we - // inject a JS shim for the descriptor. This JS shim will manufacture a - // JS `function`, and prepare it to be invoked. + // For all our descriptors we found we inject a JS shim for the + // descriptor. This JS shim will manufacture a JS `function`, and + // prepare it to be invoked. // - // Once all that's said and done we inject a new import into the wasm module - // of our new wrapper, and the `Remap` step above already wrote calls to - // this function within the module. - for (i, instr) in self.code_idx_to_descriptor.iter() { - let import_name = format!("__wbindgen_closure_wrapper{}", i); + // Once all that's said and done we inject a new import into the wasm + // module of our new wrapper, and then rewrite the appropriate call + // instruction. + for (func, instr) in self.func_to_descriptor.iter() { + let import_name = format!("__wbindgen_closure_wrapper{}", func.index()); let closure = instr.descriptor.closure().unwrap(); @@ -268,42 +215,22 @@ impl ClosureDescriptors { ); input.export(&import_name, &body, None); - let new_import = ImportEntry::new( - "__wbindgen_placeholder__".to_string(), - import_name, - External::Function(type_idx as u32), - ); - input + let id = input .module - .import_section_mut() - .unwrap() - .entries_mut() - .push(new_import); - } - Ok(()) - } + .add_import_func("__wbindgen_placeholder__", &import_name, ty); - /// The final step, rewriting calls to `__wbindgen_describe_closure` to the - /// imported functions - fn rewrite_calls(&self, input: &mut Context) { - // FIXME: Ok so this is a bit sketchy in that it introduces overhead. - // What we're doing is taking a our #[inline(never)] shim and *not* - // removing it, only switching the one function that it calls internally. - // - // This isn't great because now we have this non-inlined function which - // would certainly benefit from getting inlined. It's a tiny function - // though and surrounded by allocation so it's probably not a huge - // problem in the long run. Note that `wasm-opt` also implements - // inlining, so we can likely rely on that too. - // - // Still though, it'd be great to not only delete calls to - // `__wbindgen_describe_closure`, it'd be great to remove all of the - // `breaks_if_inlined` functions entirely. - let code = input.module.code_section_mut().unwrap(); - for (i, instr) in self.code_idx_to_descriptor.iter() { - let func = &mut code.bodies_mut()[*i as usize]; - let new_instr = Instruction::Call(instr.new_idx); - func.code_mut().elements_mut()[instr.instr_idx] = new_instr; + let local = match &mut input.module.funcs.get_mut(*func).kind { + walrus::FunctionKind::Local(l) => l, + _ => unreachable!(), + }; + match local.get_mut(instr.call) { + Expr::Call(e) => { + assert_eq!(e.func, wbindgen_describe_closure); + e.func = id; + } + _ => unreachable!(), + } } + Ok(()) } } diff --git a/crates/cli-support/src/js/js2rust.rs b/crates/cli-support/src/js/js2rust.rs index 91b4b4f2f871..af964b1231a1 100644 --- a/crates/cli-support/src/js/js2rust.rs +++ b/crates/cli-support/src/js/js2rust.rs @@ -1,7 +1,6 @@ -use failure::Error; - -use super::Context; -use descriptor::{Descriptor, Function}; +use crate::descriptor::{Descriptor, Function}; +use crate::js::Context; +use failure::{bail, Error}; /// Helper struct for manufacturing a shim in JS used to translate JS types to /// Rust, aka pass from JS back into Rust @@ -619,10 +618,13 @@ impl<'a, 'b> Js2Rust<'a, 'b> { } Descriptor::Enum { hole } => { self.ret_ty = "number | undefined".to_string(); - self.ret_expr = format!(" + self.ret_expr = format!( + " const ret = RET; return ret === {} ? undefined : ret; - ", hole); + ", + hole + ); return Ok(self); } _ => bail!( diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index c315bc39e0bd..f8e4b28e690b 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -1,17 +1,10 @@ +use crate::decode; +use crate::descriptor::{Descriptor, VectorKind}; +use crate::Bindgen; +use failure::{bail, Error, ResultExt}; use std::collections::{HashMap, HashSet}; -use std::mem; - -use decode; -use failure::{Error, ResultExt}; -use gc; -use parity_wasm::elements::Error as ParityError; -use parity_wasm::elements::*; -use shared; - -use super::Bindgen; -use descriptor::{Descriptor, VectorKind}; -use wasm_interpreter::Interpreter; -use wasm_utils::Remap; +use walrus::{MemoryId, Module}; +use wasm_bindgen_wasm_interpreter::Interpreter; mod js2rust; use self::js2rust::Js2Rust; @@ -58,7 +51,7 @@ pub struct Context<'a> { pub exported_classes: Option>, pub function_table_needed: bool, pub interpreter: &'a mut Interpreter, - pub memory_init: Option, + pub memory: MemoryId, } #[derive(Default)] @@ -156,10 +149,9 @@ impl<'a> Context<'a> { if !self.required_internal_exports.insert(name) { return Ok(()); } - if let Some(s) = self.module.export_section() { - if s.entries().iter().any(|e| e.field() == name) { - return Ok(()); - } + + if self.module.exports.iter().any(|e| e.name == name) { + return Ok(()); } bail!( @@ -542,8 +534,7 @@ impl<'a> Context<'a> { } } - self.export_table(); - self.gc(); + self.export_table()?; // Note that it's important `throw` comes last *after* we gc. The // `__wbindgen_malloc` function may call this but we only want to @@ -576,18 +567,17 @@ impl<'a> Context<'a> { if !self.config.no_modules { bail!("most use `--no-modules` with threads for now") } - self.memory(); // set `memory_limit` if it's not already set - let limits = match &self.memory_init { - Some(l) if l.shared() => l.clone(), - _ => bail!("must impot a shared memory with threads"), - }; + let mem = self.module.memories.get(self.memory); + if mem.import.is_none() { + bail!("must impot a shared memory with threads") + } let mut memory = String::from("new WebAssembly.Memory({"); - memory.push_str(&format!("initial:{}", limits.initial())); - if let Some(max) = limits.maximum() { + memory.push_str(&format!("initial:{}", mem.initial)); + if let Some(max) = mem.maximum { memory.push_str(&format!(",maximum:{}", max)); } - if limits.shared() { + if mem.shared { memory.push_str(",shared:true"); } memory.push_str("})"); @@ -800,7 +790,7 @@ impl<'a> Context<'a> { } let mut wrap_needed = class.wrap_needed; - let new_name = shared::new_function(&name); + let new_name = wasm_bindgen_shared::new_function(&name); if self.wasm_import_needed(&new_name) { self.expose_add_heap_object(); wrap_needed = true; @@ -843,7 +833,7 @@ impl<'a> Context<'a> { ", name, freeref, - shared::free_function(&name) + wasm_bindgen_shared::free_function(&name) )); dst.push_str(&format!( " @@ -867,19 +857,22 @@ impl<'a> Context<'a> { Ok(()) } - fn export_table(&mut self) { + fn export_table(&mut self) -> Result<(), Error> { if !self.function_table_needed { - return; + return Ok(()); } - for section in self.module.sections_mut() { - let exports = match *section { - Section::Export(ref mut s) => s, - _ => continue, - }; - let entry = ExportEntry::new("__wbg_function_table".to_string(), Internal::Table(0)); - exports.entries_mut().push(entry); - break; + let mut tables = self.module.tables.iter().filter_map(|t| match t.kind { + walrus::TableKind::Function(_) => Some(t.id()), + }); + let id = match tables.next() { + Some(id) => id, + None => return Ok(()), + }; + if tables.next().is_some() { + bail!("couldn't find function table to export"); } + self.module.exports.add("__wbg_function_table", id); + Ok(()) } fn rewrite_imports(&mut self, module_name: &str) { @@ -890,50 +883,43 @@ impl<'a> Context<'a> { fn _rewrite_imports(&mut self, module_name: &str) -> Vec<(String, String)> { let mut math_imports = Vec::new(); - let imports = self - .module - .sections_mut() - .iter_mut() - .filter_map(|s| match *s { - Section::Import(ref mut s) => Some(s), - _ => None, - }) - .flat_map(|s| s.entries_mut()); - - for import in imports { - if import.module() == "__wbindgen_placeholder__" { - import.module_mut().truncate(0); - if let Some((module, name)) = self.direct_imports.get(import.field()) { - import.field_mut().truncate(0); - import.module_mut().push_str(module); - import.field_mut().push_str(name); + for import in self.module.imports.iter_mut() { + if import.module == "__wbindgen_placeholder__" { + import.module.truncate(0); + if let Some((module, name)) = self.direct_imports.get(import.name.as_str()) { + import.name.truncate(0); + import.module.push_str(module); + import.name.push_str(name); } else { - import.module_mut().push_str("./"); - import.module_mut().push_str(module_name); + import.module.push_str("./"); + import.module.push_str(module_name); } continue; } - if import.module() != "env" { + if import.module != "env" { continue; } // If memory is imported we'll have exported it from the shim module // so let's import it from there. - if import.field() == "memory" { - import.module_mut().truncate(0); - import.module_mut().push_str("./"); - import.module_mut().push_str(module_name); + // + // TODO: we should track this is in a more first-class fashion + // rather than just matching on strings. + if import.name == "memory" { + import.module.truncate(0); + import.module.push_str("./"); + import.module.push_str(module_name); continue; } - let renamed_import = format!("__wbindgen_{}", import.field()); + let renamed_import = format!("__wbindgen_{}", import.name); let mut bind_math = |expr: &str| { math_imports.push((renamed_import.clone(), format!("function{}", expr))); }; // FIXME(#32): try to not use function shims - match import.field() { + match import.name.as_str() { "Math_acos" => bind_math("(x) { return Math.acos(x); }"), "Math_asin" => bind_math("(x) { return Math.asin(x); }"), "Math_atan" => bind_math("(x) { return Math.atan(x); }"), @@ -949,25 +935,38 @@ impl<'a> Context<'a> { _ => continue, } - import.module_mut().truncate(0); - import.module_mut().push_str("./"); - import.module_mut().push_str(module_name); - *import.field_mut() = renamed_import.clone(); + import.module.truncate(0); + import.module.push_str("./"); + import.module.push_str(module_name); + import.name = renamed_import.clone(); } math_imports } fn unexport_unused_internal_exports(&mut self) { - let required = &self.required_internal_exports; - for section in self.module.sections_mut() { - let exports = match *section { - Section::Export(ref mut s) => s, - _ => continue, - }; - exports.entries_mut().retain(|export| { - !export.field().starts_with("__wbindgen") || required.contains(export.field()) - }); + let mut to_remove = Vec::new(); + for export in self.module.exports.iter() { + match export.name.as_str() { + // These are some internal imports set by LLD but currently + // we've got no use case for continuing to export them, so + // blacklist them. + "__heap_base" | "__data_end" | "__indirect_function_table" => { + to_remove.push(export.id()); + } + + // 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.remove_root(id); } } @@ -1224,15 +1223,7 @@ impl<'a> Context<'a> { // creates just a view. That way in shared mode we copy more data but in // non-shared mode there's no need to copy the data except for the // string itself. - self.memory(); // set self.memory_init - let is_shared = self - .module - .memory_section() - .map(|s| s.entries()[0].limits().shared()) - .unwrap_or(match &self.memory_init { - Some(limits) => limits.shared(), - None => false, - }); + let is_shared = self.module.memories.get(self.memory).shared; let method = if is_shared { "slice" } else { "subarray" }; self.global(&format!( @@ -1574,15 +1565,10 @@ impl<'a> Context<'a> { } fn wasm_import_needed(&self, name: &str) -> bool { - let imports = match self.module.import_section() { - Some(s) => s, - None => return false, - }; - - imports - .entries() + self.module + .imports .iter() - .any(|i| i.module() == "__wbindgen_placeholder__" && i.field() == name) + .any(|i| i.module == "__wbindgen_placeholder__" && i.name == name) } fn pass_to_wasm_function(&mut self, t: VectorKind) -> Result<&'static str, Error> { @@ -1791,25 +1777,6 @@ impl<'a> Context<'a> { ); } - fn gc(&mut self) { - gc::Config::new() - .demangle(self.config.demangle) - .keep_debug(self.config.keep_debug || self.config.debug) - .run(&mut self.module); - } - - pub fn parse_wasm_names(&mut self) { - let module = mem::replace(self.module, Module::default()); - let module = module.parse_names().unwrap_or_else(|p| p.1); - *self.module = module; - if self.config.remove_name_section { - self.module.sections_mut().retain(|s| match s { - Section::Name(_) => false, - _ => true, - }); - } - } - fn describe(&mut self, name: &str) -> Option { let name = format!("__wbindgen_describe_{}", name); let descriptor = self.interpreter.interpret_descriptor(&name, self.module)?; @@ -1833,25 +1800,11 @@ impl<'a> Context<'a> { } fn memory(&mut self) -> &'static str { - if self.module.memory_section().is_some() { - return "wasm.memory"; + if self.module.memories.get(self.memory).import.is_some() { + "memory" + } else { + "wasm.memory" } - - let (entry, mem) = self - .module - .import_section() - .expect("must import memory") - .entries() - .iter() - .filter_map(|i| match i.external() { - External::Memory(m) => Some((i, m)), - _ => None, - }) - .next() - .expect("must import memory"); - assert_eq!(entry.field(), "memory"); - self.memory_init = Some(mem.limits().clone()); - "memory" } fn require_class_wrap(&mut self, class: &str) { @@ -2073,106 +2026,9 @@ impl<'a> Context<'a> { /// Specified at: /// https://github.com/WebAssembly/tool-conventions/blob/master/ProducersSection.md fn update_producers_section(&mut self) { - for section in self.module.sections_mut() { - let section = match section { - Section::Custom(s) => s, - _ => continue, - }; - if section.name() != "producers" { - return; - } - drop(update(section)); - return; - } - - // `CustomSection::new` added in paritytech/parity-wasm#244 which isn't - // merged just yet - let data = [ - ("producers".len() + 2) as u8, - "producers".len() as u8, - b'p', - b'r', - b'o', - b'd', - b'u', - b'c', - b'e', - b'r', - b's', - 0, - ]; - let mut section = CustomSection::deserialize(&mut &data[..]).unwrap(); - assert_eq!(section.name(), "producers"); - assert_eq!(section.payload(), [0]); - drop(update(&mut section)); - self.module.sections_mut().push(Section::Custom(section)); - - fn update(section: &mut CustomSection) -> Result<(), ParityError> { - struct Field { - name: String, - values: Vec, - } - struct FieldValue { - name: String, - version: String, - } - - let wasm_bindgen = || FieldValue { - name: "wasm-bindgen".to_string(), - version: shared::version(), - }; - let mut fields = Vec::new(); - - // Deserialize the fields, appending the wasm-bidngen field/value - // where applicable - { - let mut data = section.payload(); - let amt: u32 = VarUint32::deserialize(&mut data)?.into(); - let mut found_processed_by = false; - for _ in 0..amt { - let name = String::deserialize(&mut data)?; - let cnt: u32 = VarUint32::deserialize(&mut data)?.into(); - let mut values = Vec::with_capacity(cnt as usize); - for _ in 0..cnt { - let name = String::deserialize(&mut data)?; - let version = String::deserialize(&mut data)?; - values.push(FieldValue { name, version }); - } - - if name == "processed-by" { - found_processed_by = true; - values.push(wasm_bindgen()); - } - - fields.push(Field { name, values }); - } - if data.len() != 0 { - return Err(ParityError::InconsistentCode); - } - - if !found_processed_by { - fields.push(Field { - name: "processed-by".to_string(), - values: vec![wasm_bindgen()], - }); - } - } - - // re-serialize these fields back into the custom section - let dst = section.payload_mut(); - dst.truncate(0); - VarUint32::from(fields.len() as u32).serialize(dst)?; - for field in fields.iter() { - field.name.clone().serialize(dst)?; - VarUint32::from(field.values.len() as u32).serialize(dst)?; - for value in field.values.iter() { - value.name.clone().serialize(dst)?; - value.version.clone().serialize(dst)?; - } - } - - Ok(()) - } + self.module + .producers + .add_processed_by("wasm-bindgen", &wasm_bindgen_shared::version()); } fn add_start_function(&mut self) -> Result<(), Error> { @@ -2180,33 +2036,25 @@ impl<'a> Context<'a> { Some(name) => name.clone(), None => return Ok(()), }; - let idx = { - let exports = self - .module - .export_section() - .ok_or_else(|| format_err!("no export section found"))?; - let entry = exports - .entries() - .iter() - .find(|e| e.field() == start) - .ok_or_else(|| format_err!("export `{}` not found", start))?; - match entry.internal() { - Internal::Function(i) => *i, - _ => bail!("export `{}` wasn't a function", start), - } + let export = match self.module.exports.iter().find(|e| e.name == start) { + Some(export) => export, + None => bail!("export `{}` not found", start), + }; + let id = match export.item { + walrus::ExportItem::Function(i) => i, + _ => bail!("export `{}` wasn't a function", start), }; - if let Some(prev_start) = self.module.start_section() { - if let Some(NameSection::Function(n)) = self.module.names_section() { - if let Some(prev) = n.names().get(prev_start) { - bail!( - "cannot flag `{}` as start function as `{}` is \ - already the start function", - start, - prev - ); - } - } + if let Some(prev) = self.module.start { + let prev = self.module.funcs.get(prev); + if let Some(prev) = &prev.name { + bail!( + "cannot flag `{}` as start function as `{}` is \ + already the start function", + start, + prev + ); + } bail!( "cannot flag `{}` as start function as another \ function is already the start function", @@ -2214,62 +2062,20 @@ impl<'a> Context<'a> { ); } - self.set_start_section(idx); + self.module.start = Some(id); Ok(()) } - fn set_start_section(&mut self, start: u32) { - let mut pos = None; - // See http://webassembly.github.io/spec/core/binary/modules.html#binary-module - // for section ordering - for (i, section) in self.module.sections().iter().enumerate() { - match section { - Section::Type(_) - | Section::Import(_) - | Section::Function(_) - | Section::Table(_) - | Section::Memory(_) - | Section::Global(_) - | Section::Export(_) => continue, - _ => { - pos = Some(i); - break; - } - } - } - let pos = pos.unwrap_or(self.module.sections().len() - 1); - self.module - .sections_mut() - .insert(pos, Section::Start(start)); - } - /// If a start function is present, it removes it from the `start` section /// of the wasm module and then moves it to an exported function, named /// `__wbindgen_start`. fn unstart_start_function(&mut self) -> bool { - let mut pos = None; - let mut start = 0; - for (i, section) in self.module.sections().iter().enumerate() { - if let Section::Start(idx) = section { - start = *idx; - pos = Some(i); - break; - } - } - match pos { - Some(i) => { - self.module.sections_mut().remove(i); - let entry = - ExportEntry::new("__wbindgen_start".to_string(), Internal::Function(start)); - self.module - .export_section_mut() - .unwrap() - .entries_mut() - .push(entry); - true - } - None => false, - } + let start = match self.module.start.take() { + Some(id) => id, + None => return false, + }; + self.module.exports.add("__wbindgen_start", start); + true } /// Injects a `start` function into the wasm module. This start function @@ -2282,32 +2088,11 @@ impl<'a> Context<'a> { Promise.resolve().then(() => wasm.__wbindgen_start()); }"; self.export("__wbindgen_defer_start", body, None); - - let imports = self - .module - .import_section() - .map(|s| s.functions() as u32) - .unwrap_or(0); - Remap(|idx| if idx < imports { idx } else { idx + 1 }).remap_module(self.module); - - let type_idx = { - let types = self.module.type_section_mut().unwrap(); - let ty = Type::Function(FunctionType::new(Vec::new(), None)); - types.types_mut().push(ty); - (types.types_mut().len() - 1) as u32 - }; - - let entry = ImportEntry::new( - "__wbindgen_placeholder__".to_string(), - "__wbindgen_defer_start".to_string(), - External::Function(type_idx), - ); - self.module - .import_section_mut() - .unwrap() - .entries_mut() - .push(entry); - self.set_start_section(imports); + let ty = self.module.types.add(&[], &[]); + let id = + self.module + .add_import_func("__wbindgen_placeholder__", "__wbindgen_defer_start", ty); + self.module.start = Some(id); } } @@ -2393,7 +2178,8 @@ impl<'a, 'b> SubContext<'a, 'b> { class_name: &'b str, export: &decode::Export, ) -> Result<(), Error> { - let wasm_name = shared::struct_function_export_name(class_name, &export.function.name); + let wasm_name = + wasm_bindgen_shared::struct_function_export_name(class_name, &export.function.name); let descriptor = match self.cx.describe(&wasm_name) { None => return Ok(()), @@ -2612,8 +2398,8 @@ impl<'a, 'b> SubContext<'a, 'b> { let mut dst = String::new(); let mut ts_dst = String::new(); for field in struct_.fields.iter() { - let wasm_getter = shared::struct_field_get(&struct_.name, &field.name); - let wasm_setter = shared::struct_field_set(&struct_.name, &field.name); + let wasm_getter = wasm_bindgen_shared::struct_field_get(&struct_.name, &field.name); + let wasm_setter = wasm_bindgen_shared::struct_field_set(&struct_.name, &field.name); let descriptor = match self.cx.describe(&wasm_getter) { None => continue, Some(d) => d, diff --git a/crates/cli-support/src/js/rust2js.rs b/crates/cli-support/src/js/rust2js.rs index 20c60d3dd223..284ce5bcfc40 100644 --- a/crates/cli-support/src/js/rust2js.rs +++ b/crates/cli-support/src/js/rust2js.rs @@ -1,7 +1,6 @@ -use failure::Error; - -use super::{Context, ImportTarget, Js2Rust}; -use descriptor::{Descriptor, Function}; +use crate::descriptor::{Descriptor, Function}; +use crate::js::{Context, ImportTarget, Js2Rust}; +use failure::{bail, Error}; /// Helper struct for manufacturing a shim in JS used to translate Rust types to /// JS, then invoking an imported JS function. @@ -448,10 +447,13 @@ impl<'a, 'b> Rust2Js<'a, 'b> { } Descriptor::Enum { hole } => { self.cx.expose_is_like_none(); - self.ret_expr = format!(" + self.ret_expr = format!( + " const val = JS; return isLikeNone(val) ? {} : val; - ", hole); + ", + hole + ); return Ok(()); } _ => bail!( diff --git a/crates/cli-support/src/lib.rs b/crates/cli-support/src/lib.rs index 1a07c032d81e..f5176c97fd41 100644 --- a/crates/cli-support/src/lib.rs +++ b/crates/cli-support/src/lib.rs @@ -1,29 +1,18 @@ #![doc(html_root_url = "https://docs.rs/wasm-bindgen-cli-support/0.2")] -extern crate parity_wasm; -#[macro_use] -extern crate wasm_bindgen_shared as shared; -extern crate wasm_bindgen_gc as gc; -#[macro_use] -extern crate failure; -extern crate wasm_bindgen_threads_xform as threads_xform; -extern crate wasm_bindgen_wasm_interpreter as wasm_interpreter; - +use failure::{bail, Error, ResultExt}; use std::collections::BTreeSet; use std::env; use std::fs; use std::mem; use std::path::{Path, PathBuf}; use std::str; - -use failure::{Error, ResultExt}; -use parity_wasm::elements::*; +use walrus::Module; mod decode; mod descriptor; mod js; pub mod wasm2es6js; -mod wasm_utils; pub struct Bindgen { input: Input, @@ -44,7 +33,7 @@ pub struct Bindgen { weak_refs: bool, // Experimental support for the wasm threads proposal, transforms the wasm // module to be "ready to be instantiated on any thread" - threads: Option, + threads: Option, } enum Input { @@ -154,13 +143,21 @@ impl Bindgen { let (mut module, stem) = match self.input { Input::None => bail!("must have an input by now"), Input::Module(ref mut m, ref name) => { - let blank_module = Module::new(Vec::new()); + let blank_module = Module::default(); (mem::replace(m, blank_module), &name[..]) } Input::Path(ref path) => { let contents = fs::read(&path) .with_context(|_| format!("failed to read `{}`", path.display()))?; - let module = parity_wasm::deserialize_buffer::(&contents) + let module = walrus::ModuleConfig::new() + // Skip validation of the module as LLVM's output is + // generally already well-formed and so we won't gain much + // from re-validating. Additionally LLVM's current output + // for threads includes atomic instructions but doesn't + // include shared memory, so it fails that part of + // validation! + .strict_validate(false) + .parse(&contents) .context("failed to parse input file as wasm")?; let stem = match &self.out_name { Some(name) => &name, @@ -178,6 +175,10 @@ impl Bindgen { .with_context(|_| "failed to prepare module for threading")?; } + if self.demangle { + demangle(&mut module); + } + // Here we're actually instantiating the module we've parsed above for // execution. Why, you might be asking, are we executing wasm code? A // good question! @@ -191,7 +192,15 @@ impl Bindgen { // This means that whenever we encounter an import or export we'll // execute a shim function which informs us about its type so we can // then generate the appropriate bindings. - let mut instance = wasm_interpreter::Interpreter::new(&module); + let mut instance = wasm_bindgen_wasm_interpreter::Interpreter::new(&module); + + let mut memories = module.memories.iter().map(|m| m.id()); + let memory = memories.next(); + if memories.next().is_some() { + bail!("multiple memories currently not supported"); + } + drop(memories); + let memory = memory.unwrap_or_else(|| module.memories.add_local(false, 1, None)); let (js, ts) = { let mut cx = js::Context { @@ -209,13 +218,12 @@ impl Bindgen { module: &mut module, function_table_needed: false, interpreter: &mut instance, - memory_init: None, + memory, imported_functions: Default::default(), imported_statics: Default::default(), direct_imports: Default::default(), start: None, }; - cx.parse_wasm_names(); for program in programs.iter() { js::SubContext { program, @@ -253,12 +261,12 @@ impl Bindgen { if self.typescript { let ts_path = wasm_path.with_extension("d.ts"); - let ts = wasm2es6js::typescript(&module); + let ts = wasm2es6js::typescript(&module)?; fs::write(&ts_path, ts) .with_context(|_| format!("failed to write `{}`", ts_path.display()))?; } - let wasm_bytes = parity_wasm::serialize(module)?; + let wasm_bytes = module.emit_wasm()?; fs::write(&wasm_path, wasm_bytes) .with_context(|_| format!("failed to write `{}`", wasm_path.display()))?; @@ -267,10 +275,8 @@ impl Bindgen { fn generate_node_wasm_import(&self, m: &Module, path: &Path) -> String { let mut imports = BTreeSet::new(); - if let Some(i) = m.import_section() { - for i in i.entries() { - imports.insert(i.module()); - } + for import in m.imports.iter() { + imports.insert(&import.module); } let mut shim = String::new(); @@ -322,14 +328,12 @@ impl Bindgen { )); if self.nodejs_experimental_modules { - if let Some(e) = m.export_section() { - for name in e.entries().iter().map(|e| e.field()) { - shim.push_str("export const "); - shim.push_str(name); - shim.push_str(" = wasmInstance.exports."); - shim.push_str(name); - shim.push_str(";\n"); - } + for entry in m.exports.iter() { + shim.push_str("export const "); + shim.push_str(&entry.name); + shim.push_str(" = wasmInstance.exports."); + shim.push_str(&entry.name); + shim.push_str(";\n"); } } else { shim.push_str("module.exports = wasmInstance.exports;\n"); @@ -343,24 +347,20 @@ fn extract_programs<'a>( module: &mut Module, program_storage: &'a mut Vec>, ) -> Result>, Error> { - let my_version = shared::version(); + let my_version = wasm_bindgen_shared::version(); let mut to_remove = Vec::new(); assert!(program_storage.is_empty()); - for (i, s) in module.sections_mut().iter_mut().enumerate() { - let custom = match s { - Section::Custom(s) => s, - _ => continue, - }; - if custom.name() != "__wasm_bindgen_unstable" { + for (i, custom) in module.custom.iter_mut().enumerate() { + if custom.name != "__wasm_bindgen_unstable" { continue; } to_remove.push(i); - program_storage.push(mem::replace(custom.payload_mut(), Vec::new())); + program_storage.push(mem::replace(&mut custom.value, Vec::new())); } for i in to_remove.into_iter().rev() { - module.sections_mut().remove(i); + module.custom.remove(i); } let mut ret = Vec::new(); @@ -452,7 +452,7 @@ fn verify_schema_matches<'a>(data: &'a [u8]) -> Result, Error> { Some(i) => &rest[..i], None => bad!(), }; - if their_schema_version == shared::SCHEMA_VERSION { + if their_schema_version == wasm_bindgen_shared::SCHEMA_VERSION { return Ok(None); } let needle = "\"version\":\""; @@ -498,11 +498,11 @@ fn reset_indentation(s: &str) -> String { // Eventually these will all be CLI options, but while they're unstable features // they're left as environment variables. We don't guarantee anything about // backwards-compatibility with these options. -fn threads_config() -> Option { +fn threads_config() -> Option { if env::var("WASM_BINDGEN_THREADS").is_err() { return None; } - let mut cfg = threads_xform::Config::new(); + let mut cfg = wasm_bindgen_threads_xform::Config::new(); if let Ok(s) = env::var("WASM_BINDGEN_THREADS_MAX_MEMORY") { cfg.maximum_memory(s.parse().unwrap()); } @@ -511,3 +511,15 @@ fn threads_config() -> Option { } Some(cfg) } + +fn demangle(module: &mut Module) { + for func in module.funcs.iter_mut() { + let name = match &func.name { + Some(name) => name, + None => continue, + }; + if let Ok(sym) = rustc_demangle::try_demangle(name) { + func.name = Some(sym.to_string()); + } + } +} diff --git a/crates/cli-support/src/wasm2es6js.rs b/crates/cli-support/src/wasm2es6js.rs index 537f16d4029f..cdf8fa49fc1a 100644 --- a/crates/cli-support/src/wasm2es6js.rs +++ b/crates/cli-support/src/wasm2es6js.rs @@ -1,10 +1,6 @@ -extern crate base64; -extern crate tempfile; - +use failure::{bail, Error}; use std::collections::HashSet; - -use failure::Error; -use parity_wasm::elements::*; +use walrus::Module; pub struct Config { base64: bool, @@ -39,7 +35,7 @@ impl Config { if !self.base64 && !self.fetch_path.is_some() { bail!("one of --base64 or --fetch is required"); } - let module = deserialize_buffer(wasm)?; + let module = Module::from_buffer(wasm)?; Ok(Output { module, base64: self.base64, @@ -48,87 +44,62 @@ impl Config { } } -pub fn typescript(module: &Module) -> String { +pub fn typescript(module: &Module) -> Result { let mut exports = format!("/* tslint:disable */\n"); - if let Some(i) = module.export_section() { - let imported_functions = module - .import_section() - .map(|m| m.functions() as u32) - .unwrap_or(0); - for entry in i.entries() { - let idx = match *entry.internal() { - Internal::Function(i) if i < imported_functions => *module - .import_section() - .unwrap() - .entries() - .iter() - .filter_map(|f| match f.external() { - External::Function(i) => Some(i), - _ => None, - }) - .nth(i as usize) - .unwrap(), - Internal::Function(i) => { - let idx = i - imported_functions; - let functions = module - .function_section() - .expect("failed to find function section"); - functions.entries()[idx as usize].type_ref() - } - Internal::Memory(_) => { - exports.push_str(&format!( - "export const {}: WebAssembly.Memory;\n", - entry.field() - )); - continue; - } - Internal::Table(_) => { - exports.push_str(&format!( - "export const {}: WebAssembly.Table;\n", - entry.field() - )); - continue; - } - Internal::Global(_) => continue, - }; - - let types = module.type_section().expect("failed to find type section"); - let ty = match types.types()[idx as usize] { - Type::Function(ref f) => f, - }; - let mut args = String::new(); - for (i, _) in ty.params().iter().enumerate() { - if i > 0 { - args.push_str(", "); - } - args.push((b'a' + (i as u8)) as char); - args.push_str(": number"); + for entry in module.exports.iter() { + let id = match entry.item { + walrus::ExportItem::Function(i) => i, + walrus::ExportItem::Memory(_) => { + exports.push_str(&format!( + "export const {}: WebAssembly.Memory;\n", + entry.name, + )); + continue; } + walrus::ExportItem::Table(_) => { + exports.push_str(&format!( + "export const {}: WebAssembly.Table;\n", + entry.name, + )); + continue; + } + walrus::ExportItem::Global(_) => continue, + }; - exports.push_str(&format!( - "export function {name}({args}): {ret};\n", - name = entry.field(), - args = args, - ret = if ty.return_type().is_some() { - "number" - } else { - "void" - }, - )); + let func = module.funcs.get(id); + let ty = module.types.get(func.ty()); + let mut args = String::new(); + for (i, _) in ty.params().iter().enumerate() { + if i > 0 { + args.push_str(", "); + } + args.push((b'a' + (i as u8)) as char); + args.push_str(": number"); } + + exports.push_str(&format!( + "export function {name}({args}): {ret};\n", + name = entry.name, + args = args, + ret = match ty.results().len() { + 0 => "void", + 1 => "number", + _ => bail!("cannot support multi-return yet"), + }, + )); } - return exports; + Ok(exports) } impl Output { - pub fn typescript(&self) -> String { - let mut ts = typescript(&self.module); + pub fn typescript(&self) -> Result { + let mut ts = typescript(&self.module)?; if self.base64 { ts.push_str("export const booted: Promise;\n"); } - return ts; + Ok(ts) } pub fn js_and_wasm(mut self) -> Result<(String, Option>), Error> { @@ -137,33 +108,28 @@ impl Output { let mut set_exports = String::new(); let mut imports = String::new(); - if let Some(i) = self.module.import_section() { - let mut set = HashSet::new(); - for entry in i.entries() { - if !set.insert(entry.module()) { - continue; - } - - let name = (b'a' + (set.len() as u8)) as char; - js_imports.push_str(&format!( - "import * as import_{} from '{}';\n", - name, - entry.module() - )); - imports.push_str(&format!("'{}': import_{}, ", entry.module(), name)); + let mut set = HashSet::new(); + for entry in self.module.imports.iter() { + if !set.insert(&entry.module) { + continue; } + + let name = (b'a' + (set.len() as u8)) as char; + js_imports.push_str(&format!( + "import * as import_{} from '{}';\n", + name, entry.module + )); + imports.push_str(&format!("'{}': import_{}, ", entry.module, name)); } - if let Some(i) = self.module.export_section() { - for entry in i.entries() { - exports.push_str("export let "); - exports.push_str(entry.field()); - exports.push_str(";\n"); - set_exports.push_str(entry.field()); - set_exports.push_str(" = wasm.exports."); - set_exports.push_str(entry.field()); - set_exports.push_str(";\n"); - } + for entry in self.module.exports.iter() { + exports.push_str("export let "); + exports.push_str(&entry.name); + exports.push_str(";\n"); + set_exports.push_str(&entry.name); + set_exports.push_str(" = wasm.exports."); + set_exports.push_str(&entry.name); + set_exports.push_str(";\n"); } // This is sort of tricky, but the gist of it is that if there's a start @@ -199,7 +165,7 @@ impl Output { imports = imports, set_exports = set_exports, ); - let wasm = serialize(self.module).expect("failed to serialize"); + let wasm = self.module.emit_wasm().expect("failed to serialize"); let (bytes, booted) = if self.base64 { ( format!( @@ -252,24 +218,11 @@ impl Output { /// removes the start section, if any, and moves it to an exported function. /// Returns whether a start function was found and removed. fn unstart(&mut self) -> bool { - let mut start = None; - for (i, section) in self.module.sections().iter().enumerate() { - if let Section::Start(idx) = section { - start = Some((i, *idx)); - break; - } - } - let (i, idx) = match start { - Some(p) => p, + let start = match self.module.start.take() { + Some(id) => id, None => return false, }; - self.module.sections_mut().remove(i); - let entry = ExportEntry::new("__wasm2es6js_start".to_string(), Internal::Function(idx)); - self.module - .export_section_mut() - .unwrap() - .entries_mut() - .push(entry); + self.module.exports.add("__wasm2es6js_start", start); true } } diff --git a/crates/cli-support/src/wasm_utils.rs b/crates/cli-support/src/wasm_utils.rs deleted file mode 100644 index c1dfb99f38bd..000000000000 --- a/crates/cli-support/src/wasm_utils.rs +++ /dev/null @@ -1,107 +0,0 @@ -use std::mem; - -use parity_wasm::elements::*; - -pub struct Remap(pub F); - -impl Remap -where - F: FnMut(u32) -> u32, -{ - pub fn remap_module(&mut self, module: &mut Module) { - for section in module.sections_mut() { - match section { - Section::Export(e) => self.remap_export_section(e), - Section::Element(e) => self.remap_element_section(e), - Section::Code(e) => self.remap_code_section(e), - Section::Start(i) => { - self.remap_idx(i); - } - Section::Name(n) => self.remap_name_section(n), - _ => {} - } - } - } - - fn remap_export_section(&mut self, section: &mut ExportSection) { - for entry in section.entries_mut() { - self.remap_export_entry(entry); - } - } - - fn remap_export_entry(&mut self, entry: &mut ExportEntry) { - match entry.internal_mut() { - Internal::Function(i) => { - self.remap_idx(i); - } - _ => {} - } - } - - fn remap_element_section(&mut self, section: &mut ElementSection) { - for entry in section.entries_mut() { - self.remap_element_entry(entry); - } - } - - fn remap_element_entry(&mut self, entry: &mut ElementSegment) { - for member in entry.members_mut() { - self.remap_idx(member); - } - } - - fn remap_code_section(&mut self, section: &mut CodeSection) { - for body in section.bodies_mut() { - self.remap_func_body(body); - } - } - - fn remap_func_body(&mut self, body: &mut FuncBody) { - self.remap_instructions(body.code_mut()); - } - - fn remap_instructions(&mut self, code: &mut Instructions) { - for instr in code.elements_mut() { - self.remap_instruction(instr); - } - } - - fn remap_instruction(&mut self, instr: &mut Instruction) { - match instr { - Instruction::Call(i) => { - self.remap_idx(i); - } - _ => {} - } - } - - fn remap_name_section(&mut self, names: &mut NameSection) { - match names { - NameSection::Function(f) => self.remap_function_name_section(f), - NameSection::Local(f) => self.remap_local_name_section(f), - _ => {} - } - } - - fn remap_function_name_section(&mut self, names: &mut FunctionNameSection) { - let map = names.names_mut(); - let new = IndexMap::with_capacity(map.len()); - for (mut idx, name) in mem::replace(map, new) { - self.remap_idx(&mut idx); - map.insert(idx, name); - } - } - - fn remap_local_name_section(&mut self, names: &mut LocalNameSection) { - let map = names.local_names_mut(); - let new = IndexMap::with_capacity(map.len()); - for (mut idx, name) in mem::replace(map, new) { - self.remap_idx(&mut idx); - map.insert(idx, name); - } - } - - fn remap_idx(&mut self, idx: &mut u32) { - *idx = (self.0)(*idx); - } -} diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 613308b05e86..fe711e876902 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -11,6 +11,7 @@ description = """ Command line interface of the `#[wasm_bindgen]` attribute and project. For more information see https://github.com/alexcrichton/wasm-bindgen. """ +edition = '2018' [dependencies] curl = "0.4.13" @@ -18,14 +19,14 @@ docopt = "1.0" env_logger = "0.6" failure = "0.1.2" log = "0.4" -parity-wasm = "0.36" +openssl = { version = '0.10.11', optional = true } rouille = { version = "3.0.0", default-features = false } -serde = "1.0" +serde = { version = "1.0", features = ['derive'] } serde_derive = "1.0" serde_json = "1.0" +walrus = "0.1" wasm-bindgen-cli-support = { path = "../cli-support", version = "=0.2.33" } wasm-bindgen-shared = { path = "../shared", version = "=0.2.33" } -openssl = { version = '0.10.11', optional = true } [features] vendored-openssl = ['openssl/vendored'] diff --git a/crates/cli/src/bin/wasm-bindgen-test-runner/headless.rs b/crates/cli/src/bin/wasm-bindgen-test-runner/headless.rs index 8b8e87c6a376..ad6cc3a70900 100644 --- a/crates/cli/src/bin/wasm-bindgen-test-runner/headless.rs +++ b/crates/cli/src/bin/wasm-bindgen-test-runner/headless.rs @@ -1,3 +1,9 @@ +use crate::shell::Shell; +use curl::easy::Easy; +use failure::{bail, format_err, Error, ResultExt}; +use log::{debug, warn}; +use serde::{Deserialize, Serialize}; +use serde_json::{self, json}; use std::env; use std::io::{self, Read}; use std::net::{SocketAddr, TcpListener, TcpStream}; @@ -6,13 +12,6 @@ use std::process::{Child, Command, Stdio}; use std::thread; use std::time::{Duration, Instant}; -use curl::easy::Easy; -use failure::{Error, ResultExt}; -use serde::{Deserialize, Serialize}; -use serde_json; - -use shell::Shell; - /// Execute a headless browser tests against a server running on `server` /// address. /// diff --git a/crates/cli/src/bin/wasm-bindgen-test-runner/main.rs b/crates/cli/src/bin/wasm-bindgen-test-runner/main.rs index 04abb79c8f8f..de840fd83c1a 100644 --- a/crates/cli/src/bin/wasm-bindgen-test-runner/main.rs +++ b/crates/cli/src/bin/wasm-bindgen-test-runner/main.rs @@ -11,29 +11,12 @@ //! For more documentation about this see the `wasm-bindgen-test` crate README //! and source code. -extern crate curl; -extern crate env_logger; -#[macro_use] -extern crate failure; -#[macro_use] -extern crate log; -extern crate parity_wasm; -extern crate rouille; -extern crate serde; -#[macro_use] -extern crate serde_derive; -#[macro_use] -extern crate serde_json; -extern crate wasm_bindgen_cli_support; - +use failure::{bail, format_err, Error, ResultExt}; use std::env; use std::fs; use std::path::PathBuf; use std::process; use std::thread; - -use failure::{Error, ResultExt}; -use parity_wasm::elements::{Deserialize, Module, Section}; use wasm_bindgen_cli_support::Bindgen; // no need for jemalloc bloat in this binary (and we don't need speed) @@ -88,15 +71,14 @@ fn rmain() -> Result<(), Error> { // that any exported function with the prefix `__wbg_test` is a test we need // to execute. let wasm = fs::read(&wasm_file_to_test).context("failed to read wasm file")?; - let wasm = Module::deserialize(&mut &wasm[..]).context("failed to deserialize wasm module")?; + let wasm = walrus::Module::from_buffer(&wasm).context("failed to deserialize wasm module")?; let mut tests = Vec::new(); - if let Some(exports) = wasm.export_section() { - for export in exports.entries() { - if !export.field().starts_with("__wbg_test") { - continue; - } - tests.push(export.field().to_string()); + + for export in wasm.exports.iter() { + if !export.name.starts_with("__wbg_test") { + continue; } + tests.push(export.name.to_string()); } // Right now there's a bug where if no tests are present then the @@ -112,15 +94,11 @@ fn rmain() -> Result<(), Error> { // `wasm_bindgen_test_configure` macro, which emits a custom section for us // to read later on. let mut node = true; - for section in wasm.sections() { - let custom = match section { - Section::Custom(section) => section, - _ => continue, - }; - if custom.name() != "__wasm_bindgen_test_unstable" { + for custom in wasm.custom.iter() { + if custom.name != "__wasm_bindgen_test_unstable" { continue; } - node = !custom.payload().contains(&0x01); + node = !custom.value.contains(&0x01); } let headless = env::var("NO_HEADLESS").is_err(); let debug = env::var("WASM_BINDGEN_NO_DEBUG").is_err(); diff --git a/crates/cli/src/bin/wasm-bindgen-test-runner/server.rs b/crates/cli/src/bin/wasm-bindgen-test-runner/server.rs index f7e9a3d88cc6..5b6eaec045d5 100644 --- a/crates/cli/src/bin/wasm-bindgen-test-runner/server.rs +++ b/crates/cli/src/bin/wasm-bindgen-test-runner/server.rs @@ -3,8 +3,8 @@ use std::fs; use std::net::SocketAddr; use std::path::Path; -use failure::{Error, ResultExt}; -use rouille::{self, Request, Response, Server}; +use failure::{format_err, Error, ResultExt}; +use rouille::{Request, Response, Server}; use wasm_bindgen_cli_support::wasm2es6js::Config; pub fn spawn( @@ -70,7 +70,7 @@ pub fn spawn( // like an ES module with the wasm module under the hood. // // TODO: don't reparse the wasm module here, should pass the - // `parity_wasm::Module struct` directly from the output of + // `Module struct` directly from the output of // `wasm-bindgen` previously here and avoid unnecessary // parsing. let wasm_name = format!("{}_bg.wasm", module); diff --git a/crates/cli/src/bin/wasm-bindgen.rs b/crates/cli/src/bin/wasm-bindgen.rs index 70401212c6db..a745d3458af1 100644 --- a/crates/cli/src/bin/wasm-bindgen.rs +++ b/crates/cli/src/bin/wasm-bindgen.rs @@ -1,17 +1,8 @@ -extern crate wasm_bindgen_cli_support; -#[macro_use] -extern crate serde_derive; -extern crate docopt; -extern crate wasm_bindgen_shared; -#[macro_use] -extern crate failure; -extern crate env_logger; - +use docopt::Docopt; +use failure::{bail, Error}; +use serde::Deserialize; use std::path::PathBuf; use std::process; - -use docopt::Docopt; -use failure::Error; use wasm_bindgen_cli_support::Bindgen; // no need for jemalloc bloat in this binary (and we don't need speed) diff --git a/crates/cli/src/bin/wasm2es6js.rs b/crates/cli/src/bin/wasm2es6js.rs index b0ad9bf5cd37..8ad32cb4bd36 100644 --- a/crates/cli/src/bin/wasm2es6js.rs +++ b/crates/cli/src/bin/wasm2es6js.rs @@ -1,16 +1,10 @@ -#[macro_use] -extern crate serde_derive; -extern crate docopt; -extern crate failure; -extern crate wasm_bindgen_cli_support; - +use docopt::Docopt; +use failure::{Error, ResultExt}; +use serde::Deserialize; use std::fs; use std::path::PathBuf; use std::process; -use docopt::Docopt; -use failure::{Error, ResultExt}; - // no need for jemalloc bloat in this binary (and we don't need speed) #[global_allocator] static ALLOC: std::alloc::System = std::alloc::System; @@ -70,7 +64,7 @@ fn rmain(args: &Args) -> Result<(), Error> { .generate(&wasm)?; if args.flag_typescript { - let ts = object.typescript(); + let ts = object.typescript()?; write(&args, "d.ts", ts.as_bytes(), false)?; } diff --git a/crates/futures/src/lib.rs b/crates/futures/src/lib.rs index d202519fb6b0..890e41ece0ec 100644 --- a/crates/futures/src/lib.rs +++ b/crates/futures/src/lib.rs @@ -112,8 +112,8 @@ use std::rc::Rc; use std::sync::Arc; use futures::executor::{self, Notify, Spawn}; -use futures::prelude::*; use futures::future; +use futures::prelude::*; use futures::sync::oneshot; use js_sys::{Function, Promise}; use wasm_bindgen::prelude::*; @@ -389,7 +389,7 @@ fn _future_to_promise(future: Box>) -> P /// This function has the same panic behavior as `future_to_promise`. pub fn spawn_local(future: F) where - F: Future + 'static, + F: Future + 'static, { future_to_promise( future diff --git a/crates/gc/Cargo.toml b/crates/gc/Cargo.toml deleted file mode 100644 index b21961e195c7..000000000000 --- a/crates/gc/Cargo.toml +++ /dev/null @@ -1,27 +0,0 @@ -[package] -name = "wasm-bindgen-gc" -version = "0.2.33" -authors = ["The wasm-bindgen Developers"] -license = "MIT/Apache-2.0" -repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/gc" -homepage = "https://rustwasm.github.io/wasm-bindgen/" -documentation = "https://docs.rs/wasm-bindgen-gc" -description = """ -Support for removing unused items from a wasm executable -""" - -[dependencies] -parity-wasm = "0.36" -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/bitvec.rs b/crates/gc/src/bitvec.rs deleted file mode 100644 index b3585466eb58..000000000000 --- a/crates/gc/src/bitvec.rs +++ /dev/null @@ -1,73 +0,0 @@ -use std::mem; - -type T = usize; - -const BITS: usize = mem::size_of::() * 8; - -pub struct BitSet { - bits: Vec, -} - -impl BitSet { - pub fn new() -> BitSet { - BitSet { bits: Vec::new() } - } - - pub fn insert(&mut self, i: u32) -> bool { - let i = i as usize; - let idx = i / BITS; - let bit = 1 << (i % BITS); - if self.bits.len() <= idx { - self.bits.resize(idx + 1, 0); - } - let slot = &mut self.bits[idx]; - if *slot & bit != 0 { - false - } else { - *slot |= bit; - true - } - } - - 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; - let bit = 1 << (i % BITS); - self.bits.get(idx).map(|x| *x & bit != 0).unwrap_or(false) - } -} - -impl Default for BitSet { - fn default() -> BitSet { - BitSet::new() - } -} - -#[cfg(test)] -mod tests { - use super::BitSet; - - #[test] - fn simple() { - let mut x = BitSet::new(); - assert!(!x.contains(&1)); - assert!(!x.contains(&0)); - assert!(!x.contains(&3)); - assert!(x.insert(3)); - assert!(x.contains(&3)); - assert!(!x.insert(3)); - assert!(x.contains(&3)); - assert!(!x.contains(&1)); - assert!(x.insert(2)); - assert!(x.contains(&2)); - } -} diff --git a/crates/gc/src/lib.rs b/crates/gc/src/lib.rs deleted file mode 100644 index 851380e42a47..000000000000 --- a/crates/gc/src/lib.rs +++ /dev/null @@ -1,1047 +0,0 @@ -//! NO GUARANTEES ARE MADE ABOUT THE BACKWARDS COMPATIBILITY OF THIS LIBRARY -//! -//! Internal library of `wasm-gc` to remove unreferenced items from a wasm -//! executable. - -extern crate parity_wasm; -#[macro_use] -extern crate log; -extern crate rustc_demangle; - -use std::collections::{HashMap, HashSet}; -use std::iter; -use std::mem; - -use parity_wasm::elements::*; - -use bitvec::BitSet; - -mod bitvec; - -pub struct Config { - demangle: bool, - keep_debug: bool, -} - -impl Config { - /// Creates a blank slate of configuration, ready to gc wasm files. - pub fn new() -> Config { - Config { - demangle: true, - keep_debug: false, - } - } - - /// Configures whether or not this will demangle symbols as part of the gc - /// pass. - pub fn demangle(&mut self, demangle: bool) -> &mut Self { - self.demangle = demangle; - self - } - - /// Configures whether or not debug sections will be preserved. - pub fn keep_debug(&mut self, keep_debug: bool) -> &mut Self { - self.keep_debug = keep_debug; - self - } - - pub fn run(&mut self, module: &mut Module) { - run(self, module); - } -} - -fn run(config: &mut Config, module: &mut Module) { - let mut analysis = { - let mut cx = LiveContext::new(&module); - cx.blacklist.insert("rust_eh_personality"); - cx.blacklist.insert("__indirect_function_table"); - cx.blacklist.insert("__heap_base"); - cx.blacklist.insert("__data_end"); - - // 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 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()) { - continue; - } - cx.add_export_entry(entry, i as u32); - } - } - - // ... and finally, the start function - if let Some(i) = module.start_section() { - cx.add_function(i); - } - - cx.analysis - }; - - 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 { .. } => { - info!("unparsed section"); - continue; - } - Section::Custom(ref s) => { - if !cx.config.keep_debug && s.name().starts_with(".debug_") { - false - } else { - info!("skipping custom section: {}", s.name()); - continue; - } - } - Section::Reloc(..) => { - info!("skipping reloc section"); - continue; - } - Section::DataCount(..) => { - info!("skipping data count section"); - continue; - } - Section::Type(ref mut s) => cx.remap_type_section(s), - Section::Import(ref mut s) => cx.remap_import_section(s), - Section::Function(ref mut s) => cx.remap_function_section(s), - Section::Table(ref mut s) => cx.remap_table_section(s), - Section::Memory(ref mut s) => cx.remap_memory_section(s), - Section::Global(ref mut s) => cx.remap_global_section(s), - Section::Export(ref mut s) => cx.remap_export_section(s), - Section::Start(ref mut i) => { - cx.remap_function_idx(i); - true - } - Section::Element(ref mut s) => cx.remap_element_section(s), - Section::Code(ref mut s) => cx.remap_code_section(s), - Section::Data(ref mut s) => cx.remap_data_section(s), - Section::Name(ref mut s) => { - cx.remap_name_section(s); - true - } - }; - if !retain { - debug!("remove empty section"); - module.sections_mut().remove(i); - } - } - - gc_locals(module); -} - -#[derive(Default)] -struct Analysis { - tables: BitSet, - memories: BitSet, - globals: BitSet, - types: BitSet, - imports: BitSet, - exports: BitSet, - functions: BitSet, - elements: BitSet, - data_segments: BitSet, - imported_functions: u32, - imported_globals: u32, - imported_memories: u32, - imported_tables: u32, -} - -struct LiveContext<'a> { - blacklist: HashSet<&'static str>, - function_section: Option<&'a FunctionSection>, - type_section: Option<&'a TypeSection>, - data_section: Option<&'a DataSection>, - element_section: Option<&'a ElementSection>, - code_section: Option<&'a CodeSection>, - table_section: Option<&'a TableSection>, - global_section: Option<&'a GlobalSection>, - import_section: Option<&'a ImportSection>, - analysis: Analysis, -} - -impl<'a> LiveContext<'a> { - fn new(module: &'a Module) -> LiveContext<'a> { - let (mut tables, mut memories, mut globals, mut functions) = (0, 0, 0, 0); - if let Some(imports) = module.import_section() { - for entry in imports.entries() { - match entry.external() { - External::Memory(_) => memories += 1, - External::Table(_) => tables += 1, - External::Function(_) => functions += 1, - External::Global(_) => globals += 1, - } - } - } - - LiveContext { - blacklist: HashSet::new(), - function_section: module.function_section(), - type_section: module.type_section(), - data_section: module.data_section(), - element_section: module.elements_section(), - code_section: module.code_section(), - table_section: module.table_section(), - global_section: module.global_section(), - import_section: module.import_section(), - analysis: Analysis { - imported_functions: functions, - imported_globals: globals, - imported_tables: tables, - imported_memories: memories, - ..Analysis::default() - }, - } - } - - fn add_function(&mut self, idx: u32) { - if !self.analysis.functions.insert(idx) { - return; - } - debug!("adding function: {}", idx); - - if idx < self.analysis.imported_functions { - let imports = self.import_section.unwrap(); - let (i, import) = imports - .entries() - .iter() - .enumerate() - .filter(|&(_, i)| match *i.external() { - External::Function(_) => true, - _ => false, - }) - .skip(idx as usize) - .next() - .expect("expected an imported function with this index"); - let i = i as u32; - return self.add_import_entry(import, i); - } - let idx = idx - self.analysis.imported_functions; - let functions = self.function_section.expect("no functions section"); - self.add_type(functions.entries()[idx as usize].type_ref()); - let codes = self.code_section.expect("no codes section"); - self.add_func_body(&codes.bodies()[idx as usize]); - } - - fn add_table(&mut self, idx: u32) { - if !self.analysis.tables.insert(idx) { - return; - } - debug!("adding table: {}", idx); - - // Add all element segments that initialize this table - if let Some(elements) = self.element_section { - let iter = elements - .entries() - .iter() - .enumerate() - .filter(|(_, d)| !d.passive() && d.index() == idx); - for (i, _) in iter { - self.add_element_segment(i as u32); - } - } - - if idx < self.analysis.imported_tables { - let imports = self.import_section.unwrap(); - let (i, import) = imports - .entries() - .iter() - .enumerate() - .filter(|&(_, i)| match *i.external() { - External::Table(_) => true, - _ => false, - }) - .skip(idx as usize) - .next() - .expect("expected an imported table with this index"); - let i = i as u32; - return self.add_import_entry(import, i); - } - let idx = idx - self.analysis.imported_tables; - let tables = self.table_section.expect("no table section"); - let table = &tables.entries()[idx as usize]; - drop(table); - } - - fn add_memory(&mut self, idx: u32) { - if !self.analysis.memories.insert(idx) { - return; - } - debug!("adding memory: {}", idx); - - // Add all data segments that initialize this memory - if let Some(data) = self.data_section { - let iter = data - .entries() - .iter() - .enumerate() - .filter(|(_, d)| !d.passive() && d.index() == idx); - for (i, _) in iter { - self.add_data_segment(i as u32); - } - } - - // ... and add the import if it's an imported memory .. - if idx < self.analysis.imported_memories { - let imports = self.import_section.unwrap(); - let (i, import) = imports - .entries() - .iter() - .enumerate() - .filter(|&(_, i)| match *i.external() { - External::Memory(_) => true, - _ => false, - }) - .skip(idx as usize) - .next() - .expect("expected an imported memory with this index"); - let i = i as u32; - return self.add_import_entry(import, i); - } - - // ... and if it's not imported that's it! - } - - fn add_global(&mut self, idx: u32) { - if !self.analysis.globals.insert(idx) { - return; - } - debug!("adding global: {}", idx); - - if idx < self.analysis.imported_globals { - let imports = self.import_section.unwrap(); - let (i, import) = imports - .entries() - .iter() - .enumerate() - .filter(|&(_, i)| match *i.external() { - External::Global(_) => true, - _ => false, - }) - .skip(idx as usize) - .next() - .expect("expected an imported global with this index"); - let i = i as u32; - return self.add_import_entry(import, i); - } - - let idx = idx - self.analysis.imported_globals; - let globals = self.global_section.expect("no global section"); - let global = &globals.entries()[idx as usize]; - self.add_global_type(global.global_type()); - self.add_init_expr(global.init_expr()); - } - - fn add_global_type(&mut self, t: &GlobalType) { - self.add_value_type(&t.content_type()); - } - - fn add_init_expr(&mut self, t: &InitExpr) { - for opcode in t.code() { - self.add_opcode(opcode); - } - } - - fn add_type(&mut self, idx: u32) { - if !self.analysis.types.insert(idx) { - return; - } - let types = self.type_section.expect("no types section"); - match types.types()[idx as usize] { - Type::Function(ref f) => { - for param in f.params() { - self.add_value_type(param); - } - if let Some(ref ret) = f.return_type() { - self.add_value_type(ret); - } - } - } - } - - fn add_value_type(&mut self, value: &ValueType) { - match *value { - ValueType::I32 => {} - ValueType::I64 => {} - ValueType::F32 => {} - ValueType::F64 => {} - ValueType::V128 => {} - } - } - - fn add_func_body(&mut self, body: &FuncBody) { - for local in body.locals() { - self.add_value_type(&local.value_type()); - } - self.add_opcodes(body.code()); - } - - fn add_opcodes(&mut self, code: &Instructions) { - for opcode in code.elements() { - self.add_opcode(opcode); - } - } - - fn add_opcode(&mut self, code: &Instruction) { - match *code { - Instruction::Block(ref b) | Instruction::Loop(ref b) | Instruction::If(ref b) => { - self.add_block_type(b) - } - Instruction::Call(f) => self.add_function(f), - Instruction::CallIndirect(t, _) => { - self.add_type(t); - self.add_table(0); - } - 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); - } - _ => {} - } - } - - fn add_block_type(&mut self, bt: &BlockType) { - match *bt { - BlockType::Value(ref v) => self.add_value_type(v), - BlockType::NoResult => {} - } - } - - fn add_export_entry(&mut self, entry: &ExportEntry, idx: u32) { - if !self.analysis.exports.insert(idx) { - return; - } - match *entry.internal() { - Internal::Function(i) => self.add_function(i), - Internal::Table(i) => self.add_table(i), - Internal::Memory(i) => self.add_memory(i), - Internal::Global(i) => self.add_global(i), - } - } - - fn add_import_entry(&mut self, entry: &ImportEntry, idx: u32) { - if !self.analysis.imports.insert(idx) { - return; - } - debug!("adding import: {}", idx); - match *entry.external() { - External::Function(i) => self.add_type(i), - External::Table(_) => {} - External::Memory(_) => {} - External::Global(g) => self.add_value_type(&g.content_type()), - } - } - - 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) { - 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 !seg.passive() { - if let Some(offset) = seg.offset() { - self.add_table(seg.index()); - self.add_init_expr(offset); - } - } - } -} - -struct RemapContext<'a> { - analysis: &'a Analysis, - config: &'a Config, - functions: Vec, - globals: Vec, - types: Vec, - tables: Vec, - memories: Vec, - elements: Vec, - data_segments: Vec, -} - -impl<'a> 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; - let mut globals = Vec::new(); - let mut types = Vec::new(); - let mut ntables = 0; - let mut tables = Vec::new(); - let mut nmemories = 0; - let mut memories = Vec::new(); - - if let Some(s) = m.type_section() { - let mut ntypes = 0; - let mut map = HashMap::new(); - for (i, ty) in s.types().iter().enumerate() { - 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); - types.push(ntypes); - ntypes += 1; - } else { - debug!("gc type {}", i); - types.push(u32::max_value()); - } - } - } - if let Some(s) = m.import_section() { - for (i, import) in s.entries().iter().enumerate() { - let (dst, ndst) = match *import.external() { - External::Function(_) => (&mut functions, &mut nfunctions), - External::Table(_) => (&mut tables, &mut ntables), - External::Memory(_) => (&mut memories, &mut nmemories), - External::Global(_) => (&mut globals, &mut nglobals), - }; - if analysis.imports.contains(&(i as u32)) { - dst.push(*ndst); - *ndst += 1; - } else { - debug!("gc import {}", i); - dst.push(u32::max_value()); - } - } - } - if let Some(s) = m.function_section() { - for i in 0..(s.entries().len() as u32) { - if analysis - .functions - .contains(&(i + analysis.imported_functions)) - { - functions.push(nfunctions); - nfunctions += 1; - } else { - debug!("gc function {}", i); - functions.push(u32::max_value()); - } - } - } - if let Some(s) = m.global_section() { - for i in 0..(s.entries().len() as u32) { - if analysis.globals.contains(&(i + analysis.imported_globals)) { - globals.push(nglobals); - nglobals += 1; - } else { - debug!("gc global {}", i); - globals.push(u32::max_value()); - } - } - } - if let Some(s) = m.table_section() { - for i in 0..(s.entries().len() as u32) { - if analysis.tables.contains(&(i + analysis.imported_tables)) { - tables.push(ntables); - ntables += 1; - } else { - debug!("gc table {}", i); - tables.push(u32::max_value()); - } - } - } - if let Some(s) = m.memory_section() { - for i in 0..(s.entries().len() as u32) { - if analysis - .memories - .contains(&(i + analysis.imported_memories)) - { - memories.push(nmemories); - nmemories += 1; - } else { - debug!("gc memory {}", i); - memories.push(u32::max_value()); - } - } - } - - 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, - globals, - memories, - tables, - types, - config, - elements, - data_segments, - } - } - - fn retain(&self, set: &BitSet, list: &mut Vec, name: &str, offset: u32) { - for i in (0..list.len()).rev().map(|x| x as u32) { - if !set.contains(&(i + offset)) { - debug!("removing {} {}", name, i); - list.remove(i as usize); - } - } - } - - fn remap_type_section(&self, s: &mut TypeSection) -> bool { - self.retain(&self.analysis.types, s.types_mut(), "type", 0); - for t in s.types_mut() { - self.remap_type(t); - } - s.types().len() > 0 - } - - fn remap_type(&self, t: &mut Type) { - match *t { - Type::Function(ref mut t) => self.remap_function_type(t), - } - } - - fn remap_function_type(&self, t: &mut FunctionType) { - for param in t.params_mut() { - self.remap_value_type(param); - } - if let Some(m) = t.return_type_mut().as_mut() { - self.remap_value_type(m); - } - } - - fn remap_value_type(&self, t: &mut ValueType) { - match t { - ValueType::I32 => {} - ValueType::I64 => {} - ValueType::F32 => {} - ValueType::F64 => {} - ValueType::V128 => {} - } - } - - fn remap_import_section(&self, s: &mut ImportSection) -> bool { - self.retain(&self.analysis.imports, s.entries_mut(), "import", 0); - for i in s.entries_mut() { - self.remap_import_entry(i); - } - s.entries().len() > 0 - } - - fn remap_import_entry(&self, s: &mut ImportEntry) { - debug!("remap import entry {:?}", s); - match *s.external_mut() { - External::Function(ref mut f) => self.remap_type_idx(f), - External::Table(_) => {} - External::Memory(_) => {} - External::Global(_) => {} - } - } - - fn remap_function_section(&self, s: &mut FunctionSection) -> bool { - self.retain( - &self.analysis.functions, - s.entries_mut(), - "function", - self.analysis.imported_functions, - ); - for f in s.entries_mut() { - self.remap_func(f); - } - s.entries().len() > 0 - } - - fn remap_func(&self, f: &mut Func) { - self.remap_type_idx(f.type_ref_mut()); - } - - fn remap_table_section(&self, s: &mut TableSection) -> bool { - self.retain( - &self.analysis.tables, - s.entries_mut(), - "table", - self.analysis.imported_tables, - ); - for t in s.entries_mut() { - drop(t); // TODO - } - s.entries().len() > 0 - } - - fn remap_memory_section(&self, s: &mut MemorySection) -> bool { - self.retain( - &self.analysis.memories, - s.entries_mut(), - "memory", - self.analysis.imported_memories, - ); - for m in s.entries_mut() { - drop(m); // TODO - } - s.entries().len() > 0 - } - - fn remap_global_section(&self, s: &mut GlobalSection) -> bool { - self.retain( - &self.analysis.globals, - s.entries_mut(), - "global", - self.analysis.imported_globals, - ); - for g in s.entries_mut() { - self.remap_global_entry(g); - } - s.entries().len() > 0 - } - - fn remap_global_entry(&self, s: &mut GlobalEntry) { - self.remap_global_type(s.global_type_mut()); - self.remap_init_expr(s.init_expr_mut()); - } - - fn remap_global_type(&self, s: &mut GlobalType) { - drop(s); - } - - fn remap_init_expr(&self, s: &mut InitExpr) { - for code in s.code_mut() { - self.remap_instruction(code); - } - } - - fn remap_export_section(&self, s: &mut ExportSection) -> bool { - self.retain(&self.analysis.exports, s.entries_mut(), "export", 0); - for s in s.entries_mut() { - self.remap_export_entry(s); - } - s.entries().len() > 0 - } - - fn remap_export_entry(&self, s: &mut ExportEntry) { - match *s.internal_mut() { - Internal::Function(ref mut i) => self.remap_function_idx(i), - Internal::Table(ref mut i) => self.remap_table_idx(i), - Internal::Memory(ref mut i) => self.remap_memory_idx(i), - Internal::Global(ref mut i) => self.remap_global_idx(i), - } - } - - 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); - } - true - } - - fn remap_element_segment(&self, s: &mut ElementSegment) { - 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); - } - if let Some(offset) = s.offset_mut() { - self.remap_init_expr(offset); - } - } - - fn remap_code_section(&self, s: &mut CodeSection) -> bool { - self.retain( - &self.analysis.functions, - s.bodies_mut(), - "code", - self.analysis.imported_functions, - ); - for s in s.bodies_mut() { - self.remap_func_body(s); - } - s.bodies().len() > 0 - } - - fn remap_func_body(&self, b: &mut FuncBody) { - self.remap_code(b.code_mut()); - } - - fn remap_code(&self, c: &mut Instructions) { - for i in c.elements_mut() { - self.remap_instruction(i); - } - } - - fn remap_instruction(&self, i: &mut Instruction) { - match *i { - Instruction::Block(ref mut b) - | Instruction::Loop(ref mut b) - | Instruction::If(ref mut b) => self.remap_block_type(b), - Instruction::Call(ref mut f) => self.remap_function_idx(f), - 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) - } - _ => {} - } - } - - fn remap_block_type(&self, bt: &mut BlockType) { - match *bt { - BlockType::Value(ref mut v) => self.remap_value_type(v), - BlockType::NoResult => {} - } - } - - 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); - } - true - } - - fn remap_data_segment(&self, segment: &mut DataSegment) { - let mut i = segment.index(); - self.remap_memory_idx(&mut i); - assert_eq!(segment.index(), i); - if let Some(offset) = segment.offset_mut() { - self.remap_init_expr(offset); - } - } - - fn remap_type_idx(&self, i: &mut u32) { - *i = self.types[*i as usize]; - assert!(*i != u32::max_value()); - } - - fn remap_function_idx(&self, i: &mut u32) { - *i = self.functions[*i as usize]; - assert!(*i != u32::max_value()); - } - - fn remap_global_idx(&self, i: &mut u32) { - trace!("global {} => {}", *i, self.globals[*i as usize]); - *i = self.globals[*i as usize]; - assert!(*i != u32::max_value()); - } - - fn remap_table_idx(&self, i: &mut u32) { - *i = self.tables[*i as usize]; - assert!(*i != u32::max_value()); - } - - fn remap_memory_idx(&self, i: &mut u32) { - *i = self.memories[*i as usize]; - 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(_) => {} - NameSection::Function(ref mut f) => { - let map = f.names_mut(); - let new = IndexMap::with_capacity(map.len()); - for (idx, name) in mem::replace(map, new) { - let new_idx = self.functions[idx as usize]; - let name = if self.config.demangle { - rustc_demangle::demangle(&name).to_string() - } else { - name - }; - if new_idx != u32::max_value() { - map.insert(new_idx, name); - } - } - } - NameSection::Local(ref mut l) => { - let map = l.local_names_mut(); - let new = IndexMap::with_capacity(map.len()); - for (idx, value) in mem::replace(map, new) { - let new_idx = self.functions[idx as usize]; - if new_idx != u32::max_value() { - map.insert(new_idx, value); - } - } - } - NameSection::Unparsed { .. } => {} - } - } -} - -fn gc_locals(module: &mut Module) { - let mut code = None; - let mut types = None; - let mut functions = None; - let mut locals = None; - let mut imported_functions = 0; - for section in module.sections_mut() { - match section { - Section::Import(s) => imported_functions = s.functions(), - Section::Code(s) => code = Some(s), - Section::Function(s) => functions = Some(s), - Section::Type(s) => types = Some(s), - Section::Name(NameSection::Local(s)) => locals = Some(s), - _ => {} - } - } - let code = match code { - Some(s) => s, - None => return, - }; - let types = types.unwrap(); - let functions = functions.unwrap(); - - for (i, body) in code.bodies_mut().iter_mut().enumerate() { - let ty_idx = functions.entries()[i].type_ref(); - let ty = match &types.types()[ty_idx as usize] { - Type::Function(f) => f, - }; - gc_body((i + imported_functions) as u32, body, ty, &mut locals); - } -} - -/// Each body of a function has a list of locals, and each local has a type and -/// a number of how many locals of this type there are. The purpose of this is -/// to collapse something like: -/// -/// Local::new(1, ValueType::I32); -/// Local::new(1, ValueType::I32); -/// -/// to ... -/// -/// Local::new(2, ValueType::I32); -/// -/// to encode this a bit more compactly -fn gc_body( - idx: u32, - body: &mut FuncBody, - ty: &FunctionType, - names: &mut Option<&mut LocalNameSection>, -) { - let mut local_tys = ty.params().to_vec(); - for local in body.locals_mut().drain(..) { - local_tys.extend(iter::repeat(local.value_type()).take(local.count() as usize)); - } - let mut used = vec![false; local_tys.len()]; - - for instr in body.code().elements() { - match instr { - Instruction::GetLocal(i) => used[*i as usize] = true, - Instruction::SetLocal(i) => used[*i as usize] = true, - Instruction::TeeLocal(i) => used[*i as usize] = true, - _ => {} - } - } - - let mut map = vec![None; local_tys.len()]; - for i in 0..ty.params().len() { - map[i] = Some(i as u32); - } - let mut next = ty.params().len() as u32; - for i in ty.params().len()..used.len() { - if !used[i] { - continue; - } - // We're using this local, so map it to the next index (the lowest). - // Find all other locals with the same type and lump then into our - // `Local` declaration. - let before = next; - map[i] = Some(next); - next += 1; - for j in i + 1..used.len() { - if used[j] && local_tys[i] == local_tys[j] { - used[j] = false; // make sure we don't visit this later - map[j] = Some(next); - next += 1; - } - } - body.locals_mut() - .push(Local::new((next - before) as u32, local_tys[i])); - } - - for instr in body.code_mut().elements_mut() { - let get = |i: &u32| map[*i as usize].unwrap(); - match instr { - Instruction::GetLocal(i) => *i = get(i), - Instruction::SetLocal(i) => *i = get(i), - Instruction::TeeLocal(i) => *i = get(i), - _ => {} - } - } - - if let Some(names) = names { - remap_local_names(idx, &map, names); - } -} - -fn remap_local_names(idx: u32, map: &[Option], names: &mut LocalNameSection) { - let prev = match names.local_names_mut().remove(idx) { - Some(map) => map, - None => return, - }; - let mut new = IndexMap::with_capacity(map.len()); - for (idx, name) in prev { - if let Some(new_idx) = map[idx as usize] { - new.insert(new_idx, name); - } - } - names.local_names_mut().insert(idx, new); -} diff --git a/crates/gc/tests/all.rs b/crates/gc/tests/all.rs deleted file mode 100644 index 9f016775d165..000000000000 --- a/crates/gc/tests/all.rs +++ /dev/null @@ -1,146 +0,0 @@ -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 parity_wasm::elements::Module; -use rayon::prelude::*; -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("--enable-bulk-memory") - .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("--enable-bulk-memory") - .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 deleted file mode 100644 index eb3bdf89d285..000000000000 --- a/crates/gc/tests/wat/dont-remove-func1.wat +++ /dev/null @@ -1,11 +0,0 @@ -(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 deleted file mode 100644 index b97067e2ec11..000000000000 --- a/crates/gc/tests/wat/dont-remove-func2.wat +++ /dev/null @@ -1,16 +0,0 @@ -(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 deleted file mode 100644 index 313671a3a37d..000000000000 --- a/crates/gc/tests/wat/import-and-type.wat +++ /dev/null @@ -1,21 +0,0 @@ -(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 deleted file mode 100644 index 16e1bf077102..000000000000 --- a/crates/gc/tests/wat/import-memory.wat +++ /dev/null @@ -1,8 +0,0 @@ -(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 deleted file mode 100644 index 0310d9dd0b30..000000000000 --- a/crates/gc/tests/wat/keep-data.wat +++ /dev/null @@ -1,10 +0,0 @@ -(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 deleted file mode 100644 index 7aa66867625e..000000000000 --- a/crates/gc/tests/wat/keep-global.wat +++ /dev/null @@ -1,11 +0,0 @@ -(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 deleted file mode 100644 index 0df7e52a7b34..000000000000 --- a/crates/gc/tests/wat/keep-memory.wat +++ /dev/null @@ -1,8 +0,0 @@ -(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-passive-memory-segment.wat b/crates/gc/tests/wat/keep-passive-memory-segment.wat deleted file mode 100644 index d3ae85f3a54d..000000000000 --- a/crates/gc/tests/wat/keep-passive-memory-segment.wat +++ /dev/null @@ -1,27 +0,0 @@ -(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 deleted file mode 100644 index b37d5d05c5d2..000000000000 --- a/crates/gc/tests/wat/keep-passive-segment.wat +++ /dev/null @@ -1,30 +0,0 @@ -(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/keep-set-global.wat b/crates/gc/tests/wat/keep-set-global.wat deleted file mode 100644 index c23d833ba602..000000000000 --- a/crates/gc/tests/wat/keep-set-global.wat +++ /dev/null @@ -1,23 +0,0 @@ -(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 -;; global.set 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 deleted file mode 100644 index 60795979e8f4..000000000000 --- a/crates/gc/tests/wat/keep-start.wat +++ /dev/null @@ -1,14 +0,0 @@ -(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 deleted file mode 100644 index 592bef768428..000000000000 --- a/crates/gc/tests/wat/keep-table.wat +++ /dev/null @@ -1,10 +0,0 @@ -(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 deleted file mode 100644 index e264d9139df0..000000000000 --- a/crates/gc/tests/wat/keep-table2.wat +++ /dev/null @@ -1,17 +0,0 @@ -(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 deleted file mode 100644 index 2ce7d099b002..000000000000 --- a/crates/gc/tests/wat/locals-compressed.wat +++ /dev/null @@ -1,39 +0,0 @@ -(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) -;; local.get 0 -;; local.get 4 -;; local.get 1 -;; local.get 7 -;; local.get 9 -;; local.get 2 -;; local.get 5 -;; local.get 10 -;; local.get 3 -;; local.get 6 -;; local.get 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 deleted file mode 100644 index 7ed399ba0bd4..000000000000 --- a/crates/gc/tests/wat/preserve-local1.wat +++ /dev/null @@ -1,16 +0,0 @@ -(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) -;; local.get 0) -;; (export "foo" (func $foo))) -;; STDOUT diff --git a/crates/gc/tests/wat/preserve-local2.wat b/crates/gc/tests/wat/preserve-local2.wat deleted file mode 100644 index 5ef6564ff375..000000000000 --- a/crates/gc/tests/wat/preserve-local2.wat +++ /dev/null @@ -1,18 +0,0 @@ -(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) -;; local.get 0 -;; local.set 1) -;; (export "foo" (func $foo))) -;; STDOUT diff --git a/crates/gc/tests/wat/preserve-local3.wat b/crates/gc/tests/wat/preserve-local3.wat deleted file mode 100644 index 8738c3b58834..000000000000 --- a/crates/gc/tests/wat/preserve-local3.wat +++ /dev/null @@ -1,18 +0,0 @@ -(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) -;; local.get 0 -;; local.tee 1) -;; (export "foo" (func $foo))) -;; STDOUT diff --git a/crates/gc/tests/wat/remove-func.wat b/crates/gc/tests/wat/remove-func.wat deleted file mode 100644 index e37c50177b3d..000000000000 --- a/crates/gc/tests/wat/remove-func.wat +++ /dev/null @@ -1,7 +0,0 @@ -(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 deleted file mode 100644 index 406c85230c8e..000000000000 --- a/crates/gc/tests/wat/remove-heap-base.wat +++ /dev/null @@ -1,9 +0,0 @@ -(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 deleted file mode 100644 index b0592b221af9..000000000000 --- a/crates/gc/tests/wat/remove-import-function.wat +++ /dev/null @@ -1,26 +0,0 @@ -(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 deleted file mode 100644 index e0ccc861e389..000000000000 --- a/crates/gc/tests/wat/remove-import-global.wat +++ /dev/null @@ -1,7 +0,0 @@ -(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 deleted file mode 100644 index 852642e22b43..000000000000 --- a/crates/gc/tests/wat/remove-import-globals.wat +++ /dev/null @@ -1,35 +0,0 @@ -(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) -;; global.get 0 -;; drop -;; global.get 1 -;; drop -;; global.get 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 deleted file mode 100644 index 561ac9ea0c26..000000000000 --- a/crates/gc/tests/wat/remove-import-table.wat +++ /dev/null @@ -1,11 +0,0 @@ -(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 deleted file mode 100644 index 759b2782486a..000000000000 --- a/crates/gc/tests/wat/remove-local.wat +++ /dev/null @@ -1,13 +0,0 @@ -(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 deleted file mode 100644 index ca3d303dc93b..000000000000 --- a/crates/gc/tests/wat/remove-table.wat +++ /dev/null @@ -1,7 +0,0 @@ -(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/remove-unused-passive-segment.wat b/crates/gc/tests/wat/remove-unused-passive-segment.wat deleted file mode 100644 index 782adb3c9d7b..000000000000 --- a/crates/gc/tests/wat/remove-unused-passive-segment.wat +++ /dev/null @@ -1,11 +0,0 @@ -(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/remove-unused-type.wat b/crates/gc/tests/wat/remove-unused-type.wat deleted file mode 100644 index b80d55d9b3ee..000000000000 --- a/crates/gc/tests/wat/remove-unused-type.wat +++ /dev/null @@ -1,34 +0,0 @@ -(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 diff --git a/crates/gc/tests/wat/renumber-functions.wat b/crates/gc/tests/wat/renumber-functions.wat deleted file mode 100644 index 1d19197ab5c0..000000000000 --- a/crates/gc/tests/wat/renumber-functions.wat +++ /dev/null @@ -1,16 +0,0 @@ -(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 deleted file mode 100644 index 516e64ad8c27..000000000000 --- a/crates/gc/tests/wat/renumber-globals.wat +++ /dev/null @@ -1,16 +0,0 @@ -(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) -;; global.get 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 deleted file mode 100644 index f6db1ec0c04c..000000000000 --- a/crates/gc/tests/wat/renumber-locals.wat +++ /dev/null @@ -1,16 +0,0 @@ -(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) -;; local.get 0) -;; (export "foo" (func $foo))) -;; STDOUT diff --git a/crates/gc/tests/wat/renumber-types.wat b/crates/gc/tests/wat/renumber-types.wat deleted file mode 100644 index 001847db894d..000000000000 --- a/crates/gc/tests/wat/renumber-types.wat +++ /dev/null @@ -1,13 +0,0 @@ -(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 deleted file mode 100644 index 84e867624293..000000000000 --- a/crates/gc/tests/wat/smoke.wat +++ /dev/null @@ -1,5 +0,0 @@ -(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 deleted file mode 100644 index 6dd88a8ab729..000000000000 --- a/crates/gc/tests/wat/table-elem.wat +++ /dev/null @@ -1,15 +0,0 @@ -(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 deleted file mode 100644 index 1ad663d78e32..000000000000 --- a/crates/gc/tests/wat/table-elem2.wat +++ /dev/null @@ -1,25 +0,0 @@ -(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 diff --git a/crates/macro-support/src/lib.rs b/crates/macro-support/src/lib.rs index c9c704946541..cf807e86736b 100644 --- a/crates/macro-support/src/lib.rs +++ b/crates/macro-support/src/lib.rs @@ -13,11 +13,11 @@ extern crate wasm_bindgen_shared as shared; use backend::{Diagnostic, TryToTokens}; pub use parser::BindgenAttrs; -use quote::ToTokens; use parser::MacroParse; use proc_macro2::TokenStream; -use syn::parse::{Parse, ParseStream, Result as SynResult}; +use quote::ToTokens; use quote::TokenStreamExt; +use syn::parse::{Parse, ParseStream, Result as SynResult}; mod parser; @@ -41,7 +41,10 @@ pub fn expand(attr: TokenStream, input: TokenStream) -> Result Result { +pub fn expand_class_marker( + attr: TokenStream, + input: TokenStream, +) -> Result { parser::reset_attrs_used(); let mut item = syn::parse2::(input)?; let opts: ClassMarker = syn::parse2(attr)?; @@ -62,11 +65,9 @@ pub fn expand_class_marker(attr: TokenStream, input: TokenStream) -> Result true, - _ => false, - } + tokens.append_all(item.attrs.iter().filter(|attr| match attr.style { + syn::AttrStyle::Outer => true, + _ => false, })); item.vis.to_tokens(&mut tokens); item.sig.to_tokens(&mut tokens); @@ -75,17 +76,15 @@ pub fn expand_class_marker(attr: TokenStream, input: TokenStream) -> Result true, - _ => false, - } + tokens.append_all(item.attrs.iter().filter(|attr| match attr.style { + syn::AttrStyle::Inner(_) => true, + _ => false, })); tokens.append_all(&item.block.stmts); }); if let Some(err) = err { - return Err(err) + return Err(err); } Ok(tokens) diff --git a/crates/macro-support/src/parser.rs b/crates/macro-support/src/parser.rs index c3ea5ad9b814..b87f6d1a8561 100644 --- a/crates/macro-support/src/parser.rs +++ b/crates/macro-support/src/parser.rs @@ -796,7 +796,11 @@ impl<'a> MacroParse<(Option, &'a mut TokenStream)> for syn::Item { } impl<'a> MacroParse for &'a mut syn::ItemImpl { - fn macro_parse(self, _program: &mut ast::Program, opts: BindgenAttrs) -> Result<(), Diagnostic> { + fn macro_parse( + self, + _program: &mut ast::Program, + opts: BindgenAttrs, + ) -> Result<(), Diagnostic> { if self.defaultness.is_some() { bail_span!( self.defaultness, @@ -851,7 +855,7 @@ impl<'a> MacroParse for &'a mut syn::ItemImpl { fn prepare_for_impl_recursion( item: &mut syn::ImplItem, class: &Ident, - impl_opts: &BindgenAttrs + impl_opts: &BindgenAttrs, ) -> Result<(), Diagnostic> { let method = match item { syn::ImplItem::Method(m) => m, @@ -884,13 +888,16 @@ fn prepare_for_impl_recursion( .map(|s| s.0.to_string()) .unwrap_or(class.to_string()); - method.attrs.insert(0, syn::Attribute { - pound_token: Default::default(), - style: syn::AttrStyle::Outer, - bracket_token: Default::default(), - path: syn::Ident::new("__wasm_bindgen_class_marker", Span::call_site()).into(), - tts: quote::quote! { (#class = #js_class) }.into(), - }); + method.attrs.insert( + 0, + syn::Attribute { + pound_token: Default::default(), + style: syn::AttrStyle::Outer, + bracket_token: Default::default(), + path: syn::Ident::new("__wasm_bindgen_class_marker", Span::call_site()).into(), + tts: quote::quote! { (#class = #js_class) }.into(), + }, + ); Ok(()) } @@ -973,7 +980,10 @@ impl MacroParse<()> for syn::ItemEnum { // We don't really want to get in the business of emulating how // rustc assigns values to enums. if v.discriminant.is_some() != has_discriminant { - bail_span!(v, "must either annotate discriminant of all variants or none"); + bail_span!( + v, + "must either annotate discriminant of all variants or none" + ); } let value = match v.discriminant { @@ -1010,7 +1020,8 @@ impl MacroParse<()> for syn::ItemEnum { let mut values = variants.iter().map(|v| v.value).collect::>(); values.sort(); - let hole = values.windows(2) + let hole = values + .windows(2) .filter_map(|window| { if window[0] + 1 != window[1] { Some(window[0] + 1) diff --git a/crates/threads-xform/Cargo.toml b/crates/threads-xform/Cargo.toml index 1d6cc40766d6..83b82defa4e9 100644 --- a/crates/threads-xform/Cargo.toml +++ b/crates/threads-xform/Cargo.toml @@ -9,7 +9,8 @@ documentation = "https://docs.rs/wasm-bindgen-threads-xform" description = """ Support for threading-related transformations in wasm-bindgen """ +edition = "2018" [dependencies] -parity-wasm = "0.36" +walrus = "0.1" failure = "0.1" diff --git a/crates/threads-xform/src/lib.rs b/crates/threads-xform/src/lib.rs index 1fbede769aeb..b61bb5a4da98 100644 --- a/crates/threads-xform/src/lib.rs +++ b/crates/threads-xform/src/lib.rs @@ -1,11 +1,11 @@ -#[macro_use] -extern crate failure; -extern crate parity_wasm; - +use std::cmp; use std::collections::HashMap; +use std::mem; -use failure::{Error, ResultExt}; -use parity_wasm::elements::*; +use failure::{bail, format_err, Error}; +use walrus::ir::Value; +use walrus::{DataId, FunctionId, InitExpr, LocalFunction, ValType}; +use walrus::{ExportItem, GlobalId, GlobalKind, ImportKind, MemoryId, Module}; const PAGE_SIZE: u32 = 1 << 16; @@ -78,19 +78,24 @@ impl Config { /// /// More and/or less may happen here over time, stay tuned! pub fn run(&self, module: &mut Module) -> Result<(), Error> { - let segments = switch_data_segments_to_passive(module)?; - import_memory_zero(module)?; - share_imported_memory_zero(module, self.maximum_memory)?; - let stack_pointer_idx = find_stack_pointer(module)?; - let globals = inject_thread_globals(module); - let addr = inject_thread_id_counter(module)?; + let memory = update_memory(module, self.maximum_memory)?; + let segments = switch_data_segments_to_passive(module, memory)?; + let stack_pointer = find_stack_pointer(module)?; + + let zero = InitExpr::Value(Value::I32(0)); + let globals = Globals { + thread_id: module.globals.add_local(ValType::I32, true, zero), + thread_tcb: module.globals.add_local(ValType::I32, true, zero), + }; + let addr = inject_thread_id_counter(module, memory)?; start_with_init_memory( module, &segments, &globals, addr, - stack_pointer_idx, + stack_pointer, self.thread_stack_size, + memory, ); implement_thread_intrinsics(module, &globals)?; Ok(()) @@ -98,233 +103,78 @@ impl Config { } struct PassiveSegment { - idx: u32, - offset: u32, + id: DataId, + offset: InitExpr, len: u32, } -fn switch_data_segments_to_passive(module: &mut Module) -> Result, Error> { - // If there's no data, nothing to make passive! - let section = match module.data_section_mut() { - Some(section) => section, - None => return Ok(Vec::new()), - }; - +fn switch_data_segments_to_passive( + module: &mut Module, + memory: MemoryId, +) -> Result, Error> { let mut ret = Vec::new(); - for (i, segment) in section.entries_mut().iter_mut().enumerate() { - let mut offset = match segment.offset_mut().take() { - Some(offset) => offset, - // already passive ... - None => continue, - }; - assert!(!segment.passive()); - - let offset = *get_offset(&mut offset) - .with_context(|_| format!("failed to read data segment {}", i))?; - - // Flag it as passive after validation, and we've removed the offset via - // `take`, so time to process the next one - *segment.passive_mut() = true; - ret.push(PassiveSegment { - idx: i as u32, - offset: offset as u32, - len: segment.value().len() as u32, - }); + let memory = module.memories.get_mut(memory); + let data = mem::replace(&mut memory.data, Default::default()); + for (offset, value) in data.into_iter() { + let len = value.len() as u32; + let id = module.data.add(value); + ret.push(PassiveSegment { id, offset, len }); } Ok(ret) } -fn get_offset(offset: &mut InitExpr) -> Result<&mut i32, Error> { - if offset.code().len() != 2 || offset.code()[1] != Instruction::End { - bail!("unrecognized offset") - } - match &mut offset.code_mut()[0] { - Instruction::I32Const(n) => Ok(n), - _ => bail!("unrecognized offset"), +fn update_memory(module: &mut Module, max: u32) -> Result { + assert!(max % PAGE_SIZE == 0); + let mut memories = module.memories.iter_mut(); + let memory = memories + .next() + .ok_or_else(|| format_err!("currently incompatible with no memory modules"))?; + if memories.next().is_some() { + bail!("only one memory is currently supported"); } -} -fn import_memory_zero(module: &mut Module) -> Result<(), Error> { - // If memory is exported, let's switch it to imported. If memory isn't - // exported then there's nothing to do as we'll deal with importing it - // later. - let limits = { - let section = match module.memory_section_mut() { - Some(section) => section, - None => return Ok(()), - }; - let limits = match section.entries_mut().pop() { - Some(limits) => limits, - None => return Ok(()), - }; - if section.entries().len() > 0 { - bail!("too many memories in wasm module for this tool to work"); - } - limits - }; - - // Remove all memory sections as well as exported memory, we're switching to - // an import - module.sections_mut().retain(|s| match s { - Section::Memory(_) => false, - _ => true, - }); - if let Some(s) = module.export_section_mut() { - s.entries_mut().retain(|s| match s.internal() { - Internal::Memory(_) => false, - _ => true, - }); + // For multithreading if we want to use the exact same module on all + // threads we'll need to be sure to import memory, so switch it to an + // import if it's already here. + if memory.import.is_none() { + let id = module + .imports + .add("env", "memory", ImportKind::Memory(memory.id())); + memory.import = Some(id); } - // Add our new import to the import section - let pos = maybe_add_import_section(module); - let imports = match &mut module.sections_mut()[pos] { - Section::Import(s) => s, - _ => unreachable!(), - }; - - // Hardcode the field names for now, these are all internal details anyway - let entry = ImportEntry::new( - "env".to_string(), - "memory".to_string(), - External::Memory(limits), - ); - imports.entries_mut().push(entry); - Ok(()) -} - -fn maybe_add_import_section(module: &mut Module) -> usize { - let mut pos = None; - // See this URL for section orderings, but the import section comes just - // after the type section. - // - // https://github.com/WebAssembly/design/blob/master/BinaryEncoding.md#high-level-structure - for i in 0..module.sections().len() { - match &mut module.sections_mut()[i] { - Section::Type(_) => continue, - Section::Import(_) => return i, - _ => {} + // If the memory isn't already shared, make it so as that's the whole point + // here! + if !memory.shared { + memory.shared = true; + if memory.maximum.is_none() { + memory.maximum = Some(max / PAGE_SIZE); } - pos = Some(i); - break; } - let empty = ImportSection::with_entries(Vec::new()); - let section = Section::Import(empty); - let len = module.sections().len(); - let pos = pos.unwrap_or_else(|| len - 1); - module.sections_mut().insert(pos, section); - return pos; -} - -fn share_imported_memory_zero(module: &mut Module, memory_max: u32) -> Result<(), Error> { - assert!(memory_max % PAGE_SIZE == 0); - // NB: this function assumes `import_memory_zero` has been called first to - // function correctly, which means we should find an imported memory here - // which we can rewrite to be unconditionally shared. - let imports = match module.import_section_mut() { - Some(s) => s, - None => panic!("failed to find an import section"), - }; - for entry in imports.entries_mut() { - let mem = match entry.external_mut() { - External::Memory(m) => m, - _ => continue, - }; - *mem = MemoryType::new( - mem.limits().initial(), - Some(mem.limits().maximum().unwrap_or(memory_max / PAGE_SIZE)), - true, - ); - return Ok(()); - } - panic!("failed to find an imported memory") + Ok(memory.id()) } struct Globals { - thread_id: u32, - thread_tcb: u32, + thread_id: GlobalId, + thread_tcb: GlobalId, } -fn inject_thread_globals(module: &mut Module) -> Globals { - let pos = maybe_add_global_section(module); - let globals = match &mut module.sections_mut()[pos] { - Section::Global(s) => s, - _ => unreachable!(), - }; - - // First up, our thread ID. The initial expression here isn't actually ever - // used but it's required. All threads will start off by setting this - // global to the thread's id. - globals.entries_mut().push(GlobalEntry::new( - GlobalType::new(ValueType::I32, true), - InitExpr::new(vec![Instruction::I32Const(0), Instruction::End]), - )); - - // Next up the thread TCB, this is always set to null to start off with. - globals.entries_mut().push(GlobalEntry::new( - GlobalType::new(ValueType::I32, true), - InitExpr::new(vec![Instruction::I32Const(0), Instruction::End]), - )); - - // ... and note that if either of the above globals isn't actually necessary - // we'll gc it away later. - - let len = globals.entries().len() as u32; - Globals { - thread_id: len - 2, - thread_tcb: len - 1, - } -} - -fn maybe_add_global_section(module: &mut Module) -> usize { - let mut pos = None; - // See this URL for section orderings: - // - // https://github.com/WebAssembly/design/blob/master/BinaryEncoding.md#high-level-structure - for i in 0..module.sections().len() { - match &mut module.sections_mut()[i] { - Section::Type(_) - | Section::Import(_) - | Section::Function(_) - | Section::Table(_) - | Section::Memory(_) => continue, - Section::Global(_) => return i, - _ => {} - } - pos = Some(i); - break; - } - let empty = GlobalSection::with_entries(Vec::new()); - let section = Section::Global(empty); - let len = module.sections().len(); - let pos = pos.unwrap_or_else(|| len - 1); - module.sections_mut().insert(pos, section); - return pos; -} - -fn inject_thread_id_counter(module: &mut Module) -> Result { +fn inject_thread_id_counter(module: &mut Module, memory: MemoryId) -> Result { // First up, look for a `__heap_base` export which is injected by LLD as // part of the linking process. Note that `__heap_base` should in theory be // *after* the stack and data, which means it's at the very end of the // address space and should be safe for us to inject 4 bytes of data at. - let heap_base = { - let exports = match module.export_section() { - Some(s) => s, - None => bail!("failed to find `__heap_base` for injecting thread id"), - }; - - exports - .entries() - .iter() - .filter(|e| e.field() == "__heap_base") - .filter_map(|e| match e.internal() { - Internal::Global(idx) => Some(*idx), - _ => None, - }) - .next() - }; + let heap_base = module + .exports + .iter() + .filter(|e| e.name == "__heap_base") + .filter_map(|e| match e.item { + ExportItem::Global(id) => Some(id), + _ => None, + }) + .next(); let heap_base = match heap_base { Some(idx) => idx, None => bail!("failed to find `__heap_base` for injecting thread id"), @@ -345,21 +195,17 @@ fn inject_thread_id_counter(module: &mut Module) -> Result { // Otherwise here we'll rewrite the `__heap_base` global's initializer to be // 4 larger, reserving us those 4 bytes for a thread id counter. let (address, add_a_page) = { - let globals = match module.global_section_mut() { - Some(s) => s, - None => bail!("failed to find globals section"), - }; - let entry = match globals.entries_mut().get_mut(heap_base as usize) { - Some(i) => i, - None => bail!("the `__heap_base` export index is out of bounds"), - }; - if entry.global_type().content_type() != ValueType::I32 { + let global = module.globals.get_mut(heap_base); + if global.ty != ValType::I32 { bail!("the `__heap_base` global doesn't have the type `i32`"); } - if entry.global_type().is_mutable() { + if global.mutable { bail!("the `__heap_base` global is unexpectedly mutable"); } - let offset = get_offset(entry.init_expr_mut())?; + let offset = match &mut global.kind { + GlobalKind::Local(InitExpr::Value(Value::I32(n))) => n, + _ => bail!("`__heap_base` not a locally defined `i32`"), + }; let address = (*offset as u32 + 3) & !3; // align up let add_a_page = (address + 4) / PAGE_SIZE != address / PAGE_SIZE; *offset = (address + 4) as i32; @@ -367,68 +213,36 @@ fn inject_thread_id_counter(module: &mut Module) -> Result { }; if add_a_page { - add_one_to_imported_memory_limits_minimum(module); + let memory = module.memories.get_mut(memory); + memory.initial += 1; + memory.maximum = memory.maximum.map(|m| cmp::max(m, memory.initial)); } Ok(address) } -// see `inject_thread_id_counter` for why this is used and where it's called -fn add_one_to_imported_memory_limits_minimum(module: &mut Module) { - let imports = match module.import_section_mut() { - Some(s) => s, - None => panic!("failed to find import section"), - }; - - for entry in imports.entries_mut() { - let mem = match entry.external_mut() { - External::Memory(m) => m, - _ => continue, - }; - *mem = MemoryType::new( - mem.limits().initial() + 1, - mem.limits().maximum().map(|m| { - if m == mem.limits().initial() { - m + 1 - } else { - m - } - }), - mem.limits().shared(), - ); - return; - } - panic!("failed to find an imported memory") -} - -fn find_stack_pointer(module: &mut Module) -> Result, Error> { - let globals = match module.global_section() { - Some(s) => s, - None => bail!("failed to find the stack pointer"), - }; - let candidates = globals - .entries() +fn find_stack_pointer(module: &mut Module) -> Result, Error> { + let candidates = module + .globals .iter() - .enumerate() - .filter(|(_, g)| g.global_type().content_type() == ValueType::I32) - .filter(|(_, g)| g.global_type().is_mutable()) + .filter(|g| g.ty == ValType::I32) + .filter(|g| g.mutable) + .filter(|g| match g.kind { + GlobalKind::Local(_) => true, + GlobalKind::Import(_) => false, + }) .collect::>(); - // If there are no mutable i32 globals, assume this module doesn't even need - // a stack pointer! - if candidates.len() == 0 { - return Ok(None); - } + match candidates.len() { + // If there are no mutable i32 globals, assume this module doesn't even + // need a stack pointer! + 0 => Ok(None), - // Currently LLVM/LLD always use global 0 as the stack pointer, let's just - // blindly assume that. - if candidates[0].0 == 0 { - return Ok(Some(0)); + // If there's more than one global give up for now. Eventually we can + // probably do better by pattern matching on functions, but this should + // be sufficient for LLVM's output for now. + 1 => Ok(Some(candidates[0].id())), + _ => bail!("too many mutable globals to infer the stack pointer"), } - - bail!( - "the first global wasn't a mutable i32, has LLD changed or was \ - this wasm file not produced by LLD?" - ) } fn start_with_init_memory( @@ -436,224 +250,220 @@ fn start_with_init_memory( segments: &[PassiveSegment], globals: &Globals, addr: u32, - stack_pointer_idx: Option, + stack_pointer: Option, stack_size: u32, + memory: MemoryId, ) { - assert!(stack_size % PAGE_SIZE == 0); - let mut instrs = Vec::new(); - - // Execute an atomic add to learn what our thread ID is - instrs.push(Instruction::I32Const(addr as i32)); - instrs.push(Instruction::I32Const(1)); - let mem = parity_wasm::elements::MemArg { - align: 2, - offset: 0, - }; - instrs.push(Instruction::I32AtomicRmwAdd(mem)); + use walrus::ir::*; - // Store this thread ID into our thread ID global - instrs.push(Instruction::TeeLocal(0)); - instrs.push(Instruction::SetGlobal(globals.thread_id)); + assert!(stack_size % PAGE_SIZE == 0); + let mut builder = walrus::FunctionBuilder::new(); + let mut exprs = Vec::new(); + let local = module.locals.add(ValType::I32); + + let addr = builder.i32_const(addr as i32); + let one = builder.i32_const(1); + let thread_id = builder.atomic_rmw( + memory, + AtomicOp::Add, + AtomicWidth::I32, + MemArg { + align: 4, + offset: 0, + }, + addr, + one, + ); + let thread_id = builder.local_tee(local, thread_id); + let global_set = builder.global_set(globals.thread_id, thread_id); + exprs.push(global_set); // Perform an if/else based on whether we're the first thread or not. Our // thread ID will be zero if we're the first thread, otherwise it'll be // nonzero (assuming we don't overflow...) // - // In the nonzero case (the first block) we give ourselves a stack via - // memory.grow and we update our stack pointer. - // - // In the zero case (the second block) we can skip both of those operations, - // but we need to initialize all our memory data segments. - instrs.push(Instruction::GetLocal(0)); - instrs.push(Instruction::If(BlockType::NoResult)); + let thread_id_is_nonzero = builder.local_get(local); - if let Some(stack_pointer_idx) = stack_pointer_idx { + // If our thread id is nonzero then we're the second or greater thread, so + // we give ourselves a stack via memory.grow and we update our stack + // pointer as the default stack pointer is surely wrong for us. + let mut block = builder.if_else_block(Box::new([]), Box::new([])); + if let Some(stack_pointer) = stack_pointer { // local0 = grow_memory(stack_size); - instrs.push(Instruction::I32Const((stack_size / PAGE_SIZE) as i32)); - instrs.push(Instruction::GrowMemory(0)); - instrs.push(Instruction::SetLocal(0)); + let grow_amount = block.i32_const((stack_size / PAGE_SIZE) as i32); + let memory_growth = block.memory_grow(memory, grow_amount); + let set_local = block.local_set(local, memory_growth); + block.expr(set_local); // if local0 == -1 then trap - instrs.push(Instruction::Block(BlockType::NoResult)); - instrs.push(Instruction::GetLocal(0)); - instrs.push(Instruction::I32Const(-1)); - instrs.push(Instruction::I32Ne); - instrs.push(Instruction::BrIf(0)); - instrs.push(Instruction::Unreachable); - instrs.push(Instruction::End); // end block + let if_negative_trap = { + let mut block = block.block(Box::new([]), Box::new([])); + + let lhs = block.local_get(local); + let rhs = block.i32_const(-1); + let condition = block.binop(BinaryOp::I32Ne, lhs, rhs); + let id = block.id(); + let br_if = block.br_if(condition, id, Box::new([])); + block.expr(br_if); + + let unreachable = block.unreachable(); + block.expr(unreachable); + + id + }; + block.expr(if_negative_trap.into()); // stack_pointer = local0 + stack_size - instrs.push(Instruction::GetLocal(0)); - instrs.push(Instruction::I32Const(PAGE_SIZE as i32)); - instrs.push(Instruction::I32Mul); - instrs.push(Instruction::I32Const(stack_size as i32)); - instrs.push(Instruction::I32Add); - instrs.push(Instruction::SetGlobal(stack_pointer_idx)); + let get_local = block.local_get(local); + let page_size = block.i32_const(PAGE_SIZE as i32); + let sp_base = block.binop(BinaryOp::I32Mul, get_local, page_size); + let stack_size = block.i32_const(stack_size as i32); + let sp = block.binop(BinaryOp::I32Add, sp_base, stack_size); + let set_stack_pointer = block.global_set(stack_pointer, sp); + block.expr(set_stack_pointer); } + let if_nonzero_block = block.id(); + drop(block); + + // If the thread ID is zero then we can skip the update of the stack + // pointer as we know our stack pointer is valid. We need to initialize + // memory, however, so do that here. + let if_zero_block = { + let mut block = builder.if_else_block(Box::new([]), Box::new([])); + for segment in segments { + let zero = block.i32_const(0); + let offset = match segment.offset { + InitExpr::Global(id) => block.global_get(id), + InitExpr::Value(v) => block.const_(v), + }; + let len = block.i32_const(segment.len as i32); + let init = block.memory_init(memory, segment.id, offset, zero, len); + block.expr(init); + } + block.id() + }; - instrs.push(Instruction::Else); - for segment in segments { - // offset into memory - instrs.push(Instruction::I32Const(segment.offset as i32)); - // offset into segment - instrs.push(Instruction::I32Const(0)); // offset into segment - // amount to copy - instrs.push(Instruction::I32Const(segment.len as i32)); - instrs.push(Instruction::MemoryInit(segment.idx)); - } - instrs.push(Instruction::End); // endif + let block = builder.if_else(thread_id_is_nonzero, if_nonzero_block, if_zero_block); + exprs.push(block); // On all threads now memory segments are no longer needed for segment in segments { - instrs.push(Instruction::MemoryDrop(segment.idx)); + exprs.push(builder.data_drop(segment.id)); } // If a start function previously existed we're done with our own // initialization so delegate to them now. - if let Some(idx) = module.start_section() { - instrs.push(Instruction::Call(idx)); + if let Some(id) = module.start.take() { + exprs.push(builder.call(id, Box::new([]))); } - // End the function - instrs.push(Instruction::End); - - // Add this newly generated function to the code section ... - let instrs = Instructions::new(instrs); - let local = Local::new(1, ValueType::I32); - let body = FuncBody::new(vec![local], instrs); - let code_idx = { - let s = module.code_section_mut().expect("module had no code"); - s.bodies_mut().push(body); - (s.bodies().len() - 1) as u32 - }; - // ... and also be sure to add its signature to the function section ... - let type_idx = { - let section = module - .type_section_mut() - .expect("module has no type section"); - let pos = section - .types() - .iter() - .map(|t| match t { - Type::Function(t) => t, - }) - .position(|t| t.params().is_empty() && t.return_type().is_none()); - match pos { - Some(i) => i as u32, - None => { - let f = FunctionType::new(Vec::new(), None); - section.types_mut().push(Type::Function(f)); - (section.types().len() - 1) as u32 - } - } - }; - module - .function_section_mut() - .expect("module has no function section") - .entries_mut() - .push(Func::new(type_idx)); + // Finish off our newly generated function. + let ty = module.types.add(&[], &[]); + let id = builder.finish(ty, Vec::new(), exprs, module); // ... and finally flag it as the new start function - let idx = code_idx + (module.import_count(ImportCountType::Function) as u32); - update_start_section(module, idx); + module.start = Some(id); } -fn update_start_section(module: &mut Module, start: u32) { - // See this URL for section orderings: - // - // https://github.com/WebAssembly/design/blob/master/BinaryEncoding.md#high-level-structure - let mut pos = None; - for i in 0..module.sections().len() { - match &mut module.sections_mut()[i] { - Section::Type(_) - | Section::Import(_) - | Section::Function(_) - | Section::Table(_) - | Section::Memory(_) - | Section::Global(_) - | Section::Export(_) => continue, - Section::Start(start_idx) => { - *start_idx = start; - return; +fn implement_thread_intrinsics(module: &mut Module, globals: &Globals) -> Result<(), Error> { + use walrus::ir::*; + + let mut map = HashMap::new(); + + enum Intrinsic { + GetThreadId, + GetTcb, + SetTcb, + } + + let imports = module + .imports + .iter() + .filter(|i| i.module == "__wbindgen_thread_xform__"); + for import in imports { + let function = match import.kind { + ImportKind::Function(id) => module.funcs.get(id), + _ => bail!("non-function import from special module"), + }; + let ty = module.types.get(function.ty()); + + match &import.name[..] { + "__wbindgen_thread_id" => { + if !ty.params().is_empty() || ty.results() != &[ValType::I32] { + bail!("`__wbindgen_thread_id` intrinsic has the wrong signature"); + } + map.insert(function.id(), Intrinsic::GetThreadId); + } + "__wbindgen_tcb_get" => { + if !ty.params().is_empty() || ty.results() != &[ValType::I32] { + bail!("`__wbindgen_tcb_get` intrinsic has the wrong signature"); + } + map.insert(function.id(), Intrinsic::GetTcb); } - _ => {} + "__wbindgen_tcb_set" => { + if !ty.results().is_empty() || ty.params() != &[ValType::I32] { + bail!("`__wbindgen_tcb_set` intrinsic has the wrong signature"); + } + map.insert(function.id(), Intrinsic::SetTcb); + } + other => bail!("unknown thread intrinsic: {}", other), } - pos = Some(i); - break; } - let section = Section::Start(start); - let len = module.sections().len(); - let pos = pos.unwrap_or_else(|| len - 1); - module.sections_mut().insert(pos, section); -} -fn implement_thread_intrinsics(module: &mut Module, globals: &Globals) -> Result<(), Error> { - let mut map = HashMap::new(); - { - let imports = match module.import_section() { - Some(i) => i, - None => return Ok(()), - }; - let entries = imports - .entries() - .iter() - .filter(|i| match i.external() { - External::Function(_) => true, - _ => false, - }) - .enumerate() - .filter(|(_, entry)| entry.module() == "__wbindgen_thread_xform__"); - for (idx, entry) in entries { - let type_idx = match entry.external() { - External::Function(i) => *i, - _ => unreachable!(), - }; - let types = module.type_section().unwrap(); - let fty = match &types.types()[type_idx as usize] { - Type::Function(f) => f, + struct Visitor<'a> { + map: &'a HashMap, + globals: &'a Globals, + func: &'a mut LocalFunction, + } + + module.funcs.iter_local_mut().for_each(|(_id, func)| { + let mut entry = func.entry_block(); + Visitor { + map: &map, + globals, + func, + } + .visit_block_id_mut(&mut entry); + }); + + impl VisitorMut for Visitor<'_> { + fn local_function_mut(&mut self) -> &mut LocalFunction { + self.func + } + + fn visit_expr_mut(&mut self, expr: &mut Expr) { + let call = match expr { + Expr::Call(e) => e, + other => return other.visit_mut(self), }; - // Validate the type for this intrinsic - match entry.field() { - "__wbindgen_thread_id" => { - if !fty.params().is_empty() || fty.return_type() != Some(ValueType::I32) { - bail!("__wbindgen_thread_id intrinsic has the wrong signature"); + match self.map.get(&call.func) { + Some(Intrinsic::GetThreadId) => { + assert!(call.args.is_empty()); + *expr = GlobalGet { + global: self.globals.thread_id, } - map.insert(idx as u32, Instruction::GetGlobal(globals.thread_id)); + .into(); } - "__wbindgen_tcb_get" => { - if !fty.params().is_empty() || fty.return_type() != Some(ValueType::I32) { - bail!("__wbindgen_tcb_get intrinsic has the wrong signature"); + Some(Intrinsic::GetTcb) => { + assert!(call.args.is_empty()); + *expr = GlobalGet { + global: self.globals.thread_tcb, } - map.insert(idx as u32, Instruction::GetGlobal(globals.thread_tcb)); + .into(); } - "__wbindgen_tcb_set" => { - if fty.params().len() != 1 || fty.return_type().is_some() { - bail!("__wbindgen_tcb_set intrinsic has the wrong signature"); + Some(Intrinsic::SetTcb) => { + assert_eq!(call.args.len(), 1); + call.args[0].visit_mut(self); + *expr = GlobalSet { + global: self.globals.thread_tcb, + value: call.args[0], } - map.insert(idx as u32, Instruction::SetGlobal(globals.thread_tcb)); + .into(); } - other => bail!("unknown thread intrinsic: {}", other), + None => call.visit_mut(self), } } - }; - - // Rewrite everything that calls `import_idx` to instead load the global - // `thread_id` - for body in module.code_section_mut().unwrap().bodies_mut() { - for instr in body.code_mut().elements_mut() { - let other = match instr { - Instruction::Call(idx) => match map.get(idx) { - Some(other) => other, - None => continue, - }, - _ => continue, - }; - *instr = other.clone(); - } } - // ... and in theory we'd remove `import_idx` here but we let `wasm-gc` - // take care of that later. - Ok(()) } diff --git a/crates/wasm-interpreter/Cargo.toml b/crates/wasm-interpreter/Cargo.toml index 8c59b19c01e5..3c71f5a52884 100644 --- a/crates/wasm-interpreter/Cargo.toml +++ b/crates/wasm-interpreter/Cargo.toml @@ -9,9 +9,11 @@ documentation = "https://docs.rs/wasm-bindgen-wasm-interpreter" description = """ Micro-interpreter optimized for wasm-bindgen's use case """ +edition = '2018' [dependencies] -parity-wasm = "0.36" +walrus = "0.1" +log = "0.4" [dev-dependencies] tempfile = "3" diff --git a/crates/wasm-interpreter/src/lib.rs b/crates/wasm-interpreter/src/lib.rs index cd34a0e9f3a4..3483b0f87bbb 100644 --- a/crates/wasm-interpreter/src/lib.rs +++ b/crates/wasm-interpreter/src/lib.rs @@ -1,7 +1,7 @@ //! A tiny and incomplete wasm interpreter //! //! This module contains a tiny and incomplete wasm interpreter built on top of -//! `parity-wasm`'s module structure. Each `Interpreter` contains some state +//! `walrus`'s module structure. Each `Interpreter` contains some state //! about the execution of a wasm instance. The "incomplete" part here is //! related to the fact that this is *only* used to execute the various //! descriptor functions for wasm-bindgen. @@ -18,11 +18,9 @@ #![deny(missing_docs)] -extern crate parity_wasm; - -use std::collections::HashMap; - -use parity_wasm::elements::*; +use std::collections::{BTreeMap, HashMap}; +use walrus::ir::ExprId; +use walrus::{FunctionId, LocalFunction, LocalId, Module, TableId}; /// A ready-to-go interpreter of a wasm module. /// @@ -31,36 +29,24 @@ use parity_wasm::elements::*; /// state like the wasm stack, wasm memory, etc. #[derive(Default)] pub struct Interpreter { - // Number of imported functions in the wasm module (used in index - // calculations) - imports: usize, - // Function index of the `__wbindgen_describe` and // `__wbindgen_describe_closure` imported functions. We special case this // to know when the environment's imported function is called. - describe_idx: Option, - describe_closure_idx: Option, + describe_id: Option, + describe_closure_id: Option, + + // Id of the function table + functions: Option, // A mapping of string names to the function index, filled with all exported // functions. - name_map: HashMap, - - // The numerical index of the sections in the wasm module, indexed into - // the module's list of sections. - code_idx: Option, - types_idx: Option, - functions_idx: Option, - elements_idx: Option, + name_map: HashMap, // The current stack pointer (global 0) and wasm memory (the stack). Only // used in a limited capacity. sp: i32, mem: Vec, - // The wasm stack. Note how it's just `i32` which is intentional, we don't - // support other types. - stack: Vec, - // The descriptor which we're assembling, a list of `u32` entries. This is // very specific to wasm-bindgen and is the purpose for the existence of // this module. @@ -72,13 +58,6 @@ pub struct Interpreter { descriptor_table_idx: Option, } -struct Sections<'a> { - code: &'a CodeSection, - types: &'a TypeSection, - functions: &'a FunctionSection, - elements: &'a ElementSection, -} - impl Interpreter { /// Creates a new interpreter from a provided `Module`, precomputing all /// information necessary to interpret further. @@ -95,49 +74,39 @@ impl Interpreter { ret.mem = vec![0; 0x100]; ret.sp = ret.mem.len() as i32; - // Figure out where our code section, if any, is. - for (i, s) in module.sections().iter().enumerate() { - match s { - Section::Code(_) => ret.code_idx = Some(i), - Section::Element(_) => ret.elements_idx = Some(i), - Section::Type(_) => ret.types_idx = Some(i), - Section::Function(_) => ret.functions_idx = Some(i), - _ => {} - } - } - // Figure out where the `__wbindgen_describe` imported function is, if // it exists. We'll special case calls to this function as our // interpretation should only invoke this function as an imported // function. - if let Some(i) = module.import_section() { - ret.imports = i.functions(); - let mut idx = 0; - for entry in i.entries() { - match entry.external() { - External::Function(_) => idx += 1, - _ => continue, - } - if entry.module() != "__wbindgen_placeholder__" { - continue; - } - if entry.field() == "__wbindgen_describe" { - ret.describe_idx = Some(idx - 1 as u32); - } else if entry.field() == "__wbindgen_describe_closure" { - ret.describe_closure_idx = Some(idx - 1 as u32); - } + for import in module.imports.iter() { + let id = match import.kind { + walrus::ImportKind::Function(id) => id, + _ => continue, + }; + if import.module != "__wbindgen_placeholder__" { + continue; + } + if import.name == "__wbindgen_describe" { + ret.describe_id = Some(id); + } else if import.name == "__wbindgen_describe_closure" { + ret.describe_closure_id = Some(id); } } // Build up the mapping of exported functions to function indices. - if let Some(e) = module.export_section() { - for e in e.entries() { - let i = match e.internal() { - Internal::Function(i) => i, - _ => continue, - }; - ret.name_map.insert(e.field().to_string(), *i); + for export in module.exports.iter() { + let id = match export.item { + walrus::ExportItem::Function(id) => id, + _ => continue, + }; + ret.name_map.insert(export.name.to_string(), id); + } + + for table in module.tables.iter() { + match table.kind { + walrus::TableKind::Function(_) => {} } + ret.functions = Some(table.id()); } return ret; @@ -164,21 +133,17 @@ impl Interpreter { /// Returns `Some` if `func` was found in the `module` and `None` if it was /// not found in the `module`. pub fn interpret_descriptor(&mut self, func: &str, module: &Module) -> Option<&[u32]> { - let idx = *self.name_map.get(func)?; - self.with_sections(module, |me, sections| { - me.interpret_descriptor_idx(idx, sections) - }) + let id = *self.name_map.get(func)?; + self.interpret_descriptor_id(id, module) } - fn interpret_descriptor_idx(&mut self, idx: u32, sections: &Sections) -> Option<&[u32]> { + fn interpret_descriptor_id(&mut self, id: FunctionId, module: &Module) -> Option<&[u32]> { self.descriptor.truncate(0); // We should have a blank wasm and LLVM stack at both the start and end // of the call. assert_eq!(self.sp, self.mem.len() as i32); - assert_eq!(self.stack.len(), 0); - self.call(idx, sections); - assert_eq!(self.stack.len(), 0); + self.call(id, module, &[]); assert_eq!(self.sp, self.mem.len() as i32); Some(&self.descriptor) } @@ -186,7 +151,7 @@ impl Interpreter { /// Interprets a "closure descriptor", figuring out the signature of the /// closure that was intended. /// - /// This function will take a `code_idx` which is known to internally + /// This function will take an `id` which is known to internally /// execute `__wbindgen_describe_closure` and interpret it. The /// `wasm-bindgen` crate controls all callers of this internal import. It /// will then take the index passed to `__wbindgen_describe_closure` and @@ -201,22 +166,11 @@ impl Interpreter { /// section) of the function that needs to be snip'd out. pub fn interpret_closure_descriptor( &mut self, - code_idx: usize, + id: FunctionId, module: &Module, - entry_removal_list: &mut Vec<(usize, usize)>, + entry_removal_list: &mut Vec, ) -> Option<&[u32]> { - self.with_sections(module, |me, sections| { - me._interpret_closure_descriptor(code_idx, sections, entry_removal_list) - }) - } - - fn _interpret_closure_descriptor( - &mut self, - code_idx: usize, - sections: &Sections, - entry_removal_list: &mut Vec<(usize, usize)>, - ) -> Option<&[u32]> { - // Call the `code_idx` function. This is an internal `#[inline(never)]` + // Call the `id` function. This is an internal `#[inline(never)]` // whose code is completely controlled by the `wasm-bindgen` crate, so // it should take some arguments (the number of arguments depends on the // optimization level) and return one (all of which we don't care about @@ -224,215 +178,219 @@ impl Interpreter { // it'll call `__wbindgen_describe_closure` with an argument that we // look for. assert!(self.descriptor_table_idx.is_none()); - let closure_descriptor_idx = (code_idx + self.imports) as u32; - let code_sig = sections.functions.entries()[code_idx].type_ref(); - let function_ty = match §ions.types.types()[code_sig as usize] { - Type::Function(t) => t, - }; - for _ in 0..function_ty.params().len() { - self.stack.push(0); - } - - self.call(closure_descriptor_idx, sections); - assert_eq!(self.stack.len(), 1); - self.stack.pop(); - let descriptor_table_idx = self.descriptor_table_idx.take().unwrap(); + let func = module.funcs.get(id); + assert_eq!(module.types.get(func.ty()).params().len(), 2); + self.call(id, module, &[0, 0]); + let descriptor_table_idx = + self.descriptor_table_idx + .take() + .expect("descriptor function should return index") as usize; // After we've got the table index of the descriptor function we're // interested go take a look in the function table to find what the // actual index of the function is. - let (entry_idx, offset, entry) = sections + let functions = self.functions.expect("function table should be present"); + let functions = match &module.tables.get(functions).kind { + walrus::TableKind::Function(f) => f, + }; + let descriptor_id = functions .elements - .entries() - .iter() - .enumerate() - .filter_map(|(i, entry)| { - let code = match entry.offset() { - Some(offset) => offset.code(), - None => return None, - }; - if code.len() != 2 { - return None; - } - if code[1] != Instruction::End { - return None; - } - match code[0] { - Instruction::I32Const(x) => Some((i, x as u32, entry)), - _ => None, - } - }) - .find(|(_i, offset, entry)| { - *offset <= descriptor_table_idx - && descriptor_table_idx < (*offset + entry.members().len() as u32) - }) - .expect("failed to find index in table elements"); - let idx = (descriptor_table_idx - offset) as usize; - let descriptor_idx = entry.members()[idx]; + .get(descriptor_table_idx) + .expect("out of bounds read of function table") + .expect("attempting to execute null function"); // This is used later to actually remove the entry from the table, but // we don't do the removal just yet - entry_removal_list.push((entry_idx, idx)); + entry_removal_list.push(descriptor_table_idx); // And now execute the descriptor! - self.interpret_descriptor_idx(descriptor_idx, sections) + self.interpret_descriptor_id(descriptor_id, module) } - /// Returns the function space index of the `__wbindgen_describe_closure` + /// Returns the function id of the `__wbindgen_describe_closure` /// imported function. - pub fn describe_closure_idx(&self) -> Option { - self.describe_closure_idx + pub fn describe_closure_id(&self) -> Option { + self.describe_closure_id + } + + /// Returns the detected id of the function table. + pub fn function_table_id(&self) -> Option { + self.functions } - fn call(&mut self, idx: u32, sections: &Sections) { - use parity_wasm::elements::Instruction::*; - - let idx = idx as usize; - assert!(idx >= self.imports); // can't call imported functions - let code_idx = idx - self.imports; - let body = §ions.code.bodies()[code_idx]; - - // Allocate space for our call frame's local variables. All local - // variables should be of the `i32` type. - assert!(body.locals().len() <= 1, "too many local types"); - let nlocals = body - .locals() - .get(0) - .map(|i| { - assert_eq!(i.value_type(), ValueType::I32); - i.count() - }) - .unwrap_or(0); - - let code_sig = sections.functions.entries()[code_idx].type_ref(); - let function_ty = match §ions.types.types()[code_sig as usize] { - Type::Function(t) => t, + fn call(&mut self, id: FunctionId, module: &Module, args: &[i32]) -> Option { + let func = module.funcs.get(id); + log::debug!("starting a call of {:?} {:?}", id, func.name); + log::debug!("arguments {:?}", args); + let local = match &func.kind { + walrus::FunctionKind::Local(l) => l, + _ => panic!("can only call locally defined functions"), }; - let mut locals = Vec::with_capacity(function_ty.params().len() + nlocals as usize); - // Any function parameters we have get popped off the stack and put into - // the first few locals ... - for param in function_ty.params() { - assert_eq!(*param, ValueType::I32); - locals.push(self.stack.pop().unwrap()); + + let entry = local.entry_block(); + let block = local.block(entry); + + let mut frame = Frame { + module, + local, + interp: self, + locals: BTreeMap::new(), + done: false, + }; + + assert_eq!(local.args.len(), args.len()); + for (arg, val) in local.args.iter().zip(args) { + frame.locals.insert(*arg, *val); } - // ... and the remaining locals all start as zero ... - for _ in 0..nlocals { - locals.push(0); + + if block.exprs.len() > 0 { + for expr in block.exprs[..block.exprs.len() - 1].iter() { + let ret = frame.eval(*expr); + if frame.done { + return ret; + } + } } - // ... and we expect one stack slot at the end if there's a returned - // value - let before = self.stack.len(); - let stack_after = match function_ty.return_type() { - Some(t) => { - assert_eq!(t, ValueType::I32); - before + 1 + block.exprs.last().and_then(|e| frame.eval(*e)) + } +} + +struct Frame<'a> { + module: &'a Module, + local: &'a LocalFunction, + interp: &'a mut Interpreter, + locals: BTreeMap, + done: bool, +} + +impl Frame<'_> { + fn local(&self, id: LocalId) -> i32 { + self.locals.get(&id).cloned().unwrap_or(0) + } + + fn eval(&mut self, expr: ExprId) -> Option { + use walrus::ir::*; + + match self.local.get(expr) { + Expr::Const(c) => match c.value { + Value::I32(n) => Some(n), + _ => panic!("non-i32 constant"), + }, + Expr::LocalGet(e) => Some(self.local(e.local)), + Expr::LocalSet(e) => { + let val = self.eval(e.value).expect("must eval to i32"); + self.locals.insert(e.local, val); + None } - None => before, - }; - // Actual interpretation loop! We keep track of our stack's length to - // recover it as part of the `Return` instruction, and otherwise this is - // a pretty straightforward interpretation loop. - for instr in body.code().elements() { - match instr { - I32Const(x) => self.stack.push(*x), - SetLocal(i) => locals[*i as usize] = self.stack.pop().unwrap(), - GetLocal(i) => self.stack.push(locals[*i as usize]), - Call(idx) => { - // If this function is calling the `__wbindgen_describe` - // function, which we've precomputed the index for, then - // it's telling us about the next `u32` element in the - // descriptor to return. We "call" the imported function - // here by directly inlining it. - // - // Otherwise this is a normal call so we recurse. - if Some(*idx) == self.describe_idx { - self.descriptor.push(self.stack.pop().unwrap() as u32); - } else if Some(*idx) == self.describe_closure_idx { - self.descriptor_table_idx = Some(self.stack.pop().unwrap() as u32); - self.stack.pop(); - self.stack.pop(); - self.stack.push(0); - } else { - self.call(*idx, sections); - } - } - GetGlobal(0) => self.stack.push(self.sp), - SetGlobal(0) => self.sp = self.stack.pop().unwrap(), - I32Sub => { - let b = self.stack.pop().unwrap(); - let a = self.stack.pop().unwrap(); - self.stack.push(a - b); - } - I32Add => { - let a = self.stack.pop().unwrap(); - let b = self.stack.pop().unwrap(); - self.stack.push(a + b); + // Blindly assume all globals are the stack pointer + Expr::GlobalGet(_) => Some(self.interp.sp), + Expr::GlobalSet(e) => { + let val = self.eval(e.value).expect("must eval to i32"); + self.interp.sp = val; + None + } + + // Support simple arithmetic, mainly for the stack pointer + // manipulation + Expr::Binop(e) => { + let lhs = self.eval(e.lhs).expect("must eval to i32"); + let rhs = self.eval(e.rhs).expect("must eval to i32"); + match e.op { + BinaryOp::I32Sub => Some(lhs - rhs), + BinaryOp::I32Add => Some(lhs + rhs), + op => panic!("invalid binary op {:?}", op), } - I32Store(/* align = */ 2, offset) => { - let val = self.stack.pop().unwrap(); - let addr = self.stack.pop().unwrap() as u32; - self.mem[((addr + *offset) as usize) / 4] = val; + } + + // Support small loads/stores to the stack. These show up in debug + // mode where there's some traffic on the linear stack even when in + // theory there doesn't need to be. + Expr::Load(e) => { + let address = self.eval(e.address).expect("must eval to i32"); + let address = address as u32 + e.arg.offset; + assert!(address % 4 == 0); + Some(self.interp.mem[address as usize / 4]) + } + Expr::Store(e) => { + let address = self.eval(e.address).expect("must eval to i32"); + let value = self.eval(e.value).expect("must eval to i32"); + let address = address as u32 + e.arg.offset; + assert!(address % 4 == 0); + self.interp.mem[address as usize / 4] = value; + None + } + + Expr::Return(e) => { + log::debug!("return"); + self.done = true; + assert!(e.values.len() <= 1); + e.values.get(0).and_then(|id| self.eval(*id)) + } + + Expr::Drop(e) => { + log::debug!("drop"); + self.eval(e.expr); + None + } + + Expr::WithSideEffects(e) => { + log::debug!("side effects"); + let ret = self.eval(e.value); + for x in e.side_effects.iter() { + self.eval(*x); } - I32Load(/* align = */ 2, offset) => { - let addr = self.stack.pop().unwrap() as u32; - self.stack.push(self.mem[((addr + *offset) as usize) / 4]); + return ret; + } + + Expr::Call(e) => { + // If this function is calling the `__wbindgen_describe` + // function, which we've precomputed the id for, then + // it's telling us about the next `u32` element in the + // descriptor to return. We "call" the imported function + // here by directly inlining it. + if Some(e.func) == self.interp.describe_id { + assert_eq!(e.args.len(), 1); + let val = self.eval(e.args[0]).expect("must eval to i32"); + log::debug!("__wbindgen_describe({})", val); + self.interp.descriptor.push(val as u32); + None + + // If this function is calling the `__wbindgen_describe_closure` + // function then it's similar to the above, except there's a + // slightly different signature. Note that we don't eval the + // previous arguments because they shouldn't have any side + // effects we're interested in. + } else if Some(e.func) == self.interp.describe_closure_id { + assert_eq!(e.args.len(), 3); + let val = self.eval(e.args[2]).expect("must eval to i32"); + log::debug!("__wbindgen_describe_closure({})", val); + self.interp.descriptor_table_idx = Some(val as u32); + Some(0) + + // ... otherwise this is a normal call so we recurse. + } else { + let args = e + .args + .iter() + .map(|e| self.eval(*e).expect("must eval to i32")) + .collect::>(); + self.interp.call(e.func, self.module, &args); + None } - Return => self.stack.truncate(stack_after), - End => break, - - // All other instructions shouldn't be used by our various - // descriptor functions. LLVM optimizations may mean that some - // of the above instructions aren't actually needed either, but - // the above instructions have empirically been required when - // executing our own test suite in wasm-bindgen. - // - // Note that LLVM may change over time to generate new - // instructions in debug mode, and we'll have to react to those - // sorts of changes as they arise. - s => panic!("unknown instruction {:?}", s), } - } - assert_eq!(self.stack.len(), stack_after); - } - fn with_sections<'a, T>( - &'a mut self, - module: &Module, - f: impl FnOnce(&'a mut Self, &Sections) -> T, - ) -> T { - macro_rules! access_with_defaults { - ($( - let $var: ident = module.sections[self.$field:ident] - ($name:ident); - )*) => {$( - let default = Default::default(); - let $var = match self.$field { - Some(i) => { - match &module.sections()[i] { - Section::$name(s) => s, - _ => panic!(), - } - } - None => &default, - }; - )*} + // All other instructions shouldn't be used by our various + // descriptor functions. LLVM optimizations may mean that some + // of the above instructions aren't actually needed either, but + // the above instructions have empirically been required when + // executing our own test suite in wasm-bindgen. + // + // Note that LLVM may change over time to generate new + // instructions in debug mode, and we'll have to react to those + // sorts of changes as they arise. + s => panic!("unknown instruction {:?}", s), } - access_with_defaults! { - let code = module.sections[self.code_idx] (Code); - let types = module.sections[self.types_idx] (Type); - let functions = module.sections[self.functions_idx] (Function); - let elements = module.sections[self.elements_idx] (Element); - } - f( - self, - &Sections { - code, - types, - functions, - elements, - }, - ) } } diff --git a/crates/wasm-interpreter/tests/smoke.rs b/crates/wasm-interpreter/tests/smoke.rs index b74e94132ee6..79af91fb9287 100644 --- a/crates/wasm-interpreter/tests/smoke.rs +++ b/crates/wasm-interpreter/tests/smoke.rs @@ -1,7 +1,3 @@ -extern crate parity_wasm; -extern crate tempfile; -extern crate wasm_bindgen_wasm_interpreter; - use std::fs; use std::process::Command; @@ -19,7 +15,7 @@ fn interpret(wat: &str, name: &str, result: Option<&[u32]>) { .unwrap(); println!("status: {}", status); assert!(status.success()); - let module = parity_wasm::deserialize_file(output.path()).unwrap(); + let module = walrus::Module::from_file(output.path()).unwrap(); let mut i = Interpreter::new(&module); assert_eq!(i.interpret_descriptor(name, &module), result); } diff --git a/crates/web-sys/tests/wasm/main.rs b/crates/web-sys/tests/wasm/main.rs index e3d3b48af0e9..d4c01b8d1078 100644 --- a/crates/web-sys/tests/wasm/main.rs +++ b/crates/web-sys/tests/wasm/main.rs @@ -55,8 +55,8 @@ pub mod span_element; pub mod style_element; pub mod table_element; pub mod title_element; -pub mod xpath_result; pub mod whitelisted_immutable_slices; +pub mod xpath_result; #[wasm_bindgen_test] fn deref_works() { diff --git a/crates/web-sys/tests/wasm/whitelisted_immutable_slices.rs b/crates/web-sys/tests/wasm/whitelisted_immutable_slices.rs index 07804f652af0..00b341a119e0 100644 --- a/crates/web-sys/tests/wasm/whitelisted_immutable_slices.rs +++ b/crates/web-sys/tests/wasm/whitelisted_immutable_slices.rs @@ -17,8 +17,8 @@ use web_sys::WebGlRenderingContext; #[wasm_bindgen(module = "./tests/wasm/element.js")] extern "C" { fn new_webgl_rendering_context() -> WebGlRenderingContext; - // TODO: Add a function to create another type to test here. - // These functions come from element.js +// TODO: Add a function to create another type to test here. +// These functions come from element.js } // TODO: Uncomment WebGlRenderingContext test. Every now and then we can check if this works diff --git a/crates/webidl/src/first_pass.rs b/crates/webidl/src/first_pass.rs index 02ab48e07a17..f0e6133ee0cf 100644 --- a/crates/webidl/src/first_pass.rs +++ b/crates/webidl/src/first_pass.rs @@ -37,7 +37,7 @@ pub(crate) struct FirstPassRecord<'src> { pub(crate) dictionaries: BTreeMap<&'src str, DictionaryData<'src>>, pub(crate) callbacks: BTreeSet<&'src str>, pub(crate) callback_interfaces: BTreeMap<&'src str, CallbackInterfaceData<'src>>, - pub(crate) immutable_f32_whitelist: BTreeSet<&'static str> + pub(crate) immutable_f32_whitelist: BTreeSet<&'static str>, } /// We need to collect interface data during the first pass, to be used later. diff --git a/crates/webidl/src/idl_type.rs b/crates/webidl/src/idl_type.rs index ca9fcb582237..1a1d978b23ba 100644 --- a/crates/webidl/src/idl_type.rs +++ b/crates/webidl/src/idl_type.rs @@ -43,7 +43,7 @@ pub(crate) enum IdlType<'a> { Uint32Array, Float32Array { /// Whether or not the generated web-sys function should use an immutable slice - immutable: bool + immutable: bool, }, Float64Array, ArrayBufferView, @@ -332,7 +332,7 @@ impl<'a> ToIdlType<'a> for Identifier<'a> { // instead use the immutable version. impl<'a> ToIdlType<'a> for term::Float32Array { fn to_idl_type(&self, _record: &FirstPassRecord<'a>) -> IdlType<'a> { - IdlType::Float32Array {immutable: false} + IdlType::Float32Array { immutable: false } } } @@ -520,7 +520,7 @@ impl<'a> IdlType<'a> { IdlType::Uint16Array => Some(array("u16", pos, false)), IdlType::Int32Array => Some(array("i32", pos, false)), IdlType::Uint32Array => Some(array("u32", pos, false)), - IdlType::Float32Array {immutable} => Some(array("f32", pos, *immutable)), + IdlType::Float32Array { immutable } => Some(array("f32", pos, *immutable)), IdlType::Float64Array => Some(array("f64", pos, false)), IdlType::ArrayBufferView | IdlType::BufferSource => js_sys("Object"), diff --git a/crates/webidl/src/lib.rs b/crates/webidl/src/lib.rs index 637070c0ab48..eea08f7caee6 100644 --- a/crates/webidl/src/lib.rs +++ b/crates/webidl/src/lib.rs @@ -182,23 +182,21 @@ fn builtin_idents() -> BTreeSet { } fn immutable_f32_whitelist() -> BTreeSet<&'static str> { - BTreeSet::from_iter( - vec![ - // WebGlRenderingContext - "uniform1fv", - "uniform2fv", - "uniform3fv", - "uniform4fv", - "uniformMatrix2fv", - "uniformMatrix3fv", - "uniformMatrix4fv", - "vertexAttrib1fv", - "vertexAttrib2fv", - "vertexAttrib3fv", - "vertexAttrib4fv", - // TODO: Add another type's functions here. Leave a comment header with the type name - ] - ) + BTreeSet::from_iter(vec![ + // WebGlRenderingContext + "uniform1fv", + "uniform2fv", + "uniform3fv", + "uniform4fv", + "uniformMatrix2fv", + "uniformMatrix3fv", + "uniformMatrix4fv", + "vertexAttrib1fv", + "vertexAttrib2fv", + "vertexAttrib3fv", + "vertexAttrib4fv", + // TODO: Add another type's functions here. Leave a comment header with the type name + ]) } /// Run codegen on the AST to generate rust code. diff --git a/crates/webidl/src/util.rs b/crates/webidl/src/util.rs index f0e896247ef1..c14ab20e5c40 100644 --- a/crates/webidl/src/util.rs +++ b/crates/webidl/src/util.rs @@ -631,7 +631,6 @@ impl<'src> FirstPassRecord<'src> { return ret; } - /// When generating our web_sys APIs we default to setting slice references that /// get passed to JS as mutable in case they get mutated in JS. /// @@ -645,7 +644,7 @@ impl<'src> FirstPassRecord<'src> { fn maybe_adjust<'a>(&self, mut idl_type: IdlType<'a>, id: &'a OperationId) -> IdlType<'a> { let op = match id { OperationId::Operation(Some(op)) => op, - _ => return idl_type + _ => return idl_type, }; if self.immutable_f32_whitelist.contains(op) { @@ -656,8 +655,6 @@ impl<'src> FirstPassRecord<'src> { idl_type } - - } /// Search for an attribute by name in some webidl object's attributes. @@ -721,8 +718,7 @@ pub fn is_structural( // Note that once host bindings is implemented we'll want to switch this // from `true` to `false`, and then we'll want to largely read information // from the WebIDL about whether to use structural bindings or not. - true - || has_named_attribute(item_attrs, "Unforgeable") + true || has_named_attribute(item_attrs, "Unforgeable") || has_named_attribute(container_attrs, "Unforgeable") || has_ident_attribute(container_attrs, "Global") } @@ -749,9 +745,11 @@ fn flag_slices_immutable(ty: &mut IdlType) { IdlType::Record(item1, item2) => { flag_slices_immutable(item1); flag_slices_immutable(item2); - }, + } IdlType::Union(list) => { - for item in list { flag_slices_immutable(item); } + for item in list { + flag_slices_immutable(item); + } } // catch-all for everything else like Object _ => {} diff --git a/examples/fetch/src/lib.rs b/examples/fetch/src/lib.rs index 9ee7340e3bc3..f24627b70a14 100644 --- a/examples/fetch/src/lib.rs +++ b/examples/fetch/src/lib.rs @@ -1,11 +1,11 @@ use futures::{future, Future}; use js_sys::Promise; +use serde::{Deserialize, Serialize}; use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; use wasm_bindgen_futures::future_to_promise; use wasm_bindgen_futures::JsFuture; use web_sys::{Request, RequestInit, RequestMode, Response}; -use serde::{Deserialize, Serialize}; /// A struct to hold some data from the github Branch API. /// diff --git a/examples/raytrace-parallel/src/lib.rs b/examples/raytrace-parallel/src/lib.rs index 7db52101c543..c7f81ee8bba7 100644 --- a/examples/raytrace-parallel/src/lib.rs +++ b/examples/raytrace-parallel/src/lib.rs @@ -320,7 +320,7 @@ impl Shared { fn update_image( &self, done: bool, - data: MutexGuard<'_,Vec>, + data: MutexGuard<'_, Vec>, global: &DedicatedWorkerGlobalScope, ) -> Result<(), JsValue> { // This is pretty icky. We can't create an `ImageData` backed by diff --git a/examples/todomvc/src/template.rs b/examples/todomvc/src/template.rs index 9f0be88129e1..20b800ce860b 100644 --- a/examples/todomvc/src/template.rs +++ b/examples/todomvc/src/template.rs @@ -1,5 +1,5 @@ -use askama::Template as AskamaTemplate; use crate::store::{ItemList, ItemListTrait}; +use askama::Template as AskamaTemplate; #[derive(AskamaTemplate)] #[template(path = "row.html")] diff --git a/tests/headless.rs b/tests/headless.rs index ded767828fd5..87ee1145eace 100755 --- a/tests/headless.rs +++ b/tests/headless.rs @@ -28,7 +28,7 @@ fn works() { } #[wasm_bindgen] -extern { +extern "C" { #[wasm_bindgen(js_namespace = console)] pub fn log(s: &str); } diff --git a/tests/wasm/api.rs b/tests/wasm/api.rs index b72635151e34..92b7b21d1a9e 100644 --- a/tests/wasm/api.rs +++ b/tests/wasm/api.rs @@ -149,7 +149,11 @@ fn memory_accessor_appears_to_work() { #[wasm_bindgen_test] fn debug_output() { - let test_iter = debug_values().dyn_into::().unwrap().values().into_iter(); + let test_iter = debug_values() + .dyn_into::() + .unwrap() + .values() + .into_iter(); let expecteds = vec![ "JsValue(null)", "JsValue(undefined)", diff --git a/tests/wasm/classes.rs b/tests/wasm/classes.rs index e73a1b6e272f..4d1999928b58 100644 --- a/tests/wasm/classes.rs +++ b/tests/wasm/classes.rs @@ -405,8 +405,7 @@ fn renamed_export() { } #[cfg_attr(target_arch = "wasm32", wasm_bindgen)] -pub struct ConditionalBindings { -} +pub struct ConditionalBindings {} #[cfg_attr(target_arch = "wasm32", wasm_bindgen)] impl ConditionalBindings { diff --git a/tests/wasm/closures.rs b/tests/wasm/closures.rs index 8e8fb5cf2cbf..da833322e3ba 100644 --- a/tests/wasm/closures.rs +++ b/tests/wasm/closures.rs @@ -299,14 +299,21 @@ fn test_closure_returner() { #[wasm_bindgen] pub fn closure_returner() -> Result { - let o = Object::new(); let some_fn = Closure::wrap(Box::new(move || BadStruct {}) as Box); - Reflect::set(&o, &JsValue::from("someKey"), &some_fn.as_ref().unchecked_ref()) - .unwrap(); - Reflect::set(&o, &JsValue::from("handle"), &JsValue::from(ClosureHandle(some_fn))) - .unwrap(); + Reflect::set( + &o, + &JsValue::from("someKey"), + &some_fn.as_ref().unchecked_ref(), + ) + .unwrap(); + Reflect::set( + &o, + &JsValue::from("handle"), + &JsValue::from(ClosureHandle(some_fn)), + ) + .unwrap(); Ok(o) }