Migrate wasm-bindgen to using walrus

This commit moves `wasm-bindgen` the CLI tool from internally using
`parity-wasm` for wasm parsing/serialization to instead use `walrus`.
The `walrus` crate is something we've been working on recently with an
aim to replace the usage of `parity-wasm` in `wasm-bindgen` to make the
current CLI tool more maintainable as well as more future-proof.

The `walrus` crate provides a much nicer AST to work with as well as a
structured `Module`, whereas `parity-wasm` provides a very raw interface
to the wasm module which isn't really appropriate for our use case. The
many transformations and tweaks that wasm-bindgen does have a huge
amount of ad-hoc index management to carefully craft a final wasm
binary, but this is all entirely taken care for us with the `walrus`
crate.

Additionally, `wasm-bindgen` will ingest and rewrite the wasm file,
often changing the binary offsets of functions. Eventually with DWARF
debug information we'll need to be sure to preserve the debug
information throughout the transformations that `wasm-bindgen` does
today. This is practically impossible to do with the `parity-wasm`
architecture, but `walrus` was designed from the get-go to solve this
problem transparently in the `walrus` crate itself. (it doesn't today,
but this is planned work)

It is the intention that this does not end up regressing any
`wasm-bindgen` use cases, neither in functionality or in speed. As a
large change and refactoring, however, it's likely that at least
something will arise! We'll want to continue to remain vigilant to any
issues that come up with this commit.

Note that the `gc` crate has been deleted as part of this change, as the
`gc` crate is no longer necessary since `walrus` does it automatically.
Additionally the `gc` crate was one of the main problems with preserving
debug information as it often deletes wasm items!

Finally, this also starts moving crates to the 2018 edition where
necessary since `walrus` requires the 2018 edition, and in general it's
more pleasant to work within the 2018 edition!
This commit is contained in:
Alex Crichton 2019-01-31 09:54:23 -08:00
parent c30dbc3179
commit 894b479213
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)
}