Skip to content

Commit 452ce29

Browse files
committed
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!
1 parent 38b8232 commit 452ce29

File tree

2 files changed

+171
-1
lines changed

2 files changed

+171
-1
lines changed

crates/cli-support/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,7 @@ impl Bindgen {
259259
.generate_dwarf(self.keep_debug)
260260
.generate_name_section(!self.remove_name_section)
261261
.generate_producers_section(!self.remove_producers_section)
262+
.on_parse(wasm_webidl_bindings::binary::on_parse)
262263
.parse(&contents)
263264
.context("failed to parse input file as wasm")?;
264265
let stem = match &self.out_name {

crates/cli-support/src/webidl/mod.rs

Lines changed: 170 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ use crate::decode;
2727
use crate::descriptor::{Descriptor, Function};
2828
use crate::descriptors::WasmBindgenDescriptorsSection;
2929
use crate::intrinsic::Intrinsic;
30-
use failure::{bail, Error};
30+
use failure::{bail, format_err, Error};
3131
use std::borrow::Cow;
3232
use std::collections::{HashMap, HashSet};
3333
use std::path::PathBuf;
@@ -498,6 +498,10 @@ pub fn process(
498498
cx.program(program)?;
499499
}
500500

501+
if let Some(standard) = cx.module.customs.delete_typed::<ast::WebidlBindings>() {
502+
cx.standard(&standard)?;
503+
}
504+
501505
cx.verify()?;
502506

503507
let bindings = cx.module.customs.add(cx.bindings);
@@ -1241,6 +1245,171 @@ impl<'a> Context<'a> {
12411245
Ok(JsImport { name, fields })
12421246
}
12431247

1248+
/// Processes bindings from a standard WebIDL bindings custom section.
1249+
///
1250+
/// No module coming out of the Rust compiler will have one of these, but
1251+
/// eventually there's going to be other producers of the WebIDL bindings
1252+
/// custom section as well. This functionality is intended to allow
1253+
/// `wasm-bindgen`-the-CLI-tool to act as a polyfill for those modules as
1254+
/// well as Rust modules.
1255+
///
1256+
/// Here a standard `WebidlBindings` custom section is taken and we process
1257+
/// that into our own internal data structures to ensure that we have a
1258+
/// binding listed for all the described bindings.
1259+
///
1260+
/// In other words, this is a glorified conversion from the "official"
1261+
/// WebIDL bindings custom section into the wasm-bindgen internal
1262+
/// representation.
1263+
fn standard(&mut self, std: &ast::WebidlBindings) -> Result<(), Error> {
1264+
for (_id, bind) in std.binds.iter() {
1265+
let binding = self.standard_binding(std, bind)?;
1266+
let func = self.module.funcs.get(bind.func);
1267+
match &func.kind {
1268+
walrus::FunctionKind::Import(i) => {
1269+
let id = i.import;
1270+
self.standard_import(binding, id)?;
1271+
}
1272+
walrus::FunctionKind::Local(_) => {
1273+
let export = self
1274+
.module
1275+
.exports
1276+
.iter()
1277+
.find(|e| match e.item {
1278+
walrus::ExportItem::Function(f) => f == bind.func,
1279+
_ => false,
1280+
})
1281+
.ok_or_else(|| format_err!("missing export function for webidl binding"))?;
1282+
let id = export.id();
1283+
self.standard_export(binding, id)?;
1284+
}
1285+
walrus::FunctionKind::Uninitialized(_) => unreachable!(),
1286+
}
1287+
}
1288+
Ok(())
1289+
}
1290+
1291+
/// Creates a wasm-bindgen-internal `Binding` from an official `Bind`
1292+
/// structure specified in the upstream binary format.
1293+
///
1294+
/// This will largely just copy some things into our own arenas but also
1295+
/// processes the list of binding expressions into our own representations.
1296+
fn standard_binding(
1297+
&mut self,
1298+
std: &ast::WebidlBindings,
1299+
bind: &ast::Bind,
1300+
) -> Result<Binding, Error> {
1301+
let binding: &ast::FunctionBinding = std
1302+
.bindings
1303+
.get(bind.binding)
1304+
.ok_or_else(|| format_err!("bad binding id"))?;
1305+
let (wasm_ty, webidl_ty, incoming, outgoing) = match binding {
1306+
ast::FunctionBinding::Export(e) => (
1307+
e.wasm_ty,
1308+
e.webidl_ty,
1309+
e.params.bindings.as_slice(),
1310+
e.result.bindings.as_slice(),
1311+
),
1312+
ast::FunctionBinding::Import(e) => (
1313+
e.wasm_ty,
1314+
e.webidl_ty,
1315+
e.result.bindings.as_slice(),
1316+
e.params.bindings.as_slice(),
1317+
),
1318+
};
1319+
let webidl_ty = copy_ty(&mut self.bindings.types, webidl_ty, &std.types);
1320+
let webidl_ty = match webidl_ty {
1321+
ast::WebidlTypeRef::Id(id) => <ast::WebidlFunction as ast::WebidlTypeId>::wrap(id),
1322+
_ => bail!("invalid webidl type listed"),
1323+
};
1324+
return Ok(Binding {
1325+
wasm_ty,
1326+
webidl_ty,
1327+
incoming: incoming
1328+
.iter()
1329+
.cloned()
1330+
.map(NonstandardIncoming::Standard)
1331+
.collect(),
1332+
outgoing: outgoing
1333+
.iter()
1334+
.cloned()
1335+
.map(NonstandardOutgoing::Standard)
1336+
.collect(),
1337+
return_via_outptr: None,
1338+
});
1339+
1340+
/// Recursively clones `ty` into` dst` where it originally indexes
1341+
/// values in `src`, returning a new type ref which indexes inside of
1342+
/// `dst`.
1343+
fn copy_ty(
1344+
dst: &mut ast::WebidlTypes,
1345+
ty: ast::WebidlTypeRef,
1346+
src: &ast::WebidlTypes,
1347+
) -> ast::WebidlTypeRef {
1348+
let id = match ty {
1349+
ast::WebidlTypeRef::Id(id) => id,
1350+
ast::WebidlTypeRef::Scalar(_) => return ty,
1351+
};
1352+
let ty: &ast::WebidlCompoundType = src.get(id).unwrap();
1353+
match ty {
1354+
ast::WebidlCompoundType::Function(f) => {
1355+
let params = f
1356+
.params
1357+
.iter()
1358+
.map(|param| copy_ty(dst, *param, src))
1359+
.collect();
1360+
let result = f.result.map(|ty| copy_ty(dst, ty, src));
1361+
dst.insert(ast::WebidlFunction {
1362+
kind: f.kind.clone(),
1363+
params,
1364+
result,
1365+
})
1366+
.into()
1367+
}
1368+
_ => unimplemented!(),
1369+
}
1370+
}
1371+
}
1372+
1373+
/// Registers that `id` has a `binding` which was read from a standard
1374+
/// webidl bindings section, so the source of `id` is its actual module/name
1375+
/// listed in the wasm module.
1376+
fn standard_import(&mut self, binding: Binding, id: walrus::ImportId) -> Result<(), Error> {
1377+
let import = self.module.imports.get(id);
1378+
let js = JsImport {
1379+
name: JsImportName::Module {
1380+
module: import.module.clone(),
1381+
name: import.name.clone(),
1382+
},
1383+
fields: Vec::new(),
1384+
};
1385+
let value = AuxValue::Bare(js);
1386+
assert!(self
1387+
.aux
1388+
.import_map
1389+
.insert(id, AuxImport::Value(value))
1390+
.is_none());
1391+
assert!(self.bindings.imports.insert(id, binding).is_none());
1392+
1393+
Ok(())
1394+
}
1395+
1396+
/// Registers that `id` has a `binding` and comes from a standard webidl
1397+
/// bindings section so it doesn't have any documentation or debug names we
1398+
/// can work with.
1399+
fn standard_export(&mut self, binding: Binding, id: walrus::ExportId) -> Result<(), Error> {
1400+
let export = self.module.exports.get(id);
1401+
let kind = AuxExportKind::Function(export.name.clone());
1402+
let export = AuxExport {
1403+
debug_name: format!("standard export {:?}", id),
1404+
comments: String::new(),
1405+
arg_names: None,
1406+
kind,
1407+
};
1408+
assert!(self.aux.export_map.insert(id, export).is_none());
1409+
assert!(self.bindings.exports.insert(id, binding).is_none());
1410+
Ok(())
1411+
}
1412+
12441413
/// Perform a small verification pass over the module to perform some
12451414
/// internal sanity checks.
12461415
fn verify(&self) -> Result<(), Error> {

0 commit comments

Comments
 (0)