mirror of
https://github.com/rustwasm/wasm-bindgen.git
synced 2024-12-14 20:11:37 +03:00
Merge pull request #1725 from alexcrichton/real-webidl-section
Add support for emitting a Wasm Interface Types section
This commit is contained in:
commit
487289cf9b
@ -6,7 +6,7 @@ use walrus::Module;
|
||||
use wasm_bindgen_anyref_xform::Context;
|
||||
use wasm_webidl_bindings::ast;
|
||||
|
||||
pub fn process(module: &mut Module) -> Result<(), Error> {
|
||||
pub fn process(module: &mut Module, wasm_interface_types: bool) -> Result<(), Error> {
|
||||
let mut cfg = Context::default();
|
||||
cfg.prepare(module)?;
|
||||
let bindings = module
|
||||
@ -45,18 +45,24 @@ pub fn process(module: &mut Module) -> Result<(), Error> {
|
||||
|
||||
cfg.run(module)?;
|
||||
|
||||
// Make sure to export the `anyref` table for the JS bindings since it
|
||||
// will need to be initialized. If it doesn't exist though then the
|
||||
// module must not use it, so we skip it.
|
||||
let table = module.tables.iter().find(|t| match t.kind {
|
||||
walrus::TableKind::Anyref(_) => true,
|
||||
_ => false,
|
||||
});
|
||||
let table = match table {
|
||||
Some(t) => t.id(),
|
||||
None => return Ok(()),
|
||||
};
|
||||
module.exports.add("__wbg_anyref_table", table);
|
||||
// If our output is using WebAssembly interface types then our bindings will
|
||||
// never use this table, so no need to export it. Otherwise it's highly
|
||||
// likely in web/JS embeddings this will be used, so make sure we export it
|
||||
// to avoid it getting gc'd accidentally.
|
||||
if !wasm_interface_types {
|
||||
// Make sure to export the `anyref` table for the JS bindings since it
|
||||
// will need to be initialized. If it doesn't exist though then the
|
||||
// module must not use it, so we skip it.
|
||||
let table = module.tables.iter().find(|t| match t.kind {
|
||||
walrus::TableKind::Anyref(_) => true,
|
||||
_ => false,
|
||||
});
|
||||
let table = match table {
|
||||
Some(t) => t.id(),
|
||||
None => return Ok(()),
|
||||
};
|
||||
module.exports.add("__wbg_anyref_table", table);
|
||||
}
|
||||
|
||||
// Clean up now-unused intrinsics and shims and such
|
||||
walrus::passes::gc::run(module);
|
||||
|
@ -50,6 +50,15 @@ macro_rules! intrinsics {
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the symbol name of this intrinsic
|
||||
pub fn name(&self) -> &'static str {
|
||||
match self {
|
||||
$(
|
||||
Intrinsic::$name => $sym,
|
||||
)*
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -2081,11 +2081,13 @@ impl<'a> Context<'a> {
|
||||
&binding.outgoing,
|
||||
wasm_ty.params(),
|
||||
&webidl.params,
|
||||
self.config.wasm_interface_types,
|
||||
)
|
||||
&& webidl::incoming_do_not_require_glue(
|
||||
&binding.incoming,
|
||||
&webidl.result.into_iter().collect::<Vec<_>>(),
|
||||
wasm_ty.results(),
|
||||
self.config.wasm_interface_types,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -36,6 +36,7 @@ pub struct Bindgen {
|
||||
// "ready to be instantiated on any thread"
|
||||
threads: wasm_bindgen_threads_xform::Config,
|
||||
anyref: bool,
|
||||
wasm_interface_types: bool,
|
||||
encode_into: EncodeInto,
|
||||
}
|
||||
|
||||
@ -49,6 +50,7 @@ pub struct Output {
|
||||
snippets: HashMap<String, Vec<String>>,
|
||||
local_modules: HashMap<String, String>,
|
||||
npm_dependencies: HashMap<String, (PathBuf, String)>,
|
||||
wasm_interface_types: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -73,6 +75,8 @@ pub enum EncodeInto {
|
||||
|
||||
impl Bindgen {
|
||||
pub fn new() -> Bindgen {
|
||||
let anyref = env::var("WASM_BINDGEN_ANYREF").is_ok();
|
||||
let wasm_interface_types = env::var("WASM_INTERFACE_TYPES").is_ok();
|
||||
Bindgen {
|
||||
input: Input::None,
|
||||
out_name: None,
|
||||
@ -88,7 +92,8 @@ impl Bindgen {
|
||||
emit_start: true,
|
||||
weak_refs: env::var("WASM_BINDGEN_WEAKREF").is_ok(),
|
||||
threads: threads_config(),
|
||||
anyref: env::var("WASM_BINDGEN_ANYREF").is_ok(),
|
||||
anyref: anyref || wasm_interface_types,
|
||||
wasm_interface_types,
|
||||
encode_into: EncodeInto::Test,
|
||||
}
|
||||
}
|
||||
@ -315,7 +320,7 @@ impl Bindgen {
|
||||
// the webidl bindings proposal) as well as an auxiliary section for all
|
||||
// sorts of miscellaneous information and features #[wasm_bindgen]
|
||||
// supports that aren't covered by WebIDL bindings.
|
||||
webidl::process(&mut module, self.anyref, self.emit_start)?;
|
||||
webidl::process(&mut module, self.anyref, self.wasm_interface_types, self.emit_start)?;
|
||||
|
||||
// Now that we've got type information from the webidl processing pass,
|
||||
// touch up the output of rustc to insert anyref shims where necessary.
|
||||
@ -323,7 +328,7 @@ impl Bindgen {
|
||||
// currently off-by-default since `anyref` is still in development in
|
||||
// engines.
|
||||
if self.anyref {
|
||||
anyref::process(&mut module)?;
|
||||
anyref::process(&mut module, self.wasm_interface_types)?;
|
||||
}
|
||||
|
||||
let aux = module
|
||||
@ -344,6 +349,11 @@ impl Bindgen {
|
||||
(npm_dependencies, cx.finalize(stem)?)
|
||||
};
|
||||
|
||||
if self.wasm_interface_types {
|
||||
webidl::standard::add_section(&mut module, &aux, &bindings)
|
||||
.with_context(|_| "failed to generate a standard wasm bindings custom section")?;
|
||||
}
|
||||
|
||||
Ok(Output {
|
||||
module,
|
||||
stem: stem.to_string(),
|
||||
@ -354,6 +364,7 @@ impl Bindgen {
|
||||
ts,
|
||||
mode: self.mode.clone(),
|
||||
typescript: self.typescript,
|
||||
wasm_interface_types: self.wasm_interface_types,
|
||||
})
|
||||
}
|
||||
|
||||
@ -506,6 +517,7 @@ fn unexported_unused_lld_things(module: &mut Module) {
|
||||
|
||||
impl Output {
|
||||
pub fn js(&self) -> &str {
|
||||
assert!(!self.wasm_interface_types);
|
||||
&self.js
|
||||
}
|
||||
|
||||
@ -518,6 +530,23 @@ impl Output {
|
||||
}
|
||||
|
||||
fn _emit(&self, out_dir: &Path) -> Result<(), Error> {
|
||||
let wasm_name = if self.wasm_interface_types {
|
||||
self.stem.clone()
|
||||
} else {
|
||||
format!("{}_bg", self.stem)
|
||||
};
|
||||
let wasm_path = out_dir
|
||||
.join(wasm_name)
|
||||
.with_extension("wasm");
|
||||
fs::create_dir_all(out_dir)?;
|
||||
let wasm_bytes = self.module.emit_wasm()?;
|
||||
fs::write(&wasm_path, wasm_bytes)
|
||||
.with_context(|_| format!("failed to write `{}`", wasm_path.display()))?;
|
||||
|
||||
if self.wasm_interface_types {
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
// Write out all local JS snippets to the final destination now that
|
||||
// we've collected them from all the programs.
|
||||
for (identifier, list) in self.snippets.iter() {
|
||||
@ -554,7 +583,6 @@ impl Output {
|
||||
} else {
|
||||
"js"
|
||||
};
|
||||
fs::create_dir_all(out_dir)?;
|
||||
let js_path = out_dir.join(&self.stem).with_extension(extension);
|
||||
fs::write(&js_path, reset_indentation(&self.js))
|
||||
.with_context(|_| format!("failed to write `{}`", js_path.display()))?;
|
||||
@ -565,10 +593,6 @@ impl Output {
|
||||
.with_context(|_| format!("failed to write `{}`", ts_path.display()))?;
|
||||
}
|
||||
|
||||
let wasm_path = out_dir
|
||||
.join(format!("{}_bg", self.stem))
|
||||
.with_extension("wasm");
|
||||
|
||||
if self.mode.nodejs() {
|
||||
let js_path = wasm_path.with_extension(extension);
|
||||
let shim = self.generate_node_wasm_import(&self.module, &wasm_path);
|
||||
@ -583,9 +607,6 @@ impl Output {
|
||||
.with_context(|_| format!("failed to write `{}`", ts_path.display()))?;
|
||||
}
|
||||
|
||||
let wasm_bytes = self.module.emit_wasm()?;
|
||||
fs::write(&wasm_path, wasm_bytes)
|
||||
.with_context(|_| format!("failed to write `{}`", wasm_path.display()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -41,6 +41,7 @@ const PLACEHOLDER_MODULE: &str = "__wbindgen_placeholder__";
|
||||
mod bindings;
|
||||
mod incoming;
|
||||
mod outgoing;
|
||||
pub mod standard;
|
||||
|
||||
pub use self::incoming::NonstandardIncoming;
|
||||
pub use self::outgoing::NonstandardOutgoing;
|
||||
@ -474,12 +475,14 @@ struct Context<'a> {
|
||||
unique_crate_identifier: &'a str,
|
||||
descriptors: HashMap<String, Descriptor>,
|
||||
anyref_enabled: bool,
|
||||
wasm_interface_types: bool,
|
||||
support_start: bool,
|
||||
}
|
||||
|
||||
pub fn process(
|
||||
module: &mut Module,
|
||||
anyref_enabled: bool,
|
||||
wasm_interface_types: bool,
|
||||
support_start: bool,
|
||||
) -> Result<(NonstandardWebidlSectionId, WasmBindgenAuxId), Error> {
|
||||
let mut storage = Vec::new();
|
||||
@ -496,6 +499,7 @@ pub fn process(
|
||||
module,
|
||||
start_found: false,
|
||||
anyref_enabled,
|
||||
wasm_interface_types,
|
||||
support_start,
|
||||
};
|
||||
cx.init()?;
|
||||
@ -619,8 +623,12 @@ impl<'a> Context<'a> {
|
||||
// `__wbindgen_init_anyref_table` function. This'll ensure that all
|
||||
// instances of this module have the initial slots of the anyref table
|
||||
// initialized correctly.
|
||||
//
|
||||
// Note that this is disabled if WebAssembly interface types are enabled
|
||||
// since that's a slightly different environment for now which doesn't have
|
||||
// quite the same initialization.
|
||||
fn inject_anyref_initialization(&mut self) -> Result<(), Error> {
|
||||
if !self.anyref_enabled {
|
||||
if !self.anyref_enabled || self.wasm_interface_types {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@ -1345,26 +1353,49 @@ impl<'a> Context<'a> {
|
||||
.bindings
|
||||
.get(bind.binding)
|
||||
.ok_or_else(|| format_err!("bad binding id"))?;
|
||||
let mut return_via_outptr = None;
|
||||
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(),
|
||||
),
|
||||
ast::FunctionBinding::Export(e) => {
|
||||
// This `match` is weird, see the comment at the top of
|
||||
// `standard.rs` for what it's doing.
|
||||
let outgoing = match e.result.bindings.get(0) {
|
||||
Some(ast::OutgoingBindingExpression::As(a)) if a.idx == u32::max_value() => {
|
||||
return_via_outptr = Some(vec![walrus::ValType::I32, walrus::ValType::I32]);
|
||||
&e.result.bindings[1..]
|
||||
}
|
||||
_ => &e.result.bindings[..],
|
||||
};
|
||||
(
|
||||
e.wasm_ty,
|
||||
e.webidl_ty,
|
||||
e.params.bindings.as_slice(),
|
||||
outgoing,
|
||||
)
|
||||
}
|
||||
ast::FunctionBinding::Import(e) => {
|
||||
// This `match` is weird, see the comment at the top of
|
||||
// `standard.rs` for what it's doing.
|
||||
let incoming = match e.result.bindings.get(0) {
|
||||
Some(ast::IncomingBindingExpression::Get(g)) if g.idx == u32::max_value() => {
|
||||
return_via_outptr = Some(vec![walrus::ValType::I32, walrus::ValType::I32]);
|
||||
&e.result.bindings[1..]
|
||||
}
|
||||
_ => &e.result.bindings[..],
|
||||
};
|
||||
(
|
||||
e.wasm_ty,
|
||||
e.webidl_ty,
|
||||
incoming,
|
||||
e.params.bindings.as_slice(),
|
||||
)
|
||||
}
|
||||
};
|
||||
let webidl_ty = copy_ty(&mut self.bindings.types, webidl_ty, &std.types);
|
||||
let webidl_ty = standard::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 {
|
||||
Ok(Binding {
|
||||
wasm_ty,
|
||||
webidl_ty,
|
||||
incoming: incoming
|
||||
@ -1377,40 +1408,8 @@ impl<'a> Context<'a> {
|
||||
.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!(),
|
||||
}
|
||||
}
|
||||
return_via_outptr,
|
||||
})
|
||||
}
|
||||
|
||||
/// Registers that `id` has a `binding` which was read from a standard
|
||||
@ -1662,7 +1661,23 @@ pub fn incoming_do_not_require_glue(
|
||||
exprs: &[NonstandardIncoming],
|
||||
from_webidl_tys: &[ast::WebidlTypeRef],
|
||||
to_wasm_tys: &[walrus::ValType],
|
||||
standard_webidl_enabled: bool,
|
||||
) -> bool {
|
||||
// If anything is nonstandard, then we're unconditionally going to need a JS
|
||||
// shim because, well, it's not standard.
|
||||
if exprs.iter().any(|e| match e {
|
||||
NonstandardIncoming::Standard(_) => false,
|
||||
_ => true,
|
||||
}) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If everything is `Standard` and we've actually got WebIDL bindings fully
|
||||
// enabled, then we don't require any glue at all!
|
||||
if standard_webidl_enabled {
|
||||
return true;
|
||||
}
|
||||
|
||||
exprs.len() == from_webidl_tys.len()
|
||||
&& exprs.len() == to_wasm_tys.len()
|
||||
&& exprs
|
||||
@ -1685,7 +1700,19 @@ pub fn outgoing_do_not_require_glue(
|
||||
exprs: &[NonstandardOutgoing],
|
||||
from_wasm_tys: &[walrus::ValType],
|
||||
to_webidl_tys: &[ast::WebidlTypeRef],
|
||||
standard_webidl_enabled: bool,
|
||||
) -> bool {
|
||||
// Same short-circuits as above.
|
||||
if exprs.iter().any(|e| match e {
|
||||
NonstandardOutgoing::Standard(_) => false,
|
||||
_ => true,
|
||||
}) {
|
||||
return false;
|
||||
}
|
||||
if standard_webidl_enabled {
|
||||
return true;
|
||||
}
|
||||
|
||||
exprs.len() == from_wasm_tys.len()
|
||||
&& exprs.len() == to_webidl_tys.len()
|
||||
&& exprs
|
||||
|
492
crates/cli-support/src/webidl/standard.rs
Normal file
492
crates/cli-support/src/webidl/standard.rs
Normal file
@ -0,0 +1,492 @@
|
||||
//! Support for generating a standard WebIDL custom section
|
||||
//!
|
||||
//! This module has all the necessary support for generating a full-fledged
|
||||
//! standard WebIDL custom section as defined by the `wasm-webidl-bindings`
|
||||
//! crate. This module also critically assumes that the WebAssembly module
|
||||
//! being generated **must be standalone**. In this mode all sorts of features
|
||||
//! supported by `#[wasm_bindgen]` aren't actually supported, such as closures,
|
||||
//! imports of global js names, js getters/setters, exporting structs, etc.
|
||||
//! These features may all eventually come to the standard bindings proposal,
|
||||
//! but it will likely take some time. In the meantime this module simply focuses
|
||||
//! on taking what's already a valid wasm module and letting it through with a
|
||||
//! standard WebIDL custom section. All other modules generate an error during
|
||||
//! this binding process.
|
||||
//!
|
||||
//! Note that when this function is called and used we're also not actually
|
||||
//! generating any JS glue. Any JS glue currently generated is also invalid if
|
||||
//! the module contains the wasm bindings section and it's actually respected.
|
||||
|
||||
// NB: Returning strings is weird
|
||||
//
|
||||
// This module has what is currently a pretty gross hack for dealing with
|
||||
// returning strings. One of the banner features of WebIDL bindings is not
|
||||
// requiring any language-specific glue to use wasm files and you get all sorts
|
||||
// of types like integers and strings by default. Note that strings are a huge
|
||||
// thing here.
|
||||
//
|
||||
// Dealing with *incoming* strings is easy enough in that the binding expression
|
||||
// has an allocator function to call and it's filled in and passed as two
|
||||
// pointers. Outgoing strings are a little harder, however, for two reasons:
|
||||
//
|
||||
// * One is that we need to return two values, which requires multi-value
|
||||
// * Another is that someone's got to free the string at some point
|
||||
//
|
||||
// Rust/wasm-bindgen don't support multi-value, and WebIDL bindings as literally
|
||||
// spec'd right this red hot second don't support freeing strings that we pass
|
||||
// out. These both have obvious fixes (supporting multi-value and extending the
|
||||
// bindings spec to support freeing) but we also want something working right
|
||||
// this red-hot second.
|
||||
//
|
||||
// To get things working we employ a terrible hack where the first bindign
|
||||
// expression of the result a function may indicate "use a thing that's way off
|
||||
// in the ether" and that's actually a sentinel for "oh ignore everything else
|
||||
// and the string is returned through an out-ptr as the first argument". This
|
||||
// manifests in all sorts of special hacks all over the place to handle this,
|
||||
// and it's a real bummer.
|
||||
//
|
||||
// This is in general just an explainer for the current state of things and
|
||||
// also a preemptive apology for writing the code to handle this in so many
|
||||
// places. I, like you, look forward to actually fixing this for real as the
|
||||
// spec continues to evolve and we implement more in wasm-bindgen.
|
||||
|
||||
use crate::descriptor::VectorKind;
|
||||
use crate::webidl::{AuxExportKind, AuxImport, AuxValue, JsImport, JsImportName};
|
||||
use crate::webidl::{NonstandardIncoming, NonstandardOutgoing};
|
||||
use crate::webidl::{NonstandardWebidlSection, WasmBindgenAux};
|
||||
use failure::{bail, Error, ResultExt};
|
||||
use walrus::Module;
|
||||
use wasm_webidl_bindings::ast;
|
||||
|
||||
pub fn add_section(
|
||||
module: &mut Module,
|
||||
aux: &WasmBindgenAux,
|
||||
nonstandard: &NonstandardWebidlSection,
|
||||
) -> Result<(), Error> {
|
||||
let mut section = ast::WebidlBindings::default();
|
||||
let WasmBindgenAux {
|
||||
extra_typescript: _, // ignore this even if it's specified
|
||||
local_modules,
|
||||
snippets,
|
||||
package_jsons,
|
||||
export_map,
|
||||
import_map,
|
||||
imports_with_catch,
|
||||
imports_with_variadic,
|
||||
imports_with_assert_no_shim: _, // not relevant for this purpose
|
||||
enums,
|
||||
structs,
|
||||
} = aux;
|
||||
|
||||
for (export, binding) in nonstandard.exports.iter() {
|
||||
// First up make sure this is something that's actually valid to export
|
||||
// form a vanilla WebAssembly module with WebIDL bindings.
|
||||
match &export_map[export].kind {
|
||||
AuxExportKind::Function(_) => {}
|
||||
AuxExportKind::Constructor(name) => {
|
||||
bail!(
|
||||
"cannot export `{}` constructor function when generating \
|
||||
a standalone WebAssembly module with no JS glue",
|
||||
name,
|
||||
);
|
||||
}
|
||||
AuxExportKind::Getter { class, field } => {
|
||||
bail!(
|
||||
"cannot export `{}::{}` getter function when generating \
|
||||
a standalone WebAssembly module with no JS glue",
|
||||
class,
|
||||
field,
|
||||
);
|
||||
}
|
||||
AuxExportKind::Setter { class, field } => {
|
||||
bail!(
|
||||
"cannot export `{}::{}` setter function when generating \
|
||||
a standalone WebAssembly module with no JS glue",
|
||||
class,
|
||||
field,
|
||||
);
|
||||
}
|
||||
AuxExportKind::StaticFunction { class, name } => {
|
||||
bail!(
|
||||
"cannot export `{}::{}` static function when \
|
||||
generating a standalone WebAssembly module with no \
|
||||
JS glue",
|
||||
class,
|
||||
name
|
||||
);
|
||||
}
|
||||
AuxExportKind::Method { class, name, .. } => {
|
||||
bail!(
|
||||
"cannot export `{}::{}` method when \
|
||||
generating a standalone WebAssembly module with no \
|
||||
JS glue",
|
||||
class,
|
||||
name
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let name = &module.exports.get(*export).name;
|
||||
let params = extract_incoming(&binding.incoming).with_context(|_| {
|
||||
format!(
|
||||
"failed to map arguments for export `{}` to standard \
|
||||
binding expressions",
|
||||
name
|
||||
)
|
||||
})?;
|
||||
let mut result = extract_outgoing(&binding.outgoing).with_context(|_| {
|
||||
format!(
|
||||
"failed to map return value for export `{}` to standard \
|
||||
binding expressions",
|
||||
name
|
||||
)
|
||||
})?;
|
||||
|
||||
// see comment at top of this module about returning strings for what
|
||||
// this is doing and why it's weird
|
||||
if binding.return_via_outptr.is_some() {
|
||||
result.insert(
|
||||
0,
|
||||
ast::OutgoingBindingExpressionAs {
|
||||
idx: u32::max_value(),
|
||||
ty: ast::WebidlScalarType::Long.into(),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
let binding = section.bindings.insert(ast::ExportBinding {
|
||||
wasm_ty: binding.wasm_ty,
|
||||
webidl_ty: copy_ty(
|
||||
&mut section.types,
|
||||
binding.webidl_ty.into(),
|
||||
&nonstandard.types,
|
||||
),
|
||||
params: ast::IncomingBindingMap { bindings: params },
|
||||
result: ast::OutgoingBindingMap { bindings: result },
|
||||
});
|
||||
let func = match module.exports.get(*export).item {
|
||||
walrus::ExportItem::Function(f) => f,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
section.binds.insert(ast::Bind {
|
||||
func,
|
||||
binding: binding.into(),
|
||||
});
|
||||
}
|
||||
|
||||
for (import, binding) in nonstandard.imports.iter() {
|
||||
check_standard_import(&import_map[import])?;
|
||||
let (module_name, name) = {
|
||||
let import = module.imports.get(*import);
|
||||
(&import.module, &import.name)
|
||||
};
|
||||
let params = extract_outgoing(&binding.outgoing).with_context(|_| {
|
||||
format!(
|
||||
"failed to map arguments of import `{}::{}` to standard \
|
||||
binding expressions",
|
||||
module_name, name,
|
||||
)
|
||||
})?;
|
||||
let mut result = extract_incoming(&binding.incoming).with_context(|_| {
|
||||
format!(
|
||||
"failed to map return value of import `{}::{}` to standard \
|
||||
binding expressions",
|
||||
module_name, name,
|
||||
)
|
||||
})?;
|
||||
// see comment at top of this module about returning strings for what
|
||||
// this is doing and why it's weird
|
||||
if binding.return_via_outptr.is_some() {
|
||||
result.insert(
|
||||
0,
|
||||
ast::IncomingBindingExpressionGet {
|
||||
idx: u32::max_value(),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
let binding = section.bindings.insert(ast::ImportBinding {
|
||||
wasm_ty: binding.wasm_ty,
|
||||
webidl_ty: copy_ty(
|
||||
&mut section.types,
|
||||
binding.webidl_ty.into(),
|
||||
&nonstandard.types,
|
||||
),
|
||||
params: ast::OutgoingBindingMap { bindings: params },
|
||||
result: ast::IncomingBindingMap { bindings: result },
|
||||
});
|
||||
let func = match module.imports.get(*import).kind {
|
||||
walrus::ImportKind::Function(f) => f,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
section.binds.insert(ast::Bind {
|
||||
func,
|
||||
binding: binding.into(),
|
||||
});
|
||||
}
|
||||
|
||||
if let Some((name, _)) = local_modules.iter().next() {
|
||||
bail!(
|
||||
"generating a bindings section is currently incompatible with \
|
||||
local JS modules being specified as well, `{}` cannot be used \
|
||||
since a standalone wasm file is being generated",
|
||||
name,
|
||||
);
|
||||
}
|
||||
|
||||
if let Some((name, _)) = snippets.iter().filter(|(_, v)| !v.is_empty()).next() {
|
||||
bail!(
|
||||
"generating a bindings section is currently incompatible with \
|
||||
local JS snippets being specified as well, `{}` cannot be used \
|
||||
since a standalone wasm file is being generated",
|
||||
name,
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(path) = package_jsons.iter().next() {
|
||||
bail!(
|
||||
"generating a bindings section is currently incompatible with \
|
||||
package.json being consumed as well, `{}` cannot be used \
|
||||
since a standalone wasm file is being generated",
|
||||
path.display(),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(import) = imports_with_catch.iter().next() {
|
||||
let import = module.imports.get(*import);
|
||||
bail!(
|
||||
"generating a bindings section is currently incompatible with \
|
||||
`#[wasm_bindgen(catch)]` on the `{}::{}` import because a \
|
||||
a standalone wasm file is being generated",
|
||||
import.module,
|
||||
import.name,
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(import) = imports_with_variadic.iter().next() {
|
||||
let import = module.imports.get(*import);
|
||||
bail!(
|
||||
"generating a bindings section is currently incompatible with \
|
||||
`#[wasm_bindgen(variadic)]` on the `{}::{}` import because a \
|
||||
a standalone wasm file is being generated",
|
||||
import.module,
|
||||
import.name,
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(enum_) = enums.iter().next() {
|
||||
bail!(
|
||||
"generating a bindings section is currently incompatible with \
|
||||
exporting an `enum` from the wasm file, cannot export `{}`",
|
||||
enum_.name,
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(struct_) = structs.iter().next() {
|
||||
bail!(
|
||||
"generating a bindings section is currently incompatible with \
|
||||
exporting a `struct` from the wasm file, cannot export `{}`",
|
||||
struct_.name,
|
||||
);
|
||||
}
|
||||
|
||||
if nonstandard.elems.len() > 0 {
|
||||
// Note that this is a pretty cryptic error message, but we in theory
|
||||
// shouldn't ever hit this since closures always show up as some form
|
||||
// of nonstandard binding which was previously checked.
|
||||
bail!("generating a standalone wasm file requires no table element bindings");
|
||||
}
|
||||
|
||||
module.customs.add(section);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn extract_incoming(
|
||||
nonstandard: &[NonstandardIncoming],
|
||||
) -> Result<Vec<ast::IncomingBindingExpression>, Error> {
|
||||
let mut exprs = Vec::new();
|
||||
for expr in nonstandard {
|
||||
let desc = match expr {
|
||||
NonstandardIncoming::Standard(e) => {
|
||||
exprs.push(e.clone());
|
||||
continue;
|
||||
}
|
||||
NonstandardIncoming::Int64 { .. } => "64-bit integer",
|
||||
NonstandardIncoming::AllocCopyInt64 { .. } => "64-bit integer array",
|
||||
NonstandardIncoming::AllocCopyAnyrefArray { .. } => "array of JsValue",
|
||||
NonstandardIncoming::MutableSlice { .. } => "mutable slice",
|
||||
NonstandardIncoming::OptionSlice { .. } => "optional slice",
|
||||
NonstandardIncoming::OptionVector { .. } => "optional vector",
|
||||
NonstandardIncoming::OptionAnyref { .. } => "optional anyref",
|
||||
NonstandardIncoming::OptionNative { .. } => "optional integer",
|
||||
NonstandardIncoming::OptionU32Sentinel { .. } => "optional integer",
|
||||
NonstandardIncoming::OptionBool { .. } => "optional bool",
|
||||
NonstandardIncoming::OptionChar { .. } => "optional char",
|
||||
NonstandardIncoming::OptionIntegerEnum { .. } => "optional enum",
|
||||
NonstandardIncoming::OptionInt64 { .. } => "optional integer",
|
||||
NonstandardIncoming::RustType { .. } => "native Rust type",
|
||||
NonstandardIncoming::RustTypeRef { .. } => "reference to Rust type",
|
||||
NonstandardIncoming::OptionRustType { .. } => "optional Rust type",
|
||||
NonstandardIncoming::Char { .. } => "character",
|
||||
NonstandardIncoming::BorrowedAnyref { .. } => "borrowed anyref",
|
||||
};
|
||||
bail!("cannot represent {} with a standard bindings expression", desc);
|
||||
}
|
||||
Ok(exprs)
|
||||
}
|
||||
|
||||
fn extract_outgoing(
|
||||
nonstandard: &[NonstandardOutgoing],
|
||||
) -> Result<Vec<ast::OutgoingBindingExpression>, Error> {
|
||||
let mut exprs = Vec::new();
|
||||
for expr in nonstandard {
|
||||
let desc = match expr {
|
||||
NonstandardOutgoing::Standard(e) => {
|
||||
exprs.push(e.clone());
|
||||
continue;
|
||||
}
|
||||
// ... yeah ... let's just leak strings
|
||||
// see comment at top of this module about returning strings for
|
||||
// what this is doing and why it's weird
|
||||
NonstandardOutgoing::Vector {
|
||||
offset,
|
||||
length,
|
||||
kind: VectorKind::String,
|
||||
} => {
|
||||
exprs.push(
|
||||
ast::OutgoingBindingExpressionUtf8Str {
|
||||
offset: *offset,
|
||||
length: *length,
|
||||
ty: ast::WebidlScalarType::DomString.into(),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
NonstandardOutgoing::RustType { .. } => "rust type",
|
||||
NonstandardOutgoing::Char { .. } => "character",
|
||||
NonstandardOutgoing::Number64 { .. } => "64-bit integer",
|
||||
NonstandardOutgoing::BorrowedAnyref { .. } => "borrowed anyref",
|
||||
NonstandardOutgoing::Vector { .. } => "vector",
|
||||
NonstandardOutgoing::CachedString { .. } => "cached string",
|
||||
NonstandardOutgoing::View64 { .. } => "64-bit slice",
|
||||
NonstandardOutgoing::ViewAnyref { .. } => "anyref slice",
|
||||
NonstandardOutgoing::OptionVector { .. } => "optional vector",
|
||||
NonstandardOutgoing::OptionSlice { .. } => "optional slice",
|
||||
NonstandardOutgoing::OptionNative { .. } => "optional integer",
|
||||
NonstandardOutgoing::OptionU32Sentinel { .. } => "optional integer",
|
||||
NonstandardOutgoing::OptionBool { .. } => "optional boolean",
|
||||
NonstandardOutgoing::OptionChar { .. } => "optional character",
|
||||
NonstandardOutgoing::OptionIntegerEnum { .. } => "optional enum",
|
||||
NonstandardOutgoing::OptionInt64 { .. } => "optional 64-bit integer",
|
||||
NonstandardOutgoing::OptionRustType { .. } => "optional rust type",
|
||||
NonstandardOutgoing::StackClosure { .. } => "closures",
|
||||
};
|
||||
bail!("cannot represent {} with a standard bindings expression", desc);
|
||||
}
|
||||
Ok(exprs)
|
||||
}
|
||||
|
||||
/// Recursively clones `ty` into` dst` where it originally indexes values in
|
||||
/// `src`, returning a new type ref which indexes inside of `dst`.
|
||||
pub 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!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn check_standard_import(import: &AuxImport) -> Result<(), Error> {
|
||||
let desc_js = |js: &JsImport| {
|
||||
let mut extra = String::new();
|
||||
for field in js.fields.iter() {
|
||||
extra.push_str(".");
|
||||
extra.push_str(field);
|
||||
}
|
||||
match &js.name {
|
||||
JsImportName::Global { name } | JsImportName::VendorPrefixed { name, .. } => {
|
||||
format!("global `{}{}`", name, extra)
|
||||
}
|
||||
JsImportName::Module { module, name } => {
|
||||
format!("`{}{}` from '{}'", name, extra, module)
|
||||
}
|
||||
JsImportName::LocalModule { module, name } => {
|
||||
format!("`{}{}` from local module '{}'", name, extra, module)
|
||||
}
|
||||
JsImportName::InlineJs {
|
||||
unique_crate_identifier,
|
||||
name,
|
||||
..
|
||||
} => format!(
|
||||
"`{}{}` from inline js in '{}'",
|
||||
name, extra, unique_crate_identifier
|
||||
),
|
||||
}
|
||||
};
|
||||
|
||||
let item = match import {
|
||||
AuxImport::Value(AuxValue::Bare(js)) => {
|
||||
if js.fields.len() == 0 {
|
||||
if let JsImportName::Module { .. } = js.name {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
desc_js(js)
|
||||
}
|
||||
AuxImport::Value(AuxValue::Getter(js, name))
|
||||
| AuxImport::Value(AuxValue::Setter(js, name))
|
||||
| AuxImport::Value(AuxValue::ClassGetter(js, name))
|
||||
| AuxImport::Value(AuxValue::ClassSetter(js, name)) => {
|
||||
format!("field access of `{}` for {}", name, desc_js(js))
|
||||
}
|
||||
AuxImport::Instanceof(js) => format!("instance of check of {}", desc_js(js)),
|
||||
AuxImport::Static(js) => format!("static js value {}", desc_js(js)),
|
||||
AuxImport::StructuralMethod(name) => format!("structural method `{}`", name),
|
||||
AuxImport::StructuralGetter(name)
|
||||
| AuxImport::StructuralSetter(name)
|
||||
| AuxImport::StructuralClassGetter(_, name)
|
||||
| AuxImport::StructuralClassSetter(_, name) => {
|
||||
format!("structural field access of `{}`", name)
|
||||
}
|
||||
AuxImport::IndexingDeleterOfClass(_)
|
||||
| AuxImport::IndexingDeleterOfObject
|
||||
| AuxImport::IndexingGetterOfClass(_)
|
||||
| AuxImport::IndexingGetterOfObject
|
||||
| AuxImport::IndexingSetterOfClass(_)
|
||||
| AuxImport::IndexingSetterOfObject => format!("indexing getters/setters/deleters"),
|
||||
AuxImport::WrapInExportedClass(name) => {
|
||||
format!("wrapping a pointer in a `{}` js class wrapper", name)
|
||||
}
|
||||
AuxImport::Intrinsic(intrinsic) => {
|
||||
format!("wasm-bindgen specific intrinsic `{}`", intrinsic.name())
|
||||
}
|
||||
AuxImport::Closure { .. } => format!("creating a `Closure` wrapper"),
|
||||
};
|
||||
bail!(
|
||||
"cannot generate a standalone WebAssembly module which \
|
||||
contains an import of {} since it requires JS glue",
|
||||
item
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue
Block a user