Merge pull request #1237 from alexcrichton/walrus

Migrate `wasm-bindgen` to using `walrus`
This commit is contained in:
Alex Crichton 2019-02-12 09:26:46 -06:00 committed by GitHub
commit 6004454775
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
74 changed files with 1007 additions and 3522 deletions

View File

@ -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

View File

@ -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' }

View File

@ -144,4 +144,4 @@ macro_rules! decode_api {
);
}
shared_api!(decode_api);
wasm_bindgen_shared::shared_api!(decode_api);

View File

@ -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<usize>,
/// 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<u32, DescribeInstruction>,
func_to_descriptor: BTreeMap<FunctionId, DescribeInstruction>,
}
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<ExprId>,
}
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);
.add_import_func("__wbindgen_placeholder__", &import_name, ty);
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(())
}
/// 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;
}
}
}

View File

@ -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!(

View File

@ -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<HashMap<String, ExportedClass>>,
pub function_table_needed: bool,
pub interpreter: &'a mut Interpreter,
pub memory_init: Option<ResizableLimits>,
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<Descriptor> {
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<FieldValue>,
}
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,

View File

@ -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!(

View File

@ -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_xform::Config>,
threads: Option<wasm_bindgen_threads_xform::Config>,
}
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::<Module>(&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<Vec<u8>>,
) -> Result<Vec<decode::Program<'a>>, 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<Option<&'a str>, 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<threads_xform::Config> {
fn threads_config() -> Option<wasm_bindgen_threads_xform::Config> {
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<threads_xform::Config> {
}
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());
}
}
}

View File

@ -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<String, Error> {
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<String, Error> {
let mut ts = typescript(&self.module)?;
if self.base64 {
ts.push_str("export const booted: Promise<boolean>;\n");
}
return ts;
Ok(ts)
}
pub fn js_and_wasm(mut self) -> Result<(String, Option<Vec<u8>>), 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
}
}

View File

@ -1,107 +0,0 @@
use std::mem;
use parity_wasm::elements::*;
pub struct Remap<F>(pub F);
impl<F> Remap<F>
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);
}
}

View File

@ -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']

View File

@ -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.
///

View File

@ -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();

View File

@ -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);

View File

@ -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)

View File

@ -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)?;
}

View File

@ -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<Future<Item = JsValue, Error = JsValue>>) -> P
/// This function has the same panic behavior as `future_to_promise`.
pub fn spawn_local<F>(future: F)
where
F: Future<Item=(), Error=()> + 'static,
F: Future<Item = (), Error = ()> + 'static,
{
future_to_promise(
future

View File

@ -1,73 +0,0 @@
use std::mem;
type T = usize;
const BITS: usize = mem::size_of::<T>() * 8;
pub struct BitSet {
bits: Vec<T>,
}
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));
}
}

File diff suppressed because it is too large Load Diff

View File

@ -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<Test>, 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::<Vec<_>>();
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<Error>> {
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::<Vec<_>>()
.join("\n")
}
fn generate_blesssed(input: &str, actual: &str) -> String {
let mut input = input
.lines()
.filter(|l| !l.starts_with(";;"))
.collect::<Vec<_>>()
.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;
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,7 +0,0 @@
(module
(func $foo)
)
;; STDOUT (update this section with `BLESS_TESTS=1` while running tests)
;; (module)
;; STDOUT

View File

@ -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

View File

@ -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

View File

@ -1,7 +0,0 @@
(module
(import "" "" (global i32))
)
;; STDOUT (update this section with `BLESS_TESTS=1` while running tests)
;; (module)
;; STDOUT

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,7 +0,0 @@
(module
(table 0 17 anyfunc)
)
;; STDOUT (update this section with `BLESS_TESTS=1` while running tests)
;; (module)
;; STDOUT

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,5 +0,0 @@
(module)
;; STDOUT (update this section with `BLESS_TESTS=1` while running tests)
;; (module)
;; STDOUT

View File

@ -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

View File

@ -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

View File

@ -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<TokenStream, Diag
}
/// Takes the parsed input from a `#[wasm_bindgen]` macro and returns the generated bindings
pub fn expand_class_marker(attr: TokenStream, input: TokenStream) -> Result<TokenStream, Diagnostic> {
pub fn expand_class_marker(
attr: TokenStream,
input: TokenStream,
) -> Result<TokenStream, Diagnostic> {
parser::reset_attrs_used();
let mut item = syn::parse2::<syn::ImplItemMethod>(input)?;
let opts: ClassMarker = syn::parse2(attr)?;
@ -62,11 +65,9 @@ pub fn expand_class_marker(attr: TokenStream, input: TokenStream) -> Result<Toke
// We manually implement `ToTokens for ImplItemMethod` here, injecting our
// program's tokens before the actual method's inner body tokens.
let mut tokens = proc_macro2::TokenStream::new();
tokens.append_all(item.attrs.iter().filter(|attr| {
match attr.style {
syn::AttrStyle::Outer => 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<Toke
if let Err(e) = program.try_to_tokens(tokens) {
err = Some(e);
}
tokens.append_all(item.attrs.iter().filter(|attr| {
match attr.style {
syn::AttrStyle::Inner(_) => 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)

View File

@ -796,7 +796,11 @@ impl<'a> MacroParse<(Option<BindgenAttrs>, &'a mut TokenStream)> for syn::Item {
}
impl<'a> MacroParse<BindgenAttrs> 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<BindgenAttrs> 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::<Vec<_>>();
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)

View File

@ -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"

View File

@ -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<Vec<PassiveSegment>, 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<Vec<PassiveSegment>, 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")
fn update_memory(module: &mut Module, max: u32) -> Result<MemoryId, Error> {
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");
}
match &mut offset.code_mut()[0] {
Instruction::I32Const(n) => Ok(n),
_ => bail!("unrecognized offset"),
}
}
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");
// 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);
}
// 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);
}
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,
});
}
// 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,
_ => {}
}
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<u32, Error> {
fn inject_thread_id_counter(module: &mut Module, memory: MemoryId) -> Result<u32, Error> {
// 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<u32, Error> {
// 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<u32, Error> {
};
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<Option<u32>, 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<Option<GlobalId>, 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::<Vec<_>>();
// 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<u32>,
stack_pointer: Option<GlobalId>,
stack_size: u32,
memory: MemoryId,
) {
use walrus::ir::*;
assert!(stack_size % PAGE_SIZE == 0);
let mut instrs = Vec::new();
let mut builder = walrus::FunctionBuilder::new();
let mut exprs = Vec::new();
let local = module.locals.add(ValType::I32);
// 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));
// Store this thread ID into our thread ID global
instrs.push(Instruction::TeeLocal(0));
instrs.push(Instruction::SetGlobal(globals.thread_id));
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);
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
// 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()
};
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);
}
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;
}
_ => {}
}
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);
module.start = Some(id);
}
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,
};
// 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");
}
map.insert(idx as u32, Instruction::GetGlobal(globals.thread_id));
}
"__wbindgen_tcb_get" => {
if !fty.params().is_empty() || fty.return_type() != Some(ValueType::I32) {
bail!("__wbindgen_tcb_get intrinsic has the wrong signature");
}
map.insert(idx as u32, Instruction::GetGlobal(globals.thread_tcb));
}
"__wbindgen_tcb_set" => {
if fty.params().len() != 1 || fty.return_type().is_some() {
bail!("__wbindgen_tcb_set intrinsic has the wrong signature");
}
map.insert(idx as u32, Instruction::SetGlobal(globals.thread_tcb));
}
other => bail!("unknown thread intrinsic: {}", other),
}
}
};
use walrus::ir::*;
// 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();
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),
}
}
// ... and in theory we'd remove `import_idx` here but we let `wasm-gc`
// take care of that later.
struct Visitor<'a> {
map: &'a HashMap<FunctionId, Intrinsic>,
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),
};
match self.map.get(&call.func) {
Some(Intrinsic::GetThreadId) => {
assert!(call.args.is_empty());
*expr = GlobalGet {
global: self.globals.thread_id,
}
.into();
}
Some(Intrinsic::GetTcb) => {
assert!(call.args.is_empty());
*expr = GlobalGet {
global: self.globals.thread_tcb,
}
.into();
}
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],
}
.into();
}
None => call.visit_mut(self),
}
}
}
Ok(())
}

View File

@ -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"

View File

@ -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<u32>,
describe_closure_idx: Option<u32>,
describe_id: Option<FunctionId>,
describe_closure_id: Option<FunctionId>,
// Id of the function table
functions: Option<TableId>,
// A mapping of string names to the function index, filled with all exported
// functions.
name_map: HashMap<String, u32>,
// The numerical index of the sections in the wasm module, indexed into
// the module's list of sections.
code_idx: Option<usize>,
types_idx: Option<usize>,
functions_idx: Option<usize>,
elements_idx: Option<usize>,
name_map: HashMap<String, FunctionId>,
// The current stack pointer (global 0) and wasm memory (the stack). Only
// used in a limited capacity.
sp: i32,
mem: Vec<i32>,
// The wasm stack. Note how it's just `i32` which is intentional, we don't
// support other types.
stack: Vec<i32>,
// 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<u32>,
}
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<usize>,
) -> 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 &sections.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<u32> {
self.describe_closure_idx
pub fn describe_closure_id(&self) -> Option<FunctionId> {
self.describe_closure_id
}
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 = &sections.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 &sections.types.types()[code_sig as usize] {
Type::Function(t) => t,
};
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());
}
// ... and the remaining locals all start as zero ...
for _ in 0..nlocals {
locals.push(0);
}
// ... 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
}
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);
}
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;
}
I32Load(/* align = */ 2, offset) => {
let addr = self.stack.pop().unwrap() as u32;
self.stack.push(self.mem[((addr + *offset) as usize) / 4]);
}
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);
/// Returns the detected id of the function table.
pub fn function_table_id(&self) -> Option<TableId> {
self.functions
}
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,
};
)*}
fn call(&mut self, id: FunctionId, module: &Module, args: &[i32]) -> Option<i32> {
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 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);
}
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);
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;
}
}
}
block.exprs.last().and_then(|e| frame.eval(*e))
}
}
struct Frame<'a> {
module: &'a Module,
local: &'a LocalFunction,
interp: &'a mut Interpreter,
locals: BTreeMap<LocalId, i32>,
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<i32> {
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
}
// 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),
}
}
// 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);
}
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::<Vec<_>>();
self.interp.call(e.func, self.module, &args);
None
}
}
// 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),
}
f(
self,
&Sections {
code,
types,
functions,
elements,
},
)
}
}

View File

@ -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);
}

View File

@ -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() {

View File

@ -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

View File

@ -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.

View File

@ -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"),

View File

@ -182,23 +182,21 @@ fn builtin_idents() -> BTreeSet<Ident> {
}
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.

View File

@ -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
_ => {}

View File

@ -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.
///

View File

@ -320,7 +320,7 @@ impl Shared {
fn update_image(
&self,
done: bool,
data: MutexGuard<'_,Vec<u8>>,
data: MutexGuard<'_, Vec<u8>>,
global: &DedicatedWorkerGlobalScope,
) -> Result<(), JsValue> {
// This is pretty icky. We can't create an `ImageData` backed by

View File

@ -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")]

View File

@ -28,7 +28,7 @@ fn works() {
}
#[wasm_bindgen]
extern {
extern "C" {
#[wasm_bindgen(js_namespace = console)]
pub fn log(s: &str);
}

View File

@ -149,7 +149,11 @@ fn memory_accessor_appears_to_work() {
#[wasm_bindgen_test]
fn debug_output() {
let test_iter = debug_values().dyn_into::<js_sys::Array>().unwrap().values().into_iter();
let test_iter = debug_values()
.dyn_into::<js_sys::Array>()
.unwrap()
.values()
.into_iter();
let expecteds = vec![
"JsValue(null)",
"JsValue(undefined)",

View File

@ -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 {

View File

@ -299,14 +299,21 @@ fn test_closure_returner() {
#[wasm_bindgen]
pub fn closure_returner() -> Result<Object, JsValue> {
let o = Object::new();
let some_fn = Closure::wrap(Box::new(move || BadStruct {}) as Box<ClosureType>);
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)
}