mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-06-12 12:31:22 +00:00
Migrate wasm-bindgen
to using walrus
This commit moves `wasm-bindgen` the CLI tool from internally using `parity-wasm` for wasm parsing/serialization to instead use `walrus`. The `walrus` crate is something we've been working on recently with an aim to replace the usage of `parity-wasm` in `wasm-bindgen` to make the current CLI tool more maintainable as well as more future-proof. The `walrus` crate provides a much nicer AST to work with as well as a structured `Module`, whereas `parity-wasm` provides a very raw interface to the wasm module which isn't really appropriate for our use case. The many transformations and tweaks that wasm-bindgen does have a huge amount of ad-hoc index management to carefully craft a final wasm binary, but this is all entirely taken care for us with the `walrus` crate. Additionally, `wasm-bindgen` will ingest and rewrite the wasm file, often changing the binary offsets of functions. Eventually with DWARF debug information we'll need to be sure to preserve the debug information throughout the transformations that `wasm-bindgen` does today. This is practically impossible to do with the `parity-wasm` architecture, but `walrus` was designed from the get-go to solve this problem transparently in the `walrus` crate itself. (it doesn't today, but this is planned work) It is the intention that this does not end up regressing any `wasm-bindgen` use cases, neither in functionality or in speed. As a large change and refactoring, however, it's likely that at least something will arise! We'll want to continue to remain vigilant to any issues that come up with this commit. Note that the `gc` crate has been deleted as part of this change, as the `gc` crate is no longer necessary since `walrus` does it automatically. Additionally the `gc` crate was one of the main problems with preserving debug information as it often deletes wasm items! Finally, this also starts moving crates to the 2018 edition where necessary since `walrus` requires the 2018 edition, and in general it's more pleasant to work within the 2018 edition!
This commit is contained in:
@ -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);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user