Skip to content

Commit

Permalink
Merge pull request #1690 from alexcrichton/webidl-polyfill
Browse files Browse the repository at this point in the history
Add support as a vanilla polyfill of WebIDL bindings
  • Loading branch information
alexcrichton committed Aug 2, 2019
2 parents 117fce1 + 452ce29 commit 732b691
Show file tree
Hide file tree
Showing 2 changed files with 171 additions and 1 deletion.
1 change: 1 addition & 0 deletions crates/cli-support/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
171 changes: 170 additions & 1 deletion crates/cli-support/src/webidl/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -504,6 +504,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);
Expand Down Expand Up @@ -1287,6 +1291,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> {
Expand Down

0 comments on commit 732b691

Please sign in to comment.