diff --git a/.travis.yml b/.travis.yml index 8ef712e68da..6a4c7f8c513 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/crates/cli-support/Cargo.toml b/crates/cli-support/Cargo.toml index c860d6a3d0f..0a8a21c1c83 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" +rustc-demangle = "0.1.13" tempfile = "3.0" -wasm-bindgen-gc = { path = '../gc', version = '=0.2.34' } +walrus = "0.1" wasm-bindgen-shared = { path = "../shared", version = '=0.2.34' } wasm-bindgen-threads-xform = { path = '../threads-xform', version = '=0.2.34' } wasm-bindgen-wasm-interpreter = { path = "../wasm-interpreter", version = '=0.2.34' } diff --git a/crates/cli-support/src/decode.rs b/crates/cli-support/src/decode.rs index d6dfb6b2cf5..e5283139af2 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 8f88b738704..8cc8c13866f 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 91b4b4f2f87..af964b1231a 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 c315bc39e0b..f8e4b28e690 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 20c60d3dd22..284ce5bcfc4 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 1a07c032d81..f5176c97fd4 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 537f16d4029..cdf8fa49fc1 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 c1dfb99f38b..00000000000 --- 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 eef6115e2f9..722b3f13419 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.34" } wasm-bindgen-shared = { path = "../shared", version = "=0.2.34" } -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 8b8e87c6a37..ad6cc3a7090 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 04abb79c8f8..de840fd83c1 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 f7e9a3d88cc..5b6eaec045d 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 70401212c6d..a745d3458af 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 b0ad9bf5cd3..8ad32cb4bd3 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 d202519fb6b..890e41ece0e 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/src/bitvec.rs b/crates/gc/src/bitvec.rs deleted file mode 100644 index b3585466eb5..00000000000 --- 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 851380e42a4..00000000000 --- 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 9f016775d16..00000000000 --- 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 eb3bdf89d28..00000000000 --- 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 b97067e2ec1..00000000000 --- 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 313671a3a37..00000000000 --- 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 16e1bf07710..00000000000 --- 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 0310d9dd0b3..00000000000 --- 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 7aa66867625..00000000000 --- 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 0df7e52a7b3..00000000000 --- 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 d3ae85f3a54..00000000000 --- 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 b37d5d05c5d..00000000000 --- 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 c23d833ba60..00000000000 --- 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 60795979e8f..00000000000 --- 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 592bef76842..00000000000 --- 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 e264d9139df..00000000000 --- 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 2ce7d099b00..00000000000 --- 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 7ed399ba0bd..00000000000 --- 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 5ef6564ff37..00000000000 --- 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 8738c3b5883..00000000000 --- 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 e37c50177b3..00000000000 --- 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 406c85230c8..00000000000 --- 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 b0592b221af..00000000000 --- 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 e0ccc861e38..00000000000 --- 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 852642e22b4..00000000000 --- 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 561ac9ea0c2..00000000000 --- 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 759b2782486..00000000000 --- 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 ca3d303dc93..00000000000 --- 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 782adb3c9d7..00000000000 --- 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 b80d55d9b3e..00000000000 --- 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 1d19197ab5c..00000000000 --- 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 516e64ad8c2..00000000000 --- 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 f6db1ec0c04..00000000000 --- 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 001847db894..00000000000 --- 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 84e86762429..00000000000 --- 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 6dd88a8ab72..00000000000 --- 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 1ad663d78e3..00000000000 --- 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 c9c70494654..cf807e86736 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 c3ea5ad9b81..b87f6d1a856 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 af7072c2b8b..50d2bd3529c 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 1fbede769ae..b61bb5a4da9 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 47252d5cb23..d55c07d93f2 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 cd34a0e9f3a..b96151a001b 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); + // Build up the mapping of exported functions to function ids. + 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 b74e94132ee..79af91fb928 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 e3d3b48af0e..d4c01b8d107 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 07804f652af..00b341a119e 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 02ab48e07a1..f0e6133ee0c 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 ca9fcb58223..1a1d978b23b 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 637070c0ab4..eea08f7caee 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 f0e896247ef..c14ab20e5c4 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 9ee7340e3bc..f24627b70a1 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 7db52101c54..c7f81ee8bba 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 9f0be88129e..20b800ce860 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 ded767828fd..87ee1145eac 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 b72635151e3..92b7b21d1a9 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 e73a1b6e272..4d1999928b5 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 8e8fb5cf2cb..da833322e3b 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) }