Add support as a vanilla polyfill of WebIDL bindings

This commit adds support to `wasm-bindgen` to be a drop-in polyfill for
the WebIDL bindings proposal. Lots of internal refactoring has happened
previously to `wasm-bindgen` to make this possible, so this actually
ends up being a very small PR!

Most of `wasm-bindgen` is geared towards Rust-specific types and
Rust-specific support, but with the advent of WebIDL bindings this is a
standard way for a WebAssembly module to communicate its intended
interface in terms of higher level types. This PR allows `wasm-bindgen`
to be a polyfill for any WebAssembly module that has a valid WebIDL
bindings section, regardless of its producer. A standard WebIDL bindings
section is recognized in any input wasm module and that is slurped up
into wasm-bindgen's own internal data structures to get processed in the
same way that all Rust imports/exports are already processed.

The workflow for `wasm-bindgen` looks the same way that it does in Rust
today. You'd execute `wasm-bindgen path/to/foo.wasm --out-dir .` which
would output a new wasm file and a JS shim with the desired interface,
and the new wasm file would be suitable for loading in MVP
implementations of WebAssembly.

Note that this isn't super thoroughly tested, so there's likely still
some lingering assumptions that `wasm-bindgen` makes (such as
`__wbindgen_malloc` and others) which will need to be patched in the
future, but the intention of this commit is to start us down a road of
becoming a drop-in polyfill for WebIDL bindings, regardless of the
source. Also note that there's not actually any producer (AFAIK) of a
WebIDL bindings custom section, so it'd be that much harder to write
tests to do so!
This commit is contained in:
Alex Crichton 2019-07-31 11:55:38 -07:00
parent 38b8232f55
commit 452ce2916e
2 changed files with 171 additions and 1 deletions

View File

@ -259,6 +259,7 @@ impl Bindgen {
.generate_dwarf(self.keep_debug)
.generate_name_section(!self.remove_name_section)
.generate_producers_section(!self.remove_producers_section)
.on_parse(wasm_webidl_bindings::binary::on_parse)
.parse(&contents)
.context("failed to parse input file as wasm")?;
let stem = match &self.out_name {

View File

@ -27,7 +27,7 @@ use crate::decode;
use crate::descriptor::{Descriptor, Function};
use crate::descriptors::WasmBindgenDescriptorsSection;
use crate::intrinsic::Intrinsic;
use failure::{bail, Error};
use failure::{bail, format_err, Error};
use std::borrow::Cow;
use std::collections::{HashMap, HashSet};
use std::path::PathBuf;
@ -498,6 +498,10 @@ pub fn process(
cx.program(program)?;
}
if let Some(standard) = cx.module.customs.delete_typed::<ast::WebidlBindings>() {
cx.standard(&standard)?;
}
cx.verify()?;
let bindings = cx.module.customs.add(cx.bindings);
@ -1241,6 +1245,171 @@ impl<'a> Context<'a> {
Ok(JsImport { name, fields })
}
/// Processes bindings from a standard WebIDL bindings custom section.
///
/// No module coming out of the Rust compiler will have one of these, but
/// eventually there's going to be other producers of the WebIDL bindings
/// custom section as well. This functionality is intended to allow
/// `wasm-bindgen`-the-CLI-tool to act as a polyfill for those modules as
/// well as Rust modules.
///
/// Here a standard `WebidlBindings` custom section is taken and we process
/// that into our own internal data structures to ensure that we have a
/// binding listed for all the described bindings.
///
/// In other words, this is a glorified conversion from the "official"
/// WebIDL bindings custom section into the wasm-bindgen internal
/// representation.
fn standard(&mut self, std: &ast::WebidlBindings) -> Result<(), Error> {
for (_id, bind) in std.binds.iter() {
let binding = self.standard_binding(std, bind)?;
let func = self.module.funcs.get(bind.func);
match &func.kind {
walrus::FunctionKind::Import(i) => {
let id = i.import;
self.standard_import(binding, id)?;
}
walrus::FunctionKind::Local(_) => {
let export = self
.module
.exports
.iter()
.find(|e| match e.item {
walrus::ExportItem::Function(f) => f == bind.func,
_ => false,
})
.ok_or_else(|| format_err!("missing export function for webidl binding"))?;
let id = export.id();
self.standard_export(binding, id)?;
}
walrus::FunctionKind::Uninitialized(_) => unreachable!(),
}
}
Ok(())
}
/// Creates a wasm-bindgen-internal `Binding` from an official `Bind`
/// structure specified in the upstream binary format.
///
/// This will largely just copy some things into our own arenas but also
/// processes the list of binding expressions into our own representations.
fn standard_binding(
&mut self,
std: &ast::WebidlBindings,
bind: &ast::Bind,
) -> Result<Binding, Error> {
let binding: &ast::FunctionBinding = std
.bindings
.get(bind.binding)
.ok_or_else(|| format_err!("bad binding id"))?;
let (wasm_ty, webidl_ty, incoming, outgoing) = match binding {
ast::FunctionBinding::Export(e) => (
e.wasm_ty,
e.webidl_ty,
e.params.bindings.as_slice(),
e.result.bindings.as_slice(),
),
ast::FunctionBinding::Import(e) => (
e.wasm_ty,
e.webidl_ty,
e.result.bindings.as_slice(),
e.params.bindings.as_slice(),
),
};
let webidl_ty = copy_ty(&mut self.bindings.types, webidl_ty, &std.types);
let webidl_ty = match webidl_ty {
ast::WebidlTypeRef::Id(id) => <ast::WebidlFunction as ast::WebidlTypeId>::wrap(id),
_ => bail!("invalid webidl type listed"),
};
return Ok(Binding {
wasm_ty,
webidl_ty,
incoming: incoming
.iter()
.cloned()
.map(NonstandardIncoming::Standard)
.collect(),
outgoing: outgoing
.iter()
.cloned()
.map(NonstandardOutgoing::Standard)
.collect(),
return_via_outptr: None,
});
/// Recursively clones `ty` into` dst` where it originally indexes
/// values in `src`, returning a new type ref which indexes inside of
/// `dst`.
fn copy_ty(
dst: &mut ast::WebidlTypes,
ty: ast::WebidlTypeRef,
src: &ast::WebidlTypes,
) -> ast::WebidlTypeRef {
let id = match ty {
ast::WebidlTypeRef::Id(id) => id,
ast::WebidlTypeRef::Scalar(_) => return ty,
};
let ty: &ast::WebidlCompoundType = src.get(id).unwrap();
match ty {
ast::WebidlCompoundType::Function(f) => {
let params = f
.params
.iter()
.map(|param| copy_ty(dst, *param, src))
.collect();
let result = f.result.map(|ty| copy_ty(dst, ty, src));
dst.insert(ast::WebidlFunction {
kind: f.kind.clone(),
params,
result,
})
.into()
}
_ => unimplemented!(),
}
}
}
/// Registers that `id` has a `binding` which was read from a standard
/// webidl bindings section, so the source of `id` is its actual module/name
/// listed in the wasm module.
fn standard_import(&mut self, binding: Binding, id: walrus::ImportId) -> Result<(), Error> {
let import = self.module.imports.get(id);
let js = JsImport {
name: JsImportName::Module {
module: import.module.clone(),
name: import.name.clone(),
},
fields: Vec::new(),
};
let value = AuxValue::Bare(js);
assert!(self
.aux
.import_map
.insert(id, AuxImport::Value(value))
.is_none());
assert!(self.bindings.imports.insert(id, binding).is_none());
Ok(())
}
/// Registers that `id` has a `binding` and comes from a standard webidl
/// bindings section so it doesn't have any documentation or debug names we
/// can work with.
fn standard_export(&mut self, binding: Binding, id: walrus::ExportId) -> Result<(), Error> {
let export = self.module.exports.get(id);
let kind = AuxExportKind::Function(export.name.clone());
let export = AuxExport {
debug_name: format!("standard export {:?}", id),
comments: String::new(),
arg_names: None,
kind,
};
assert!(self.aux.export_map.insert(id, export).is_none());
assert!(self.bindings.exports.insert(id, binding).is_none());
Ok(())
}
/// Perform a small verification pass over the module to perform some
/// internal sanity checks.
fn verify(&self) -> Result<(), Error> {