mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-03-16 18:20:51 +00:00
Merge pull request #1237 from alexcrichton/walrus
Migrate `wasm-bindgen` to using `walrus`
This commit is contained in:
commit
6004454775
14
.travis.yml
14
.travis.yml
@ -173,17 +173,9 @@ matrix:
|
||||
script: cargo test -p ui-tests
|
||||
if: branch = master
|
||||
|
||||
# wasm-gc tests work alright
|
||||
- name: "test wasm-bindgen-gc crate"
|
||||
install:
|
||||
- git clone https://github.com/WebAssembly/wabt
|
||||
- mkdir -p wabt/build
|
||||
- (cd wabt/build && cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=sccache -DCMAKE_CXX_COMPILER_ARG1=c++ -DBUILD_TESTS=OFF && cmake --build . -- -j4)
|
||||
- export PATH=$PATH:`pwd`/wabt/build
|
||||
script:
|
||||
- cargo test -p wasm-bindgen-gc
|
||||
# Interpreter tests should quickly pass
|
||||
- cargo test -p wasm-bindgen-wasm-interpreter
|
||||
# wasm-interpreter tests work alright
|
||||
- name: "test wasm-bindgen-wasm-interpreter crate"
|
||||
script: cargo test -p wasm-bindgen-wasm-interpreter
|
||||
if: branch = master
|
||||
|
||||
# Dist linux binary
|
||||
|
@ -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' }
|
||||
|
@ -144,4 +144,4 @@ macro_rules! decode_api {
|
||||
);
|
||||
}
|
||||
|
||||
shared_api!(decode_api);
|
||||
wasm_bindgen_shared::shared_api!(decode_api);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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!(
|
||||
|
@ -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,
|
||||
|
@ -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!(
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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']
|
||||
|
@ -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.
|
||||
///
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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)?;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
1047
crates/gc/src/lib.rs
1047
crates/gc/src/lib.rs
File diff suppressed because it is too large
Load Diff
@ -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;
|
||||
}
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -1,7 +0,0 @@
|
||||
(module
|
||||
(func $foo)
|
||||
)
|
||||
|
||||
;; STDOUT (update this section with `BLESS_TESTS=1` while running tests)
|
||||
;; (module)
|
||||
;; STDOUT
|
@ -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
|
@ -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
|
@ -1,7 +0,0 @@
|
||||
(module
|
||||
(import "" "" (global i32))
|
||||
)
|
||||
|
||||
;; STDOUT (update this section with `BLESS_TESTS=1` while running tests)
|
||||
;; (module)
|
||||
;; STDOUT
|
@ -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
|
@ -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
|
@ -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
|
@ -1,7 +0,0 @@
|
||||
(module
|
||||
(table 0 17 anyfunc)
|
||||
)
|
||||
|
||||
;; STDOUT (update this section with `BLESS_TESTS=1` while running tests)
|
||||
;; (module)
|
||||
;; STDOUT
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -1,5 +0,0 @@
|
||||
(module)
|
||||
|
||||
;; STDOUT (update this section with `BLESS_TESTS=1` while running tests)
|
||||
;; (module)
|
||||
;; STDOUT
|
@ -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
|
@ -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
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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"
|
||||
|
@ -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(())
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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 §ions.types.types()[code_sig as usize] {
|
||||
Type::Function(t) => t,
|
||||
};
|
||||
for _ in 0..function_ty.params().len() {
|
||||
self.stack.push(0);
|
||||
}
|
||||
|
||||
self.call(closure_descriptor_idx, sections);
|
||||
assert_eq!(self.stack.len(), 1);
|
||||
self.stack.pop();
|
||||
let descriptor_table_idx = self.descriptor_table_idx.take().unwrap();
|
||||
let func = module.funcs.get(id);
|
||||
assert_eq!(module.types.get(func.ty()).params().len(), 2);
|
||||
self.call(id, module, &[0, 0]);
|
||||
let descriptor_table_idx =
|
||||
self.descriptor_table_idx
|
||||
.take()
|
||||
.expect("descriptor function should return index") as usize;
|
||||
|
||||
// After we've got the table index of the descriptor function we're
|
||||
// interested go take a look in the function table to find what the
|
||||
// actual index of the function is.
|
||||
let (entry_idx, offset, entry) = sections
|
||||
let functions = self.functions.expect("function table should be present");
|
||||
let functions = match &module.tables.get(functions).kind {
|
||||
walrus::TableKind::Function(f) => f,
|
||||
};
|
||||
let descriptor_id = functions
|
||||
.elements
|
||||
.entries()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, entry)| {
|
||||
let code = match entry.offset() {
|
||||
Some(offset) => offset.code(),
|
||||
None => return None,
|
||||
};
|
||||
if code.len() != 2 {
|
||||
return None;
|
||||
}
|
||||
if code[1] != Instruction::End {
|
||||
return None;
|
||||
}
|
||||
match code[0] {
|
||||
Instruction::I32Const(x) => Some((i, x as u32, entry)),
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
.find(|(_i, offset, entry)| {
|
||||
*offset <= descriptor_table_idx
|
||||
&& descriptor_table_idx < (*offset + entry.members().len() as u32)
|
||||
})
|
||||
.expect("failed to find index in table elements");
|
||||
let idx = (descriptor_table_idx - offset) as usize;
|
||||
let descriptor_idx = entry.members()[idx];
|
||||
.get(descriptor_table_idx)
|
||||
.expect("out of bounds read of function table")
|
||||
.expect("attempting to execute null function");
|
||||
|
||||
// This is used later to actually remove the entry from the table, but
|
||||
// we don't do the removal just yet
|
||||
entry_removal_list.push((entry_idx, idx));
|
||||
entry_removal_list.push(descriptor_table_idx);
|
||||
|
||||
// And now execute the descriptor!
|
||||
self.interpret_descriptor_idx(descriptor_idx, sections)
|
||||
self.interpret_descriptor_id(descriptor_id, module)
|
||||
}
|
||||
|
||||
/// Returns the function space index of the `__wbindgen_describe_closure`
|
||||
/// Returns the function id of the `__wbindgen_describe_closure`
|
||||
/// imported function.
|
||||
pub fn describe_closure_idx(&self) -> Option<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 = §ions.code.bodies()[code_idx];
|
||||
|
||||
// Allocate space for our call frame's local variables. All local
|
||||
// variables should be of the `i32` type.
|
||||
assert!(body.locals().len() <= 1, "too many local types");
|
||||
let nlocals = body
|
||||
.locals()
|
||||
.get(0)
|
||||
.map(|i| {
|
||||
assert_eq!(i.value_type(), ValueType::I32);
|
||||
i.count()
|
||||
})
|
||||
.unwrap_or(0);
|
||||
|
||||
let code_sig = sections.functions.entries()[code_idx].type_ref();
|
||||
let function_ty = match §ions.types.types()[code_sig as usize] {
|
||||
Type::Function(t) => t,
|
||||
};
|
||||
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,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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"),
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
_ => {}
|
||||
|
@ -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.
|
||||
///
|
||||
|
@ -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
|
||||
|
@ -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")]
|
||||
|
@ -28,7 +28,7 @@ fn works() {
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern {
|
||||
extern "C" {
|
||||
#[wasm_bindgen(js_namespace = console)]
|
||||
pub fn log(s: &str);
|
||||
}
|
||||
|
@ -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)",
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user