//! 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. use crate::descriptor::VectorKind; use crate::webidl::{AuxExportKind, AuxImport, AuxValue, JsImport, JsImportName}; use crate::webidl::{NonstandardIncoming, NonstandardOutgoing}; use crate::webidl::{NonstandardWebidlSection, WasmBindgenAux}; use anyhow::{bail, Context, Error}; use walrus::Module; use wasm_bindgen_multi_value_xform as multi_value_xform; use wasm_bindgen_wasm_conventions as wasm_conventions; use wasm_webidl_bindings::ast; pub fn add_multi_value( module: &mut Module, bindings: &mut NonstandardWebidlSection, ) -> Result<(), Error> { let mut to_xform = vec![]; for (id, binding) in &bindings.exports { if let Some(ref results) = binding.return_via_outptr { // LLVM currently always uses the first parameter for the return // pointer. We hard code that here, since we have no better option. let return_pointer_index = 0; to_xform.push((*id, return_pointer_index, &results[..])); } } if to_xform.is_empty() { // Early exit to avoid failing if we don't have a memory or shadow stack // pointer because this is a minimal module that doesn't use linear // memory. return Ok(()); } let shadow_stack_pointer = wasm_conventions::get_shadow_stack_pointer(module)?; let memory = wasm_conventions::get_memory(module)?; multi_value_xform::run(module, memory, shadow_stack_pointer, &to_xform)?; // Finally, unset `return_via_outptr`, fix up its incoming bindings' // argument numberings, and update its function type. for (id, binding) in &mut bindings.exports { if binding.return_via_outptr.take().is_none() { continue; } if binding.incoming.is_empty() { bail!("missing incoming binding expression for return pointer parameter"); } if !is_ret_ptr_bindings(binding.incoming.remove(0)) { bail!("unexpected incoming binding expression for return pointer parameter"); } fixup_binding_argument_gets(&mut binding.incoming)?; let func = match module.exports.get(*id).item { walrus::ExportItem::Function(f) => f, _ => unreachable!(), }; binding.wasm_ty = module.funcs.get(func).ty(); // Be sure to delete the out-param pointer from the WebIDL type as well. let webidl_ty = bindings .types .get::(binding.webidl_ty) .unwrap(); let mut new_ty = webidl_ty.clone(); new_ty.params.remove(0); binding.webidl_ty = bindings.types.insert(new_ty); } Ok(()) } fn is_ret_ptr_bindings(b: NonstandardIncoming) -> bool { match b { NonstandardIncoming::Standard(ast::IncomingBindingExpression::As( ast::IncomingBindingExpressionAs { ty: walrus::ValType::I32, expr, }, )) => match *expr { ast::IncomingBindingExpression::Get(ast::IncomingBindingExpressionGet { idx: 0 }) => { true } _ => false, }, _ => false, } } // Since we removed the first parameter (which was the return pointer) now all // of the `Get` binding expression's are off by one. This function fixes these // `Get`s. fn fixup_binding_argument_gets(incoming: &mut [NonstandardIncoming]) -> Result<(), Error> { for inc in incoming { fixup_nonstandard_incoming(inc)?; } return Ok(()); fn fixup_nonstandard_incoming(inc: &mut NonstandardIncoming) -> Result<(), Error> { match inc { NonstandardIncoming::Standard(s) => fixup_standard_incoming(s), _ => bail!("found usage of non-standard bindings when in standard-bindings-only mode"), } } fn fixup_standard_incoming(s: &mut ast::IncomingBindingExpression) -> Result<(), Error> { match s { ast::IncomingBindingExpression::Get(e) => { if e.idx == 0 { bail!( "found usage of removed return pointer parameter in \ non-return pointer bindings" ); } else { e.idx -= 1; Ok(()) } } ast::IncomingBindingExpression::As(e) => fixup_standard_incoming(&mut e.expr), ast::IncomingBindingExpression::AllocUtf8Str(e) => fixup_standard_incoming(&mut e.expr), ast::IncomingBindingExpression::AllocCopy(e) => fixup_standard_incoming(&mut e.expr), ast::IncomingBindingExpression::EnumToI32(e) => fixup_standard_incoming(&mut e.expr), ast::IncomingBindingExpression::Field(e) => fixup_standard_incoming(&mut e.expr), ast::IncomingBindingExpression::BindImport(e) => fixup_standard_incoming(&mut e.expr), } } } 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 result = extract_outgoing(&binding.outgoing).with_context(|| { format!( "failed to map return value for export `{}` to standard \ binding expressions", name ) })?; assert!(binding.return_via_outptr.is_none()); 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 result = extract_incoming(&binding.incoming).with_context(|| { format!( "failed to map return value of import `{}::{}` to standard \ binding expressions", module_name, name, ) })?; assert!(binding.return_via_outptr.is_none()); 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, 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, 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::ValueWithThis(js, method) => format!("method `{}.{}`", desc_js(js), method), 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 ); }