201 lines
7.3 KiB
Rust
Raw Normal View History

First refactor for WebIDL bindings This commit starts the `wasm-bindgen` CLI tool down the road to being a true polyfill for WebIDL bindings. This refactor is probably the first of a few, but is hopefully the largest and most sprawling and everything will be a bit more targeted from here on out. The goal of this refactoring is to separate out the massive `crates/cli-support/src/js/mod.rs` into a number of separate pieces of functionality. It currently takes care of basically everything including: * Binding intrinsics * Handling anyref transformations * Generating all JS for imports/exports * All the logic for how to import and how to name imports * Execution and management of wasm-bindgen closures Many of these are separable concerns and most overlap with WebIDL bindings. The internal refactoring here is intended to make it more clear who's responsible for what as well as making some existing operations much more straightforward. At a high-level, the following changes are done: 1. A `src/webidl.rs` module is introduced. The purpose of this module is to take all of the raw wasm-bindgen custom sections from the module and transform them into a WebIDL bindings section. This module has a placeholder `WebidlCustomSection` which is nowhere near the actual custom section but if you squint is in theory very similar. It's hoped that this will eventually become the true WebIDL custom section, currently being developed in an external crate. Currently, however, the WebIDL bindings custom section only covers a subset of the functionality we export to wasm-bindgen users. To avoid leaving them high and dry this module also contains an auxiliary custom section named `WasmBindgenAux`. This custom section isn't intended to have a binary format, but is intended to represent a theoretical custom section necessary to couple with WebIDL bindings to achieve all our desired functionality in `wasm-bindgen`. It'll never be standardized, but it'll also never be serialized :) 2. The `src/webidl.rs` module now takes over quite a bit of functionality from `src/js/mod.rs`. Namely it handles synthesis of an `export_map` and an `import_map` mapping export/import IDs to exactly what's expected to be hooked up there. This does not include type information (as that's in the bindings section) but rather includes things like "this is the method of class A" or "this import is from module `foo`" and things like that. These could arguably be subsumed by future JS features as well, but that's for another time! 3. All handling of wasm-bindgen "descriptor functions" now happens in a dedicated `src/descriptors.rs` module. The output of this module is its own custom section (intended to be immediately consumed by the WebIDL module) which is in theory what we want to ourselves emit one day but rustc isn't capable of doing so right now. 4. Invocations and generations of imports are completely overhauled. Using the `import_map` generated in the WebIDL step all imports are now handled much more precisely in one location rather than haphazardly throughout the module. This means we have precise information about each import of the module and we only modify exactly what we're looking at. This also vastly simplifies intrinsic generation since it's all simply a codegen part of the `rust2js.rs` module now. 5. Handling of direct imports which don't have a JS shim generated is slightly different from before and is intended to be future-compatible with WebIDL bindings in its full glory, but we'll need to update it to handle cases for constructors and method calls eventually as well. 6. Intrinsic definitions now live in their own file (`src/intrinsic.rs`) and have a separated definition for their symbol name and signature. The actual implementation of each intrinsic lives in `rust2js.rs` There's a number of TODO items to finish before this merges. This includes reimplementing the anyref pass and actually implementing import maps for other targets. Those will come soon in follow-up commits, but the entire `tests/wasm/main.rs` suite is currently passing and this seems like a good checkpoint.
2019-05-23 09:15:26 -07:00
//! Management of wasm-bindgen descriptor functions.
//!
//! The purpose of this module is to basically execute a pass on a raw wasm
//! module that just came out of the compiler. The pass will execute all
//! relevant descriptor functions contained in the module which wasm-bindgen
//! uses to convey type infomation here, to the CLI.
//!
//! All descriptor functions are removed after this pass runs and in their stead
//! a new custom section, defined in this module, is inserted into the
//! `walrus::Module` which contains all the results of all the descriptor
//! functions.
use crate::descriptor::{Closure, Descriptor};
use failure::Error;
use std::borrow::Cow;
use std::collections::{HashMap, HashSet};
use std::mem;
use walrus::ImportId;
use walrus::{CustomSection, FunctionId, LocalFunction, Module, TypedCustomSectionId};
use wasm_bindgen_wasm_interpreter::Interpreter;
#[derive(Default, Debug)]
pub struct WasmBindgenDescriptorsSection {
pub descriptors: HashMap<String, Descriptor>,
pub closure_imports: HashMap<ImportId, Closure>,
}
pub type WasmBindgenDescriptorsSectionId = TypedCustomSectionId<WasmBindgenDescriptorsSection>;
/// Execute all `__wbindgen_describe_*` functions in a module, inserting a
/// custom section which represents the executed value of each descriptor.
///
/// Afterwards this will delete all descriptor functions from the module.
pub fn execute(module: &mut Module) -> Result<WasmBindgenDescriptorsSectionId, Error> {
let mut section = WasmBindgenDescriptorsSection::default();
let mut interpreter = Interpreter::new(module)?;
section.execute_exports(module, &mut interpreter)?;
section.execute_closures(module, &mut interpreter)?;
// Delete all descriptor functions and imports from the module now that
// we've executed all of them.
walrus::passes::gc::run(module);
Ok(module.customs.add(section))
}
impl WasmBindgenDescriptorsSection {
fn execute_exports(
&mut self,
module: &mut Module,
interpreter: &mut Interpreter,
) -> Result<(), Error> {
let mut to_remove = Vec::new();
for export in module.exports.iter() {
let prefix = "__wbindgen_describe_";
if !export.name.starts_with(prefix) {
continue;
}
let id = match export.item {
walrus::ExportItem::Function(id) => id,
_ => panic!("{} export not a function", export.name),
};
if let Some(d) = interpreter.interpret_descriptor(id, module) {
let name = &export.name[prefix.len()..];
let descriptor = Descriptor::decode(d);
self.descriptors.insert(name.to_string(), descriptor);
}
to_remove.push(export.id());
}
for id in to_remove {
module.exports.delete(id);
}
Ok(())
}
fn execute_closures(
&mut self,
module: &mut Module,
interpreter: &mut Interpreter,
) -> Result<(), Error> {
use walrus::ir::*;
// If our describe closure intrinsic isn't present or wasn't linked
// then there's no closures, so nothing to do!
let wbindgen_describe_closure = match interpreter.describe_closure_id() {
Some(i) => i,
None => return Ok(()),
};
// Find all functions which call `wbindgen_describe_closure`. These are
// specially codegen'd so we know the rough structure of them. For each
// one we delegate to the interpreter to figure out the actual result.
let mut element_removal_list = HashSet::new();
let mut func_to_descriptor = HashMap::new();
for (id, local) in module.funcs.iter_local() {
let entry = local.entry_block();
let mut find = FindDescribeClosure {
func: local,
wbindgen_describe_closure,
cur: entry.into(),
call: None,
};
find.visit_block_id(&entry);
if let Some(call) = find.call {
let descriptor = interpreter
.interpret_closure_descriptor(id, module, &mut element_removal_list)
.unwrap();
func_to_descriptor.insert(id, (call, Descriptor::decode(descriptor)));
}
}
// For all indirect functions that were closure descriptors, delete them
// from the function table since we've executed them and they're not
// necessary in the final binary.
let table_id = match interpreter.function_table_id() {
Some(id) => id,
None => return Ok(()),
};
let table = module.tables.get_mut(table_id);
let table = match &mut table.kind {
walrus::TableKind::Function(f) => f,
_ => unreachable!(),
};
for idx in element_removal_list {
log::trace!("delete element {}", idx);
assert!(table.elements[idx].is_some());
table.elements[idx] = None;
}
// And finally replace all calls of `wbindgen_describe_closure` with a
// freshly manufactured import. Save off the type of this import in
// ourselves, and then we're good to go.
let ty = module.funcs.get(wbindgen_describe_closure).ty();
for (func, (call_instr, descriptor)) in func_to_descriptor {
let import_name = format!("__wbindgen_closure_wrapper{}", func.index());
let id = module.add_import_func("__wbindgen_placeholder__", &import_name, ty);
let import_id = module
.imports
.iter()
.find(|i| i.name == import_name)
.unwrap()
.id();
module.funcs.get_mut(id).name = Some(import_name);
let local = match &mut module.funcs.get_mut(func).kind {
walrus::FunctionKind::Local(l) => l,
_ => unreachable!(),
};
match local.get_mut(call_instr) {
Expr::Call(e) => {
assert_eq!(e.func, wbindgen_describe_closure);
e.func = id;
}
_ => unreachable!(),
}
self.closure_imports
.insert(import_id, descriptor.unwrap_closure());
}
return Ok(());
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);
}
}
}
}
}
impl CustomSection for WasmBindgenDescriptorsSection {
fn name(&self) -> &str {
"wasm-bindgen descriptors"
}
fn data(&self, _: &walrus::IdsToIndices) -> Cow<[u8]> {
panic!("shouldn't emit custom sections just yet");
}
}