From 5595132645e8db12a868a51adfa69da7cb32848f Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 20 Nov 2019 07:20:30 -0800 Subject: [PATCH 01/35] wip --- crates/cli-support/Cargo.toml | 1 - crates/cli-support/src/anyref.rs | 12 ++++++------ crates/cli-support/src/lib.rs | 14 +++++++------- crates/cli-support/src/{webidl => wit}/bindings.rs | 0 crates/cli-support/src/{webidl => wit}/incoming.rs | 0 crates/cli-support/src/{webidl => wit}/mod.rs | 4 ++-- crates/cli-support/src/{webidl => wit}/outgoing.rs | 0 crates/cli-support/src/{webidl => wit}/standard.rs | 0 8 files changed, 15 insertions(+), 16 deletions(-) rename crates/cli-support/src/{webidl => wit}/bindings.rs (100%) rename crates/cli-support/src/{webidl => wit}/incoming.rs (100%) rename crates/cli-support/src/{webidl => wit}/mod.rs (99%) rename crates/cli-support/src/{webidl => wit}/outgoing.rs (100%) rename crates/cli-support/src/{webidl => wit}/standard.rs (100%) diff --git a/crates/cli-support/Cargo.toml b/crates/cli-support/Cargo.toml index b505efb96f6..6156aa7d27b 100644 --- a/crates/cli-support/Cargo.toml +++ b/crates/cli-support/Cargo.toml @@ -25,4 +25,3 @@ wasm-bindgen-multi-value-xform = { path = '../multi-value-xform', version = '=0. wasm-bindgen-threads-xform = { path = '../threads-xform', version = '=0.2.55' } wasm-bindgen-wasm-conventions = { path = '../wasm-conventions', version = '=0.2.55' } wasm-bindgen-wasm-interpreter = { path = "../wasm-interpreter", version = '=0.2.55' } -wasm-webidl-bindings = "0.6.0" diff --git a/crates/cli-support/src/anyref.rs b/crates/cli-support/src/anyref.rs index 82ac0d179ae..a1e6c6361d1 100644 --- a/crates/cli-support/src/anyref.rs +++ b/crates/cli-support/src/anyref.rs @@ -1,5 +1,5 @@ -use crate::webidl::{NonstandardIncoming, NonstandardOutgoing}; -use crate::webidl::{NonstandardWebidlSection, WasmBindgenAux}; +use crate::wit::NonstandardInstr; +use crate::wit::{NonstandardWitSection, WasmBindgenAux}; use anyhow::Error; use std::collections::HashSet; use walrus::Module; @@ -11,8 +11,8 @@ pub fn process(module: &mut Module, wasm_interface_types: bool) -> Result<(), Er cfg.prepare(module)?; let bindings = module .customs - .get_typed_mut::() - .expect("webidl custom section should exist"); + .get_typed_mut::() + .expect("wit custom section should exist"); // Transform all exported functions in the module, using the bindings listed // for each exported function. @@ -78,8 +78,8 @@ pub fn process(module: &mut Module, wasm_interface_types: bool) -> Result<(), Er .collect::>(); module .customs - .get_typed_mut::() - .expect("webidl custom section should exist") + .get_typed_mut::() + .expect("wit custom section should exist") .imports .retain(|id, _| remaining_imports.contains(id)); module diff --git a/crates/cli-support/src/lib.rs b/crates/cli-support/src/lib.rs index 20ee1e38db4..743e4a4a252 100755 --- a/crates/cli-support/src/lib.rs +++ b/crates/cli-support/src/lib.rs @@ -17,7 +17,7 @@ mod descriptors; mod intrinsic; mod js; pub mod wasm2es6js; -mod webidl; +mod wit; pub struct Bindgen { input: Input, @@ -329,11 +329,11 @@ impl Bindgen { // Process and remove our raw custom sections emitted by the // #[wasm_bindgen] macro and the compiler. In their stead insert a - // forward-compatible WebIDL bindings section (forward-compatible with - // 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( + // forward-compatible wasm interface types section as well as an + // auxiliary section for all sorts of miscellaneous information and + // features #[wasm_bindgen] supports that aren't covered by wasm + // interface types. + wit::process( &mut module, self.anyref, self.wasm_interface_types, @@ -355,7 +355,7 @@ impl Bindgen { .expect("aux section should be present"); let mut bindings = module .customs - .delete_typed::() + .delete_typed::() .unwrap(); // Now that our module is massaged and good to go, feed it into the JS diff --git a/crates/cli-support/src/webidl/bindings.rs b/crates/cli-support/src/wit/bindings.rs similarity index 100% rename from crates/cli-support/src/webidl/bindings.rs rename to crates/cli-support/src/wit/bindings.rs diff --git a/crates/cli-support/src/webidl/incoming.rs b/crates/cli-support/src/wit/incoming.rs similarity index 100% rename from crates/cli-support/src/webidl/incoming.rs rename to crates/cli-support/src/wit/incoming.rs diff --git a/crates/cli-support/src/webidl/mod.rs b/crates/cli-support/src/wit/mod.rs similarity index 99% rename from crates/cli-support/src/webidl/mod.rs rename to crates/cli-support/src/wit/mod.rs index 3519d1a4c61..9e9e1859980 100644 --- a/crates/cli-support/src/webidl/mod.rs +++ b/crates/cli-support/src/wit/mod.rs @@ -58,7 +58,7 @@ pub use self::outgoing::NonstandardOutgoing; /// bindings. There it can be transformed, however, into an actual WebIDL /// binding section using all of the values it has internally. #[derive(Default, Debug)] -pub struct NonstandardWebidlSection { +pub struct NonstandardWitSection { /// Store of all WebIDL types. Used currently to store all function types /// specified in `Bindings`. This is intended to be passed through verbatim /// to a final WebIDL bindings section. @@ -87,7 +87,7 @@ pub struct NonstandardWebidlSection { pub elems: Vec<(u32, Binding)>, } -pub type NonstandardWebidlSectionId = TypedCustomSectionId; +pub type NonstandardWitSectionId = TypedCustomSectionId; /// A non-standard wasm-bindgen-specifi WebIDL binding. This is meant to vaguely /// resemble a `FuctionBinding` in the official WebIDL bindings proposal, or at diff --git a/crates/cli-support/src/webidl/outgoing.rs b/crates/cli-support/src/wit/outgoing.rs similarity index 100% rename from crates/cli-support/src/webidl/outgoing.rs rename to crates/cli-support/src/wit/outgoing.rs diff --git a/crates/cli-support/src/webidl/standard.rs b/crates/cli-support/src/wit/standard.rs similarity index 100% rename from crates/cli-support/src/webidl/standard.rs rename to crates/cli-support/src/wit/standard.rs From 9c63620ef3ed07ed6ac806bde57a77594d87b376 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 21 Nov 2019 08:53:25 -0800 Subject: [PATCH 02/35] Lots of initial work for wasm interface types --- Cargo.toml | 1 + crates/cli-support/Cargo.toml | 2 + crates/cli-support/src/intrinsic.rs | 2 +- crates/cli-support/src/lib.rs | 140 +- crates/cli-support/src/wit/adapters.rs | 192 ++ crates/cli-support/src/wit/bindings.rs | 250 -- crates/cli-support/src/wit/incoming.rs | 578 ++--- crates/cli-support/src/wit/mod.rs | 2526 +++++++++------------ crates/cli-support/src/wit/nonstandard.rs | 352 +++ crates/cli-support/src/wit/outgoing.rs | 1045 +++++---- crates/cli-support/src/wit/standard.rs | 1271 ++++++----- src/convert/impls.rs | 3 - 12 files changed, 3118 insertions(+), 3244 deletions(-) create mode 100644 crates/cli-support/src/wit/adapters.rs delete mode 100644 crates/cli-support/src/wit/bindings.rs create mode 100644 crates/cli-support/src/wit/nonstandard.rs diff --git a/Cargo.toml b/Cargo.toml index b19cde12686..6958fb6335f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -93,3 +93,4 @@ wasm-bindgen = { path = '.' } wasm-bindgen-futures = { path = 'crates/futures' } js-sys = { path = 'crates/js-sys' } web-sys = { path = 'crates/web-sys' } +walrus = { git = 'https://github.com/rustwasm/walrus' } diff --git a/crates/cli-support/Cargo.toml b/crates/cli-support/Cargo.toml index 6156aa7d27b..7761c748347 100644 --- a/crates/cli-support/Cargo.toml +++ b/crates/cli-support/Cargo.toml @@ -25,3 +25,5 @@ wasm-bindgen-multi-value-xform = { path = '../multi-value-xform', version = '=0. wasm-bindgen-threads-xform = { path = '../threads-xform', version = '=0.2.55' } wasm-bindgen-wasm-conventions = { path = '../wasm-conventions', version = '=0.2.55' } wasm-bindgen-wasm-interpreter = { path = "../wasm-interpreter", version = '=0.2.55' } +#wit-walrus = { git = 'https://github.com/alexcrichton/wasm-interface-types', branch = 'walrus' } +wit-walrus = { path = "../../../wasm-interface-types/crates/walrus" } diff --git a/crates/cli-support/src/intrinsic.rs b/crates/cli-support/src/intrinsic.rs index a54926f3bcb..654f10607b0 100644 --- a/crates/cli-support/src/intrinsic.rs +++ b/crates/cli-support/src/intrinsic.rs @@ -36,7 +36,7 @@ macro_rules! intrinsics { /// Returns the expected signature of this intrinsic, used for /// generating a JS shim. - pub fn binding(&self) -> Function { + pub fn signature(&self) -> Function { use crate::descriptor::Descriptor::*; match self { $( diff --git a/crates/cli-support/src/lib.rs b/crates/cli-support/src/lib.rs index 743e4a4a252..6475aa6cf44 100755 --- a/crates/cli-support/src/lib.rs +++ b/crates/cli-support/src/lib.rs @@ -1,4 +1,5 @@ #![doc(html_root_url = "https://docs.rs/wasm-bindgen-cli-support/0.2")] +#![allow(dead_code, unused)] use anyhow::{bail, Context, Error}; use std::collections::{BTreeMap, BTreeSet, HashMap}; @@ -10,12 +11,12 @@ use std::str; use walrus::Module; use wasm_bindgen_wasm_conventions as wasm_conventions; -mod anyref; +// mod anyref; mod decode; mod descriptor; mod descriptors; mod intrinsic; -mod js; +// mod js; pub mod wasm2es6js; mod wit; @@ -268,7 +269,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) + .on_parse(wit_walrus::on_parse) .parse(&contents) .context("failed to parse input file as wasm")?; let stem = match &self.out_name { @@ -340,72 +341,73 @@ impl Bindgen { 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. - // This is only done if the anyref pass is enabled, which it's - // currently off-by-default since `anyref` is still in development in - // engines. - if self.anyref { - anyref::process(&mut module, self.wasm_interface_types)?; - } - - let aux = module - .customs - .delete_typed::() - .expect("aux section should be present"); - let mut bindings = module - .customs - .delete_typed::() - .unwrap(); - - // Now that our module is massaged and good to go, feed it into the JS - // shim generation which will actually generate JS for all this. - let (npm_dependencies, (js, ts)) = { - let mut cx = js::Context::new(&mut module, self)?; - cx.generate(&aux, &bindings)?; - let npm_dependencies = cx.npm_dependencies.clone(); - (npm_dependencies, cx.finalize(stem)?) - }; - - if self.wasm_interface_types { - if self.multi_value { - webidl::standard::add_multi_value(&mut module, &mut bindings) - .context("failed to transform return pointers into multi-value Wasm")?; - } - webidl::standard::add_section(&mut module, &aux, &bindings) - .with_context(|| "failed to generate a standard wasm bindings custom section")?; - } else { - if self.multi_value { - anyhow::bail!( - "Wasm multi-value is currently only available when \ - Wasm interface types is also enabled" - ); - } - } - - // If we exported the shadow stack pointer earlier, remove it from the - // export set now. - if exported_shadow_stack_pointer { - wasm_conventions::unexport_shadow_stack_pointer(&mut module)?; - // The shadow stack pointer is potentially unused now, but since it - // most likely _is_ in use, we don't pay the cost of a full GC here - // just to remove one potentially unnecessary global. - // - // walrus::passes::gc::run(&mut module); - } - - Ok(Output { - module, - stem: stem.to_string(), - snippets: aux.snippets.clone(), - local_modules: aux.local_modules.clone(), - npm_dependencies, - js, - ts, - mode: self.mode.clone(), - typescript: self.typescript, - wasm_interface_types: self.wasm_interface_types, - }) + // // Now that we've got type information from the webidl processing pass, + // // touch up the output of rustc to insert anyref shims where necessary. + // // This is only done if the anyref pass is enabled, which it's + // // currently off-by-default since `anyref` is still in development in + // // engines. + // if self.anyref { + // anyref::process(&mut module, self.wasm_interface_types)?; + // } + // + // let aux = module + // .customs + // .delete_typed::() + // .expect("aux section should be present"); + // let mut bindings = module + // .customs + // .delete_typed::() + // .unwrap(); + + panic!() + // // Now that our module is massaged and good to go, feed it into the JS + // // shim generation which will actually generate JS for all this. + // let (npm_dependencies, (js, ts)) = { + // let mut cx = js::Context::new(&mut module, self)?; + // cx.generate(&aux, &bindings)?; + // let npm_dependencies = cx.npm_dependencies.clone(); + // (npm_dependencies, cx.finalize(stem)?) + // }; + // + // if self.wasm_interface_types { + // if self.multi_value { + // webidl::standard::add_multi_value(&mut module, &mut bindings) + // .context("failed to transform return pointers into multi-value Wasm")?; + // } + // webidl::standard::add_section(&mut module, &aux, &bindings) + // .with_context(|| "failed to generate a standard wasm bindings custom section")?; + // } else { + // if self.multi_value { + // anyhow::bail!( + // "Wasm multi-value is currently only available when \ + // Wasm interface types is also enabled" + // ); + // } + // } + // + // // If we exported the shadow stack pointer earlier, remove it from the + // // export set now. + // if exported_shadow_stack_pointer { + // wasm_conventions::unexport_shadow_stack_pointer(&mut module)?; + // // The shadow stack pointer is potentially unused now, but since it + // // most likely _is_ in use, we don't pay the cost of a full GC here + // // just to remove one potentially unnecessary global. + // // + // // walrus::passes::gc::run(&mut module); + // } + // + // Ok(Output { + // module, + // stem: stem.to_string(), + // snippets: aux.snippets.clone(), + // local_modules: aux.local_modules.clone(), + // npm_dependencies, + // js, + // ts, + // mode: self.mode.clone(), + // typescript: self.typescript, + // wasm_interface_types: self.wasm_interface_types, + // }) } fn local_module_name(&self, module: &str) -> String { diff --git a/crates/cli-support/src/wit/adapters.rs b/crates/cli-support/src/wit/adapters.rs new file mode 100644 index 00000000000..bca40bc2e40 --- /dev/null +++ b/crates/cli-support/src/wit/adapters.rs @@ -0,0 +1,192 @@ +//! Location where `Adapter` functions are actually created. +//! +//! This module is tasked with converting `Descriptor::Function` instances to +//! `Adapter` functions. It uses the incoming/outgoing modules/builders to do +//! most of the heavy lifting, and then this is the glue around the edges to +//! make sure everything is processed, hooked up in the second, and then +//! inserted into the right map. +//! +//! This module is called from `src/wit/mod.rs` exclusively to populate the +//! imports/exports/elements of the bindings section. Most of this module is +//! largely just connecting the dots! + +use crate::descriptor::Function; +use crate::wit::incoming::IncomingBuilder; +use crate::wit::outgoing::OutgoingBuilder; +use crate::wit::{Adapter, AdapterId, AdapterJsImportKind, NonstandardWitSection}; +use crate::wit::{AdapterKind, AdapterType, Instruction}; +use anyhow::{format_err, Error}; +use walrus::{FunctionId, Module, ValType}; + +/// Adds an element to the `bindings.imports` map for the `import` specified +/// that is supposed to have the signature specified in `binding`. This also +/// expects that the imported item is called as `kind`. +pub fn import( + module: &mut Module, + adapters: &mut NonstandardWitSection, + import: walrus::ImportId, + signature: Function, + kind: AdapterJsImportKind, +) -> Result { + let import = module.imports.get(import); + let (import_module, import_name) = (import.module.clone(), import.name.clone()); + let id = match import.kind { + walrus::ImportKind::Function(f) => f, + _ => unreachable!(), + }; + let import_id = import.id(); + + // Process the returned type first to see if it needs an out-pointer. This + // happens if the results of the incoming arguments translated to wasm take + // up more than one type. + let mut incoming = IncomingBuilder::default(); + incoming.process(&signature.ret)?; + let uses_retptr = incoming.output.len() > 1; + + // Process the argument next, allocating space of the return value if one + // was present. Additionally configure the `module` and `adapters` to allow + // usage of closures going out to the import. + let mut outgoing = OutgoingBuilder::default(); + outgoing.module = Some(module); + outgoing.adapters = Some(adapters); + if uses_retptr { + outgoing.input.push(AdapterType::I32); + } + for arg in signature.arguments.iter() { + outgoing.process(arg)?; + } + + // A bit of destructuring to kill the borrow that the outgoing builder has + // on the module/bindings. + let OutgoingBuilder { + input: outgoing_input, + output: outgoing_output, + instructions: outgoing_instructions, + .. + } = outgoing; + + // Build up the list of instructions for our adapter function. We start out + // with all the outgoing instructions which convert all wasm params to the + // desired types to call our import... + let mut instructions = outgoing_instructions; + + // ... and then we actually call our import. We synthesize an adapter + // definition for it with the appropriate types here on the fly. + let f = adapters.append( + outgoing_output, + incoming.input, + AdapterKind::Import { + module: import_module, + name: import_name, + kind, + }, + ); + instructions.push(Instruction::CallAdapter(f)); + + // ... and then we follow up with a conversion of the incoming type back to + // wasm. + instructions.extend(incoming.instructions); + + // ... and if a return pointer is in use then we need to store the types on + // the stack into the wasm return pointer. Note that we iterate in reverse + // here because the last result is the top value on the stack. + let results = if uses_retptr { + for (i, ty) in incoming.output.into_iter().enumerate().rev() { + instructions.push(Instruction::StoreRetptr { offset: i, ty }); + } + Vec::new() + } else { + incoming.output + }; + let id = adapters.append(outgoing_input, results, AdapterKind::Local { instructions }); + adapters.implements.push((import_id, id)); + Ok(id) +} + +/// Adds an element to `bindings.exports` for the `export` specified to have the +/// `binding` given. +pub fn export( + module: &mut Module, + adapters: &mut NonstandardWitSection, + export: walrus::ExportId, + signature: Function, +) -> Result { + let export = module.exports.get(export); + let name = export.name.clone(); + let id = match export.item { + walrus::ExportItem::Function(f) => f, + _ => unreachable!(), + }; + let export_id = export.id(); + // Do the actual heavy lifting elsewhere to generate the `binding`. + let id = register_wasm_export(module, adapters, id, signature)?; + adapters.exports.push((name, id)); + Ok(id) +} + +/// Like `export` except registers an adapter for a table element. In +/// this case ensures that the table element `idx` is specified to have the +/// `signature` specified. +pub fn table_element( + module: &mut Module, + adapters: &mut NonstandardWitSection, + idx: u32, + signature: Function, +) -> Result { + let table = module + .tables + .main_function_table()? + .ok_or_else(|| format_err!("no function table found"))?; + let table = module.tables.get(table); + let functions = match &table.kind { + walrus::TableKind::Function(f) => f, + _ => unreachable!(), + }; + let id = functions.elements[idx as usize].unwrap(); + // like above, largely just defer the work elsewhere + Ok(register_wasm_export(module, adapters, id, signature)?) +} + +fn register_wasm_export( + module: &mut Module, + adapters: &mut NonstandardWitSection, + id: walrus::FunctionId, + signature: Function, +) -> Result { + // Figure out how to translate all the incoming arguments ... + let mut incoming = IncomingBuilder::default(); + for arg in signature.arguments.iter() { + incoming.process(arg)?; + } + + // ... then the returned value being translated back + let mut outgoing = OutgoingBuilder::default(); + outgoing.process(&signature.ret)?; + let uses_retptr = outgoing.input.len() > 1; + + // Our instruction stream starts out with the return pointer as the first + // argument to the wasm function, if one is in use. Then we convert + // everything to wasm types. + // + // After calling the core wasm function we need to load all the return + // pointer arguments if there were any, otherwise we simply convert + // everything into the outgoing arguments. + let mut instructions = Vec::new(); + if uses_retptr { + instructions.push(Instruction::Retptr); + } + instructions.extend(incoming.instructions); + instructions.push(Instruction::Standard(wit_walrus::Instruction::CallCore(id))); + if uses_retptr { + for (i, ty) in incoming.output.into_iter().enumerate() { + instructions.push(Instruction::LoadRetptr { offset: i, ty }); + } + } + instructions.extend(outgoing.instructions); + + Ok(adapters.append( + incoming.input, + outgoing.output, + AdapterKind::Local { instructions }, + )) +} diff --git a/crates/cli-support/src/wit/bindings.rs b/crates/cli-support/src/wit/bindings.rs deleted file mode 100644 index cc6db92ff00..00000000000 --- a/crates/cli-support/src/wit/bindings.rs +++ /dev/null @@ -1,250 +0,0 @@ -//! Location where `Binding` structures are actually created. -//! -//! This module is tasked with converting `Descriptor::Function` instances to -//! `Binding`s. It uses the incoming/outgoing modules/builders to do most of the -//! heavy lifting, and then this is the glue around the edges to make sure -//! everything is processed, hooked up in the second, and then inserted into the -//! right map. -//! -//! This module is called from `src/webidl/mod.rs` exclusively to populate the -//! imports/exports/elements of the bindings section. Most of this module is -//! largely just connecting the dots! - -use crate::descriptor::Function; -use crate::webidl::incoming::IncomingBuilder; -use crate::webidl::outgoing::OutgoingBuilder; -use crate::webidl::{Binding, NonstandardWebidlSection}; -use anyhow::{format_err, Error}; -use walrus::{FunctionId, Module, ValType}; -use wasm_webidl_bindings::ast; - -/// Adds an element to the `bindings.imports` map for the `import` specified -/// that is supposed to have the signature specified in `binding`. This also -/// expects that the imported item is called as `kind`. -pub fn register_import( - module: &mut Module, - bindings: &mut NonstandardWebidlSection, - import: walrus::ImportId, - binding: Function, - kind: ast::WebidlFunctionKind, -) -> Result<(), Error> { - let import = module.imports.get(import); - let id = match import.kind { - walrus::ImportKind::Function(f) => f, - _ => unreachable!(), - }; - let import_id = import.id(); - - // Process the return value first to determine if we need a return pointer - // since that is always the first argument. - let mut incoming = IncomingBuilder::default(); - incoming.process(&binding.ret)?; - - // Next process all outgoing arguments, and configure the module/bindings - // section to be available to the builder so we can recursively register - // stack closures. - let mut outgoing = OutgoingBuilder::default(); - outgoing.module = Some(module); - outgoing.bindings_section = Some(bindings); - if incoming.wasm.len() > 1 { - outgoing.process_retptr(); - } - for arg in binding.arguments.iter() { - outgoing.process(arg)?; - } - - // A bit of destructuring to kill the borrow that the outgoing builder has - // on the module/bindings. - let OutgoingBuilder { - wasm: outgoing_wasm, - webidl: outgoing_webidl, - bindings: outgoing_bindings, - .. - } = outgoing; - - // Boilerplate to assemble the `webidl_ty` and `wasm_ty` values. - let webidl_ty = webidl_ty( - &mut bindings.types, - kind, - &outgoing_webidl, - &incoming.webidl, - ); - let (wasm_ty, return_via_outptr) = - assert_signature_match(module, id, &outgoing_wasm, &incoming.wasm); - - // ... and finally insert it into our map! - bindings.imports.insert( - import_id, - Binding { - return_via_outptr, - wasm_ty, - incoming: incoming.bindings, - outgoing: outgoing_bindings, - webidl_ty, - }, - ); - Ok(()) -} - -/// Adds an element to `bindings.exports` for the `export` specified to have the -/// `binding` given. -pub fn register_export( - module: &mut Module, - bindings: &mut NonstandardWebidlSection, - export: walrus::ExportId, - binding: Function, -) -> Result<(), Error> { - let export = module.exports.get(export); - let id = match export.item { - walrus::ExportItem::Function(f) => f, - _ => unreachable!(), - }; - let export_id = export.id(); - // Do the actual heavy lifting elsewhere to generate the `binding`. - let binding = register_wasm_export(module, bindings, id, binding)?; - bindings.exports.insert(export_id, binding); - Ok(()) -} - -/// Like `register_export` except registers a binding for a table element. In -/// this case ensures that the table element `idx` is specified to have the -/// `binding` signature specified, eventually updating `bindings.elems` list. -/// -/// Returns the index of the item added in the `bindings.elems` list. -pub fn register_table_element( - module: &mut Module, - bindings: &mut NonstandardWebidlSection, - idx: u32, - binding: Function, -) -> Result { - let table = module - .tables - .main_function_table()? - .ok_or_else(|| format_err!("no function table found"))?; - let table = module.tables.get(table); - let functions = match &table.kind { - walrus::TableKind::Function(f) => f, - _ => unreachable!(), - }; - let id = functions.elements[idx as usize].unwrap(); - let ret = bindings.elems.len() as u32; - // like above, largely just defer the work elsewhere - let binding = register_wasm_export(module, bindings, id, binding)?; - bindings.elems.push((idx, binding)); - Ok(ret) -} - -/// Common routine to create a `Binding` for an exported wasm function, using -/// incoming arguments and an outgoing return value. -fn register_wasm_export( - module: &mut Module, - bindings: &mut NonstandardWebidlSection, - id: walrus::FunctionId, - binding: Function, -) -> Result { - // Like imports, process the return value first to determine if we need a - // return pointer - let mut outgoing = OutgoingBuilder::default(); - outgoing.process(&binding.ret)?; - - // Afterwards process all arguments... - let mut incoming = IncomingBuilder::default(); - if outgoing.wasm.len() > 1 { - incoming.process_retptr(); - } - for arg in binding.arguments.iter() { - incoming.process(arg)?; - } - - // ... do similar boilerplate to imports (but with incoming/outgoing - // swapped) to produce some types ... - let webidl_ty = webidl_ty( - &mut bindings.types, - ast::WebidlFunctionKind::Static, - &incoming.webidl, - &outgoing.webidl, - ); - let (wasm_ty, return_via_outptr) = - assert_signature_match(module, id, &incoming.wasm, &outgoing.wasm); - - // ... and there's our `Binding`! - Ok(Binding { - wasm_ty, - incoming: incoming.bindings, - outgoing: outgoing.bindings, - webidl_ty, - return_via_outptr, - }) -} - -/// Asserts that the `params` and `results` we've determined from an -/// incoming/outgoing builder actually matches the signature of `id` in the -/// `module` provided. This is a somewhat loose comparison since `anyref` in the -/// expected lists will be present as `i32` in the actual module due to rustc -/// limitations. -/// -/// This at the end manufactures an actual `walrus::Type` that will be used to -/// describe a WebIDL value. This manufactured value actually has `anyref` types -/// in it and also respects the out ptr ABI that we currently use to handle -/// multiple-value returns. -fn assert_signature_match( - module: &mut Module, - id: FunctionId, - params: &[ValType], - mut results: &[ValType], -) -> (walrus::TypeId, Option>) { - let ty = module.funcs.get(id).ty(); - let ty = module.types.get(ty); - - fn assert_eq(expected: ValType, actual: ValType) { - match expected { - ValType::Anyref => assert_eq!(actual, ValType::I32), - _ => assert_eq!(expected, actual), - } - } - let mut ret_outptr = None; - - match results.len() { - 0 => assert_eq!(ty.results().len(), 0), - 1 => assert_eq(results[0], ty.results()[0]), - - // multi value isn't supported yet so all aggregate returns are done - // through an outptr as the first argument. This means that our - // signature should have no results. The new signature we create will - // also have no results. - _ => { - assert_eq!(ty.results().len(), 0); - ret_outptr = Some(results.to_vec()); - results = &[]; - } - } - - let mut iter = params.iter(); - for actual in ty.params().iter() { - let expected = iter.next().unwrap(); - assert_eq(*expected, *actual); - } - assert!(iter.next().is_none()); - - (module.types.add(params, results), ret_outptr) -} - -// boilerplate to convert arguments to a `WebidlFunctionId`. -fn webidl_ty( - types: &mut ast::WebidlTypes, - kind: ast::WebidlFunctionKind, - params: &[ast::WebidlScalarType], - results: &[ast::WebidlScalarType], -) -> ast::WebidlFunctionId { - let result = match results.len() { - 0 => None, - 1 => Some(results[0].into()), - _ => panic!("too many results in a webidl return value"), - }; - let func = ast::WebidlFunction { - kind, - params: params.iter().cloned().map(|x| x.into()).collect(), - result, - }; - types.insert(func) -} diff --git a/crates/cli-support/src/wit/incoming.rs b/crates/cli-support/src/wit/incoming.rs index 247919140c8..0435e126fce 100644 --- a/crates/cli-support/src/wit/incoming.rs +++ b/crates/cli-support/src/wit/incoming.rs @@ -1,154 +1,28 @@ -//! Nonstandard and wasm-bindgen specific definition of incoming bindings to a -//! wasm module. +//! Definition of how to convert Rust types (`Description`) into wasm types +//! through adapter functions. //! -//! This module provides a builder which is used to translate Rust types (aka a -//! `Descriptor`) to a `NonstandardIncoming` definition which describes how the -//! JS type is converted into a Rust type. We try to use standard webidl -//! bindings as much as possible, but we have quite a few other bindings which -//! require custom code and shims currently still. +//! Note that many Rust types use "nonstandard" instructions which only work in +//! the JS output, not for the "pure wasm interface types" output. //! //! Note that the mirror operation, going from WebAssembly to JS, is found in //! the `outgoing.rs` module. use crate::descriptor::{Descriptor, VectorKind}; +use crate::wit::{AdapterType, Instruction}; use anyhow::{bail, format_err, Error}; use walrus::ValType; -use wasm_webidl_bindings::ast; -/// A list of all incoming bindings from JS to WebAssembly that wasm-bindgen -/// will take advantage of. -#[derive(Debug, Clone)] -pub enum NonstandardIncoming { - /// This is a standard vanilla incoming binding. When WebIDL bindings are - /// implemented, this can be used as-is. - Standard(ast::IncomingBindingExpression), - - /// JS is passing a `BigInt` to Rust. - Int64 { - val: ast::IncomingBindingExpression, - /// Whether it's a `u64` or `i64` in Rust. - signed: bool, - }, - - /// JS is passing a `BigInt64Array` or `BigUint64Array` to Rust - /// - /// A copy of the array needs to be made into the Rust address space. - AllocCopyInt64 { - alloc_func_name: String, - expr: Box, - /// Whether or not this is for &[u64] or &[i64] - signed: bool, - }, - - /// JS is passing an array of anyref values into Rust, and all the values - /// need to be copied in. - AllocCopyAnyrefArray { - alloc_func_name: String, - expr: Box, - }, - - /// A mutable slice of values going from JS to Rust, and after Rust finishes - /// the JS slice is updated with the current value of the slice. - MutableSlice { - kind: VectorKind, - val: ast::IncomingBindingExpression, - }, - - /// This is either a slice or `undefined` being passed into Rust. - OptionSlice { - kind: VectorKind, - val: ast::IncomingBindingExpression, - mutable: bool, - }, - - /// This is either a vector or `undefined` being passed into Rust. - OptionVector { - kind: VectorKind, - val: ast::IncomingBindingExpression, - }, - - /// Not actually used for `JsValue` but used for imported types, this is - /// either `undefined` or the imported type getting passed into Rust. - OptionAnyref { val: ast::IncomingBindingExpression }, - - /// An optional "native type" which includes i32/u32/f32/f64, all of which - /// require a discriminant. - OptionNative { val: ast::IncomingBindingExpression }, - - /// An optional integer type which uses an 0xffffff sentinel value for - /// "none" - OptionU32Sentinel { val: ast::IncomingBindingExpression }, - - /// An optional boolean using a special ABI for communicating `undefined` - OptionBool { val: ast::IncomingBindingExpression }, - - /// An optional `char` which uses an ABI where `undefined` is a hole in the - /// range of valid values for a `char` in Rust. Note that in JS a string is - /// passed in. - OptionChar { val: ast::IncomingBindingExpression }, - - /// An optional integral enum where `undefined` is the hole specified. - OptionIntegerEnum { - val: ast::IncomingBindingExpression, - hole: u32, - }, - - /// An optional `BigInt`. - OptionInt64 { - val: ast::IncomingBindingExpression, - signed: bool, - }, - - /// An optional Rust-based type which internally has a pointer that's - /// wrapped up in a JS class. This transfers ownership from JS to Rust. - RustType { - class: String, - val: ast::IncomingBindingExpression, - }, - - /// A reference to a Rust-based type where Rust won't take ownership of the - /// value, it just has a temporary borrow on the input. - RustTypeRef { - class: String, - val: ast::IncomingBindingExpression, - }, - - /// An optional owned Rust type being transferred from JS to Rust. - OptionRustType { - class: String, - val: ast::IncomingBindingExpression, - }, - - /// A string from JS where the first character goes through to Rust. - Char { val: ast::IncomingBindingExpression }, - - /// An arbitrary `anyref` being passed into Rust, but explicitly one that's - /// borrowed and doesn't need to be persisted in a heap table. - BorrowedAnyref { val: ast::IncomingBindingExpression }, -} - -/// Builder used to create a incomig binding from a `Descriptor`. #[derive(Default)] pub struct IncomingBuilder { - /// The wasm types that needs to be used to represent all the descriptors in - /// Rust. - pub wasm: Vec, - /// The WebIDL scalar types which match what JS will be providing. - pub webidl: Vec, - /// The list of bindings necessary to connect `wasm` to `webidl` above. - pub bindings: Vec, + pub input: Vec, + pub output: Vec, + pub instructions: Vec, } impl IncomingBuilder { - /// Adds an initial argument which is passed through verbatim, currently - /// used to handle return pointers in Rust. - pub fn process_retptr(&mut self) { - self.number(ValType::I32, ast::WebidlScalarType::Long); - } - /// Process a `Descriptor` as if it's being passed from JS to Rust. This - /// will skip `Unit` and otherwise internally add a `NonstandardIncoming` - /// binding necessary for the descriptor. + /// will skip `Unit` and otherwise internally add instructions necessary to + /// convert the foreign type into the Rust bits. pub fn process(&mut self, arg: &Descriptor) -> Result<(), Error> { if let Descriptor::Unit = arg { return Ok(()); @@ -157,66 +31,68 @@ impl IncomingBuilder { // that we don't forget things. We should always produce at least one // wasm arge and exactly one webidl arg. Additionally the number of // bindings should always match the number of webidl types for now. - assert_eq!(self.webidl.len(), self.bindings.len()); - let wasm_before = self.wasm.len(); - let webidl_before = self.webidl.len(); + let input_before = self.input.len(); + let output_before = self.output.len(); self._process(arg)?; - assert_eq!(self.webidl.len(), self.bindings.len()); - assert_eq!(webidl_before + 1, self.webidl.len()); - assert!(wasm_before < self.wasm.len()); + assert_eq!(output_before + 1, self.output.len()); + assert!(input_before < self.input.len()); Ok(()) } fn _process(&mut self, arg: &Descriptor) -> Result<(), Error> { + use walrus::ValType as WasmVT; + use wit_walrus::ValType as WitVT; match arg { Descriptor::Boolean => { - let binding = self.expr_as(ValType::I32); - self.wasm.push(ValType::I32); - self.webidl.push(ast::WebidlScalarType::Boolean); - self.bindings.push(NonstandardIncoming::Standard(binding)); + self.get(AdapterType::Bool); + self.instructions.push(Instruction::I32FromBool); + self.output.push(AdapterType::I32); } Descriptor::Char => { - let expr = self.expr_get(); - self.wasm.push(ValType::I32); - self.webidl.push(ast::WebidlScalarType::DomString); - self.bindings.push(NonstandardIncoming::Char { val: expr }); + self.get(AdapterType::String); + self.instructions.push(Instruction::I32FromStringFirstChar); + self.output.push(AdapterType::I32); } Descriptor::Anyref => { - let expr = self.expr_as(ValType::Anyref); - self.wasm.push(ValType::Anyref); - self.webidl.push(ast::WebidlScalarType::Any); - self.bindings.push(NonstandardIncoming::Standard(expr)); + self.get(AdapterType::Anyref); + self.instructions.push(Instruction::I32FromAnyrefOwned); + self.output.push(AdapterType::I32); } Descriptor::RustStruct(class) => { - let expr = self.expr_get(); - self.wasm.push(ValType::I32); - self.webidl.push(ast::WebidlScalarType::Any); - self.bindings.push(NonstandardIncoming::RustType { - val: expr, - class: class.to_string(), + self.get(AdapterType::Anyref); + self.instructions.push(Instruction::I32FromAnyrefRustOwned { + class: class.clone(), }); + self.output.push(AdapterType::I32); } - Descriptor::I8 => self.number(ValType::I32, ast::WebidlScalarType::Byte), - Descriptor::U8 => self.number(ValType::I32, ast::WebidlScalarType::Octet), - Descriptor::I16 => self.number(ValType::I32, ast::WebidlScalarType::Short), - Descriptor::U16 => self.number(ValType::I32, ast::WebidlScalarType::UnsignedShort), - Descriptor::I32 => self.number(ValType::I32, ast::WebidlScalarType::Long), - Descriptor::U32 => self.number(ValType::I32, ast::WebidlScalarType::UnsignedLong), + Descriptor::I8 => self.number(WitVT::S8, WasmVT::I32), + Descriptor::U8 => self.number(WitVT::U8, WasmVT::I32), + Descriptor::I16 => self.number(WitVT::S16, WasmVT::I32), + Descriptor::U16 => self.number(WitVT::U16, WasmVT::I32), + Descriptor::I32 => self.number(WitVT::S32, WasmVT::I32), + Descriptor::U32 => self.number(WitVT::U32, WasmVT::I32), Descriptor::I64 => self.number64(true), Descriptor::U64 => self.number64(false), - Descriptor::F32 => self.number(ValType::F32, ast::WebidlScalarType::Float), - Descriptor::F64 => self.number(ValType::F64, ast::WebidlScalarType::Double), - Descriptor::Enum { .. } => self.number(ValType::I32, ast::WebidlScalarType::Long), + Descriptor::F32 => { + self.get(AdapterType::F32); + self.output.push(AdapterType::F32); + } + Descriptor::F64 => { + self.get(AdapterType::F64); + self.output.push(AdapterType::F64); + } + Descriptor::Enum { .. } => self.number(WitVT::U32, WasmVT::I32), Descriptor::Ref(d) => self.process_ref(false, d)?, Descriptor::RefMut(d) => self.process_ref(true, d)?, Descriptor::Option(d) => self.process_option(d)?, Descriptor::String | Descriptor::CachedString | Descriptor::Vector(_) => { - let kind = arg.vector_kind().ok_or_else(|| { - format_err!("unsupported argument type for calling Rust function from JS {:?}", arg) - })? ; - self.wasm.extend(&[ValType::I32; 2]); - self.alloc_copy_kind(kind) + panic!() + // let kind = arg.vector_kind().ok_or_else(|| { + // format_err!("unsupported argument type for calling Rust function from JS {:?}", arg) + // })? ; + // self.wasm.extend(&[ValType::I32; 2]); + // self.alloc_copy_kind(kind) } // Can't be passed from JS to Rust yet @@ -241,38 +117,36 @@ impl IncomingBuilder { fn process_ref(&mut self, mutable: bool, arg: &Descriptor) -> Result<(), Error> { match arg { Descriptor::RustStruct(class) => { - let expr = self.expr_get(); - self.wasm.push(ValType::I32); - self.webidl.push(ast::WebidlScalarType::Any); - self.bindings.push(NonstandardIncoming::RustTypeRef { - val: expr, - class: class.to_string(), - }); + self.get(AdapterType::Anyref); + self.instructions + .push(Instruction::I32FromAnyrefRustBorrow { + class: class.clone(), + }); + self.output.push(AdapterType::I32); } Descriptor::Anyref => { - let expr = self.expr_get(); - self.wasm.push(ValType::Anyref); - self.webidl.push(ast::WebidlScalarType::Any); - self.bindings - .push(NonstandardIncoming::BorrowedAnyref { val: expr }); + self.get(AdapterType::Anyref); + self.instructions.push(Instruction::I32FromAnyrefBorrow); + self.output.push(AdapterType::I32); } Descriptor::String | Descriptor::CachedString | Descriptor::Slice(_) => { - let kind = arg.vector_kind().ok_or_else(|| { - format_err!( - "unsupported slice type for calling Rust function from JS {:?}", - arg - ) - })?; - self.wasm.extend(&[ValType::I32; 2]); - if mutable { - self.bindings.push(NonstandardIncoming::MutableSlice { - kind, - val: self.expr_get(), - }); - self.webidl.push(ast::WebidlScalarType::Any); - } else { - self.alloc_copy_kind(kind) - } + panic!() + // let kind = arg.vector_kind().ok_or_else(|| { + // format_err!( + // "unsupported slice type for calling Rust function from JS {:?}", + // arg + // ) + // })?; + // self.wasm.extend(&[ValType::I32; 2]); + // if mutable { + // self.bindings.push(NonstandardIncoming::MutableSlice { + // kind, + // val: self.expr_get(), + // }); + // self.webidl.push(ast::WebidlScalarType::Any); + // } else { + // self.alloc_copy_kind(kind) + // } } _ => bail!( "unsupported reference argument type for calling Rust function from JS: {:?}", @@ -285,11 +159,9 @@ impl IncomingBuilder { fn process_option(&mut self, arg: &Descriptor) -> Result<(), Error> { match arg { Descriptor::Anyref => { - self.wasm.push(ValType::I32); - self.bindings.push(NonstandardIncoming::OptionAnyref { - val: self.expr_get(), - }); - self.webidl.push(ast::WebidlScalarType::Any); + self.get(AdapterType::Anyref); + self.instructions.push(Instruction::I32FromOptionAnyref); + self.output.push(AdapterType::I32); } Descriptor::I8 => self.option_sentinel(), Descriptor::U8 => self.option_sentinel(), @@ -300,84 +172,73 @@ impl IncomingBuilder { Descriptor::F32 => self.option_native(ValType::F32), Descriptor::F64 => self.option_native(ValType::F64), Descriptor::I64 | Descriptor::U64 => { - let expr = self.expr_get(); + self.get(AdapterType::Anyref); let signed = match arg { Descriptor::I64 => true, _ => false, }; - self.wasm.extend(&[walrus::ValType::I32; 4]); - self.webidl.push(ast::WebidlScalarType::Any); - self.bindings - .push(NonstandardIncoming::OptionInt64 { val: expr, signed }); + self.instructions + .push(Instruction::I32SplitOption64 { signed }); + self.output.extend(&[AdapterType::I32; 4]); } Descriptor::Boolean => { - let expr = self.expr_get(); - self.wasm.push(walrus::ValType::I32); - self.webidl.push(ast::WebidlScalarType::Any); - self.bindings - .push(NonstandardIncoming::OptionBool { val: expr }); + self.get(AdapterType::Anyref); + self.instructions.push(Instruction::I32FromOptionBool); + self.output.push(AdapterType::I32); } Descriptor::Char => { - let expr = self.expr_get(); - self.wasm.push(walrus::ValType::I32); - self.webidl.push(ast::WebidlScalarType::Any); - self.bindings - .push(NonstandardIncoming::OptionChar { val: expr }); + self.get(AdapterType::Anyref); + self.instructions.push(Instruction::I32FromOptionChar); + self.output.push(AdapterType::I32); } Descriptor::Enum { hole } => { - let expr = self.expr_get(); - self.wasm.push(walrus::ValType::I32); - self.webidl.push(ast::WebidlScalarType::Any); - self.bindings.push(NonstandardIncoming::OptionIntegerEnum { - val: expr, - hole: *hole, - }); + self.get(AdapterType::Anyref); + self.instructions + .push(Instruction::I32FromOptionEnum { hole: *hole }); + self.output.push(AdapterType::I32); } Descriptor::RustStruct(name) => { - let expr = self.expr_get(); - self.wasm.push(walrus::ValType::I32); - self.webidl.push(ast::WebidlScalarType::Any); - self.bindings.push(NonstandardIncoming::OptionRustType { - val: expr, + self.get(AdapterType::Anyref); + self.instructions.push(Instruction::I32FromOptionRust { class: name.to_string(), }); + self.output.push(AdapterType::I32); } - Descriptor::Ref(_) | Descriptor::RefMut(_) => { - let mutable = match arg { - Descriptor::Ref(_) => false, - _ => true, - }; - let kind = arg.vector_kind().ok_or_else(|| { - format_err!( - "unsupported optional slice type for calling Rust function from JS {:?}", - arg - ) - })?; - self.bindings.push(NonstandardIncoming::OptionSlice { - kind, - val: self.expr_get(), - mutable, - }); - self.wasm.extend(&[ValType::I32; 2]); - self.webidl.push(ast::WebidlScalarType::Any); - } - - Descriptor::String | Descriptor::CachedString | Descriptor::Vector(_) => { - let kind = arg.vector_kind().ok_or_else(|| { - format_err!( - "unsupported optional slice type for calling Rust function from JS {:?}", - arg - ) - })?; - self.bindings.push(NonstandardIncoming::OptionVector { - kind, - val: self.expr_get(), - }); - self.wasm.extend(&[ValType::I32; 2]); - self.webidl.push(ast::WebidlScalarType::Any); - } - + // Descriptor::Ref(_) | Descriptor::RefMut(_) => { + // let mutable = match arg { + // Descriptor::Ref(_) => false, + // _ => true, + // }; + // let kind = arg.vector_kind().ok_or_else(|| { + // format_err!( + // "unsupported optional slice type for calling Rust function from JS {:?}", + // arg + // ) + // })?; + // self.bindings.push(NonstandardIncoming::OptionSlice { + // kind, + // val: self.expr_get(), + // mutable, + // }); + // self.wasm.extend(&[ValType::I32; 2]); + // self.webidl.push(ast::WebidlScalarType::Any); + // } + // + // Descriptor::String | Descriptor::CachedString | Descriptor::Vector(_) => { + // let kind = arg.vector_kind().ok_or_else(|| { + // format_err!( + // "unsupported optional slice type for calling Rust function from JS {:?}", + // arg + // ) + // })?; + // self.bindings.push(NonstandardIncoming::OptionVector { + // kind, + // val: self.expr_get(), + // }); + // self.wasm.extend(&[ValType::I32; 2]); + // self.webidl.push(ast::WebidlScalarType::Any); + // } _ => bail!( "unsupported optional argument type for calling Rust function from JS: {:?}", arg @@ -386,107 +247,106 @@ impl IncomingBuilder { Ok(()) } - fn expr_get(&self) -> ast::IncomingBindingExpression { - let idx = self.webidl.len() as u32; - ast::IncomingBindingExpressionGet { idx }.into() - } - - fn expr_as(&self, ty: ValType) -> ast::IncomingBindingExpression { - ast::IncomingBindingExpressionAs { - ty, - expr: Box::new(self.expr_get()), - } - .into() - } - - fn alloc_func_name(&self) -> String { - "__wbindgen_malloc".to_string() + fn get(&mut self, ty: AdapterType) { + let idx = self.input.len() as u32 - 1; + self.input.push(ty); + let std = wit_walrus::Instruction::ArgGet(idx); + self.instructions.push(Instruction::Standard(std)); } - fn alloc_copy_kind(&mut self, kind: VectorKind) { - use wasm_webidl_bindings::ast::WebidlScalarType::*; - - match kind { - VectorKind::I8 => self.alloc_copy(Int8Array), - VectorKind::U8 => self.alloc_copy(Uint8Array), - VectorKind::ClampedU8 => self.alloc_copy(Uint8ClampedArray), - VectorKind::I16 => self.alloc_copy(Int16Array), - VectorKind::U16 => self.alloc_copy(Uint16Array), - VectorKind::I32 => self.alloc_copy(Int32Array), - VectorKind::U32 => self.alloc_copy(Uint32Array), - VectorKind::F32 => self.alloc_copy(Float32Array), - VectorKind::F64 => self.alloc_copy(Float64Array), - VectorKind::String => { - let expr = ast::IncomingBindingExpressionAllocUtf8Str { - alloc_func_name: self.alloc_func_name(), - expr: Box::new(self.expr_get()), - }; - self.webidl.push(DomString); - self.bindings - .push(NonstandardIncoming::Standard(expr.into())); - } - VectorKind::I64 | VectorKind::U64 => { - let signed = match kind { - VectorKind::I64 => true, - _ => false, - }; - self.bindings.push(NonstandardIncoming::AllocCopyInt64 { - alloc_func_name: self.alloc_func_name(), - expr: Box::new(self.expr_get()), - signed, - }); - self.webidl.push(Any); - } - VectorKind::Anyref => { - self.bindings - .push(NonstandardIncoming::AllocCopyAnyrefArray { - alloc_func_name: self.alloc_func_name(), - expr: Box::new(self.expr_get()), - }); - self.webidl.push(Any); - } - } - } - - fn alloc_copy(&mut self, webidl: ast::WebidlScalarType) { - let expr = ast::IncomingBindingExpressionAllocCopy { - alloc_func_name: self.alloc_func_name(), - expr: Box::new(self.expr_get()), + // fn alloc_func_name(&self) -> String { + // "__wbindgen_malloc".to_string() + // } + // + // fn alloc_copy_kind(&mut self, kind: VectorKind) { + // use wasm_webidl_bindings::ast::WebidlScalarType::*; + // + // match kind { + // VectorKind::I8 => self.alloc_copy(Int8Array), + // VectorKind::U8 => self.alloc_copy(Uint8Array), + // VectorKind::ClampedU8 => self.alloc_copy(Uint8ClampedArray), + // VectorKind::I16 => self.alloc_copy(Int16Array), + // VectorKind::U16 => self.alloc_copy(Uint16Array), + // VectorKind::I32 => self.alloc_copy(Int32Array), + // VectorKind::U32 => self.alloc_copy(Uint32Array), + // VectorKind::F32 => self.alloc_copy(Float32Array), + // VectorKind::F64 => self.alloc_copy(Float64Array), + // VectorKind::String => { + // let expr = ast::IncomingBindingExpressionAllocUtf8Str { + // alloc_func_name: self.alloc_func_name(), + // expr: Box::new(self.expr_get()), + // }; + // self.webidl.push(DomString); + // self.bindings + // .push(NonstandardIncoming::Standard(expr.into())); + // } + // VectorKind::I64 | VectorKind::U64 => { + // let signed = match kind { + // VectorKind::I64 => true, + // _ => false, + // }; + // self.bindings.push(NonstandardIncoming::AllocCopyInt64 { + // alloc_func_name: self.alloc_func_name(), + // expr: Box::new(self.expr_get()), + // signed, + // }); + // self.webidl.push(Any); + // } + // VectorKind::Anyref => { + // self.bindings + // .push(NonstandardIncoming::AllocCopyAnyrefArray { + // alloc_func_name: self.alloc_func_name(), + // expr: Box::new(self.expr_get()), + // }); + // self.webidl.push(Any); + // } + // } + // } + // + // fn alloc_copy(&mut self, webidl: ast::WebidlScalarType) { + // let expr = ast::IncomingBindingExpressionAllocCopy { + // alloc_func_name: self.alloc_func_name(), + // expr: Box::new(self.expr_get()), + // }; + // self.webidl.push(webidl); + // self.bindings + // .push(NonstandardIncoming::Standard(expr.into())); + // } + + fn number(&mut self, input: wit_walrus::ValType, output: walrus::ValType) { + self.get(AdapterType::from_wit(input)); + let std = wit_walrus::Instruction::IntToWasm { + input, + output, + trap: false, }; - self.webidl.push(webidl); - self.bindings - .push(NonstandardIncoming::Standard(expr.into())); - } - - fn number(&mut self, wasm: ValType, webidl: ast::WebidlScalarType) { - let binding = self.expr_as(wasm); - self.wasm.push(wasm); - self.webidl.push(webidl); - self.bindings.push(NonstandardIncoming::Standard(binding)); + self.instructions.push(Instruction::Standard(std)); + self.output.push(AdapterType::from_wasm(output).unwrap()); } fn number64(&mut self, signed: bool) { - let expr = self.expr_get(); - self.wasm.extend(&[ValType::I32; 2]); - self.webidl.push(ast::WebidlScalarType::Any); - self.bindings - .push(NonstandardIncoming::Int64 { val: expr, signed }); + self.get(if signed { + AdapterType::S64 + } else { + AdapterType::U64 + }); + self.instructions.push(Instruction::I32Split64 { signed }); + self.output.push(AdapterType::I32); + self.output.push(AdapterType::I32); } fn option_native(&mut self, wasm: ValType) { - let expr = self.expr_get(); - self.wasm.push(ValType::I32); - self.wasm.push(wasm); - self.webidl.push(ast::WebidlScalarType::Any); - self.bindings - .push(NonstandardIncoming::OptionNative { val: expr }); + self.get(AdapterType::Anyref); + self.instructions + .push(Instruction::OptionNative { ty: wasm }); + self.output.push(AdapterType::I32); + self.output.push(AdapterType::from_wasm(wasm).unwrap()); } fn option_sentinel(&mut self) { - let expr = self.expr_get(); - self.wasm.push(ValType::I32); - self.webidl.push(ast::WebidlScalarType::Any); - self.bindings - .push(NonstandardIncoming::OptionU32Sentinel { val: expr }); + self.get(AdapterType::Anyref); + self.instructions + .push(Instruction::I32FromOptionU32Sentinel); + self.output.push(AdapterType::I32); } } diff --git a/crates/cli-support/src/wit/mod.rs b/crates/cli-support/src/wit/mod.rs index 9e9e1859980..74f3082e224 100644 --- a/crates/cli-support/src/wit/mod.rs +++ b/crates/cli-support/src/wit/mod.rs @@ -1,28 +1,3 @@ -//! The polyfill for the WebIDL bindings proposal in wasm-bindgen. -//! -//! This module contains the polyfill (or at least the current state of and as -//! closely as we can match) the WebIDL bindings proposal. The module exports -//! one main function, `process`, which takes a `walrus::Module`. This module is -//! expected to have two items: -//! -//! * First it contains all of the raw wasm-bindgen modules emitted by the Rust -//! compiler. These raw custom sections are extracted, removed, decoded, and -//! handled here. They contain information such as what's exported where, -//! what's imported, comments, etc. -//! * Second, the `descriptors.rs` pass must have run previously to execute all -//! the descriptor functions in the wasm module. Through the synthesized -//! custom section there we learn the type information of all -//! functions/imports/exports in the module. -//! -//! The output of this function is then a new `walrus::Module` with the previous -//! custom sections all removed and two new ones inserted. One is the webidl -//! bindings custom section (or at least a close approximate) and the second is -//! an auxiliary section for wasm-bindgen itself. The goal is for this auxiliary -//! section to eventually be empty or inconsequential, allowing us to emit -//! something that doesn't even need a JS shim one day. For now we're still -//! pretty far away from that, so we'll settle for using webidl bindings as -//! aggressively as possible! - use crate::decode; use crate::descriptor::{Descriptor, Function}; use crate::descriptors::WasmBindgenDescriptorsSection; @@ -34,444 +9,32 @@ use std::path::PathBuf; use std::str; use walrus::{ExportId, FunctionId, ImportId, Module, TypedCustomSectionId}; use wasm_bindgen_shared::struct_function_export_name; -use wasm_webidl_bindings::ast; const PLACEHOLDER_MODULE: &str = "__wbindgen_placeholder__"; -mod bindings; +mod adapters; mod incoming; +mod nonstandard; mod outgoing; -pub mod standard; - -pub use self::incoming::NonstandardIncoming; -pub use self::outgoing::NonstandardOutgoing; - -/// A nonstandard wasm-bindgen-specific WebIDL custom section. -/// -/// This nonstandard section is intended to convey all information that -/// wasm-bindgen itself needs to know about binding functions. This means that -/// it basically uses `NonstandardIncoming` instead of -/// `IncomingBindingExpression` and such. It's also in a bit easier to work with -/// format than the official WebIDL bindings custom section. -/// -/// Note that this is intended to be consumed during generation of JS shims and -/// bindings. There it can be transformed, however, into an actual WebIDL -/// binding section using all of the values it has internally. -#[derive(Default, Debug)] -pub struct NonstandardWitSection { - /// Store of all WebIDL types. Used currently to store all function types - /// specified in `Bindings`. This is intended to be passed through verbatim - /// to a final WebIDL bindings section. - pub types: ast::WebidlTypes, - - /// A mapping from all bound exported functions to the binding that we have - /// listed for them. This is the master list of every binding that will be - /// bound and have a shim generated for it in the wasm module. - pub exports: HashMap, - - /// Similar to `exports` above, except for imports. This will describe all - /// imports from the wasm module to indicate what the JS shim is expected to - /// do. - pub imports: HashMap, - - /// For closures and such we'll be calling entries in the function table - /// with rich arguments (just like we call exports) so to do that we - /// describe all the elem indices that we need to modify here as well. - /// - /// This is a list of pairs where the first element in the list is the - /// element index in the function table being described and the `Binding` - /// describes the signature that it's supposed to have. - /// - /// The index within this table itself is then used to call actually - /// transformed functions. - pub elems: Vec<(u32, Binding)>, -} - -pub type NonstandardWitSectionId = TypedCustomSectionId; - -/// A non-standard wasm-bindgen-specifi WebIDL binding. This is meant to vaguely -/// resemble a `FuctionBinding` in the official WebIDL bindings proposal, or at -/// least make it very easy to manufacture an official value from this one. -#[derive(Debug, Clone)] -pub struct Binding { - /// The WebAssembly type that the function is expected to have. Note that - /// this may not match the actual bound function's type! That's because this - /// type includes `anyref` but the Rust compiler never emits anyref. This - /// is, however, used for the `anyref` pass to know what to transform to - /// `anyref`. - pub wasm_ty: walrus::TypeId, - - /// The WebIDL type of this binding, which is an index into the webidl - /// binding section's `types` field. - pub webidl_ty: ast::WebidlFunctionId, - - /// A list of incoming bindings. For exports this is the list of arguments, - /// and for imports this is the return value. - pub incoming: Vec, - - /// A list of outgoing bindings. For exports this is the return value and - /// for imports this is the list of arguments. - pub outgoing: Vec, - - /// An unfortunate necessity of today's implementation. Ideally WebIDL - /// bindings are used with multi-value support in wasm everywhere, but today - /// few engines support multi-value and LLVM certainly doesn't. Aggregates - /// are then always returned through an out-ptr, so this indicates that if - /// an out-ptr is present what wasm types are being transmitted through it. - pub return_via_outptr: Option>, -} - -impl Binding { - /// Does this binding's wasm function signature have any `anyref`s? - pub fn contains_anyref(&self, module: &walrus::Module) -> bool { - let ty = module.types.get(self.wasm_ty); - ty.params() - .iter() - .chain(ty.results()) - .any(|ty| *ty == walrus::ValType::Anyref) - } -} - -/// A synthetic custom section which is not standardized, never will be, and -/// cannot be serialized or parsed. This is synthesized from all of the -/// compiler-emitted wasm-bindgen sections and then immediately removed to be -/// processed in the JS generation pass. -#[derive(Default, Debug)] -pub struct WasmBindgenAux { - /// Extra typescript annotations that should be appended to the generated - /// TypeScript file. This is provided via a custom attribute in Rust code. - pub extra_typescript: String, - - /// A map from identifier to the contents of each local module defined via - /// the `#[wasm_bindgen(module = "/foo.js")]` import options. - pub local_modules: HashMap, - - /// A map from unique crate identifier to the list of inline JS snippets for - /// that crate identifier. - pub snippets: HashMap>, - - /// A list of all `package.json` files that are intended to be included in - /// the final build. - pub package_jsons: HashSet, - - /// A map from exported function id to where it's expected to be exported - /// to. - pub export_map: HashMap, - - /// A map from imported function id to what it's expected to import. - pub import_map: HashMap, - - /// Small bits of metadata about imports. - pub imports_with_catch: HashSet, - pub imports_with_variadic: HashSet, - pub imports_with_assert_no_shim: HashSet, - - /// Auxiliary information to go into JS/TypeScript bindings describing the - /// exported enums from Rust. - pub enums: Vec, - - /// Auxiliary information to go into JS/TypeScript bindings describing the - /// exported structs from Rust and their fields they've got exported. - pub structs: Vec, -} - -pub type WasmBindgenAuxId = TypedCustomSectionId; - -#[derive(Debug)] -pub struct AuxExport { - /// When generating errors about this export, a helpful name to remember it - /// by. - pub debug_name: String, - /// Comments parsed in Rust and forwarded here to show up in JS bindings. - pub comments: String, - /// Argument names in Rust forwarded here to configure the names that show - /// up in TypeScript bindings. - pub arg_names: Option>, - /// What kind of function this is and where it shows up - pub kind: AuxExportKind, -} - -/// All possible kinds of exports from a wasm module. -/// -/// This `enum` says where to place an exported wasm function. For example it -/// may want to get hooked up to a JS class, or it may want to be exported as a -/// free function (etc). -/// -/// TODO: it feels like this should not really be here per se. We probably want -/// to either construct the JS object itself from within wasm or somehow move -/// more of this information into some other section. Really what this is is -/// sort of an "export map" saying how to wire up all the free functions from -/// the wasm module into the output expected JS module. All our functions here -/// currently take integer parameters and require a JS wrapper, but ideally -/// we'd change them one day to taking/receiving `anyref` which then use some -/// sort of webidl import to customize behavior or something like that. In any -/// case this doesn't feel quite right in terms of priviledge separation, so -/// we'll want to work on this. For now though it works. -#[derive(Debug)] -pub enum AuxExportKind { - /// A free function that's just listed on the exported module - Function(String), - - /// A function that's used to create an instane of a class. The function - /// actually return just an integer which is put on an JS object currently. - Constructor(String), - - /// This function is intended to be a getter for a field on a class. The - /// first argument is the internal pointer and the returned value is - /// expected to be the field. - Getter { class: String, field: String }, - - /// This function is intended to be a setter for a field on a class. The - /// first argument is the internal pointer and the second argument is - /// expected to be the field's new value. - Setter { class: String, field: String }, - - /// This is a free function (ish) but scoped inside of a class name. - StaticFunction { class: String, name: String }, - - /// This is a member function of a class where the first parameter is the - /// implicit integer stored in the class instance. - Method { - class: String, - name: String, - /// Whether or not this is calling a by-value method in Rust and should - /// clear the internal pointer in JS automatically. - consumed: bool, - }, -} - -#[derive(Debug)] -pub struct AuxEnum { - /// The name of this enum - pub name: String, - /// The copied Rust comments to forward to JS - pub comments: String, - /// A list of variants with their name and value - pub variants: Vec<(String, u32)>, -} - -#[derive(Debug)] -pub struct AuxStruct { - /// The name of this struct - pub name: String, - /// The copied Rust comments to forward to JS - pub comments: String, -} - -/// All possible types of imports that can be imported by a wasm module. -/// -/// This `enum` is intended to map out what an imported value is. For example -/// this contains a ton of shims and various ways you can call a function. The -/// base variant here is `Value` which simply means "hook this up to the import" -/// and the signatures will match up. -/// -/// Note that this is *not* the same as the webidl bindings section. This is -/// intended to be coupled with that to map out what actually gets hooked up to -/// an import in the wasm module. The two work in tandem. -/// -/// Some of these items here are native to JS (like `Value`, indexing -/// operations, etc). Others are shims generated by wasm-bindgen (like `Closure` -/// or `Instanceof`). -#[derive(Debug)] -pub enum AuxImport { - /// This import is expected to simply be whatever is the value that's - /// imported - Value(AuxValue), - - /// A static method on a class is being imported, and the `this` of the - /// function call is expected to always be the class. - ValueWithThis(JsImport, String), - - /// This import is expected to be a function that takes an `anyref` and - /// returns a `bool`. It's expected that it tests if the argument is an - /// instance of (using `instanceof`) the name specified. - /// - /// TODO: can we use `Reflect` or something like that to avoid an extra kind - /// of import here? - Instanceof(JsImport), - - /// This import is expected to be a shim that returns the JS value named by - /// `JsImport`. - Static(JsImport), - - /// This import is intended to manufacture a JS closure with the given - /// signature and then return that back to Rust. - Closure { - mutable: bool, // whether or not this was a `FnMut` closure - dtor: u32, // table element index of the destructor function - binding_idx: u32, - nargs: usize, - }, - - /// This import is expected to be a shim that simply calls the `foo` method - /// on the first object, passing along all other parameters and returning - /// the resulting value. - StructuralMethod(String), - - /// This import is a "structural getter" which simply returns the `.field` - /// value of the first argument as an object. - /// - /// e.g. `function(x) { return x.foo; }` - StructuralGetter(String), - - /// This import is a "structural getter" which simply returns the `.field` - /// value of the specified class - /// - /// e.g. `function() { return TheClass.foo; }` - StructuralClassGetter(JsImport, String), - - /// This import is a "structural setter" which simply sets the `.field` - /// value of the first argument to the second argument. - /// - /// e.g. `function(x, y) { x.foo = y; }` - StructuralSetter(String), - - /// This import is a "structural setter" which simply sets the `.field` - /// value of the specified class to the first argument of the function. - /// - /// e.g. `function(x) { TheClass.foo = x; }` - StructuralClassSetter(JsImport, String), - - /// This import is expected to be a shim that is an indexing getter of the - /// JS class here, where the first argument of the function is the field to - /// look up. The return value is the value of the field. - /// - /// e.g. `function(x) { return TheClass[x]; }` - /// - /// TODO: can we use `Reflect` or something like that to avoid an extra kind - /// of import here? - IndexingGetterOfClass(JsImport), - - /// This import is expected to be a shim that is an indexing getter of the - /// first argument interpreted as an object where the field to look up is - /// the second argument. - /// - /// e.g. `function(x, y) { return x[y]; }` - /// - /// TODO: can we use `Reflect` or something like that to avoid an extra kind - /// of import here? - IndexingGetterOfObject, - - /// This import is expected to be a shim that is an indexing setter of the - /// JS class here, where the first argument of the function is the field to - /// set and the second is the value to set it to. - /// - /// e.g. `function(x, y) { TheClass[x] = y; }` - /// - /// TODO: can we use `Reflect` or something like that to avoid an extra kind - /// of import here? - IndexingSetterOfClass(JsImport), - - /// This import is expected to be a shim that is an indexing setter of the - /// first argument interpreted as an object where the next two fields are - /// the field to set and the value to set it to. - /// - /// e.g. `function(x, y, z) { x[y] = z; }` - /// - /// TODO: can we use `Reflect` or something like that to avoid an extra kind - /// of import here? - IndexingSetterOfObject, - - /// This import is expected to be a shim that is an indexing deleter of the - /// JS class here, where the first argument of the function is the field to - /// delete. - /// - /// e.g. `function(x) { delete TheClass[x]; }` - /// - /// TODO: can we use `Reflect` or something like that to avoid an extra kind - /// of import here? - IndexingDeleterOfClass(JsImport), - - /// This import is expected to be a shim that is an indexing deleter of the - /// first argument interpreted as an object where the second argument is - /// the field to delete. - /// - /// e.g. `function(x, y) { delete x[y]; }` - /// - /// TODO: can we use `Reflect` or something like that to avoid an extra kind - /// of import here? - IndexingDeleterOfObject, - - /// This import is a generated shim which will wrap the provided pointer in - /// a JS object corresponding to the Class name given here. The class name - /// is one that is exported from the Rust/wasm. - /// - /// TODO: sort of like the export map below we should ideally create the - /// `anyref` from within Rust itself and then return it directly rather than - /// requiring an intrinsic here to do so. - WrapInExportedClass(String), - - /// This is an intrinsic function expected to be implemented with a JS glue - /// shim. Each intrinsic has its own expected signature and implementation. - Intrinsic(Intrinsic), -} - -/// Values that can be imported verbatim to hook up to an import. -#[derive(Debug)] -pub enum AuxValue { - /// A bare JS value, no transformations, just put it in the slot. - Bare(JsImport), - - /// A getter function for the class listed for the field, acquired using - /// `getOwnPropertyDescriptor`. - Getter(JsImport, String), - - /// Like `Getter`, but accesses a field of a class instead of an instance - /// of the class. - ClassGetter(JsImport, String), - - /// Like `Getter`, except the `set` property. - Setter(JsImport, String), - - /// Like `Setter`, but for class fields instead of instance fields. - ClassSetter(JsImport, String), -} - -/// What can actually be imported and typically a value in each of the variants -/// above of `AuxImport` -/// -/// A `JsImport` is intended to indicate what exactly is being imported for a -/// particular operation. -#[derive(Debug, Hash, Eq, PartialEq, Clone)] -pub struct JsImport { - /// The base of whatever is being imported, either from a module, the global - /// namespace, or similar. - pub name: JsImportName, - /// Various field accesses (like `.foo.bar.baz`) to hang off the `name` - /// above. - pub fields: Vec, -} - -/// Return value of `determine_import` which is where we look at an imported -/// function AST and figure out where it's actually being imported from -/// (performing some validation checks and whatnot). -#[derive(Debug, Hash, Eq, PartialEq, Clone)] -pub enum JsImportName { - /// An item is imported from the global scope. The `name` is what's - /// imported. - Global { name: String }, - /// Same as `Global`, except the `name` is imported via an ESM import from - /// the specified `module` path. - Module { module: String, name: String }, - /// Same as `Module`, except we're importing from a local module defined in - /// a local JS snippet. - LocalModule { module: String, name: String }, - /// Same as `Module`, except we're importing from an `inline_js` attribute - InlineJs { - unique_crate_identifier: String, - snippet_idx_in_crate: usize, - name: String, - }, - /// A global import which may have a number of vendor prefixes associated - /// with it, like `webkitAudioPrefix`. The `name` is the name to test - /// whether it's prefixed. - VendorPrefixed { name: String, prefixes: Vec }, -} - +mod standard; +pub use self::nonstandard::*; +pub use self::standard::*; + +// pub mod standard; +// impl Binding { +// /// Does this binding's wasm function signature have any `anyref`s? +// pub fn contains_anyref(&self, module: &walrus::Module) -> bool { +// let ty = module.types.get(self.wasm_ty); +// ty.params() +// .iter() +// .chain(ty.results()) +// .any(|ty| *ty == walrus::ValType::Anyref) +// } +// } struct Context<'a> { start_found: bool, module: &'a mut Module, - bindings: NonstandardWebidlSection, + adapters: NonstandardWitSection, aux: WasmBindgenAux, function_exports: HashMap, function_imports: HashMap, @@ -488,12 +51,12 @@ pub fn process( anyref_enabled: bool, wasm_interface_types: bool, support_start: bool, -) -> Result<(NonstandardWebidlSectionId, WasmBindgenAuxId), Error> { +) -> Result<(NonstandardWitSectionId, WasmBindgenAuxId), Error> { let mut storage = Vec::new(); let programs = extract_programs(module, &mut storage)?; let mut cx = Context { - bindings: Default::default(), + adapters: Default::default(), aux: Default::default(), function_exports: Default::default(), function_imports: Default::default(), @@ -516,15 +79,15 @@ pub fn process( cx.discover_main()?; } - if let Some(standard) = cx.module.customs.delete_typed::() { + if let Some(standard) = cx.module.customs.delete_typed() { cx.standard(&standard)?; } cx.verify()?; - let bindings = cx.module.customs.add(cx.bindings); + let adapters = cx.module.customs.add(cx.adapters); let aux = cx.module.customs.add(cx.aux); - Ok((bindings, aux)) + Ok((adapters, aux)) } impl<'a> Context<'a> { @@ -557,1007 +120,972 @@ impl<'a> Context<'a> { self.bind_intrinsic(id, intrinsic)?; } - self.inject_anyref_initialization()?; - - if let Some(custom) = self - .module - .customs - .delete_typed::() - { - let WasmBindgenDescriptorsSection { - descriptors, - closure_imports, - } = *custom; - // Store all the executed descriptors in our own field so we have - // access to them while processing programs. - self.descriptors.extend(descriptors); - - // Register all the injected closure imports as that they're expected - // to manufacture a particular type of closure. - // - // First we register the imported function shim which returns a - // `JsValue` for the closure. We manufacture this signature's - // binding since it's not listed anywhere. - // - // Next we register the corresponding table element's binding in - // the webidl bindings section. This binding will later be used to - // generate a shim (if necessary) for the table element. - // - // Finally we store all this metadata in the import map which we've - // learned so when a binding for the import is generated we can - // generate all the appropriate shims. - for (id, descriptor) in closure_imports { - let binding = Function { - shim_idx: 0, - arguments: vec![Descriptor::I32; 3], - ret: Descriptor::Anyref, - }; - bindings::register_import( - self.module, - &mut self.bindings, - id, - binding, - ast::WebidlFunctionKind::Static, - )?; - // Synthesize the two integer pointers we pass through which - // aren't present in the signature but are present in the wasm - // signature. - let mut function = descriptor.function.clone(); - let nargs = function.arguments.len(); - function.arguments.insert(0, Descriptor::I32); - function.arguments.insert(0, Descriptor::I32); - let binding_idx = bindings::register_table_element( - self.module, - &mut self.bindings, - descriptor.shim_idx, - function, - )?; - self.aux.import_map.insert( - id, - AuxImport::Closure { - dtor: descriptor.dtor_idx, - mutable: descriptor.mutable, - binding_idx, - nargs, - }, - ); - } - } + // self.inject_anyref_initialization()?; + // + // if let Some(custom) = self + // .module + // .customs + // .delete_typed::() + // { + // let WasmBindgenDescriptorsSection { + // descriptors, + // closure_imports, + // } = *custom; + // // Store all the executed descriptors in our own field so we have + // // access to them while processing programs. + // self.descriptors.extend(descriptors); + // + // // Register all the injected closure imports as that they're expected + // // to manufacture a particular type of closure. + // // + // // First we register the imported function shim which returns a + // // `JsValue` for the closure. We manufacture this signature's + // // binding since it's not listed anywhere. + // // + // // Next we register the corresponding table element's binding in + // // the webidl bindings section. This binding will later be used to + // // generate a shim (if necessary) for the table element. + // // + // // Finally we store all this metadata in the import map which we've + // // learned so when a binding for the import is generated we can + // // generate all the appropriate shims. + // for (id, descriptor) in closure_imports { + // let binding = Function { + // shim_idx: 0, + // arguments: vec![Descriptor::I32; 3], + // ret: Descriptor::Anyref, + // }; + // bindings::register_import( + // self.module, + // &mut self.bindings, + // id, + // binding, + // ast::WebidlFunctionKind::Static, + // )?; + // // Synthesize the two integer pointers we pass through which + // // aren't present in the signature but are present in the wasm + // // signature. + // let mut function = descriptor.function.clone(); + // let nargs = function.arguments.len(); + // function.arguments.insert(0, Descriptor::I32); + // function.arguments.insert(0, Descriptor::I32); + // let binding_idx = bindings::register_table_element( + // self.module, + // &mut self.bindings, + // descriptor.shim_idx, + // function, + // )?; + // self.aux.import_map.insert( + // id, + // AuxImport::Closure { + // dtor: descriptor.dtor_idx, + // mutable: descriptor.mutable, + // binding_idx, + // nargs, + // }, + // ); + // } + // } Ok(()) } // Discover a function `main(i32, i32) -> i32` and, if it exists, make that function run at module start. fn discover_main(&mut self) -> Result<(), Error> { - // find a `main(i32, i32) -> i32` - let main_id = self - .module - .functions() - .find(|x| { - use walrus::ValType::I32; - // name has to be `main` - let name_matches = x.name.as_ref().map_or(false, |x| x == "main"); - // type has to be `(i32, i32) -> i32` - let ty = self.module.types.get(x.ty()); - let type_matches = ty.params() == [I32, I32] && ty.results() == [I32]; - name_matches && type_matches - }) - .map(|x| x.id()); - let main_id = match main_id { - Some(x) => x, - None => return Ok(()), - }; - - // build a wrapper to zero out the arguments and ignore the return value - let mut wrapper = walrus::FunctionBuilder::new(&mut self.module.types, &[], &[]); - wrapper - .func_body() - .i32_const(0) - .i32_const(0) - .call(main_id) - .drop() - .return_(); - let wrapper = wrapper.finish(vec![], &mut self.module.funcs); - - // call that wrapper when the module starts - self.add_start_function(wrapper)?; + // // find a `main(i32, i32) -> i32` + // let main_id = self + // .module + // .functions() + // .find(|x| { + // use walrus::ValType::I32; + // // name has to be `main` + // let name_matches = x.name.as_ref().map_or(false, |x| x == "main"); + // // type has to be `(i32, i32) -> i32` + // let ty = self.module.types.get(x.ty()); + // let type_matches = ty.params() == [I32, I32] && ty.results() == [I32]; + // name_matches && type_matches + // }) + // .map(|x| x.id()); + // let main_id = match main_id { + // Some(x) => x, + // None => return Ok(()), + // }; + // + // // build a wrapper to zero out the arguments and ignore the return value + // let mut wrapper = walrus::FunctionBuilder::new(&mut self.module.types, &[], &[]); + // wrapper + // .func_body() + // .i32_const(0) + // .i32_const(0) + // .call(main_id) + // .drop() + // .return_(); + // let wrapper = wrapper.finish(vec![], &mut self.module.funcs); + // + // // call that wrapper when the module starts + // self.add_start_function(wrapper)?; Ok(()) } - // Ensure that the `start` function for this module calls the - // `__wbindgen_init_anyref_table` function. This'll ensure that all - // instances of this module have the initial slots of the anyref table - // initialized correctly. + // // Ensure that the `start` function for this module calls the + // // `__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 || self.wasm_interface_types { + // return Ok(()); + // } // - // 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 || self.wasm_interface_types { - return Ok(()); - } - - let ty = self.module.types.add(&[], &[]); - let (import, import_id) = - self.module - .add_import_func(PLACEHOLDER_MODULE, "__wbindgen_init_anyref_table", ty); - - self.module.start = Some(match self.module.start { - Some(prev_start) => { - let mut builder = walrus::FunctionBuilder::new(&mut self.module.types, &[], &[]); - builder.func_body().call(import).call(prev_start); - builder.finish(Vec::new(), &mut self.module.funcs) - } - None => import, - }); - self.bind_intrinsic(import_id, Intrinsic::InitAnyrefTable)?; - - Ok(()) - } + // let ty = self.module.types.add(&[], &[]); + // let (import, import_id) = + // self.module + // .add_import_func(PLACEHOLDER_MODULE, "__wbindgen_init_anyref_table", ty); + // + // self.module.start = Some(match self.module.start { + // Some(prev_start) => { + // let mut builder = walrus::FunctionBuilder::new(&mut self.module.types, &[], &[]); + // builder.func_body().call(import).call(prev_start); + // builder.finish(Vec::new(), &mut self.module.funcs) + // } + // None => import, + // }); + // self.bind_intrinsic(import_id, Intrinsic::InitAnyrefTable)?; + // + // Ok(()) + // } fn bind_intrinsic(&mut self, id: ImportId, intrinsic: Intrinsic) -> Result<(), Error> { - bindings::register_import( + adapters::import( self.module, - &mut self.bindings, + &mut self.adapters, id, - intrinsic.binding(), - ast::WebidlFunctionKind::Static, + intrinsic.signature(), + AdapterJsImportKind::Normal, )?; - self.aux - .import_map - .insert(id, AuxImport::Intrinsic(intrinsic)); + // self.aux + // .import_map + // .insert(id, AuxImport::Intrinsic(intrinsic)); Ok(()) } fn program(&mut self, program: decode::Program<'a>) -> Result<(), Error> { - self.unique_crate_identifier = program.unique_crate_identifier; - let decode::Program { - exports, - enums, - imports, - structs, - typescript_custom_sections, - local_modules, - inline_js, - unique_crate_identifier, - package_json, - } = program; - - for module in local_modules { - // All local modules we find should be unique, but the same module - // may have showed up in a few different blocks. If that's the case - // all the same identifiers should have the same contents. - if let Some(prev) = self - .aux - .local_modules - .insert(module.identifier.to_string(), module.contents.to_string()) - { - assert_eq!(prev, module.contents); - } - } - if let Some(s) = package_json { - self.aux.package_jsons.insert(s.into()); - } - for export in exports { - self.export(export)?; - } - - // Register vendor prefixes for all types before we walk over all the - // imports to ensure that if a vendor prefix is listed somewhere it'll - // apply to all the imports. - for import in imports.iter() { - if let decode::ImportKind::Type(ty) = &import.kind { - if ty.vendor_prefixes.len() == 0 { - continue; - } - self.vendor_prefixes - .entry(ty.name.to_string()) - .or_insert(Vec::new()) - .extend(ty.vendor_prefixes.iter().map(|s| s.to_string())); - } - } - for import in imports { - self.import(import)?; - } - - for enum_ in enums { - self.enum_(enum_)?; - } - for struct_ in structs { - self.struct_(struct_)?; - } - for section in typescript_custom_sections { - self.aux.extra_typescript.push_str(section); - self.aux.extra_typescript.push_str("\n\n"); - } - self.aux - .snippets - .entry(unique_crate_identifier.to_string()) - .or_insert(Vec::new()) - .extend(inline_js.iter().map(|s| s.to_string())); - Ok(()) - } - - fn export(&mut self, export: decode::Export<'_>) -> Result<(), Error> { - let wasm_name = match &export.class { - Some(class) => struct_function_export_name(class, export.function.name), - None => export.function.name.to_string(), - }; - let mut descriptor = match self.descriptors.remove(&wasm_name) { - None => return Ok(()), - Some(d) => d.unwrap_function(), - }; - let (export_id, id) = self.function_exports[&wasm_name]; - if export.start { - self.add_start_function(id)?; - } - - let kind = match export.class { - Some(class) => { - let class = class.to_string(); - match export.method_kind { - decode::MethodKind::Constructor => AuxExportKind::Constructor(class), - decode::MethodKind::Operation(op) => match op.kind { - decode::OperationKind::Getter(f) => { - descriptor.arguments.insert(0, Descriptor::I32); - AuxExportKind::Getter { - class, - field: f.to_string(), - } - } - decode::OperationKind::Setter(f) => { - descriptor.arguments.insert(0, Descriptor::I32); - AuxExportKind::Setter { - class, - field: f.to_string(), - } - } - _ if op.is_static => AuxExportKind::StaticFunction { - class, - name: export.function.name.to_string(), - }, - _ => { - descriptor.arguments.insert(0, Descriptor::I32); - AuxExportKind::Method { - class, - name: export.function.name.to_string(), - consumed: export.consumed, - } - } - }, - } - } - None => AuxExportKind::Function(export.function.name.to_string()), - }; - - self.aux.export_map.insert( - export_id, - AuxExport { - debug_name: wasm_name, - comments: concatenate_comments(&export.comments), - arg_names: Some(export.function.arg_names), - kind, - }, - ); - bindings::register_export(self.module, &mut self.bindings, export_id, descriptor)?; + // self.unique_crate_identifier = program.unique_crate_identifier; + // let decode::Program { + // exports, + // enums, + // imports, + // structs, + // typescript_custom_sections, + // local_modules, + // inline_js, + // unique_crate_identifier, + // package_json, + // } = program; + // + // for module in local_modules { + // // All local modules we find should be unique, but the same module + // // may have showed up in a few different blocks. If that's the case + // // all the same identifiers should have the same contents. + // if let Some(prev) = self + // .aux + // .local_modules + // .insert(module.identifier.to_string(), module.contents.to_string()) + // { + // assert_eq!(prev, module.contents); + // } + // } + // if let Some(s) = package_json { + // self.aux.package_jsons.insert(s.into()); + // } + // for export in exports { + // self.export(export)?; + // } + // + // // Register vendor prefixes for all types before we walk over all the + // // imports to ensure that if a vendor prefix is listed somewhere it'll + // // apply to all the imports. + // for import in imports.iter() { + // if let decode::ImportKind::Type(ty) = &import.kind { + // if ty.vendor_prefixes.len() == 0 { + // continue; + // } + // self.vendor_prefixes + // .entry(ty.name.to_string()) + // .or_insert(Vec::new()) + // .extend(ty.vendor_prefixes.iter().map(|s| s.to_string())); + // } + // } + // for import in imports { + // self.import(import)?; + // } + // + // for enum_ in enums { + // self.enum_(enum_)?; + // } + // for struct_ in structs { + // self.struct_(struct_)?; + // } + // for section in typescript_custom_sections { + // self.aux.extra_typescript.push_str(section); + // self.aux.extra_typescript.push_str("\n\n"); + // } + // self.aux + // .snippets + // .entry(unique_crate_identifier.to_string()) + // .or_insert(Vec::new()) + // .extend(inline_js.iter().map(|s| s.to_string())); Ok(()) } - fn add_start_function(&mut self, id: FunctionId) -> Result<(), Error> { - if self.start_found { - bail!("cannot specify two `start` functions"); - } - self.start_found = true; - - // Skip this when we're generating tests - if !self.support_start { - return Ok(()); - } - - let prev_start = match self.module.start { - Some(f) => f, - None => { - self.module.start = Some(id); - return Ok(()); - } - }; - - // Note that we call the previous start function, if any, first. This is - // because the start function currently only shows up when it's injected - // through thread/anyref transforms. These injected start functions need - // to happen before user code, so we always schedule them first. - let mut builder = walrus::FunctionBuilder::new(&mut self.module.types, &[], &[]); - builder.func_body().call(prev_start).call(id); - let new_start = builder.finish(Vec::new(), &mut self.module.funcs); - self.module.start = Some(new_start); - Ok(()) - } - - fn import(&mut self, import: decode::Import<'_>) -> Result<(), Error> { - match &import.kind { - decode::ImportKind::Function(f) => self.import_function(&import, f), - decode::ImportKind::Static(s) => self.import_static(&import, s), - decode::ImportKind::Type(t) => self.import_type(&import, t), - decode::ImportKind::Enum(_) => Ok(()), - } - } - - fn import_function( - &mut self, - import: &decode::Import<'_>, - function: &decode::ImportFunction<'_>, - ) -> Result<(), Error> { - let decode::ImportFunction { - shim, - catch, - variadic, - method, - structural, - function, - assert_no_shim, - } = function; - let (import_id, _id) = match self.function_imports.get(*shim) { - Some(pair) => *pair, - None => return Ok(()), - }; - let descriptor = match self.descriptors.remove(*shim) { - None => return Ok(()), - Some(d) => d.unwrap_function(), - }; - - // Record this for later as it affects JS binding generation, but note - // that this doesn't affect the WebIDL interface at all. - if *variadic { - self.aux.imports_with_variadic.insert(import_id); - } - if *catch { - self.aux.imports_with_catch.insert(import_id); - } - if *assert_no_shim { - self.aux.imports_with_assert_no_shim.insert(import_id); - } - - // Perform two functions here. First we're saving off our WebIDL - // bindings signature, indicating what we think our import is going to - // be. Next we're saving off other metadata indicating where this item - // is going to be imported from. The `import_map` table will record, for - // each import, what is getting hooked up to that slot of the import - // table to the WebAssembly instance. - let import = match method { - Some(data) => { - let class = self.determine_import(import, &data.class)?; - match &data.kind { - // NB: `structural` is ignored for constructors since the - // js type isn't expected to change anyway. - decode::MethodKind::Constructor => { - bindings::register_import( - self.module, - &mut self.bindings, - import_id, - descriptor, - ast::WebidlFunctionKind::Constructor, - )?; - AuxImport::Value(AuxValue::Bare(class)) - } - decode::MethodKind::Operation(op) => { - let (import, method) = - self.determine_import_op(class, function, *structural, op)?; - let kind = if method { - let kind = ast::WebidlFunctionKindMethod { - // TODO: what should this actually be? - ty: ast::WebidlScalarType::Any.into(), - }; - ast::WebidlFunctionKind::Method(kind) - } else { - ast::WebidlFunctionKind::Static - }; - bindings::register_import( - self.module, - &mut self.bindings, - import_id, - descriptor, - kind, - )?; - import - } - } - } - - // NB: `structural` is ignored for free functions since it's - // expected that the binding isn't changing anyway. - None => { - bindings::register_import( - self.module, - &mut self.bindings, - import_id, - descriptor, - ast::WebidlFunctionKind::Static, - )?; - let name = self.determine_import(import, function.name)?; - AuxImport::Value(AuxValue::Bare(name)) - } - }; - - self.aux.import_map.insert(import_id, import); - Ok(()) - } - - /// The `bool` returned indicates whether the imported value should be - /// invoked as a method (first arg is implicitly `this`) or if the imported - /// value is a simple function-like shim - fn determine_import_op( - &mut self, - mut class: JsImport, - function: &decode::Function<'_>, - structural: bool, - op: &decode::Operation<'_>, - ) -> Result<(AuxImport, bool), Error> { - match op.kind { - decode::OperationKind::Regular => { - if op.is_static { - Ok(( - AuxImport::ValueWithThis(class, function.name.to_string()), - false, - )) - } else if structural { - Ok(( - AuxImport::StructuralMethod(function.name.to_string()), - false, - )) - } else { - class.fields.push("prototype".to_string()); - class.fields.push(function.name.to_string()); - Ok((AuxImport::Value(AuxValue::Bare(class)), true)) - } - } - - decode::OperationKind::Getter(field) => { - if structural { - if op.is_static { - Ok(( - AuxImport::StructuralClassGetter(class, field.to_string()), - false, - )) - } else { - Ok((AuxImport::StructuralGetter(field.to_string()), false)) - } - } else { - let val = if op.is_static { - AuxValue::ClassGetter(class, field.to_string()) - } else { - AuxValue::Getter(class, field.to_string()) - }; - Ok((AuxImport::Value(val), true)) - } - } - - decode::OperationKind::Setter(field) => { - if structural { - if op.is_static { - Ok(( - AuxImport::StructuralClassSetter(class, field.to_string()), - false, - )) - } else { - Ok((AuxImport::StructuralSetter(field.to_string()), false)) - } - } else { - let val = if op.is_static { - AuxValue::ClassSetter(class, field.to_string()) - } else { - AuxValue::Setter(class, field.to_string()) - }; - Ok((AuxImport::Value(val), true)) - } - } - - decode::OperationKind::IndexingGetter => { - if !structural { - bail!("indexing getters must always be structural"); - } - if op.is_static { - Ok((AuxImport::IndexingGetterOfClass(class), false)) - } else { - Ok((AuxImport::IndexingGetterOfObject, false)) - } - } - - decode::OperationKind::IndexingSetter => { - if !structural { - bail!("indexing setters must always be structural"); - } - if op.is_static { - Ok((AuxImport::IndexingSetterOfClass(class), false)) - } else { - Ok((AuxImport::IndexingSetterOfObject, false)) - } - } - - decode::OperationKind::IndexingDeleter => { - if !structural { - bail!("indexing deleters must always be structural"); - } - if op.is_static { - Ok((AuxImport::IndexingDeleterOfClass(class), false)) - } else { - Ok((AuxImport::IndexingDeleterOfObject, false)) - } - } - } - } - - fn import_static( - &mut self, - import: &decode::Import<'_>, - static_: &decode::ImportStatic<'_>, - ) -> Result<(), Error> { - let (import_id, _id) = match self.function_imports.get(static_.shim) { - Some(pair) => *pair, - None => return Ok(()), - }; - - let descriptor = match self.descriptors.remove(static_.shim) { - None => return Ok(()), - Some(d) => d, - }; - - // Register the signature of this imported shim - bindings::register_import( - self.module, - &mut self.bindings, - import_id, - Function { - arguments: Vec::new(), - shim_idx: 0, - ret: descriptor, - }, - ast::WebidlFunctionKind::Static, - )?; - - // And then save off that this function is is an instanceof shim for an - // imported item. - let import = self.determine_import(import, &static_.name)?; - self.aux - .import_map - .insert(import_id, AuxImport::Static(import)); - Ok(()) - } - - fn import_type( - &mut self, - import: &decode::Import<'_>, - type_: &decode::ImportType<'_>, - ) -> Result<(), Error> { - let (import_id, _id) = match self.function_imports.get(type_.instanceof_shim) { - Some(pair) => *pair, - None => return Ok(()), - }; - - // Register the signature of this imported shim - bindings::register_import( - self.module, - &mut self.bindings, - import_id, - Function { - arguments: vec![Descriptor::Ref(Box::new(Descriptor::Anyref))], - shim_idx: 0, - ret: Descriptor::Boolean, - }, - ast::WebidlFunctionKind::Static, - )?; - - // And then save off that this function is is an instanceof shim for an - // imported item. - let import = self.determine_import(import, &type_.name)?; - self.aux - .import_map - .insert(import_id, AuxImport::Instanceof(import)); - Ok(()) - } - - fn enum_(&mut self, enum_: decode::Enum<'_>) -> Result<(), Error> { - let aux = AuxEnum { - name: enum_.name.to_string(), - comments: concatenate_comments(&enum_.comments), - variants: enum_ - .variants - .iter() - .map(|v| (v.name.to_string(), v.value)) - .collect(), - }; - self.aux.enums.push(aux); - Ok(()) - } - - fn struct_(&mut self, struct_: decode::Struct<'_>) -> Result<(), Error> { - for field in struct_.fields { - let getter = wasm_bindgen_shared::struct_field_get(&struct_.name, &field.name); - let setter = wasm_bindgen_shared::struct_field_set(&struct_.name, &field.name); - let descriptor = match self.descriptors.remove(&getter) { - None => continue, - Some(d) => d, - }; - - // Register a webidl transformation for the getter - let (getter_id, _) = self.function_exports[&getter]; - let getter_descriptor = Function { - arguments: vec![Descriptor::I32], - shim_idx: 0, - ret: descriptor.clone(), - }; - bindings::register_export( - self.module, - &mut self.bindings, - getter_id, - getter_descriptor, - )?; - self.aux.export_map.insert( - getter_id, - AuxExport { - debug_name: format!("getter for `{}::{}`", struct_.name, field.name), - arg_names: None, - comments: concatenate_comments(&field.comments), - kind: AuxExportKind::Getter { - class: struct_.name.to_string(), - field: field.name.to_string(), - }, - }, - ); - - // If present, register information for the setter as well. - if field.readonly { - continue; - } - - let (setter_id, _) = self.function_exports[&setter]; - let setter_descriptor = Function { - arguments: vec![Descriptor::I32, descriptor], - shim_idx: 0, - ret: Descriptor::Unit, - }; - bindings::register_export( - self.module, - &mut self.bindings, - setter_id, - setter_descriptor, - )?; - self.aux.export_map.insert( - setter_id, - AuxExport { - debug_name: format!("setter for `{}::{}`", struct_.name, field.name), - arg_names: None, - comments: concatenate_comments(&field.comments), - kind: AuxExportKind::Setter { - class: struct_.name.to_string(), - field: field.name.to_string(), - }, - }, - ); - } - let aux = AuxStruct { - name: struct_.name.to_string(), - comments: concatenate_comments(&struct_.comments), - }; - self.aux.structs.push(aux); - - let wrap_constructor = wasm_bindgen_shared::new_function(struct_.name); - if let Some((import_id, _id)) = self.function_imports.get(&wrap_constructor) { - self.aux.import_map.insert( - *import_id, - AuxImport::WrapInExportedClass(struct_.name.to_string()), - ); - let binding = Function { - shim_idx: 0, - arguments: vec![Descriptor::I32], - ret: Descriptor::Anyref, - }; - bindings::register_import( - self.module, - &mut self.bindings, - *import_id, - binding, - ast::WebidlFunctionKind::Static, - )?; - } - - Ok(()) - } - - fn determine_import(&self, import: &decode::Import<'_>, item: &str) -> Result { - let is_local_snippet = match import.module { - decode::ImportModule::Named(s) => self.aux.local_modules.contains_key(s), - decode::ImportModule::RawNamed(_) => false, - decode::ImportModule::Inline(_) => true, - decode::ImportModule::None => false, - }; - - // Similar to `--target no-modules`, only allow vendor prefixes - // basically for web apis, shouldn't be necessary for things like npm - // packages or other imported items. - let vendor_prefixes = self.vendor_prefixes.get(item); - if let Some(vendor_prefixes) = vendor_prefixes { - assert!(vendor_prefixes.len() > 0); - - if is_local_snippet { - bail!( - "local JS snippets do not support vendor prefixes for \ - the import of `{}` with a polyfill of `{}`", - item, - &vendor_prefixes[0] - ); - } - if let decode::ImportModule::Named(module) = &import.module { - bail!( - "import of `{}` from `{}` has a polyfill of `{}` listed, but - vendor prefixes aren't supported when importing from modules", - item, - module, - &vendor_prefixes[0], - ); - } - if let Some(ns) = &import.js_namespace { - bail!( - "import of `{}` through js namespace `{}` isn't supported \ - right now when it lists a polyfill", - item, - ns - ); - } - return Ok(JsImport { - name: JsImportName::VendorPrefixed { - name: item.to_string(), - prefixes: vendor_prefixes.clone(), - }, - fields: Vec::new(), - }); - } - - let (name, fields) = match import.js_namespace { - Some(ns) => (ns, vec![item.to_string()]), - None => (item, Vec::new()), - }; - - let name = match import.module { - decode::ImportModule::Named(module) if is_local_snippet => JsImportName::LocalModule { - module: module.to_string(), - name: name.to_string(), - }, - decode::ImportModule::Named(module) | decode::ImportModule::RawNamed(module) => { - JsImportName::Module { - module: module.to_string(), - name: name.to_string(), - } - } - decode::ImportModule::Inline(idx) => { - let offset = self - .aux - .snippets - .get(self.unique_crate_identifier) - .map(|s| s.len()) - .unwrap_or(0); - JsImportName::InlineJs { - unique_crate_identifier: self.unique_crate_identifier.to_string(), - snippet_idx_in_crate: idx as usize + offset, - name: name.to_string(), - } - } - decode::ImportModule::None => JsImportName::Global { - name: name.to_string(), - }, - }; - 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(|| anyhow!("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 { - let binding: &ast::FunctionBinding = std - .bindings - .get(bind.binding) - .ok_or_else(|| anyhow!("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[..], - ), - ast::FunctionBinding::Import(e) => ( - e.wasm_ty, - e.webidl_ty, - &e.result.bindings[..], - e.params.bindings.as_slice(), - ), - }; - let webidl_ty = standard::copy_ty(&mut self.bindings.types, webidl_ty, &std.types); - let webidl_ty = match webidl_ty { - ast::WebidlTypeRef::Id(id) => ::wrap(id), - _ => bail!("invalid webidl type listed"), - }; - - 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, - }) - } - - /// 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()); - + // fn export(&mut self, export: decode::Export<'_>) -> Result<(), Error> { + // let wasm_name = match &export.class { + // Some(class) => struct_function_export_name(class, export.function.name), + // None => export.function.name.to_string(), + // }; + // let mut descriptor = match self.descriptors.remove(&wasm_name) { + // None => return Ok(()), + // Some(d) => d.unwrap_function(), + // }; + // let (export_id, id) = self.function_exports[&wasm_name]; + // if export.start { + // self.add_start_function(id)?; + // } + // + // let kind = match export.class { + // Some(class) => { + // let class = class.to_string(); + // match export.method_kind { + // decode::MethodKind::Constructor => AuxExportKind::Constructor(class), + // decode::MethodKind::Operation(op) => match op.kind { + // decode::OperationKind::Getter(f) => { + // descriptor.arguments.insert(0, Descriptor::I32); + // AuxExportKind::Getter { + // class, + // field: f.to_string(), + // } + // } + // decode::OperationKind::Setter(f) => { + // descriptor.arguments.insert(0, Descriptor::I32); + // AuxExportKind::Setter { + // class, + // field: f.to_string(), + // } + // } + // _ if op.is_static => AuxExportKind::StaticFunction { + // class, + // name: export.function.name.to_string(), + // }, + // _ => { + // descriptor.arguments.insert(0, Descriptor::I32); + // AuxExportKind::Method { + // class, + // name: export.function.name.to_string(), + // consumed: export.consumed, + // } + // } + // }, + // } + // } + // None => AuxExportKind::Function(export.function.name.to_string()), + // }; + // + // self.aux.export_map.insert( + // export_id, + // AuxExport { + // debug_name: wasm_name, + // comments: concatenate_comments(&export.comments), + // arg_names: Some(export.function.arg_names), + // kind, + // }, + // ); + // bindings::register_export(self.module, &mut self.bindings, export_id, descriptor)?; + // Ok(()) + // } + // + // fn add_start_function(&mut self, id: FunctionId) -> Result<(), Error> { + // if self.start_found { + // bail!("cannot specify two `start` functions"); + // } + // self.start_found = true; + // + // // Skip this when we're generating tests + // if !self.support_start { + // return Ok(()); + // } + // + // let prev_start = match self.module.start { + // Some(f) => f, + // None => { + // self.module.start = Some(id); + // return Ok(()); + // } + // }; + // + // // Note that we call the previous start function, if any, first. This is + // // because the start function currently only shows up when it's injected + // // through thread/anyref transforms. These injected start functions need + // // to happen before user code, so we always schedule them first. + // let mut builder = walrus::FunctionBuilder::new(&mut self.module.types, &[], &[]); + // builder.func_body().call(prev_start).call(id); + // let new_start = builder.finish(Vec::new(), &mut self.module.funcs); + // self.module.start = Some(new_start); + // Ok(()) + // } + // + // fn import(&mut self, import: decode::Import<'_>) -> Result<(), Error> { + // match &import.kind { + // decode::ImportKind::Function(f) => self.import_function(&import, f), + // decode::ImportKind::Static(s) => self.import_static(&import, s), + // decode::ImportKind::Type(t) => self.import_type(&import, t), + // decode::ImportKind::Enum(_) => Ok(()), + // } + // } + // + // fn import_function( + // &mut self, + // import: &decode::Import<'_>, + // function: &decode::ImportFunction<'_>, + // ) -> Result<(), Error> { + // let decode::ImportFunction { + // shim, + // catch, + // variadic, + // method, + // structural, + // function, + // assert_no_shim, + // } = function; + // let (import_id, _id) = match self.function_imports.get(*shim) { + // Some(pair) => *pair, + // None => return Ok(()), + // }; + // let descriptor = match self.descriptors.remove(*shim) { + // None => return Ok(()), + // Some(d) => d.unwrap_function(), + // }; + // + // // Record this for later as it affects JS binding generation, but note + // // that this doesn't affect the WebIDL interface at all. + // if *variadic { + // self.aux.imports_with_variadic.insert(import_id); + // } + // if *catch { + // self.aux.imports_with_catch.insert(import_id); + // } + // if *assert_no_shim { + // self.aux.imports_with_assert_no_shim.insert(import_id); + // } + // + // // Perform two functions here. First we're saving off our WebIDL + // // bindings signature, indicating what we think our import is going to + // // be. Next we're saving off other metadata indicating where this item + // // is going to be imported from. The `import_map` table will record, for + // // each import, what is getting hooked up to that slot of the import + // // table to the WebAssembly instance. + // let import = match method { + // Some(data) => { + // let class = self.determine_import(import, &data.class)?; + // match &data.kind { + // // NB: `structural` is ignored for constructors since the + // // js type isn't expected to change anyway. + // decode::MethodKind::Constructor => { + // bindings::register_import( + // self.module, + // &mut self.bindings, + // import_id, + // descriptor, + // ast::WebidlFunctionKind::Constructor, + // )?; + // AuxImport::Value(AuxValue::Bare(class)) + // } + // decode::MethodKind::Operation(op) => { + // let (import, method) = + // self.determine_import_op(class, function, *structural, op)?; + // let kind = if method { + // let kind = ast::WebidlFunctionKindMethod { + // // TODO: what should this actually be? + // ty: ast::WebidlScalarType::Any.into(), + // }; + // ast::WebidlFunctionKind::Method(kind) + // } else { + // ast::WebidlFunctionKind::Static + // }; + // bindings::register_import( + // self.module, + // &mut self.bindings, + // import_id, + // descriptor, + // kind, + // )?; + // import + // } + // } + // } + // + // // NB: `structural` is ignored for free functions since it's + // // expected that the binding isn't changing anyway. + // None => { + // bindings::register_import( + // self.module, + // &mut self.bindings, + // import_id, + // descriptor, + // ast::WebidlFunctionKind::Static, + // )?; + // let name = self.determine_import(import, function.name)?; + // AuxImport::Value(AuxValue::Bare(name)) + // } + // }; + // + // self.aux.import_map.insert(import_id, import); + // Ok(()) + // } + // + // /// The `bool` returned indicates whether the imported value should be + // /// invoked as a method (first arg is implicitly `this`) or if the imported + // /// value is a simple function-like shim + // fn determine_import_op( + // &mut self, + // mut class: JsImport, + // function: &decode::Function<'_>, + // structural: bool, + // op: &decode::Operation<'_>, + // ) -> Result<(AuxImport, bool), Error> { + // match op.kind { + // decode::OperationKind::Regular => { + // if op.is_static { + // Ok(( + // AuxImport::ValueWithThis(class, function.name.to_string()), + // false, + // )) + // } else if structural { + // Ok(( + // AuxImport::StructuralMethod(function.name.to_string()), + // false, + // )) + // } else { + // class.fields.push("prototype".to_string()); + // class.fields.push(function.name.to_string()); + // Ok((AuxImport::Value(AuxValue::Bare(class)), true)) + // } + // } + // + // decode::OperationKind::Getter(field) => { + // if structural { + // if op.is_static { + // Ok(( + // AuxImport::StructuralClassGetter(class, field.to_string()), + // false, + // )) + // } else { + // Ok((AuxImport::StructuralGetter(field.to_string()), false)) + // } + // } else { + // let val = if op.is_static { + // AuxValue::ClassGetter(class, field.to_string()) + // } else { + // AuxValue::Getter(class, field.to_string()) + // }; + // Ok((AuxImport::Value(val), true)) + // } + // } + // + // decode::OperationKind::Setter(field) => { + // if structural { + // if op.is_static { + // Ok(( + // AuxImport::StructuralClassSetter(class, field.to_string()), + // false, + // )) + // } else { + // Ok((AuxImport::StructuralSetter(field.to_string()), false)) + // } + // } else { + // let val = if op.is_static { + // AuxValue::ClassSetter(class, field.to_string()) + // } else { + // AuxValue::Setter(class, field.to_string()) + // }; + // Ok((AuxImport::Value(val), true)) + // } + // } + // + // decode::OperationKind::IndexingGetter => { + // if !structural { + // bail!("indexing getters must always be structural"); + // } + // if op.is_static { + // Ok((AuxImport::IndexingGetterOfClass(class), false)) + // } else { + // Ok((AuxImport::IndexingGetterOfObject, false)) + // } + // } + // + // decode::OperationKind::IndexingSetter => { + // if !structural { + // bail!("indexing setters must always be structural"); + // } + // if op.is_static { + // Ok((AuxImport::IndexingSetterOfClass(class), false)) + // } else { + // Ok((AuxImport::IndexingSetterOfObject, false)) + // } + // } + // + // decode::OperationKind::IndexingDeleter => { + // if !structural { + // bail!("indexing deleters must always be structural"); + // } + // if op.is_static { + // Ok((AuxImport::IndexingDeleterOfClass(class), false)) + // } else { + // Ok((AuxImport::IndexingDeleterOfObject, false)) + // } + // } + // } + // } + // + // fn import_static( + // &mut self, + // import: &decode::Import<'_>, + // static_: &decode::ImportStatic<'_>, + // ) -> Result<(), Error> { + // let (import_id, _id) = match self.function_imports.get(static_.shim) { + // Some(pair) => *pair, + // None => return Ok(()), + // }; + // + // let descriptor = match self.descriptors.remove(static_.shim) { + // None => return Ok(()), + // Some(d) => d, + // }; + // + // // Register the signature of this imported shim + // bindings::register_import( + // self.module, + // &mut self.bindings, + // import_id, + // Function { + // arguments: Vec::new(), + // shim_idx: 0, + // ret: descriptor, + // }, + // ast::WebidlFunctionKind::Static, + // )?; + // + // // And then save off that this function is is an instanceof shim for an + // // imported item. + // let import = self.determine_import(import, &static_.name)?; + // self.aux + // .import_map + // .insert(import_id, AuxImport::Static(import)); + // Ok(()) + // } + // + // fn import_type( + // &mut self, + // import: &decode::Import<'_>, + // type_: &decode::ImportType<'_>, + // ) -> Result<(), Error> { + // let (import_id, _id) = match self.function_imports.get(type_.instanceof_shim) { + // Some(pair) => *pair, + // None => return Ok(()), + // }; + // + // // Register the signature of this imported shim + // bindings::register_import( + // self.module, + // &mut self.bindings, + // import_id, + // Function { + // arguments: vec![Descriptor::Ref(Box::new(Descriptor::Anyref))], + // shim_idx: 0, + // ret: Descriptor::Boolean, + // }, + // ast::WebidlFunctionKind::Static, + // )?; + // + // // And then save off that this function is is an instanceof shim for an + // // imported item. + // let import = self.determine_import(import, &type_.name)?; + // self.aux + // .import_map + // .insert(import_id, AuxImport::Instanceof(import)); + // Ok(()) + // } + // + // fn enum_(&mut self, enum_: decode::Enum<'_>) -> Result<(), Error> { + // let aux = AuxEnum { + // name: enum_.name.to_string(), + // comments: concatenate_comments(&enum_.comments), + // variants: enum_ + // .variants + // .iter() + // .map(|v| (v.name.to_string(), v.value)) + // .collect(), + // }; + // self.aux.enums.push(aux); + // Ok(()) + // } + // + // fn struct_(&mut self, struct_: decode::Struct<'_>) -> Result<(), Error> { + // for field in struct_.fields { + // let getter = wasm_bindgen_shared::struct_field_get(&struct_.name, &field.name); + // let setter = wasm_bindgen_shared::struct_field_set(&struct_.name, &field.name); + // let descriptor = match self.descriptors.remove(&getter) { + // None => continue, + // Some(d) => d, + // }; + // + // // Register a webidl transformation for the getter + // let (getter_id, _) = self.function_exports[&getter]; + // let getter_descriptor = Function { + // arguments: vec![Descriptor::I32], + // shim_idx: 0, + // ret: descriptor.clone(), + // }; + // bindings::register_export( + // self.module, + // &mut self.bindings, + // getter_id, + // getter_descriptor, + // )?; + // self.aux.export_map.insert( + // getter_id, + // AuxExport { + // debug_name: format!("getter for `{}::{}`", struct_.name, field.name), + // arg_names: None, + // comments: concatenate_comments(&field.comments), + // kind: AuxExportKind::Getter { + // class: struct_.name.to_string(), + // field: field.name.to_string(), + // }, + // }, + // ); + // + // // If present, register information for the setter as well. + // if field.readonly { + // continue; + // } + // + // let (setter_id, _) = self.function_exports[&setter]; + // let setter_descriptor = Function { + // arguments: vec![Descriptor::I32, descriptor], + // shim_idx: 0, + // ret: Descriptor::Unit, + // }; + // bindings::register_export( + // self.module, + // &mut self.bindings, + // setter_id, + // setter_descriptor, + // )?; + // self.aux.export_map.insert( + // setter_id, + // AuxExport { + // debug_name: format!("setter for `{}::{}`", struct_.name, field.name), + // arg_names: None, + // comments: concatenate_comments(&field.comments), + // kind: AuxExportKind::Setter { + // class: struct_.name.to_string(), + // field: field.name.to_string(), + // }, + // }, + // ); + // } + // let aux = AuxStruct { + // name: struct_.name.to_string(), + // comments: concatenate_comments(&struct_.comments), + // }; + // self.aux.structs.push(aux); + // + // let wrap_constructor = wasm_bindgen_shared::new_function(struct_.name); + // if let Some((import_id, _id)) = self.function_imports.get(&wrap_constructor) { + // self.aux.import_map.insert( + // *import_id, + // AuxImport::WrapInExportedClass(struct_.name.to_string()), + // ); + // let binding = Function { + // shim_idx: 0, + // arguments: vec![Descriptor::I32], + // ret: Descriptor::Anyref, + // }; + // bindings::register_import( + // self.module, + // &mut self.bindings, + // *import_id, + // binding, + // ast::WebidlFunctionKind::Static, + // )?; + // } + // + // Ok(()) + // } + // + // fn determine_import(&self, import: &decode::Import<'_>, item: &str) -> Result { + // let is_local_snippet = match import.module { + // decode::ImportModule::Named(s) => self.aux.local_modules.contains_key(s), + // decode::ImportModule::RawNamed(_) => false, + // decode::ImportModule::Inline(_) => true, + // decode::ImportModule::None => false, + // }; + // + // // Similar to `--target no-modules`, only allow vendor prefixes + // // basically for web apis, shouldn't be necessary for things like npm + // // packages or other imported items. + // let vendor_prefixes = self.vendor_prefixes.get(item); + // if let Some(vendor_prefixes) = vendor_prefixes { + // assert!(vendor_prefixes.len() > 0); + // + // if is_local_snippet { + // bail!( + // "local JS snippets do not support vendor prefixes for \ + // the import of `{}` with a polyfill of `{}`", + // item, + // &vendor_prefixes[0] + // ); + // } + // if let decode::ImportModule::Named(module) = &import.module { + // bail!( + // "import of `{}` from `{}` has a polyfill of `{}` listed, but + // vendor prefixes aren't supported when importing from modules", + // item, + // module, + // &vendor_prefixes[0], + // ); + // } + // if let Some(ns) = &import.js_namespace { + // bail!( + // "import of `{}` through js namespace `{}` isn't supported \ + // right now when it lists a polyfill", + // item, + // ns + // ); + // } + // return Ok(JsImport { + // name: JsImportName::VendorPrefixed { + // name: item.to_string(), + // prefixes: vendor_prefixes.clone(), + // }, + // fields: Vec::new(), + // }); + // } + // + // let (name, fields) = match import.js_namespace { + // Some(ns) => (ns, vec![item.to_string()]), + // None => (item, Vec::new()), + // }; + // + // let name = match import.module { + // decode::ImportModule::Named(module) if is_local_snippet => JsImportName::LocalModule { + // module: module.to_string(), + // name: name.to_string(), + // }, + // decode::ImportModule::Named(module) | decode::ImportModule::RawNamed(module) => { + // JsImportName::Module { + // module: module.to_string(), + // name: name.to_string(), + // } + // } + // decode::ImportModule::Inline(idx) => { + // let offset = self + // .aux + // .snippets + // .get(self.unique_crate_identifier) + // .map(|s| s.len()) + // .unwrap_or(0); + // JsImportName::InlineJs { + // unique_crate_identifier: self.unique_crate_identifier.to_string(), + // snippet_idx_in_crate: idx as usize + offset, + // name: name.to_string(), + // } + // } + // decode::ImportModule::None => JsImportName::Global { + // name: name.to_string(), + // }, + // }; + // Ok(JsImport { name, fields }) + // } + + fn standard(&mut self, std: &wit_walrus::WasmInterfaceTypes) -> 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(|| anyhow!("missing export function for webidl binding"))?; + // let id = export.id(); + // self.standard_export(binding, id)?; + // } + // walrus::FunctionKind::Uninitialized(_) => unreachable!(), + // } + // } 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(()) - } + // /// 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 { + // let binding: &ast::FunctionBinding = std + // .bindings + // .get(bind.binding) + // .ok_or_else(|| anyhow!("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[..], + // ), + // ast::FunctionBinding::Import(e) => ( + // e.wasm_ty, + // e.webidl_ty, + // &e.result.bindings[..], + // e.params.bindings.as_slice(), + // ), + // }; + // let webidl_ty = standard::copy_ty(&mut self.bindings.types, webidl_ty, &std.types); + // let webidl_ty = match webidl_ty { + // ast::WebidlTypeRef::Id(id) => ::wrap(id), + // _ => bail!("invalid webidl type listed"), + // }; + // + // 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, + // }) + // } + // + // /// 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> { - let mut imports_counted = 0; - for import in self.module.imports.iter() { - if import.module != PLACEHOLDER_MODULE { - continue; - } - match import.kind { - walrus::ImportKind::Function(_) => {} - _ => bail!("import from `{}` was not a function", PLACEHOLDER_MODULE), - } - - // Ensure that everything imported from the `__wbindgen_placeholder__` - // module has a location listed as to where it's expected to be - // imported from. - if !self.aux.import_map.contains_key(&import.id()) { - bail!( - "import of `{}` doesn't have an import map item listed", - import.name - ); - } - - // Also make sure there's a binding listed for it. - if !self.bindings.imports.contains_key(&import.id()) { - bail!("import of `{}` doesn't have a binding listed", import.name); - } - imports_counted += 1; - } - - // Make sure there's no extraneous bindings that weren't actually - // imported in the module. - if self.aux.import_map.len() != imports_counted { - bail!("import map is larger than the number of imports"); - } - if self.bindings.imports.len() != imports_counted { - bail!("import binding map is larger than the number of imports"); - } - - // Make sure the export map and export bindings map contain the same - // number of entries. - for id in self.bindings.exports.keys() { - if !self.aux.export_map.contains_key(id) { - bail!("bindings map has an entry that the export map does not"); - } - } - - if self.bindings.exports.len() != self.aux.export_map.len() { - bail!("export map and export bindings map have different sizes"); - } + // let mut imports_counted = 0; + // for import in self.module.imports.iter() { + // if import.module != PLACEHOLDER_MODULE { + // continue; + // } + // match import.kind { + // walrus::ImportKind::Function(_) => {} + // _ => bail!("import from `{}` was not a function", PLACEHOLDER_MODULE), + // } + // + // // Ensure that everything imported from the `__wbindgen_placeholder__` + // // module has a location listed as to where it's expected to be + // // imported from. + // if !self.aux.import_map.contains_key(&import.id()) { + // bail!( + // "import of `{}` doesn't have an import map item listed", + // import.name + // ); + // } + // + // // Also make sure there's a binding listed for it. + // if !self.bindings.imports.contains_key(&import.id()) { + // bail!("import of `{}` doesn't have a binding listed", import.name); + // } + // imports_counted += 1; + // } + // + // // Make sure there's no extraneous bindings that weren't actually + // // imported in the module. + // if self.aux.import_map.len() != imports_counted { + // bail!("import map is larger than the number of imports"); + // } + // if self.bindings.imports.len() != imports_counted { + // bail!("import binding map is larger than the number of imports"); + // } + // + // // Make sure the export map and export bindings map contain the same + // // number of entries. + // for id in self.bindings.exports.keys() { + // if !self.aux.export_map.contains_key(id) { + // bail!("bindings map has an entry that the export map does not"); + // } + // } + // + // if self.bindings.exports.len() != self.aux.export_map.len() { + // bail!("export map and export bindings map have different sizes"); + // } Ok(()) } } -impl walrus::CustomSection for NonstandardWebidlSection { - fn name(&self) -> &str { - "webidl custom section" - } - - fn data(&self, _: &walrus::IdsToIndices) -> Cow<[u8]> { - panic!("shouldn't emit custom sections just yet"); - } -} - -impl walrus::CustomSection for WasmBindgenAux { - fn name(&self) -> &str { - "wasm-bindgen custom section" - } - - fn data(&self, _: &walrus::IdsToIndices) -> Cow<[u8]> { - panic!("shouldn't emit custom sections just yet"); - } -} - fn extract_programs<'a>( module: &mut Module, program_storage: &'a mut Vec>, @@ -1687,76 +1215,76 @@ fn concatenate_comments(comments: &[&str]) -> String { .join("\n") } -/// Do we need to generate JS glue shims for these incoming bindings? -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 - .iter() - .zip(from_webidl_tys) - .zip(to_wasm_tys) - .enumerate() - .all(|(i, ((expr, from_webidl_ty), to_wasm_ty))| match expr { - NonstandardIncoming::Standard(e) => e.is_expressible_in_js_without_webidl_bindings( - *from_webidl_ty, - *to_wasm_ty, - i as u32, - ), - _ => false, - }) -} - -/// Do we need to generate JS glue shims for these outgoing bindings? -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 - .iter() - .zip(from_wasm_tys) - .zip(to_webidl_tys) - .enumerate() - .all(|(i, ((expr, from_wasm_ty), to_webidl_ty))| match expr { - NonstandardOutgoing::Standard(e) => e.is_expressible_in_js_without_webidl_bindings( - *from_wasm_ty, - *to_webidl_ty, - i as u32, - ), - _ => false, - }) -} +// /// Do we need to generate JS glue shims for these incoming bindings? +// 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 +// .iter() +// .zip(from_webidl_tys) +// .zip(to_wasm_tys) +// .enumerate() +// .all(|(i, ((expr, from_webidl_ty), to_wasm_ty))| match expr { +// NonstandardIncoming::Standard(e) => e.is_expressible_in_js_without_webidl_bindings( +// *from_webidl_ty, +// *to_wasm_ty, +// i as u32, +// ), +// _ => false, +// }) +// } +// +// /// Do we need to generate JS glue shims for these outgoing bindings? +// 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 +// .iter() +// .zip(from_wasm_tys) +// .zip(to_webidl_tys) +// .enumerate() +// .all(|(i, ((expr, from_wasm_ty), to_webidl_ty))| match expr { +// NonstandardOutgoing::Standard(e) => e.is_expressible_in_js_without_webidl_bindings( +// *from_wasm_ty, +// *to_webidl_ty, +// i as u32, +// ), +// _ => false, +// }) +// } diff --git a/crates/cli-support/src/wit/nonstandard.rs b/crates/cli-support/src/wit/nonstandard.rs new file mode 100644 index 00000000000..083064589d0 --- /dev/null +++ b/crates/cli-support/src/wit/nonstandard.rs @@ -0,0 +1,352 @@ +use crate::intrinsic::Intrinsic; +use crate::wit::AdapterId; +use std::borrow::Cow; +use std::collections::{HashMap, HashSet}; +use std::path::PathBuf; +use walrus::TypedCustomSectionId; + +/// A synthetic custom section which is not standardized, never will be, and +/// cannot be serialized or parsed. This is synthesized from all of the +/// compiler-emitted wasm-bindgen sections and then immediately removed to be +/// processed in the JS generation pass. +#[derive(Default, Debug)] +pub struct WasmBindgenAux { + /// Extra typescript annotations that should be appended to the generated + /// TypeScript file. This is provided via a custom attribute in Rust code. + pub extra_typescript: String, + + /// A map from identifier to the contents of each local module defined via + /// the `#[wasm_bindgen(module = "/foo.js")]` import options. + pub local_modules: HashMap, + + /// A map from unique crate identifier to the list of inline JS snippets for + /// that crate identifier. + pub snippets: HashMap>, + + /// A list of all `package.json` files that are intended to be included in + /// the final build. + pub package_jsons: HashSet, + + /// A map from exported function id to where it's expected to be exported + /// to. + pub export_map: HashMap, + + /// A map from imported function id to what it's expected to import. + pub import_map: HashMap, + + /// Small bits of metadata about imports. + pub imports_with_catch: HashSet, + pub imports_with_variadic: HashSet, + pub imports_with_assert_no_shim: HashSet, + + /// Auxiliary information to go into JS/TypeScript bindings describing the + /// exported enums from Rust. + pub enums: Vec, + + /// Auxiliary information to go into JS/TypeScript bindings describing the + /// exported structs from Rust and their fields they've got exported. + pub structs: Vec, +} + +pub type WasmBindgenAuxId = TypedCustomSectionId; + +#[derive(Debug)] +pub struct AuxExport { + /// When generating errors about this export, a helpful name to remember it + /// by. + pub debug_name: String, + /// Comments parsed in Rust and forwarded here to show up in JS bindings. + pub comments: String, + /// Argument names in Rust forwarded here to configure the names that show + /// up in TypeScript bindings. + pub arg_names: Option>, + /// What kind of function this is and where it shows up + pub kind: AuxExportKind, +} + +/// All possible kinds of exports from a wasm module. +/// +/// This `enum` says where to place an exported wasm function. For example it +/// may want to get hooked up to a JS class, or it may want to be exported as a +/// free function (etc). +/// +/// TODO: it feels like this should not really be here per se. We probably want +/// to either construct the JS object itself from within wasm or somehow move +/// more of this information into some other section. Really what this is is +/// sort of an "export map" saying how to wire up all the free functions from +/// the wasm module into the output expected JS module. All our functions here +/// currently take integer parameters and require a JS wrapper, but ideally +/// we'd change them one day to taking/receiving `anyref` which then use some +/// sort of webidl import to customize behavior or something like that. In any +/// case this doesn't feel quite right in terms of priviledge separation, so +/// we'll want to work on this. For now though it works. +#[derive(Debug)] +pub enum AuxExportKind { + /// A free function that's just listed on the exported module + Function(String), + + /// A function that's used to create an instane of a class. The function + /// actually return just an integer which is put on an JS object currently. + Constructor(String), + + /// This function is intended to be a getter for a field on a class. The + /// first argument is the internal pointer and the returned value is + /// expected to be the field. + Getter { class: String, field: String }, + + /// This function is intended to be a setter for a field on a class. The + /// first argument is the internal pointer and the second argument is + /// expected to be the field's new value. + Setter { class: String, field: String }, + + /// This is a free function (ish) but scoped inside of a class name. + StaticFunction { class: String, name: String }, + + /// This is a member function of a class where the first parameter is the + /// implicit integer stored in the class instance. + Method { + class: String, + name: String, + /// Whether or not this is calling a by-value method in Rust and should + /// clear the internal pointer in JS automatically. + consumed: bool, + }, +} + +#[derive(Debug)] +pub struct AuxEnum { + /// The name of this enum + pub name: String, + /// The copied Rust comments to forward to JS + pub comments: String, + /// A list of variants with their name and value + pub variants: Vec<(String, u32)>, +} + +#[derive(Debug)] +pub struct AuxStruct { + /// The name of this struct + pub name: String, + /// The copied Rust comments to forward to JS + pub comments: String, +} + +/// All possible types of imports that can be imported by a wasm module. +/// +/// This `enum` is intended to map out what an imported value is. For example +/// this contains a ton of shims and various ways you can call a function. The +/// base variant here is `Value` which simply means "hook this up to the import" +/// and the signatures will match up. +/// +/// Note that this is *not* the same as the webidl bindings section. This is +/// intended to be coupled with that to map out what actually gets hooked up to +/// an import in the wasm module. The two work in tandem. +/// +/// Some of these items here are native to JS (like `Value`, indexing +/// operations, etc). Others are shims generated by wasm-bindgen (like `Closure` +/// or `Instanceof`). +#[derive(Debug)] +pub enum AuxImport { + /// This import is expected to simply be whatever is the value that's + /// imported + Value(AuxValue), + + /// A static method on a class is being imported, and the `this` of the + /// function call is expected to always be the class. + ValueWithThis(JsImport, String), + + /// This import is expected to be a function that takes an `anyref` and + /// returns a `bool`. It's expected that it tests if the argument is an + /// instance of (using `instanceof`) the name specified. + /// + /// TODO: can we use `Reflect` or something like that to avoid an extra kind + /// of import here? + Instanceof(JsImport), + + /// This import is expected to be a shim that returns the JS value named by + /// `JsImport`. + Static(JsImport), + + /// This import is intended to manufacture a JS closure with the given + /// signature and then return that back to Rust. + Closure { + mutable: bool, // whether or not this was a `FnMut` closure + dtor: u32, // table element index of the destructor function + binding_idx: u32, + nargs: usize, + }, + + /// This import is expected to be a shim that simply calls the `foo` method + /// on the first object, passing along all other parameters and returning + /// the resulting value. + StructuralMethod(String), + + /// This import is a "structural getter" which simply returns the `.field` + /// value of the first argument as an object. + /// + /// e.g. `function(x) { return x.foo; }` + StructuralGetter(String), + + /// This import is a "structural getter" which simply returns the `.field` + /// value of the specified class + /// + /// e.g. `function() { return TheClass.foo; }` + StructuralClassGetter(JsImport, String), + + /// This import is a "structural setter" which simply sets the `.field` + /// value of the first argument to the second argument. + /// + /// e.g. `function(x, y) { x.foo = y; }` + StructuralSetter(String), + + /// This import is a "structural setter" which simply sets the `.field` + /// value of the specified class to the first argument of the function. + /// + /// e.g. `function(x) { TheClass.foo = x; }` + StructuralClassSetter(JsImport, String), + + /// This import is expected to be a shim that is an indexing getter of the + /// JS class here, where the first argument of the function is the field to + /// look up. The return value is the value of the field. + /// + /// e.g. `function(x) { return TheClass[x]; }` + /// + /// TODO: can we use `Reflect` or something like that to avoid an extra kind + /// of import here? + IndexingGetterOfClass(JsImport), + + /// This import is expected to be a shim that is an indexing getter of the + /// first argument interpreted as an object where the field to look up is + /// the second argument. + /// + /// e.g. `function(x, y) { return x[y]; }` + /// + /// TODO: can we use `Reflect` or something like that to avoid an extra kind + /// of import here? + IndexingGetterOfObject, + + /// This import is expected to be a shim that is an indexing setter of the + /// JS class here, where the first argument of the function is the field to + /// set and the second is the value to set it to. + /// + /// e.g. `function(x, y) { TheClass[x] = y; }` + /// + /// TODO: can we use `Reflect` or something like that to avoid an extra kind + /// of import here? + IndexingSetterOfClass(JsImport), + + /// This import is expected to be a shim that is an indexing setter of the + /// first argument interpreted as an object where the next two fields are + /// the field to set and the value to set it to. + /// + /// e.g. `function(x, y, z) { x[y] = z; }` + /// + /// TODO: can we use `Reflect` or something like that to avoid an extra kind + /// of import here? + IndexingSetterOfObject, + + /// This import is expected to be a shim that is an indexing deleter of the + /// JS class here, where the first argument of the function is the field to + /// delete. + /// + /// e.g. `function(x) { delete TheClass[x]; }` + /// + /// TODO: can we use `Reflect` or something like that to avoid an extra kind + /// of import here? + IndexingDeleterOfClass(JsImport), + + /// This import is expected to be a shim that is an indexing deleter of the + /// first argument interpreted as an object where the second argument is + /// the field to delete. + /// + /// e.g. `function(x, y) { delete x[y]; }` + /// + /// TODO: can we use `Reflect` or something like that to avoid an extra kind + /// of import here? + IndexingDeleterOfObject, + + /// This import is a generated shim which will wrap the provided pointer in + /// a JS object corresponding to the Class name given here. The class name + /// is one that is exported from the Rust/wasm. + /// + /// TODO: sort of like the export map below we should ideally create the + /// `anyref` from within Rust itself and then return it directly rather than + /// requiring an intrinsic here to do so. + WrapInExportedClass(String), + + /// This is an intrinsic function expected to be implemented with a JS glue + /// shim. Each intrinsic has its own expected signature and implementation. + Intrinsic(Intrinsic), +} + +/// Values that can be imported verbatim to hook up to an import. +#[derive(Debug)] +pub enum AuxValue { + /// A bare JS value, no transformations, just put it in the slot. + Bare(JsImport), + + /// A getter function for the class listed for the field, acquired using + /// `getOwnPropertyDescriptor`. + Getter(JsImport, String), + + /// Like `Getter`, but accesses a field of a class instead of an instance + /// of the class. + ClassGetter(JsImport, String), + + /// Like `Getter`, except the `set` property. + Setter(JsImport, String), + + /// Like `Setter`, but for class fields instead of instance fields. + ClassSetter(JsImport, String), +} + +/// What can actually be imported and typically a value in each of the variants +/// above of `AuxImport` +/// +/// A `JsImport` is intended to indicate what exactly is being imported for a +/// particular operation. +#[derive(Debug, Hash, Eq, PartialEq, Clone)] +pub struct JsImport { + /// The base of whatever is being imported, either from a module, the global + /// namespace, or similar. + pub name: JsImportName, + /// Various field accesses (like `.foo.bar.baz`) to hang off the `name` + /// above. + pub fields: Vec, +} + +/// Return value of `determine_import` which is where we look at an imported +/// function AST and figure out where it's actually being imported from +/// (performing some validation checks and whatnot). +#[derive(Debug, Hash, Eq, PartialEq, Clone)] +pub enum JsImportName { + /// An item is imported from the global scope. The `name` is what's + /// imported. + Global { name: String }, + /// Same as `Global`, except the `name` is imported via an ESM import from + /// the specified `module` path. + Module { module: String, name: String }, + /// Same as `Module`, except we're importing from a local module defined in + /// a local JS snippet. + LocalModule { module: String, name: String }, + /// Same as `Module`, except we're importing from an `inline_js` attribute + InlineJs { + unique_crate_identifier: String, + snippet_idx_in_crate: usize, + name: String, + }, + /// A global import which may have a number of vendor prefixes associated + /// with it, like `webkitAudioPrefix`. The `name` is the name to test + /// whether it's prefixed. + VendorPrefixed { name: String, prefixes: Vec }, +} + +impl walrus::CustomSection for WasmBindgenAux { + fn name(&self) -> &str { + "wasm-bindgen custom section" + } + + fn data(&self, _: &walrus::IdsToIndices) -> Cow<[u8]> { + panic!("shouldn't emit custom sections just yet"); + } +} diff --git a/crates/cli-support/src/wit/outgoing.rs b/crates/cli-support/src/wit/outgoing.rs index ba57ab54e0a..ddb302229d6 100644 --- a/crates/cli-support/src/wit/outgoing.rs +++ b/crates/cli-support/src/wit/outgoing.rs @@ -1,188 +1,179 @@ -//! This module is used to define `NonstandardOutgoing`, a list of possible ways -//! values in Rust can be passed to JS. -//! -//! Like the `NonstandardIncoming` list we attempt to use a standard -//! `OutgoingBindingExpression` wherever possible but we naturally have a lot of -//! features in `wasm-bindgen` which haven't been upstreamed into the WebIDL -//! bindings standard yet (nor which are likely to ever get standardized). We -//! attempt to use standard bindings aggressively and wherever possible, but -//! sometimes we need to resort to our own custom bindings with our own custom -//! JS shims for now. -//! -//! This module also houses the definition of converting a `Descriptor` to a -//! `NonstandardOutgoing` binding, effectively defining how to translate from a -//! Rust type to an outgoing binding. +// //! This module is used to define `NonstandardOutgoing`, a list of possible ways +// //! values in Rust can be passed to JS. +// //! +// //! Like the `NonstandardIncoming` list we attempt to use a standard +// //! `OutgoingBindingExpression` wherever possible but we naturally have a lot of +// //! features in `wasm-bindgen` which haven't been upstreamed into the WebIDL +// //! bindings standard yet (nor which are likely to ever get standardized). We +// //! attempt to use standard bindings aggressively and wherever possible, but +// //! sometimes we need to resort to our own custom bindings with our own custom +// //! JS shims for now. +// //! +// //! This module also houses the definition of converting a `Descriptor` to a +// //! `NonstandardOutgoing` binding, effectively defining how to translate from a +// //! Rust type to an outgoing binding. use crate::descriptor::{Descriptor, VectorKind}; -use crate::webidl::NonstandardWebidlSection; +use crate::wit::{AdapterType, Instruction, NonstandardWitSection}; use anyhow::{bail, format_err, Error}; use walrus::{Module, ValType}; -use wasm_webidl_bindings::ast; -/// A list of all possible outgoing bindings which can be used when converting -/// Rust types to JS. This is predominantly used when calling an imported JS -/// function. -#[derive(Debug, Clone)] -pub enum NonstandardOutgoing { - /// This is a standard upstream WebIDL outgoing binding expression. Where - /// possible we can actually leave this in the wasm file and generate even - /// less JS shim code. - Standard(ast::OutgoingBindingExpression), - - /// We're returning a pointer from Rust to JS to get wrapped in a JS class - /// which has memory management around it. - RustType { class: String, idx: u32 }, - - /// A single rust `char` value which is converted to a `string` in JS. - Char { idx: u32 }, - - /// An `i64` or `u64` in Rust converted to a `BigInt` in JS - Number64 { - lo_idx: u32, - hi_idx: u32, - signed: bool, - }, - - /// A *borrowed* anyref value which has special meanings about ownership, - /// namely Rust is still using the underlying value after the call returns. - BorrowedAnyref { idx: u32 }, - - /// An owned vector is passed from Rust to JS. Note that this is currently a - /// special binding because it requires memory management via deallocation - /// in the JS shim. - /// - /// TODO: we should strive to not have this nonstandard binding and instead - /// do all the memory management in Rust. Ideally we'd use `AllocCopy` in - /// place of this. - Vector { - offset: u32, - length: u32, - kind: VectorKind, - }, - - /// A Rust String (or &str) which might be cached, or might be `None`. - /// - /// If `offset` is 0 then it is cached, and the cached JsValue's index is in `length`. - /// - /// If `offset` and `length` are both 0, then it is `None`. - CachedString { - offset: u32, - length: u32, - owned: bool, - optional: bool, - }, - - /// A `&[u64]` or `&[i64]` is being passed to JS, and the 64-bit sizes here - /// aren't supported by WebIDL bindings yet. - View64 { - offset: u32, - length: u32, - signed: bool, - }, - - /// A list of `anyref` is being passed to JS, and it's got a somewhat - /// magical representation with indics which doesn't map to WebIDL bindings. - ViewAnyref { offset: u32, length: u32 }, - - /// An optional owned vector of data is being passed to JS. - /// - /// TODO: with some cleverness this could probably use `AllocCopy`. - OptionVector { - offset: u32, - length: u32, - kind: VectorKind, - }, - - /// An optional slice of data is being passed into JS. - /// - /// TODO: with some cleverness this could probably use `AllocCopy`. - OptionSlice { - kind: VectorKind, - offset: u32, - length: u32, - }, - - /// An optional "native type" like i32/u32/f32/f64 is being passed to JS, - /// and this requires a discriminant in the ABI. - OptionNative { - present: u32, - val: u32, - signed: bool, - }, - - /// An optional number is being passed to JS where the number uses a - /// sentinel value to represent `None` - OptionU32Sentinel { idx: u32 }, - - /// An optional boolean with a special value for `None` - OptionBool { idx: u32 }, - - /// An optional character with a special value for `None` - OptionChar { idx: u32 }, - - /// An optional integral enum value with the specified `hole` being used for - /// `None`. - OptionIntegerEnum { idx: u32, hole: u32 }, - - /// An optional 64-bit integer being used. - OptionInt64 { - present: u32, - _ignored: u32, - lo: u32, - hi: u32, - signed: bool, - }, - - /// An optional owned Rust type being transferred from Rust to JS. - OptionRustType { class: String, idx: u32 }, - - /// A temporary stack closure being passed from Rust to JS. A JS function is - /// manufactured and then neutered just before the call returns. - StackClosure { - /// Argument index of the first data pointer Rust needs - a: u32, - /// Argument index of the second data pointer Rust needs - b: u32, - /// The index of the shim in the element bindings section that we're - /// going to be invoking. - binding_idx: u32, - /// Number of arguments to the closure - nargs: usize, - /// Whether or not this is a mutable closure (affects codegen and how - /// it's called recursively) - mutable: bool, - }, -} - -/// A definition of building `NonstandardOutgoing` expressions from a -/// `Descriptor`. -/// -/// This will internally keep track of wasm/webidl types generated as we visit -/// `Descriptor` arguments and add more for a function signature. +// /// A list of all possible outgoing bindings which can be used when converting +// /// Rust types to JS. This is predominantly used when calling an imported JS +// /// function. +// #[derive(Debug, Clone)] +// pub enum NonstandardOutgoing { +// /// This is a standard upstream WebIDL outgoing binding expression. Where +// /// possible we can actually leave this in the wasm file and generate even +// /// less JS shim code. +// Standard(ast::OutgoingBindingExpression), +// +// /// We're returning a pointer from Rust to JS to get wrapped in a JS class +// /// which has memory management around it. +// RustType { class: String, idx: u32 }, +// +// /// A single rust `char` value which is converted to a `string` in JS. +// Char { idx: u32 }, +// +// /// An `i64` or `u64` in Rust converted to a `BigInt` in JS +// Number64 { +// lo_idx: u32, +// hi_idx: u32, +// signed: bool, +// }, +// +// /// A *borrowed* anyref value which has special meanings about ownership, +// /// namely Rust is still using the underlying value after the call returns. +// BorrowedAnyref { idx: u32 }, +// +// /// An owned vector is passed from Rust to JS. Note that this is currently a +// /// special binding because it requires memory management via deallocation +// /// in the JS shim. +// /// +// /// TODO: we should strive to not have this nonstandard binding and instead +// /// do all the memory management in Rust. Ideally we'd use `AllocCopy` in +// /// place of this. +// Vector { +// offset: u32, +// length: u32, +// kind: VectorKind, +// }, +// +// /// A Rust String (or &str) which might be cached, or might be `None`. +// /// +// /// If `offset` is 0 then it is cached, and the cached JsValue's index is in `length`. +// /// +// /// If `offset` and `length` are both 0, then it is `None`. +// CachedString { +// offset: u32, +// length: u32, +// owned: bool, +// optional: bool, +// }, +// +// /// A `&[u64]` or `&[i64]` is being passed to JS, and the 64-bit sizes here +// /// aren't supported by WebIDL bindings yet. +// View64 { +// offset: u32, +// length: u32, +// signed: bool, +// }, +// +// /// A list of `anyref` is being passed to JS, and it's got a somewhat +// /// magical representation with indics which doesn't map to WebIDL bindings. +// ViewAnyref { offset: u32, length: u32 }, +// +// /// An optional owned vector of data is being passed to JS. +// /// +// /// TODO: with some cleverness this could probably use `AllocCopy`. +// OptionVector { +// offset: u32, +// length: u32, +// kind: VectorKind, +// }, +// +// /// An optional slice of data is being passed into JS. +// /// +// /// TODO: with some cleverness this could probably use `AllocCopy`. +// OptionSlice { +// kind: VectorKind, +// offset: u32, +// length: u32, +// }, +// +// /// An optional "native type" like i32/u32/f32/f64 is being passed to JS, +// /// and this requires a discriminant in the ABI. +// OptionNative { +// present: u32, +// val: u32, +// signed: bool, +// }, +// +// /// An optional number is being passed to JS where the number uses a +// /// sentinel value to represent `None` +// OptionU32Sentinel { idx: u32 }, +// +// /// An optional boolean with a special value for `None` +// OptionBool { idx: u32 }, +// +// /// An optional character with a special value for `None` +// OptionChar { idx: u32 }, +// +// /// An optional integral enum value with the specified `hole` being used for +// /// `None`. +// OptionIntegerEnum { idx: u32, hole: u32 }, +// +// /// An optional 64-bit integer being used. +// OptionInt64 { +// present: u32, +// _ignored: u32, +// lo: u32, +// hi: u32, +// signed: bool, +// }, +// +// /// An optional owned Rust type being transferred from Rust to JS. +// OptionRustType { class: String, idx: u32 }, +// +// /// A temporary stack closure being passed from Rust to JS. A JS function is +// /// manufactured and then neutered just before the call returns. +// StackClosure { +// /// Argument index of the first data pointer Rust needs +// a: u32, +// /// Argument index of the second data pointer Rust needs +// b: u32, +// /// The index of the shim in the element bindings section that we're +// /// going to be invoking. +// binding_idx: u32, +// /// Number of arguments to the closure +// nargs: usize, +// /// Whether or not this is a mutable closure (affects codegen and how +// /// it's called recursively) +// mutable: bool, +// }, +// } +// #[derive(Default)] pub struct OutgoingBuilder<'a> { - /// All wasm types used so far to produce the resulting JS values. - pub wasm: Vec, - /// The WebIDL types that we're passing along out of wasm. - pub webidl: Vec, - /// The list of bindings we've created, currently 1:1 with the webidl above. - pub bindings: Vec, + pub input: Vec, + pub output: Vec, + pub instructions: Vec, // These two arguments are optional and, if set, will enable creating // `StackClosure` bindings. They're not present for return values from // exported Rust functions, but they are available for the arguments of // calling imported functions. pub module: Option<&'a mut Module>, - pub bindings_section: Option<&'a mut NonstandardWebidlSection>, + pub adapters: Option<&'a mut NonstandardWitSection>, } impl OutgoingBuilder<'_> { - /// Adds a dummy first argument which is passed through as an integer - /// representing the return pointer. - pub fn process_retptr(&mut self) { - self.standard_as(ValType::I32, ast::WebidlScalarType::Long); - } - + // /// Adds a dummy first argument which is passed through as an integer + // /// representing the return pointer. + // pub fn process_retptr(&mut self) { + // self.standard_as(ValType::I32, ast::WebidlScalarType::Long); + // } + // /// Processes one more `Descriptor` as an argument to a JS function that /// wasm is calling. /// @@ -192,365 +183,365 @@ impl OutgoingBuilder<'_> { if let Descriptor::Unit = arg { return Ok(()); } - assert_eq!(self.webidl.len(), self.bindings.len()); - let wasm_before = self.wasm.len(); - let webidl_before = self.webidl.len(); - self._process(arg)?; - assert_eq!(self.webidl.len(), self.bindings.len()); - assert_eq!(webidl_before + 1, self.webidl.len()); - assert!(wasm_before < self.wasm.len()); + // assert_eq!(self.webidl.len(), self.bindings.len()); + // let wasm_before = self.wasm.len(); + // let webidl_before = self.webidl.len(); + // self._process(arg)?; + // assert_eq!(self.webidl.len(), self.bindings.len()); + // assert_eq!(webidl_before + 1, self.webidl.len()); + // assert!(wasm_before < self.wasm.len()); Ok(()) } - fn _process(&mut self, arg: &Descriptor) -> Result<(), Error> { - match arg { - Descriptor::Boolean => self.standard_as(ValType::I32, ast::WebidlScalarType::Boolean), - Descriptor::Anyref => self.standard_as(ValType::Anyref, ast::WebidlScalarType::Any), - Descriptor::I8 => self.standard_as(ValType::I32, ast::WebidlScalarType::Byte), - Descriptor::U8 => self.standard_as(ValType::I32, ast::WebidlScalarType::Octet), - Descriptor::I16 => self.standard_as(ValType::I32, ast::WebidlScalarType::Short), - Descriptor::U16 => self.standard_as(ValType::I32, ast::WebidlScalarType::UnsignedShort), - Descriptor::I32 => self.standard_as(ValType::I32, ast::WebidlScalarType::Long), - Descriptor::U32 => self.standard_as(ValType::I32, ast::WebidlScalarType::UnsignedLong), - Descriptor::F32 => { - self.standard_as(ValType::F32, ast::WebidlScalarType::UnrestrictedFloat) - } - Descriptor::F64 => { - self.standard_as(ValType::F64, ast::WebidlScalarType::UnrestrictedDouble) - } - Descriptor::Enum { .. } => self.standard_as(ValType::I32, ast::WebidlScalarType::Long), - - Descriptor::Char => { - let idx = self.push_wasm(ValType::I32); - self.webidl.push(ast::WebidlScalarType::DomString); - self.bindings.push(NonstandardOutgoing::Char { idx }); - } - - Descriptor::I64 | Descriptor::U64 => { - let signed = match arg { - Descriptor::I64 => true, - _ => false, - }; - let lo_idx = self.push_wasm(ValType::I32); - let hi_idx = self.push_wasm(ValType::I32); - self.webidl.push(ast::WebidlScalarType::Any); - self.bindings.push(NonstandardOutgoing::Number64 { - lo_idx, - hi_idx, - signed, - }); - } - - Descriptor::RustStruct(class) => { - let idx = self.push_wasm(ValType::I32); - self.webidl.push(ast::WebidlScalarType::Any); - self.bindings.push(NonstandardOutgoing::RustType { - idx, - class: class.to_string(), - }); - } - Descriptor::Ref(d) => self.process_ref(false, d)?, - Descriptor::RefMut(d) => self.process_ref(true, d)?, - - Descriptor::CachedString => self.cached_string(false, true), - - Descriptor::Vector(_) | Descriptor::String => { - let kind = arg.vector_kind().ok_or_else(|| { - format_err!( - "unsupported argument type for calling JS function from Rust {:?}", - arg - ) - })?; - let offset = self.push_wasm(ValType::I32); - let length = self.push_wasm(ValType::I32); - self.webidl.push(ast::WebidlScalarType::Any); - self.bindings.push(NonstandardOutgoing::Vector { - offset, - kind, - length, - }) - } - - Descriptor::Option(d) => self.process_option(d)?, - - Descriptor::Function(_) | Descriptor::Closure(_) | Descriptor::Slice(_) => bail!( - "unsupported argument type for calling JS function from Rust: {:?}", - arg - ), - - // nothing to do - Descriptor::Unit => {} - - // Largely synthetic and can't show up - Descriptor::ClampedU8 => unreachable!(), - } - Ok(()) - } - - fn process_ref(&mut self, mutable: bool, arg: &Descriptor) -> Result<(), Error> { - match arg { - Descriptor::Anyref => { - let idx = self.push_wasm(ValType::Anyref); - self.webidl.push(ast::WebidlScalarType::Any); - self.bindings - .push(NonstandardOutgoing::BorrowedAnyref { idx }); - } - Descriptor::CachedString => self.cached_string(false, false), - Descriptor::Slice(_) | Descriptor::String => { - use wasm_webidl_bindings::ast::WebidlScalarType::*; - - let kind = arg.vector_kind().ok_or_else(|| { - format_err!( - "unsupported argument type for calling JS function from Rust {:?}", - arg - ) - })?; - let offset = self.push_wasm(ValType::I32); - let length = self.push_wasm(ValType::I32); - match kind { - VectorKind::I8 => self.standard_view(offset, length, Int8Array), - VectorKind::U8 => self.standard_view(offset, length, Uint8Array), - VectorKind::ClampedU8 => self.standard_view(offset, length, Uint8ClampedArray), - VectorKind::I16 => self.standard_view(offset, length, Int16Array), - VectorKind::U16 => self.standard_view(offset, length, Uint16Array), - VectorKind::I32 => self.standard_view(offset, length, Int32Array), - VectorKind::U32 => self.standard_view(offset, length, Uint32Array), - VectorKind::F32 => self.standard_view(offset, length, Float32Array), - VectorKind::F64 => self.standard_view(offset, length, Float64Array), - VectorKind::String => { - self.webidl.push(DomString); - let binding = ast::OutgoingBindingExpressionUtf8Str { - ty: ast::WebidlScalarType::DomString.into(), - offset, - length, - }; - self.bindings - .push(NonstandardOutgoing::Standard(binding.into())); - } - VectorKind::I64 | VectorKind::U64 => { - let signed = match kind { - VectorKind::I64 => true, - _ => false, - }; - self.webidl.push(Any); - self.bindings.push(NonstandardOutgoing::View64 { - offset, - length, - signed, - }); - } - VectorKind::Anyref => { - self.webidl.push(Any); - self.bindings - .push(NonstandardOutgoing::ViewAnyref { offset, length }); - } - } - } - - Descriptor::Function(descriptor) => { - let module = self - .module - .as_mut() - .ok_or_else(|| format_err!("cannot return a closure from Rust"))?; - let section = self.bindings_section.as_mut().unwrap(); - // synthesize the a/b arguments that aren't present in the - // signature from wasm-bindgen but are present in the wasm file. - let mut descriptor = (**descriptor).clone(); - let nargs = descriptor.arguments.len(); - descriptor.arguments.insert(0, Descriptor::I32); - descriptor.arguments.insert(0, Descriptor::I32); - let binding_idx = super::bindings::register_table_element( - module, - section, - descriptor.shim_idx, - descriptor, - )?; - let a = self.push_wasm(ValType::I32); - let b = self.push_wasm(ValType::I32); - self.webidl.push(ast::WebidlScalarType::Any); - self.bindings.push(NonstandardOutgoing::StackClosure { - a, - b, - binding_idx, - nargs, - mutable, - }); - } - - _ => bail!( - "unsupported reference argument type for calling JS function from Rust: {:?}", - arg - ), - } - Ok(()) - } - - fn process_option(&mut self, arg: &Descriptor) -> Result<(), Error> { - match arg { - Descriptor::Anyref => self.standard_as(ValType::Anyref, ast::WebidlScalarType::Any), - Descriptor::I8 => self.option_sentinel(), - Descriptor::U8 => self.option_sentinel(), - Descriptor::I16 => self.option_sentinel(), - Descriptor::U16 => self.option_sentinel(), - Descriptor::I32 => self.option_native(true, ValType::I32), - Descriptor::U32 => self.option_native(false, ValType::I32), - Descriptor::F32 => self.option_native(true, ValType::F32), - Descriptor::F64 => self.option_native(true, ValType::F64), - Descriptor::I64 | Descriptor::U64 => { - let signed = match arg { - Descriptor::I64 => true, - _ => false, - }; - let binding = NonstandardOutgoing::OptionInt64 { - present: self.push_wasm(ValType::I32), - _ignored: self.push_wasm(ValType::I32), - lo: self.push_wasm(ValType::I32), - hi: self.push_wasm(ValType::I32), - signed, - }; - self.webidl.push(ast::WebidlScalarType::Any); - self.bindings.push(binding); - } - Descriptor::Boolean => { - let idx = self.push_wasm(ValType::I32); - self.webidl.push(ast::WebidlScalarType::Any); - self.bindings.push(NonstandardOutgoing::OptionBool { idx }); - } - Descriptor::Char => { - let idx = self.push_wasm(ValType::I32); - self.webidl.push(ast::WebidlScalarType::Any); - self.bindings.push(NonstandardOutgoing::OptionChar { idx }); - } - Descriptor::Enum { hole } => { - let idx = self.push_wasm(ValType::I32); - self.webidl.push(ast::WebidlScalarType::Long); - self.bindings - .push(NonstandardOutgoing::OptionIntegerEnum { idx, hole: *hole }); - } - Descriptor::RustStruct(name) => { - let idx = self.push_wasm(ValType::I32); - self.webidl.push(ast::WebidlScalarType::Any); - self.bindings.push(NonstandardOutgoing::OptionRustType { - idx, - class: name.to_string(), - }); - } - Descriptor::Ref(d) => self.process_option_ref(false, d)?, - Descriptor::RefMut(d) => self.process_option_ref(true, d)?, - - Descriptor::CachedString => self.cached_string(true, true), - - Descriptor::String | Descriptor::Vector(_) => { - let kind = arg.vector_kind().ok_or_else(|| { - format_err!( - "unsupported optional slice type for calling JS function from Rust {:?}", - arg - ) - })?; - let offset = self.push_wasm(ValType::I32); - let length = self.push_wasm(ValType::I32); - self.webidl.push(ast::WebidlScalarType::Any); - self.bindings.push(NonstandardOutgoing::OptionVector { - kind, - offset, - length, - }) - } - - _ => bail!( - "unsupported optional argument type for calling JS function from Rust: {:?}", - arg - ), - } - Ok(()) - } - - fn process_option_ref(&mut self, _mutable: bool, arg: &Descriptor) -> Result<(), Error> { - match arg { - Descriptor::Anyref => { - let idx = self.push_wasm(ValType::Anyref); - self.webidl.push(ast::WebidlScalarType::Any); - self.bindings - .push(NonstandardOutgoing::BorrowedAnyref { idx }); - } - Descriptor::CachedString => self.cached_string(true, false), - Descriptor::String | Descriptor::Slice(_) => { - let kind = arg.vector_kind().ok_or_else(|| { - format_err!( - "unsupported optional slice type for calling JS function from Rust {:?}", - arg - ) - })?; - let offset = self.push_wasm(ValType::I32); - let length = self.push_wasm(ValType::I32); - self.webidl.push(ast::WebidlScalarType::Any); - self.bindings.push(NonstandardOutgoing::OptionSlice { - kind, - offset, - length, - }); - } - _ => bail!( - "unsupported optional ref argument type for calling JS function from Rust: {:?}", - arg - ), - } - Ok(()) - } - - fn push_wasm(&mut self, ty: ValType) -> u32 { - self.wasm.push(ty); - self.wasm.len() as u32 - 1 - } - - fn standard_as(&mut self, wasm: ValType, webidl: ast::WebidlScalarType) { - let binding = ast::OutgoingBindingExpressionAs { - ty: webidl.into(), - idx: self.push_wasm(wasm), - }; - self.webidl.push(webidl); - self.bindings - .push(NonstandardOutgoing::Standard(binding.into())); - } - - fn standard_view(&mut self, offset: u32, length: u32, ty: ast::WebidlScalarType) { - let binding = ast::OutgoingBindingExpressionView { - ty: ty.into(), - offset, - length, - }; - self.webidl.push(ty); - self.bindings - .push(NonstandardOutgoing::Standard(binding.into())); - } - - fn cached_string(&mut self, optional: bool, owned: bool) { - let offset = self.push_wasm(ValType::I32); - let length = self.push_wasm(ValType::I32); - self.webidl.push(ast::WebidlScalarType::DomString); - self.bindings.push(NonstandardOutgoing::CachedString { - offset, - length, - owned, - optional, - }) - } - - fn option_native(&mut self, signed: bool, ty: ValType) { - let present = self.push_wasm(ValType::I32); - let val = self.push_wasm(ty); - self.webidl.push(ast::WebidlScalarType::Any); - self.bindings.push(NonstandardOutgoing::OptionNative { - signed, - present, - val, - }); - } - - fn option_sentinel(&mut self) { - let idx = self.push_wasm(ValType::I32); - self.webidl.push(ast::WebidlScalarType::Any); - self.bindings - .push(NonstandardOutgoing::OptionU32Sentinel { idx }); - } + // fn _process(&mut self, arg: &Descriptor) -> Result<(), Error> { + // match arg { + // Descriptor::Boolean => self.standard_as(ValType::I32, ast::WebidlScalarType::Boolean), + // Descriptor::Anyref => self.standard_as(ValType::Anyref, ast::WebidlScalarType::Any), + // Descriptor::I8 => self.standard_as(ValType::I32, ast::WebidlScalarType::Byte), + // Descriptor::U8 => self.standard_as(ValType::I32, ast::WebidlScalarType::Octet), + // Descriptor::I16 => self.standard_as(ValType::I32, ast::WebidlScalarType::Short), + // Descriptor::U16 => self.standard_as(ValType::I32, ast::WebidlScalarType::UnsignedShort), + // Descriptor::I32 => self.standard_as(ValType::I32, ast::WebidlScalarType::Long), + // Descriptor::U32 => self.standard_as(ValType::I32, ast::WebidlScalarType::UnsignedLong), + // Descriptor::F32 => { + // self.standard_as(ValType::F32, ast::WebidlScalarType::UnrestrictedFloat) + // } + // Descriptor::F64 => { + // self.standard_as(ValType::F64, ast::WebidlScalarType::UnrestrictedDouble) + // } + // Descriptor::Enum { .. } => self.standard_as(ValType::I32, ast::WebidlScalarType::Long), + // + // Descriptor::Char => { + // let idx = self.push_wasm(ValType::I32); + // self.webidl.push(ast::WebidlScalarType::DomString); + // self.bindings.push(NonstandardOutgoing::Char { idx }); + // } + // + // Descriptor::I64 | Descriptor::U64 => { + // let signed = match arg { + // Descriptor::I64 => true, + // _ => false, + // }; + // let lo_idx = self.push_wasm(ValType::I32); + // let hi_idx = self.push_wasm(ValType::I32); + // self.webidl.push(ast::WebidlScalarType::Any); + // self.bindings.push(NonstandardOutgoing::Number64 { + // lo_idx, + // hi_idx, + // signed, + // }); + // } + // + // Descriptor::RustStruct(class) => { + // let idx = self.push_wasm(ValType::I32); + // self.webidl.push(ast::WebidlScalarType::Any); + // self.bindings.push(NonstandardOutgoing::RustType { + // idx, + // class: class.to_string(), + // }); + // } + // Descriptor::Ref(d) => self.process_ref(false, d)?, + // Descriptor::RefMut(d) => self.process_ref(true, d)?, + // + // Descriptor::CachedString => self.cached_string(false, true), + // + // Descriptor::Vector(_) | Descriptor::String => { + // let kind = arg.vector_kind().ok_or_else(|| { + // format_err!( + // "unsupported argument type for calling JS function from Rust {:?}", + // arg + // ) + // })?; + // let offset = self.push_wasm(ValType::I32); + // let length = self.push_wasm(ValType::I32); + // self.webidl.push(ast::WebidlScalarType::Any); + // self.bindings.push(NonstandardOutgoing::Vector { + // offset, + // kind, + // length, + // }) + // } + // + // Descriptor::Option(d) => self.process_option(d)?, + // + // Descriptor::Function(_) | Descriptor::Closure(_) | Descriptor::Slice(_) => bail!( + // "unsupported argument type for calling JS function from Rust: {:?}", + // arg + // ), + // + // // nothing to do + // Descriptor::Unit => {} + // + // // Largely synthetic and can't show up + // Descriptor::ClampedU8 => unreachable!(), + // } + // Ok(()) + // } + // + // fn process_ref(&mut self, mutable: bool, arg: &Descriptor) -> Result<(), Error> { + // match arg { + // Descriptor::Anyref => { + // let idx = self.push_wasm(ValType::Anyref); + // self.webidl.push(ast::WebidlScalarType::Any); + // self.bindings + // .push(NonstandardOutgoing::BorrowedAnyref { idx }); + // } + // Descriptor::CachedString => self.cached_string(false, false), + // Descriptor::Slice(_) | Descriptor::String => { + // use wasm_webidl_bindings::ast::WebidlScalarType::*; + // + // let kind = arg.vector_kind().ok_or_else(|| { + // format_err!( + // "unsupported argument type for calling JS function from Rust {:?}", + // arg + // ) + // })?; + // let offset = self.push_wasm(ValType::I32); + // let length = self.push_wasm(ValType::I32); + // match kind { + // VectorKind::I8 => self.standard_view(offset, length, Int8Array), + // VectorKind::U8 => self.standard_view(offset, length, Uint8Array), + // VectorKind::ClampedU8 => self.standard_view(offset, length, Uint8ClampedArray), + // VectorKind::I16 => self.standard_view(offset, length, Int16Array), + // VectorKind::U16 => self.standard_view(offset, length, Uint16Array), + // VectorKind::I32 => self.standard_view(offset, length, Int32Array), + // VectorKind::U32 => self.standard_view(offset, length, Uint32Array), + // VectorKind::F32 => self.standard_view(offset, length, Float32Array), + // VectorKind::F64 => self.standard_view(offset, length, Float64Array), + // VectorKind::String => { + // self.webidl.push(DomString); + // let binding = ast::OutgoingBindingExpressionUtf8Str { + // ty: ast::WebidlScalarType::DomString.into(), + // offset, + // length, + // }; + // self.bindings + // .push(NonstandardOutgoing::Standard(binding.into())); + // } + // VectorKind::I64 | VectorKind::U64 => { + // let signed = match kind { + // VectorKind::I64 => true, + // _ => false, + // }; + // self.webidl.push(Any); + // self.bindings.push(NonstandardOutgoing::View64 { + // offset, + // length, + // signed, + // }); + // } + // VectorKind::Anyref => { + // self.webidl.push(Any); + // self.bindings + // .push(NonstandardOutgoing::ViewAnyref { offset, length }); + // } + // } + // } + // + // Descriptor::Function(descriptor) => { + // let module = self + // .module + // .as_mut() + // .ok_or_else(|| format_err!("cannot return a closure from Rust"))?; + // let section = self.bindings_section.as_mut().unwrap(); + // // synthesize the a/b arguments that aren't present in the + // // signature from wasm-bindgen but are present in the wasm file. + // let mut descriptor = (**descriptor).clone(); + // let nargs = descriptor.arguments.len(); + // descriptor.arguments.insert(0, Descriptor::I32); + // descriptor.arguments.insert(0, Descriptor::I32); + // let binding_idx = super::bindings::register_table_element( + // module, + // section, + // descriptor.shim_idx, + // descriptor, + // )?; + // let a = self.push_wasm(ValType::I32); + // let b = self.push_wasm(ValType::I32); + // self.webidl.push(ast::WebidlScalarType::Any); + // self.bindings.push(NonstandardOutgoing::StackClosure { + // a, + // b, + // binding_idx, + // nargs, + // mutable, + // }); + // } + // + // _ => bail!( + // "unsupported reference argument type for calling JS function from Rust: {:?}", + // arg + // ), + // } + // Ok(()) + // } + // + // fn process_option(&mut self, arg: &Descriptor) -> Result<(), Error> { + // match arg { + // Descriptor::Anyref => self.standard_as(ValType::Anyref, ast::WebidlScalarType::Any), + // Descriptor::I8 => self.option_sentinel(), + // Descriptor::U8 => self.option_sentinel(), + // Descriptor::I16 => self.option_sentinel(), + // Descriptor::U16 => self.option_sentinel(), + // Descriptor::I32 => self.option_native(true, ValType::I32), + // Descriptor::U32 => self.option_native(false, ValType::I32), + // Descriptor::F32 => self.option_native(true, ValType::F32), + // Descriptor::F64 => self.option_native(true, ValType::F64), + // Descriptor::I64 | Descriptor::U64 => { + // let signed = match arg { + // Descriptor::I64 => true, + // _ => false, + // }; + // let binding = NonstandardOutgoing::OptionInt64 { + // present: self.push_wasm(ValType::I32), + // _ignored: self.push_wasm(ValType::I32), + // lo: self.push_wasm(ValType::I32), + // hi: self.push_wasm(ValType::I32), + // signed, + // }; + // self.webidl.push(ast::WebidlScalarType::Any); + // self.bindings.push(binding); + // } + // Descriptor::Boolean => { + // let idx = self.push_wasm(ValType::I32); + // self.webidl.push(ast::WebidlScalarType::Any); + // self.bindings.push(NonstandardOutgoing::OptionBool { idx }); + // } + // Descriptor::Char => { + // let idx = self.push_wasm(ValType::I32); + // self.webidl.push(ast::WebidlScalarType::Any); + // self.bindings.push(NonstandardOutgoing::OptionChar { idx }); + // } + // Descriptor::Enum { hole } => { + // let idx = self.push_wasm(ValType::I32); + // self.webidl.push(ast::WebidlScalarType::Long); + // self.bindings + // .push(NonstandardOutgoing::OptionIntegerEnum { idx, hole: *hole }); + // } + // Descriptor::RustStruct(name) => { + // let idx = self.push_wasm(ValType::I32); + // self.webidl.push(ast::WebidlScalarType::Any); + // self.bindings.push(NonstandardOutgoing::OptionRustType { + // idx, + // class: name.to_string(), + // }); + // } + // Descriptor::Ref(d) => self.process_option_ref(false, d)?, + // Descriptor::RefMut(d) => self.process_option_ref(true, d)?, + // + // Descriptor::CachedString => self.cached_string(true, true), + // + // Descriptor::String | Descriptor::Vector(_) => { + // let kind = arg.vector_kind().ok_or_else(|| { + // format_err!( + // "unsupported optional slice type for calling JS function from Rust {:?}", + // arg + // ) + // })?; + // let offset = self.push_wasm(ValType::I32); + // let length = self.push_wasm(ValType::I32); + // self.webidl.push(ast::WebidlScalarType::Any); + // self.bindings.push(NonstandardOutgoing::OptionVector { + // kind, + // offset, + // length, + // }) + // } + // + // _ => bail!( + // "unsupported optional argument type for calling JS function from Rust: {:?}", + // arg + // ), + // } + // Ok(()) + // } + // + // fn process_option_ref(&mut self, _mutable: bool, arg: &Descriptor) -> Result<(), Error> { + // match arg { + // Descriptor::Anyref => { + // let idx = self.push_wasm(ValType::Anyref); + // self.webidl.push(ast::WebidlScalarType::Any); + // self.bindings + // .push(NonstandardOutgoing::BorrowedAnyref { idx }); + // } + // Descriptor::CachedString => self.cached_string(true, false), + // Descriptor::String | Descriptor::Slice(_) => { + // let kind = arg.vector_kind().ok_or_else(|| { + // format_err!( + // "unsupported optional slice type for calling JS function from Rust {:?}", + // arg + // ) + // })?; + // let offset = self.push_wasm(ValType::I32); + // let length = self.push_wasm(ValType::I32); + // self.webidl.push(ast::WebidlScalarType::Any); + // self.bindings.push(NonstandardOutgoing::OptionSlice { + // kind, + // offset, + // length, + // }); + // } + // _ => bail!( + // "unsupported optional ref argument type for calling JS function from Rust: {:?}", + // arg + // ), + // } + // Ok(()) + // } + // + // fn push_wasm(&mut self, ty: ValType) -> u32 { + // self.wasm.push(ty); + // self.wasm.len() as u32 - 1 + // } + // + // fn standard_as(&mut self, wasm: ValType, webidl: ast::WebidlScalarType) { + // let binding = ast::OutgoingBindingExpressionAs { + // ty: webidl.into(), + // idx: self.push_wasm(wasm), + // }; + // self.webidl.push(webidl); + // self.bindings + // .push(NonstandardOutgoing::Standard(binding.into())); + // } + // + // fn standard_view(&mut self, offset: u32, length: u32, ty: ast::WebidlScalarType) { + // let binding = ast::OutgoingBindingExpressionView { + // ty: ty.into(), + // offset, + // length, + // }; + // self.webidl.push(ty); + // self.bindings + // .push(NonstandardOutgoing::Standard(binding.into())); + // } + // + // fn cached_string(&mut self, optional: bool, owned: bool) { + // let offset = self.push_wasm(ValType::I32); + // let length = self.push_wasm(ValType::I32); + // self.webidl.push(ast::WebidlScalarType::DomString); + // self.bindings.push(NonstandardOutgoing::CachedString { + // offset, + // length, + // owned, + // optional, + // }) + // } + // + // fn option_native(&mut self, signed: bool, ty: ValType) { + // let present = self.push_wasm(ValType::I32); + // let val = self.push_wasm(ty); + // self.webidl.push(ast::WebidlScalarType::Any); + // self.bindings.push(NonstandardOutgoing::OptionNative { + // signed, + // present, + // val, + // }); + // } + // + // fn option_sentinel(&mut self) { + // let idx = self.push_wasm(ValType::I32); + // self.webidl.push(ast::WebidlScalarType::Any); + // self.bindings + // .push(NonstandardOutgoing::OptionU32Sentinel { idx }); + // } } diff --git a/crates/cli-support/src/wit/standard.rs b/crates/cli-support/src/wit/standard.rs index 228977eeb08..81ddf883754 100644 --- a/crates/cli-support/src/wit/standard.rs +++ b/crates/cli-support/src/wit/standard.rs @@ -1,562 +1,761 @@ -//! 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)?; +use std::borrow::Cow; +use std::collections::HashMap; +use walrus::{ImportId, TypedCustomSectionId}; + +#[derive(Default, Debug)] +pub struct NonstandardWitSection { + /// A list of adapter functions, keyed by their id. + pub adapters: HashMap, + + /// A list of pairs for adapter functions that implement core wasm imports. + pub implements: Vec<(ImportId, AdapterId)>, + + /// A list of adapter functions and the names they're exported under. + pub exports: Vec<(String, AdapterId)>, + // /// A list of table elements that are wrapped by the given adapter + // /// function. + // pub elems: Vec<(u32, AdapterId)>, +} - let func = match module.exports.get(*id).item { - walrus::ExportItem::Function(f) => f, - _ => unreachable!(), - }; - binding.wasm_ty = module.funcs.get(func).ty(); +pub type NonstandardWitSectionId = TypedCustomSectionId; - // 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); - } +#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)] +pub struct AdapterId(usize); - Ok(()) +#[derive(Debug, Clone)] +pub struct Adapter { + pub id: AdapterId, + pub params: Vec, + pub results: Vec, + pub kind: AdapterKind, } -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, - } +#[derive(Debug, Clone)] +pub enum AdapterKind { + Local { + instructions: Vec, + }, + Import { + module: String, + name: String, + kind: AdapterJsImportKind, + }, } -// 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"), - } - } +#[derive(Debug, Clone)] +pub enum AdapterJsImportKind { + /// The first argument is an `anyref` which is the `this` of the function + /// call + Method, + /// The value imported should be invoked as `new` + Constructor, + /// A bland function import + Normal, +} - 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), - } - } +#[derive(Debug, Clone, Copy)] +pub enum AdapterType { + S8, + S16, + S32, + S64, + U8, + U16, + U32, + U64, + F32, + F64, + String, + Anyref, + Bool, + I32, + I64, } -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; +#[derive(Debug, Clone)] +pub enum Instruction { + /// A known instruction in the "standard" + Standard(wit_walrus::Instruction), + + /// A call to one of our own defined adapters, similar to the standard + /// call-adapter instruction + CallAdapter(AdapterId), + + /// An instruction to store `ty` at the `offset` index in the return pointer + StoreRetptr { ty: AdapterType, offset: usize }, + /// An instruction to load `ty` at the `offset` index from the return pointer + LoadRetptr { ty: AdapterType, offset: usize }, + /// An instruction which pushes the return pointer onto the stack. + Retptr, + /// Pops a `bool` from the stack and pushes an `i32` equivalent + I32FromBool, + /// Pops a `string` from the stack and pushes the first character as `i32` + I32FromStringFirstChar, + /// Pops an `anyref` from the stack, allocates space in the anyref table, + /// returns the index it was stored at. + I32FromAnyrefOwned, + /// Pops an `anyref` from the stack, pushes it onto the anyref wasm table + /// stack, and returns the index it was stored at. + I32FromAnyrefBorrow, + /// Pops an `anyref` from the stack, assumes it's a Rust class given, and + /// deallocates the JS object and returns the i32 Rust pointer. + I32FromAnyrefRustOwned { class: String }, + /// Pops an `anyref` from the stack, assumes it's a Rust class given, and + /// passes the pointer to Rust which will be borrowed for the duration of a + /// call + I32FromAnyrefRustBorrow { class: String }, + /// Pops an `anyref` from the stack, pushes 0 if it's "none" or the + /// consumed pointer value if it's "some". + I32FromOptionRust { class: String }, + /// Pops an `s64` or `u64` from the stack, pushing two `i32` values. + I32Split64 { signed: bool }, + /// Pops an `s64` or `u64` from the stack, pushing three `i32` values. + /// First is the "some/none" bit, and the next is the low bits, and the + /// next is the high bits. + I32SplitOption64 { signed: bool }, + /// Pops an `anyref` from the stack, pushes either 0 if it's "none" or and + /// index into the owned wasm table it was stored at if it's "some" + I32FromOptionAnyref, + /// Pops an `anyref` from the stack, pushes either a sentinel value if it's + /// "none" or the integer value of it if it's "some" + I32FromOptionU32Sentinel, + /// Pops an `anyref` from the stack, pushes 0 for "none", 1 for + /// "some(false)', and 2 for "some(true)" + I32FromOptionBool, + /// Pops an `anyref` from the stack, pushes a sentinel for "none" or the + /// value if it's "some" + I32FromOptionChar, + /// Pops an `anyref` from the stack, pushes `hole` for "none" or the + /// value if it's "some" + I32FromOptionEnum { hole: u32 }, + /// Pops any anyref from the stack and then pushes two values. First is a + /// 0/1 if it's none/some and second is `ty` value if it was there or 0 if + /// it wasn't there. + OptionNative { ty: walrus::ValType }, +} - 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 - ); - } +impl AdapterType { + pub fn from_wit(wit: wit_walrus::ValType) -> AdapterType { + match wit { + wit_walrus::ValType::S8 => AdapterType::S8, + wit_walrus::ValType::S16 => AdapterType::S16, + wit_walrus::ValType::S32 => AdapterType::S32, + wit_walrus::ValType::S64 => AdapterType::S64, + wit_walrus::ValType::U8 => AdapterType::U8, + wit_walrus::ValType::U16 => AdapterType::U16, + wit_walrus::ValType::U32 => AdapterType::U32, + wit_walrus::ValType::U64 => AdapterType::U64, + wit_walrus::ValType::F32 => AdapterType::F32, + wit_walrus::ValType::F64 => AdapterType::F64, + wit_walrus::ValType::String => AdapterType::String, + wit_walrus::ValType::Anyref => AdapterType::Anyref, + wit_walrus::ValType::I32 => AdapterType::I32, + wit_walrus::ValType::I64 => AdapterType::I64, } - - 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, - ); + pub fn from_wasm(wasm: walrus::ValType) -> Option { + Some(match wasm { + walrus::ValType::I32 => AdapterType::S32, + walrus::ValType::I64 => AdapterType::S64, + walrus::ValType::F32 => AdapterType::F32, + walrus::ValType::F64 => AdapterType::F64, + walrus::ValType::Anyref => AdapterType::Anyref, + walrus::ValType::V128 => return None, + }) } - - 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 +impl NonstandardWitSection { + pub fn append( + &mut self, + params: Vec, + results: Vec, + kind: AdapterKind, + ) -> AdapterId { + let id = AdapterId(self.adapters.len()); + self.adapters.insert( + id, + Adapter { + id, + params, + results, + kind, + }, ); + return id; } - 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 - ); +impl walrus::CustomSection for NonstandardWitSection { + fn name(&self) -> &str { + "nonstandard wit section" } - 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 data(&self, _: &walrus::IdsToIndices) -> Cow<[u8]> { + panic!("shouldn't emit custom sections just yet"); } } -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 - ); -} +// //! 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 +// ); +// } diff --git a/src/convert/impls.rs b/src/convert/impls.rs index 91d08dec647..89f74dbfa16 100644 --- a/src/convert/impls.rs +++ b/src/convert/impls.rs @@ -51,7 +51,6 @@ unsafe impl WasmAbi for Wasm64 {} #[repr(C)] pub struct WasmOptional64 { pub present: u32, - pub padding: u32, pub low: u32, pub high: u32, } @@ -177,13 +176,11 @@ macro_rules! type_64 { match self { None => WasmOptional64 { present: 0, - padding: 0, low: 0 as u32, high: 0 as u32, }, Some(me) => WasmOptional64 { present: 1, - padding: 0, low: me as u32, high: (me >> 32) as u32, }, From 061e4631e6d61853160bbe332e0a50117bb1ca07 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 21 Nov 2019 09:26:16 -0800 Subject: [PATCH 03/35] Uncomment and fix `wit/mod.rs` --- crates/cli-support/src/wit/mod.rs | 1535 ++++++++++----------- crates/cli-support/src/wit/nonstandard.rs | 1 - crates/cli-support/src/wit/standard.rs | 1 + 3 files changed, 760 insertions(+), 777 deletions(-) diff --git a/crates/cli-support/src/wit/mod.rs b/crates/cli-support/src/wit/mod.rs index 74f3082e224..8a142f9b598 100644 --- a/crates/cli-support/src/wit/mod.rs +++ b/crates/cli-support/src/wit/mod.rs @@ -20,17 +20,6 @@ mod standard; pub use self::nonstandard::*; pub use self::standard::*; -// pub mod standard; -// impl Binding { -// /// Does this binding's wasm function signature have any `anyref`s? -// pub fn contains_anyref(&self, module: &walrus::Module) -> bool { -// let ty = module.types.get(self.wasm_ty); -// ty.params() -// .iter() -// .chain(ty.results()) -// .any(|ty| *ty == walrus::ValType::Anyref) -// } -// } struct Context<'a> { start_found: bool, module: &'a mut Module, @@ -120,797 +109,791 @@ impl<'a> Context<'a> { self.bind_intrinsic(id, intrinsic)?; } - // self.inject_anyref_initialization()?; - // - // if let Some(custom) = self - // .module - // .customs - // .delete_typed::() - // { - // let WasmBindgenDescriptorsSection { - // descriptors, - // closure_imports, - // } = *custom; - // // Store all the executed descriptors in our own field so we have - // // access to them while processing programs. - // self.descriptors.extend(descriptors); - // - // // Register all the injected closure imports as that they're expected - // // to manufacture a particular type of closure. - // // - // // First we register the imported function shim which returns a - // // `JsValue` for the closure. We manufacture this signature's - // // binding since it's not listed anywhere. - // // - // // Next we register the corresponding table element's binding in - // // the webidl bindings section. This binding will later be used to - // // generate a shim (if necessary) for the table element. - // // - // // Finally we store all this metadata in the import map which we've - // // learned so when a binding for the import is generated we can - // // generate all the appropriate shims. - // for (id, descriptor) in closure_imports { - // let binding = Function { - // shim_idx: 0, - // arguments: vec![Descriptor::I32; 3], - // ret: Descriptor::Anyref, - // }; - // bindings::register_import( - // self.module, - // &mut self.bindings, - // id, - // binding, - // ast::WebidlFunctionKind::Static, - // )?; - // // Synthesize the two integer pointers we pass through which - // // aren't present in the signature but are present in the wasm - // // signature. - // let mut function = descriptor.function.clone(); - // let nargs = function.arguments.len(); - // function.arguments.insert(0, Descriptor::I32); - // function.arguments.insert(0, Descriptor::I32); - // let binding_idx = bindings::register_table_element( - // self.module, - // &mut self.bindings, - // descriptor.shim_idx, - // function, - // )?; - // self.aux.import_map.insert( - // id, - // AuxImport::Closure { - // dtor: descriptor.dtor_idx, - // mutable: descriptor.mutable, - // binding_idx, - // nargs, - // }, - // ); - // } - // } + self.inject_anyref_initialization()?; + + if let Some(custom) = self + .module + .customs + .delete_typed::() + { + let WasmBindgenDescriptorsSection { + descriptors, + closure_imports, + } = *custom; + // Store all the executed descriptors in our own field so we have + // access to them while processing programs. + self.descriptors.extend(descriptors); + + // Register all the injected closure imports as that they're expected + // to manufacture a particular type of closure. + // + // First we register the imported function shim which returns a + // `JsValue` for the closure. We manufacture this signature + // since it's not listed anywhere. + // + // Next we register the corresponding table element's signature in + // the interface types section. This adapter will later be used to + // generate a shim (if necessary) for the table element. + // + // Finally we store all this metadata in the import map which we've + // learned so when a binding for the import is generated we can + // generate all the appropriate shims. + for (id, descriptor) in closure_imports { + let signature = Function { + shim_idx: 0, + arguments: vec![Descriptor::I32; 3], + ret: Descriptor::Anyref, + }; + adapters::import( + self.module, + &mut self.adapters, + id, + signature, + AdapterJsImportKind::Normal, + )?; + // Synthesize the two integer pointers we pass through which + // aren't present in the signature but are present in the wasm + // signature. + let mut function = descriptor.function.clone(); + let nargs = function.arguments.len(); + function.arguments.insert(0, Descriptor::I32); + function.arguments.insert(0, Descriptor::I32); + let id = adapters::table_element( + self.module, + &mut self.adapters, + descriptor.shim_idx, + function, + )?; + self.aux.import_map.insert( + id, + AuxImport::Closure { + dtor: descriptor.dtor_idx, + mutable: descriptor.mutable, + nargs, + }, + ); + } + } Ok(()) } // Discover a function `main(i32, i32) -> i32` and, if it exists, make that function run at module start. fn discover_main(&mut self) -> Result<(), Error> { - // // find a `main(i32, i32) -> i32` - // let main_id = self - // .module - // .functions() - // .find(|x| { - // use walrus::ValType::I32; - // // name has to be `main` - // let name_matches = x.name.as_ref().map_or(false, |x| x == "main"); - // // type has to be `(i32, i32) -> i32` - // let ty = self.module.types.get(x.ty()); - // let type_matches = ty.params() == [I32, I32] && ty.results() == [I32]; - // name_matches && type_matches - // }) - // .map(|x| x.id()); - // let main_id = match main_id { - // Some(x) => x, - // None => return Ok(()), - // }; - // - // // build a wrapper to zero out the arguments and ignore the return value - // let mut wrapper = walrus::FunctionBuilder::new(&mut self.module.types, &[], &[]); - // wrapper - // .func_body() - // .i32_const(0) - // .i32_const(0) - // .call(main_id) - // .drop() - // .return_(); - // let wrapper = wrapper.finish(vec![], &mut self.module.funcs); - // - // // call that wrapper when the module starts - // self.add_start_function(wrapper)?; + // find a `main(i32, i32) -> i32` + let main_id = self + .module + .functions() + .find(|x| { + use walrus::ValType::I32; + // name has to be `main` + let name_matches = x.name.as_ref().map_or(false, |x| x == "main"); + // type has to be `(i32, i32) -> i32` + let ty = self.module.types.get(x.ty()); + let type_matches = ty.params() == [I32, I32] && ty.results() == [I32]; + name_matches && type_matches + }) + .map(|x| x.id()); + let main_id = match main_id { + Some(x) => x, + None => return Ok(()), + }; + + // build a wrapper to zero out the arguments and ignore the return value + let mut wrapper = walrus::FunctionBuilder::new(&mut self.module.types, &[], &[]); + wrapper + .func_body() + .i32_const(0) + .i32_const(0) + .call(main_id) + .drop() + .return_(); + let wrapper = wrapper.finish(vec![], &mut self.module.funcs); + + // call that wrapper when the module starts + self.add_start_function(wrapper)?; Ok(()) } - // // Ensure that the `start` function for this module calls the - // // `__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 || self.wasm_interface_types { - // return Ok(()); - // } - // - // let ty = self.module.types.add(&[], &[]); - // let (import, import_id) = - // self.module - // .add_import_func(PLACEHOLDER_MODULE, "__wbindgen_init_anyref_table", ty); + // Ensure that the `start` function for this module calls the + // `__wbindgen_init_anyref_table` function. This'll ensure that all + // instances of this module have the initial slots of the anyref table + // initialized correctly. // - // self.module.start = Some(match self.module.start { - // Some(prev_start) => { - // let mut builder = walrus::FunctionBuilder::new(&mut self.module.types, &[], &[]); - // builder.func_body().call(import).call(prev_start); - // builder.finish(Vec::new(), &mut self.module.funcs) - // } - // None => import, - // }); - // self.bind_intrinsic(import_id, Intrinsic::InitAnyrefTable)?; - // - // Ok(()) - // } + // 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 || self.wasm_interface_types { + return Ok(()); + } + + let ty = self.module.types.add(&[], &[]); + let (import, import_id) = + self.module + .add_import_func(PLACEHOLDER_MODULE, "__wbindgen_init_anyref_table", ty); + + self.module.start = Some(match self.module.start { + Some(prev_start) => { + let mut builder = walrus::FunctionBuilder::new(&mut self.module.types, &[], &[]); + builder.func_body().call(import).call(prev_start); + builder.finish(Vec::new(), &mut self.module.funcs) + } + None => import, + }); + self.bind_intrinsic(import_id, Intrinsic::InitAnyrefTable)?; + + Ok(()) + } fn bind_intrinsic(&mut self, id: ImportId, intrinsic: Intrinsic) -> Result<(), Error> { - adapters::import( + let id = adapters::import( self.module, &mut self.adapters, id, intrinsic.signature(), AdapterJsImportKind::Normal, )?; - // self.aux - // .import_map - // .insert(id, AuxImport::Intrinsic(intrinsic)); + self.aux + .import_map + .insert(id, AuxImport::Intrinsic(intrinsic)); Ok(()) } fn program(&mut self, program: decode::Program<'a>) -> Result<(), Error> { - // self.unique_crate_identifier = program.unique_crate_identifier; - // let decode::Program { - // exports, - // enums, - // imports, - // structs, - // typescript_custom_sections, - // local_modules, - // inline_js, - // unique_crate_identifier, - // package_json, - // } = program; - // - // for module in local_modules { - // // All local modules we find should be unique, but the same module - // // may have showed up in a few different blocks. If that's the case - // // all the same identifiers should have the same contents. - // if let Some(prev) = self - // .aux - // .local_modules - // .insert(module.identifier.to_string(), module.contents.to_string()) - // { - // assert_eq!(prev, module.contents); - // } - // } - // if let Some(s) = package_json { - // self.aux.package_jsons.insert(s.into()); - // } - // for export in exports { - // self.export(export)?; - // } - // - // // Register vendor prefixes for all types before we walk over all the - // // imports to ensure that if a vendor prefix is listed somewhere it'll - // // apply to all the imports. - // for import in imports.iter() { - // if let decode::ImportKind::Type(ty) = &import.kind { - // if ty.vendor_prefixes.len() == 0 { - // continue; - // } - // self.vendor_prefixes - // .entry(ty.name.to_string()) - // .or_insert(Vec::new()) - // .extend(ty.vendor_prefixes.iter().map(|s| s.to_string())); - // } - // } - // for import in imports { - // self.import(import)?; - // } - // - // for enum_ in enums { - // self.enum_(enum_)?; - // } - // for struct_ in structs { - // self.struct_(struct_)?; - // } - // for section in typescript_custom_sections { - // self.aux.extra_typescript.push_str(section); - // self.aux.extra_typescript.push_str("\n\n"); - // } - // self.aux - // .snippets - // .entry(unique_crate_identifier.to_string()) - // .or_insert(Vec::new()) - // .extend(inline_js.iter().map(|s| s.to_string())); + self.unique_crate_identifier = program.unique_crate_identifier; + let decode::Program { + exports, + enums, + imports, + structs, + typescript_custom_sections, + local_modules, + inline_js, + unique_crate_identifier, + package_json, + } = program; + + for module in local_modules { + // All local modules we find should be unique, but the same module + // may have showed up in a few different blocks. If that's the case + // all the same identifiers should have the same contents. + if let Some(prev) = self + .aux + .local_modules + .insert(module.identifier.to_string(), module.contents.to_string()) + { + assert_eq!(prev, module.contents); + } + } + if let Some(s) = package_json { + self.aux.package_jsons.insert(s.into()); + } + for export in exports { + self.export(export)?; + } + + // Register vendor prefixes for all types before we walk over all the + // imports to ensure that if a vendor prefix is listed somewhere it'll + // apply to all the imports. + for import in imports.iter() { + if let decode::ImportKind::Type(ty) = &import.kind { + if ty.vendor_prefixes.len() == 0 { + continue; + } + self.vendor_prefixes + .entry(ty.name.to_string()) + .or_insert(Vec::new()) + .extend(ty.vendor_prefixes.iter().map(|s| s.to_string())); + } + } + for import in imports { + self.import(import)?; + } + + for enum_ in enums { + self.enum_(enum_)?; + } + for struct_ in structs { + self.struct_(struct_)?; + } + for section in typescript_custom_sections { + self.aux.extra_typescript.push_str(section); + self.aux.extra_typescript.push_str("\n\n"); + } + self.aux + .snippets + .entry(unique_crate_identifier.to_string()) + .or_insert(Vec::new()) + .extend(inline_js.iter().map(|s| s.to_string())); Ok(()) } - // fn export(&mut self, export: decode::Export<'_>) -> Result<(), Error> { - // let wasm_name = match &export.class { - // Some(class) => struct_function_export_name(class, export.function.name), - // None => export.function.name.to_string(), - // }; - // let mut descriptor = match self.descriptors.remove(&wasm_name) { - // None => return Ok(()), - // Some(d) => d.unwrap_function(), - // }; - // let (export_id, id) = self.function_exports[&wasm_name]; - // if export.start { - // self.add_start_function(id)?; - // } - // - // let kind = match export.class { - // Some(class) => { - // let class = class.to_string(); - // match export.method_kind { - // decode::MethodKind::Constructor => AuxExportKind::Constructor(class), - // decode::MethodKind::Operation(op) => match op.kind { - // decode::OperationKind::Getter(f) => { - // descriptor.arguments.insert(0, Descriptor::I32); - // AuxExportKind::Getter { - // class, - // field: f.to_string(), - // } - // } - // decode::OperationKind::Setter(f) => { - // descriptor.arguments.insert(0, Descriptor::I32); - // AuxExportKind::Setter { - // class, - // field: f.to_string(), - // } - // } - // _ if op.is_static => AuxExportKind::StaticFunction { - // class, - // name: export.function.name.to_string(), - // }, - // _ => { - // descriptor.arguments.insert(0, Descriptor::I32); - // AuxExportKind::Method { - // class, - // name: export.function.name.to_string(), - // consumed: export.consumed, - // } - // } - // }, - // } - // } - // None => AuxExportKind::Function(export.function.name.to_string()), - // }; - // - // self.aux.export_map.insert( - // export_id, - // AuxExport { - // debug_name: wasm_name, - // comments: concatenate_comments(&export.comments), - // arg_names: Some(export.function.arg_names), - // kind, - // }, - // ); - // bindings::register_export(self.module, &mut self.bindings, export_id, descriptor)?; - // Ok(()) - // } - // - // fn add_start_function(&mut self, id: FunctionId) -> Result<(), Error> { - // if self.start_found { - // bail!("cannot specify two `start` functions"); - // } - // self.start_found = true; - // - // // Skip this when we're generating tests - // if !self.support_start { - // return Ok(()); - // } - // - // let prev_start = match self.module.start { - // Some(f) => f, - // None => { - // self.module.start = Some(id); - // return Ok(()); - // } - // }; - // - // // Note that we call the previous start function, if any, first. This is - // // because the start function currently only shows up when it's injected - // // through thread/anyref transforms. These injected start functions need - // // to happen before user code, so we always schedule them first. - // let mut builder = walrus::FunctionBuilder::new(&mut self.module.types, &[], &[]); - // builder.func_body().call(prev_start).call(id); - // let new_start = builder.finish(Vec::new(), &mut self.module.funcs); - // self.module.start = Some(new_start); - // Ok(()) - // } - // - // fn import(&mut self, import: decode::Import<'_>) -> Result<(), Error> { - // match &import.kind { - // decode::ImportKind::Function(f) => self.import_function(&import, f), - // decode::ImportKind::Static(s) => self.import_static(&import, s), - // decode::ImportKind::Type(t) => self.import_type(&import, t), - // decode::ImportKind::Enum(_) => Ok(()), - // } - // } - // - // fn import_function( - // &mut self, - // import: &decode::Import<'_>, - // function: &decode::ImportFunction<'_>, - // ) -> Result<(), Error> { - // let decode::ImportFunction { - // shim, - // catch, - // variadic, - // method, - // structural, - // function, - // assert_no_shim, - // } = function; - // let (import_id, _id) = match self.function_imports.get(*shim) { - // Some(pair) => *pair, - // None => return Ok(()), - // }; - // let descriptor = match self.descriptors.remove(*shim) { - // None => return Ok(()), - // Some(d) => d.unwrap_function(), - // }; - // - // // Record this for later as it affects JS binding generation, but note - // // that this doesn't affect the WebIDL interface at all. - // if *variadic { - // self.aux.imports_with_variadic.insert(import_id); - // } - // if *catch { - // self.aux.imports_with_catch.insert(import_id); - // } - // if *assert_no_shim { - // self.aux.imports_with_assert_no_shim.insert(import_id); - // } - // - // // Perform two functions here. First we're saving off our WebIDL - // // bindings signature, indicating what we think our import is going to - // // be. Next we're saving off other metadata indicating where this item - // // is going to be imported from. The `import_map` table will record, for - // // each import, what is getting hooked up to that slot of the import - // // table to the WebAssembly instance. - // let import = match method { - // Some(data) => { - // let class = self.determine_import(import, &data.class)?; - // match &data.kind { - // // NB: `structural` is ignored for constructors since the - // // js type isn't expected to change anyway. - // decode::MethodKind::Constructor => { - // bindings::register_import( - // self.module, - // &mut self.bindings, - // import_id, - // descriptor, - // ast::WebidlFunctionKind::Constructor, - // )?; - // AuxImport::Value(AuxValue::Bare(class)) - // } - // decode::MethodKind::Operation(op) => { - // let (import, method) = - // self.determine_import_op(class, function, *structural, op)?; - // let kind = if method { - // let kind = ast::WebidlFunctionKindMethod { - // // TODO: what should this actually be? - // ty: ast::WebidlScalarType::Any.into(), - // }; - // ast::WebidlFunctionKind::Method(kind) - // } else { - // ast::WebidlFunctionKind::Static - // }; - // bindings::register_import( - // self.module, - // &mut self.bindings, - // import_id, - // descriptor, - // kind, - // )?; - // import - // } - // } - // } - // - // // NB: `structural` is ignored for free functions since it's - // // expected that the binding isn't changing anyway. - // None => { - // bindings::register_import( - // self.module, - // &mut self.bindings, - // import_id, - // descriptor, - // ast::WebidlFunctionKind::Static, - // )?; - // let name = self.determine_import(import, function.name)?; - // AuxImport::Value(AuxValue::Bare(name)) - // } - // }; - // - // self.aux.import_map.insert(import_id, import); - // Ok(()) - // } - // - // /// The `bool` returned indicates whether the imported value should be - // /// invoked as a method (first arg is implicitly `this`) or if the imported - // /// value is a simple function-like shim - // fn determine_import_op( - // &mut self, - // mut class: JsImport, - // function: &decode::Function<'_>, - // structural: bool, - // op: &decode::Operation<'_>, - // ) -> Result<(AuxImport, bool), Error> { - // match op.kind { - // decode::OperationKind::Regular => { - // if op.is_static { - // Ok(( - // AuxImport::ValueWithThis(class, function.name.to_string()), - // false, - // )) - // } else if structural { - // Ok(( - // AuxImport::StructuralMethod(function.name.to_string()), - // false, - // )) - // } else { - // class.fields.push("prototype".to_string()); - // class.fields.push(function.name.to_string()); - // Ok((AuxImport::Value(AuxValue::Bare(class)), true)) - // } - // } - // - // decode::OperationKind::Getter(field) => { - // if structural { - // if op.is_static { - // Ok(( - // AuxImport::StructuralClassGetter(class, field.to_string()), - // false, - // )) - // } else { - // Ok((AuxImport::StructuralGetter(field.to_string()), false)) - // } - // } else { - // let val = if op.is_static { - // AuxValue::ClassGetter(class, field.to_string()) - // } else { - // AuxValue::Getter(class, field.to_string()) - // }; - // Ok((AuxImport::Value(val), true)) - // } - // } - // - // decode::OperationKind::Setter(field) => { - // if structural { - // if op.is_static { - // Ok(( - // AuxImport::StructuralClassSetter(class, field.to_string()), - // false, - // )) - // } else { - // Ok((AuxImport::StructuralSetter(field.to_string()), false)) - // } - // } else { - // let val = if op.is_static { - // AuxValue::ClassSetter(class, field.to_string()) - // } else { - // AuxValue::Setter(class, field.to_string()) - // }; - // Ok((AuxImport::Value(val), true)) - // } - // } - // - // decode::OperationKind::IndexingGetter => { - // if !structural { - // bail!("indexing getters must always be structural"); - // } - // if op.is_static { - // Ok((AuxImport::IndexingGetterOfClass(class), false)) - // } else { - // Ok((AuxImport::IndexingGetterOfObject, false)) - // } - // } - // - // decode::OperationKind::IndexingSetter => { - // if !structural { - // bail!("indexing setters must always be structural"); - // } - // if op.is_static { - // Ok((AuxImport::IndexingSetterOfClass(class), false)) - // } else { - // Ok((AuxImport::IndexingSetterOfObject, false)) - // } - // } - // - // decode::OperationKind::IndexingDeleter => { - // if !structural { - // bail!("indexing deleters must always be structural"); - // } - // if op.is_static { - // Ok((AuxImport::IndexingDeleterOfClass(class), false)) - // } else { - // Ok((AuxImport::IndexingDeleterOfObject, false)) - // } - // } - // } - // } - // - // fn import_static( - // &mut self, - // import: &decode::Import<'_>, - // static_: &decode::ImportStatic<'_>, - // ) -> Result<(), Error> { - // let (import_id, _id) = match self.function_imports.get(static_.shim) { - // Some(pair) => *pair, - // None => return Ok(()), - // }; - // - // let descriptor = match self.descriptors.remove(static_.shim) { - // None => return Ok(()), - // Some(d) => d, - // }; - // - // // Register the signature of this imported shim - // bindings::register_import( - // self.module, - // &mut self.bindings, - // import_id, - // Function { - // arguments: Vec::new(), - // shim_idx: 0, - // ret: descriptor, - // }, - // ast::WebidlFunctionKind::Static, - // )?; - // - // // And then save off that this function is is an instanceof shim for an - // // imported item. - // let import = self.determine_import(import, &static_.name)?; - // self.aux - // .import_map - // .insert(import_id, AuxImport::Static(import)); - // Ok(()) - // } - // - // fn import_type( - // &mut self, - // import: &decode::Import<'_>, - // type_: &decode::ImportType<'_>, - // ) -> Result<(), Error> { - // let (import_id, _id) = match self.function_imports.get(type_.instanceof_shim) { - // Some(pair) => *pair, - // None => return Ok(()), - // }; - // - // // Register the signature of this imported shim - // bindings::register_import( - // self.module, - // &mut self.bindings, - // import_id, - // Function { - // arguments: vec![Descriptor::Ref(Box::new(Descriptor::Anyref))], - // shim_idx: 0, - // ret: Descriptor::Boolean, - // }, - // ast::WebidlFunctionKind::Static, - // )?; - // - // // And then save off that this function is is an instanceof shim for an - // // imported item. - // let import = self.determine_import(import, &type_.name)?; - // self.aux - // .import_map - // .insert(import_id, AuxImport::Instanceof(import)); - // Ok(()) - // } - // - // fn enum_(&mut self, enum_: decode::Enum<'_>) -> Result<(), Error> { - // let aux = AuxEnum { - // name: enum_.name.to_string(), - // comments: concatenate_comments(&enum_.comments), - // variants: enum_ - // .variants - // .iter() - // .map(|v| (v.name.to_string(), v.value)) - // .collect(), - // }; - // self.aux.enums.push(aux); - // Ok(()) - // } - // - // fn struct_(&mut self, struct_: decode::Struct<'_>) -> Result<(), Error> { - // for field in struct_.fields { - // let getter = wasm_bindgen_shared::struct_field_get(&struct_.name, &field.name); - // let setter = wasm_bindgen_shared::struct_field_set(&struct_.name, &field.name); - // let descriptor = match self.descriptors.remove(&getter) { - // None => continue, - // Some(d) => d, - // }; - // - // // Register a webidl transformation for the getter - // let (getter_id, _) = self.function_exports[&getter]; - // let getter_descriptor = Function { - // arguments: vec![Descriptor::I32], - // shim_idx: 0, - // ret: descriptor.clone(), - // }; - // bindings::register_export( - // self.module, - // &mut self.bindings, - // getter_id, - // getter_descriptor, - // )?; - // self.aux.export_map.insert( - // getter_id, - // AuxExport { - // debug_name: format!("getter for `{}::{}`", struct_.name, field.name), - // arg_names: None, - // comments: concatenate_comments(&field.comments), - // kind: AuxExportKind::Getter { - // class: struct_.name.to_string(), - // field: field.name.to_string(), - // }, - // }, - // ); - // - // // If present, register information for the setter as well. - // if field.readonly { - // continue; - // } - // - // let (setter_id, _) = self.function_exports[&setter]; - // let setter_descriptor = Function { - // arguments: vec![Descriptor::I32, descriptor], - // shim_idx: 0, - // ret: Descriptor::Unit, - // }; - // bindings::register_export( - // self.module, - // &mut self.bindings, - // setter_id, - // setter_descriptor, - // )?; - // self.aux.export_map.insert( - // setter_id, - // AuxExport { - // debug_name: format!("setter for `{}::{}`", struct_.name, field.name), - // arg_names: None, - // comments: concatenate_comments(&field.comments), - // kind: AuxExportKind::Setter { - // class: struct_.name.to_string(), - // field: field.name.to_string(), - // }, - // }, - // ); - // } - // let aux = AuxStruct { - // name: struct_.name.to_string(), - // comments: concatenate_comments(&struct_.comments), - // }; - // self.aux.structs.push(aux); - // - // let wrap_constructor = wasm_bindgen_shared::new_function(struct_.name); - // if let Some((import_id, _id)) = self.function_imports.get(&wrap_constructor) { - // self.aux.import_map.insert( - // *import_id, - // AuxImport::WrapInExportedClass(struct_.name.to_string()), - // ); - // let binding = Function { - // shim_idx: 0, - // arguments: vec![Descriptor::I32], - // ret: Descriptor::Anyref, - // }; - // bindings::register_import( - // self.module, - // &mut self.bindings, - // *import_id, - // binding, - // ast::WebidlFunctionKind::Static, - // )?; - // } - // - // Ok(()) - // } - // - // fn determine_import(&self, import: &decode::Import<'_>, item: &str) -> Result { - // let is_local_snippet = match import.module { - // decode::ImportModule::Named(s) => self.aux.local_modules.contains_key(s), - // decode::ImportModule::RawNamed(_) => false, - // decode::ImportModule::Inline(_) => true, - // decode::ImportModule::None => false, - // }; - // - // // Similar to `--target no-modules`, only allow vendor prefixes - // // basically for web apis, shouldn't be necessary for things like npm - // // packages or other imported items. - // let vendor_prefixes = self.vendor_prefixes.get(item); - // if let Some(vendor_prefixes) = vendor_prefixes { - // assert!(vendor_prefixes.len() > 0); - // - // if is_local_snippet { - // bail!( - // "local JS snippets do not support vendor prefixes for \ - // the import of `{}` with a polyfill of `{}`", - // item, - // &vendor_prefixes[0] - // ); - // } - // if let decode::ImportModule::Named(module) = &import.module { - // bail!( - // "import of `{}` from `{}` has a polyfill of `{}` listed, but - // vendor prefixes aren't supported when importing from modules", - // item, - // module, - // &vendor_prefixes[0], - // ); - // } - // if let Some(ns) = &import.js_namespace { - // bail!( - // "import of `{}` through js namespace `{}` isn't supported \ - // right now when it lists a polyfill", - // item, - // ns - // ); - // } - // return Ok(JsImport { - // name: JsImportName::VendorPrefixed { - // name: item.to_string(), - // prefixes: vendor_prefixes.clone(), - // }, - // fields: Vec::new(), - // }); - // } - // - // let (name, fields) = match import.js_namespace { - // Some(ns) => (ns, vec![item.to_string()]), - // None => (item, Vec::new()), - // }; - // - // let name = match import.module { - // decode::ImportModule::Named(module) if is_local_snippet => JsImportName::LocalModule { - // module: module.to_string(), - // name: name.to_string(), - // }, - // decode::ImportModule::Named(module) | decode::ImportModule::RawNamed(module) => { - // JsImportName::Module { - // module: module.to_string(), - // name: name.to_string(), - // } - // } - // decode::ImportModule::Inline(idx) => { - // let offset = self - // .aux - // .snippets - // .get(self.unique_crate_identifier) - // .map(|s| s.len()) - // .unwrap_or(0); - // JsImportName::InlineJs { - // unique_crate_identifier: self.unique_crate_identifier.to_string(), - // snippet_idx_in_crate: idx as usize + offset, - // name: name.to_string(), - // } - // } - // decode::ImportModule::None => JsImportName::Global { - // name: name.to_string(), - // }, - // }; - // Ok(JsImport { name, fields }) - // } + fn export(&mut self, export: decode::Export<'_>) -> Result<(), Error> { + let wasm_name = match &export.class { + Some(class) => struct_function_export_name(class, export.function.name), + None => export.function.name.to_string(), + }; + let mut descriptor = match self.descriptors.remove(&wasm_name) { + None => return Ok(()), + Some(d) => d.unwrap_function(), + }; + let (export_id, id) = self.function_exports[&wasm_name]; + if export.start { + self.add_start_function(id)?; + } + + let kind = match export.class { + Some(class) => { + let class = class.to_string(); + match export.method_kind { + decode::MethodKind::Constructor => AuxExportKind::Constructor(class), + decode::MethodKind::Operation(op) => match op.kind { + decode::OperationKind::Getter(f) => { + descriptor.arguments.insert(0, Descriptor::I32); + AuxExportKind::Getter { + class, + field: f.to_string(), + } + } + decode::OperationKind::Setter(f) => { + descriptor.arguments.insert(0, Descriptor::I32); + AuxExportKind::Setter { + class, + field: f.to_string(), + } + } + _ if op.is_static => AuxExportKind::StaticFunction { + class, + name: export.function.name.to_string(), + }, + _ => { + descriptor.arguments.insert(0, Descriptor::I32); + AuxExportKind::Method { + class, + name: export.function.name.to_string(), + consumed: export.consumed, + } + } + }, + } + } + None => AuxExportKind::Function(export.function.name.to_string()), + }; + + let id = adapters::export(self.module, &mut self.adapters, export_id, descriptor)?; + self.aux.export_map.insert( + id, + AuxExport { + debug_name: wasm_name, + comments: concatenate_comments(&export.comments), + arg_names: Some(export.function.arg_names), + kind, + }, + ); + Ok(()) + } + + fn add_start_function(&mut self, id: FunctionId) -> Result<(), Error> { + if self.start_found { + bail!("cannot specify two `start` functions"); + } + self.start_found = true; + + // Skip this when we're generating tests + if !self.support_start { + return Ok(()); + } + + let prev_start = match self.module.start { + Some(f) => f, + None => { + self.module.start = Some(id); + return Ok(()); + } + }; + + // Note that we call the previous start function, if any, first. This is + // because the start function currently only shows up when it's injected + // through thread/anyref transforms. These injected start functions need + // to happen before user code, so we always schedule them first. + let mut builder = walrus::FunctionBuilder::new(&mut self.module.types, &[], &[]); + builder.func_body().call(prev_start).call(id); + let new_start = builder.finish(Vec::new(), &mut self.module.funcs); + self.module.start = Some(new_start); + Ok(()) + } + + fn import(&mut self, import: decode::Import<'_>) -> Result<(), Error> { + match &import.kind { + decode::ImportKind::Function(f) => self.import_function(&import, f), + decode::ImportKind::Static(s) => self.import_static(&import, s), + decode::ImportKind::Type(t) => self.import_type(&import, t), + decode::ImportKind::Enum(_) => Ok(()), + } + } + + fn import_function( + &mut self, + import: &decode::Import<'_>, + function: &decode::ImportFunction<'_>, + ) -> Result<(), Error> { + let decode::ImportFunction { + shim, + catch, + variadic, + method, + structural, + function, + assert_no_shim, + } = function; + let (import_id, _id) = match self.function_imports.get(*shim) { + Some(pair) => *pair, + None => return Ok(()), + }; + let descriptor = match self.descriptors.remove(*shim) { + None => return Ok(()), + Some(d) => d.unwrap_function(), + }; + + // Perform two functions here. First we're saving off our adapter + // signature, indicating what we think our import is going to be. Next + // we're saving off other metadata indicating where this item is going + // to be imported from. The `import_map` table will record, for each + // import, what is getting hooked up to that slot of the import table + // to the WebAssembly instance. + let (id, import) = match method { + Some(data) => { + let class = self.determine_import(import, &data.class)?; + match &data.kind { + // NB: `structural` is ignored for constructors since the + // js type isn't expected to change anyway. + decode::MethodKind::Constructor => { + let id = adapters::import( + self.module, + &mut self.adapters, + import_id, + descriptor, + AdapterJsImportKind::Constructor, + )?; + (id, AuxImport::Value(AuxValue::Bare(class))) + } + decode::MethodKind::Operation(op) => { + let (import, method) = + self.determine_import_op(class, function, *structural, op)?; + let kind = if method { + AdapterJsImportKind::Method + } else { + AdapterJsImportKind::Normal + }; + ( + adapters::import( + self.module, + &mut self.adapters, + import_id, + descriptor, + kind, + )?, + import, + ) + } + } + } + + // NB: `structural` is ignored for free functions since it's + // expected that the binding isn't changing anyway. + None => { + let id = adapters::import( + self.module, + &mut self.adapters, + import_id, + descriptor, + AdapterJsImportKind::Normal, + )?; + let name = self.determine_import(import, function.name)?; + (id, AuxImport::Value(AuxValue::Bare(name))) + } + }; + + // Record this for later as it affects JS binding generation, but note + // that this doesn't affect the WebIDL interface at all. + if *variadic { + self.aux.imports_with_variadic.insert(id); + } + if *catch { + self.aux.imports_with_catch.insert(id); + } + if *assert_no_shim { + self.aux.imports_with_assert_no_shim.insert(id); + } + + self.aux.import_map.insert(id, import); + Ok(()) + } + + /// The `bool` returned indicates whether the imported value should be + /// invoked as a method (first arg is implicitly `this`) or if the imported + /// value is a simple function-like shim + fn determine_import_op( + &mut self, + mut class: JsImport, + function: &decode::Function<'_>, + structural: bool, + op: &decode::Operation<'_>, + ) -> Result<(AuxImport, bool), Error> { + match op.kind { + decode::OperationKind::Regular => { + if op.is_static { + Ok(( + AuxImport::ValueWithThis(class, function.name.to_string()), + false, + )) + } else if structural { + Ok(( + AuxImport::StructuralMethod(function.name.to_string()), + false, + )) + } else { + class.fields.push("prototype".to_string()); + class.fields.push(function.name.to_string()); + Ok((AuxImport::Value(AuxValue::Bare(class)), true)) + } + } + + decode::OperationKind::Getter(field) => { + if structural { + if op.is_static { + Ok(( + AuxImport::StructuralClassGetter(class, field.to_string()), + false, + )) + } else { + Ok((AuxImport::StructuralGetter(field.to_string()), false)) + } + } else { + let val = if op.is_static { + AuxValue::ClassGetter(class, field.to_string()) + } else { + AuxValue::Getter(class, field.to_string()) + }; + Ok((AuxImport::Value(val), true)) + } + } + + decode::OperationKind::Setter(field) => { + if structural { + if op.is_static { + Ok(( + AuxImport::StructuralClassSetter(class, field.to_string()), + false, + )) + } else { + Ok((AuxImport::StructuralSetter(field.to_string()), false)) + } + } else { + let val = if op.is_static { + AuxValue::ClassSetter(class, field.to_string()) + } else { + AuxValue::Setter(class, field.to_string()) + }; + Ok((AuxImport::Value(val), true)) + } + } + + decode::OperationKind::IndexingGetter => { + if !structural { + bail!("indexing getters must always be structural"); + } + if op.is_static { + Ok((AuxImport::IndexingGetterOfClass(class), false)) + } else { + Ok((AuxImport::IndexingGetterOfObject, false)) + } + } + + decode::OperationKind::IndexingSetter => { + if !structural { + bail!("indexing setters must always be structural"); + } + if op.is_static { + Ok((AuxImport::IndexingSetterOfClass(class), false)) + } else { + Ok((AuxImport::IndexingSetterOfObject, false)) + } + } + + decode::OperationKind::IndexingDeleter => { + if !structural { + bail!("indexing deleters must always be structural"); + } + if op.is_static { + Ok((AuxImport::IndexingDeleterOfClass(class), false)) + } else { + Ok((AuxImport::IndexingDeleterOfObject, false)) + } + } + } + } + + fn import_static( + &mut self, + import: &decode::Import<'_>, + static_: &decode::ImportStatic<'_>, + ) -> Result<(), Error> { + let (import_id, _id) = match self.function_imports.get(static_.shim) { + Some(pair) => *pair, + None => return Ok(()), + }; + + let descriptor = match self.descriptors.remove(static_.shim) { + None => return Ok(()), + Some(d) => d, + }; + + // Register the signature of this imported shim + let id = adapters::import( + self.module, + &mut self.adapters, + import_id, + Function { + arguments: Vec::new(), + shim_idx: 0, + ret: descriptor, + }, + AdapterJsImportKind::Normal, + )?; + + // And then save off that this function is is an instanceof shim for an + // imported item. + let import = self.determine_import(import, &static_.name)?; + self.aux.import_map.insert(id, AuxImport::Static(import)); + Ok(()) + } + + fn import_type( + &mut self, + import: &decode::Import<'_>, + type_: &decode::ImportType<'_>, + ) -> Result<(), Error> { + let (import_id, _id) = match self.function_imports.get(type_.instanceof_shim) { + Some(pair) => *pair, + None => return Ok(()), + }; + + // Register the signature of this imported shim + let id = adapters::import( + self.module, + &mut self.adapters, + import_id, + Function { + arguments: vec![Descriptor::Ref(Box::new(Descriptor::Anyref))], + shim_idx: 0, + ret: Descriptor::Boolean, + }, + AdapterJsImportKind::Normal, + )?; + + // And then save off that this function is is an instanceof shim for an + // imported item. + let import = self.determine_import(import, &type_.name)?; + self.aux + .import_map + .insert(id, AuxImport::Instanceof(import)); + Ok(()) + } + + fn enum_(&mut self, enum_: decode::Enum<'_>) -> Result<(), Error> { + let aux = AuxEnum { + name: enum_.name.to_string(), + comments: concatenate_comments(&enum_.comments), + variants: enum_ + .variants + .iter() + .map(|v| (v.name.to_string(), v.value)) + .collect(), + }; + self.aux.enums.push(aux); + Ok(()) + } + + fn struct_(&mut self, struct_: decode::Struct<'_>) -> Result<(), Error> { + for field in struct_.fields { + let getter = wasm_bindgen_shared::struct_field_get(&struct_.name, &field.name); + let setter = wasm_bindgen_shared::struct_field_set(&struct_.name, &field.name); + let descriptor = match self.descriptors.remove(&getter) { + None => continue, + Some(d) => d, + }; + + // Register a webidl transformation for the getter + let (getter_id, _) = self.function_exports[&getter]; + let getter_descriptor = Function { + arguments: vec![Descriptor::I32], + shim_idx: 0, + ret: descriptor.clone(), + }; + let getter_id = adapters::export( + self.module, + &mut self.adapters, + getter_id, + getter_descriptor, + )?; + self.aux.export_map.insert( + getter_id, + AuxExport { + debug_name: format!("getter for `{}::{}`", struct_.name, field.name), + arg_names: None, + comments: concatenate_comments(&field.comments), + kind: AuxExportKind::Getter { + class: struct_.name.to_string(), + field: field.name.to_string(), + }, + }, + ); + + // If present, register information for the setter as well. + if field.readonly { + continue; + } + + let (setter_id, _) = self.function_exports[&setter]; + let setter_descriptor = Function { + arguments: vec![Descriptor::I32, descriptor], + shim_idx: 0, + ret: Descriptor::Unit, + }; + let setter_id = adapters::export( + self.module, + &mut self.adapters, + setter_id, + setter_descriptor, + )?; + self.aux.export_map.insert( + setter_id, + AuxExport { + debug_name: format!("setter for `{}::{}`", struct_.name, field.name), + arg_names: None, + comments: concatenate_comments(&field.comments), + kind: AuxExportKind::Setter { + class: struct_.name.to_string(), + field: field.name.to_string(), + }, + }, + ); + } + let aux = AuxStruct { + name: struct_.name.to_string(), + comments: concatenate_comments(&struct_.comments), + }; + self.aux.structs.push(aux); + + let wrap_constructor = wasm_bindgen_shared::new_function(struct_.name); + if let Some((import_id, _id)) = self.function_imports.get(&wrap_constructor) { + let signature = Function { + shim_idx: 0, + arguments: vec![Descriptor::I32], + ret: Descriptor::Anyref, + }; + let id = adapters::import( + self.module, + &mut self.adapters, + *import_id, + signature, + AdapterJsImportKind::Normal, + )?; + self.aux + .import_map + .insert(id, AuxImport::WrapInExportedClass(struct_.name.to_string())); + } + + Ok(()) + } + + fn determine_import(&self, import: &decode::Import<'_>, item: &str) -> Result { + let is_local_snippet = match import.module { + decode::ImportModule::Named(s) => self.aux.local_modules.contains_key(s), + decode::ImportModule::RawNamed(_) => false, + decode::ImportModule::Inline(_) => true, + decode::ImportModule::None => false, + }; + + // Similar to `--target no-modules`, only allow vendor prefixes + // basically for web apis, shouldn't be necessary for things like npm + // packages or other imported items. + let vendor_prefixes = self.vendor_prefixes.get(item); + if let Some(vendor_prefixes) = vendor_prefixes { + assert!(vendor_prefixes.len() > 0); + + if is_local_snippet { + bail!( + "local JS snippets do not support vendor prefixes for \ + the import of `{}` with a polyfill of `{}`", + item, + &vendor_prefixes[0] + ); + } + if let decode::ImportModule::Named(module) = &import.module { + bail!( + "import of `{}` from `{}` has a polyfill of `{}` listed, but + vendor prefixes aren't supported when importing from modules", + item, + module, + &vendor_prefixes[0], + ); + } + if let Some(ns) = &import.js_namespace { + bail!( + "import of `{}` through js namespace `{}` isn't supported \ + right now when it lists a polyfill", + item, + ns + ); + } + return Ok(JsImport { + name: JsImportName::VendorPrefixed { + name: item.to_string(), + prefixes: vendor_prefixes.clone(), + }, + fields: Vec::new(), + }); + } + + let (name, fields) = match import.js_namespace { + Some(ns) => (ns, vec![item.to_string()]), + None => (item, Vec::new()), + }; + + let name = match import.module { + decode::ImportModule::Named(module) if is_local_snippet => JsImportName::LocalModule { + module: module.to_string(), + name: name.to_string(), + }, + decode::ImportModule::Named(module) | decode::ImportModule::RawNamed(module) => { + JsImportName::Module { + module: module.to_string(), + name: name.to_string(), + } + } + decode::ImportModule::Inline(idx) => { + let offset = self + .aux + .snippets + .get(self.unique_crate_identifier) + .map(|s| s.len()) + .unwrap_or(0); + JsImportName::InlineJs { + unique_crate_identifier: self.unique_crate_identifier.to_string(), + snippet_idx_in_crate: idx as usize + offset, + name: name.to_string(), + } + } + decode::ImportModule::None => JsImportName::Global { + name: name.to_string(), + }, + }; + Ok(JsImport { name, fields }) + } fn standard(&mut self, std: &wit_walrus::WasmInterfaceTypes) -> Result<(), Error> { // for (_id, bind) in std.binds.iter() { diff --git a/crates/cli-support/src/wit/nonstandard.rs b/crates/cli-support/src/wit/nonstandard.rs index 083064589d0..ca355713af9 100644 --- a/crates/cli-support/src/wit/nonstandard.rs +++ b/crates/cli-support/src/wit/nonstandard.rs @@ -172,7 +172,6 @@ pub enum AuxImport { Closure { mutable: bool, // whether or not this was a `FnMut` closure dtor: u32, // table element index of the destructor function - binding_idx: u32, nargs: usize, }, diff --git a/crates/cli-support/src/wit/standard.rs b/crates/cli-support/src/wit/standard.rs index 81ddf883754..3a235ded43a 100644 --- a/crates/cli-support/src/wit/standard.rs +++ b/crates/cli-support/src/wit/standard.rs @@ -87,6 +87,7 @@ pub enum Instruction { LoadRetptr { ty: AdapterType, offset: usize }, /// An instruction which pushes the return pointer onto the stack. Retptr, + /// Pops a `bool` from the stack and pushes an `i32` equivalent I32FromBool, /// Pops a `string` from the stack and pushes the first character as `i32` From 41455dee32672d5beca7e186d729ceba75a4e97c Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 21 Nov 2019 12:18:37 -0800 Subject: [PATCH 04/35] Implement support for loading standard sections --- crates/cli-support/src/wit/mod.rs | 337 ++++++++++++++++-------------- 1 file changed, 175 insertions(+), 162 deletions(-) diff --git a/crates/cli-support/src/wit/mod.rs b/crates/cli-support/src/wit/mod.rs index 8a142f9b598..36cd9de2b6c 100644 --- a/crates/cli-support/src/wit/mod.rs +++ b/crates/cli-support/src/wit/mod.rs @@ -896,174 +896,187 @@ impl<'a> Context<'a> { } fn standard(&mut self, std: &wit_walrus::WasmInterfaceTypes) -> 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(|| anyhow!("missing export function for webidl binding"))?; - // let id = export.id(); - // self.standard_export(binding, id)?; - // } - // walrus::FunctionKind::Uninitialized(_) => unreachable!(), - // } - // } + let mut walrus2us = HashMap::new(); + let params_and_results = |id: wit_walrus::TypeId| -> (Vec<_>, Vec<_>) { + let ty = std.types.get(id); + let params = ty + .params() + .iter() + .cloned() + .map(AdapterType::from_wit) + .collect(); + let results = ty + .results() + .iter() + .cloned() + .map(AdapterType::from_wit) + .collect(); + (params, results) + }; + + // Register all imports, allocating our own id for them and configuring + // where the JS value for the import is coming from. + for import in std.imports.iter() { + let func = std.funcs.get(import.func); + let (params, results) = params_and_results(func.ty); + let id = self.adapters.append( + params, + results, + AdapterKind::Import { + module: import.module.clone(), + name: import.name.clone(), + kind: AdapterJsImportKind::Normal, + }, + ); + walrus2us.insert(import.func, 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()); + } + + // Register all functions, allocating our own id system for each of the + // functions. + for func in std.funcs.iter() { + if let wit_walrus::FuncKind::Import(_) = func.kind { + continue; + } + let (params, results) = params_and_results(func.ty); + walrus2us.insert( + func.id(), + self.adapters.append( + params, + results, + AdapterKind::Local { + instructions: Vec::new(), + }, + ), + ); + } + + // .. and then actually translate all functions using our id mapping, + // now that we're able to remap all the `CallAdapter` instructions. + for func in std.funcs.iter() { + let instrs = match &func.kind { + wit_walrus::FuncKind::Local(instrs) => instrs, + wit_walrus::FuncKind::Import(_) => continue, + }; + let instrs = instrs + .iter() + .map(|i| match i { + wit_walrus::Instruction::CallAdapter(f) => { + Instruction::CallAdapter(walrus2us[&f]) + } + other => Instruction::Standard(other.clone()), + }) + .collect::>(); + + // Store the instrs into the adapter function directly. + let adapter = self + .adapters + .adapters + .get_mut(&walrus2us[&func.id()]) + .unwrap(); + match &mut adapter.kind { + AdapterKind::Local { instructions } => *instructions = instrs, + _ => unreachable!(), + } + } + + // next up register all exports, ensuring that our export map says + // what's happening as well for JS + for export in std.exports.iter() { + let id = walrus2us[&export.func]; + self.adapters.exports.push((export.name.clone(), 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()); + } + + // ... and finally the `implements` section + for i in std.implements.iter() { + let import_id = match &self.module.funcs.get(i.core_func).kind { + walrus::FunctionKind::Import(i) => i.import, + _ => panic!("malformed wasm interface typess section"), + }; + self.adapters + .implements + .push((import_id, walrus2us[&i.adapter_func])); + } 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 { - // let binding: &ast::FunctionBinding = std - // .bindings - // .get(bind.binding) - // .ok_or_else(|| anyhow!("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[..], - // ), - // ast::FunctionBinding::Import(e) => ( - // e.wasm_ty, - // e.webidl_ty, - // &e.result.bindings[..], - // e.params.bindings.as_slice(), - // ), - // }; - // let webidl_ty = standard::copy_ty(&mut self.bindings.types, webidl_ty, &std.types); - // let webidl_ty = match webidl_ty { - // ast::WebidlTypeRef::Id(id) => ::wrap(id), - // _ => bail!("invalid webidl type listed"), - // }; - // - // 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, - // }) - // } - // - // /// 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> { - // let mut imports_counted = 0; - // for import in self.module.imports.iter() { - // if import.module != PLACEHOLDER_MODULE { - // continue; - // } - // match import.kind { - // walrus::ImportKind::Function(_) => {} - // _ => bail!("import from `{}` was not a function", PLACEHOLDER_MODULE), - // } - // - // // Ensure that everything imported from the `__wbindgen_placeholder__` - // // module has a location listed as to where it's expected to be - // // imported from. - // if !self.aux.import_map.contains_key(&import.id()) { - // bail!( - // "import of `{}` doesn't have an import map item listed", - // import.name - // ); - // } - // - // // Also make sure there's a binding listed for it. - // if !self.bindings.imports.contains_key(&import.id()) { - // bail!("import of `{}` doesn't have a binding listed", import.name); - // } - // imports_counted += 1; - // } - // - // // Make sure there's no extraneous bindings that weren't actually - // // imported in the module. - // if self.aux.import_map.len() != imports_counted { - // bail!("import map is larger than the number of imports"); - // } - // if self.bindings.imports.len() != imports_counted { - // bail!("import binding map is larger than the number of imports"); - // } - // - // // Make sure the export map and export bindings map contain the same - // // number of entries. - // for id in self.bindings.exports.keys() { - // if !self.aux.export_map.contains_key(id) { - // bail!("bindings map has an entry that the export map does not"); - // } - // } - // - // if self.bindings.exports.len() != self.aux.export_map.len() { - // bail!("export map and export bindings map have different sizes"); - // } + let mut imports_counted = 0; + let mut implemented = HashMap::new(); + for (core, adapter) in self.adapters.implements.iter() { + implemented.insert(core, adapter); + } + for import in self.module.imports.iter() { + if import.module != PLACEHOLDER_MODULE { + continue; + } + match import.kind { + walrus::ImportKind::Function(_) => {} + _ => bail!("import from `{}` was not a function", PLACEHOLDER_MODULE), + } + let adapter = match implemented.remove(&import.id()) { + Some(id) => id, + None => { + bail!("import of `{}` doesn't have an adapter listed", import.name); + } + }; + + // Ensure that everything imported from the `__wbindgen_placeholder__` + // module has a location listed as to where it's expected to be + // imported from. + if !self.aux.import_map.contains_key(&adapter) { + bail!( + "import of `{}` doesn't have an import map item listed", + import.name + ); + } + + imports_counted += 1; + } + + // Make sure there's no extraneous adapters that weren't actually + // imported in the module. + if self.aux.import_map.len() != imports_counted { + bail!("import map is larger than the number of imports"); + } + if implemented.len() != 0 { + bail!("more implementations listed than imports"); + } + + // Make sure the export map and export adapters map contain the same + // number of entries. + for (_, id) in self.adapters.exports.iter() { + if !self.aux.export_map.contains_key(id) { + bail!("adapters map has an entry that the export map does not"); + } + } + + if self.adapters.exports.len() != self.aux.export_map.len() { + bail!("export map and export adapters map have different sizes"); + } Ok(()) } From f6710bffe796897749fa2e1a0f6e063083406f6e Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 22 Nov 2019 07:59:21 -0800 Subject: [PATCH 05/35] wip --- crates/cli-support/src/lib.rs | 76 +-- crates/cli-support/src/multivalue.rs | 82 ++++ crates/cli-support/src/wit/adapters.rs | 192 -------- crates/cli-support/src/wit/incoming.rs | 245 +++++----- crates/cli-support/src/wit/mod.rs | 271 ++++++++--- crates/cli-support/src/wit/outgoing.rs | 72 ++- crates/cli-support/src/wit/section.rs | 408 ++++++++++++++++ crates/cli-support/src/wit/standard.rs | 619 +++---------------------- crates/multi-value-xform/src/lib.rs | 37 +- 9 files changed, 932 insertions(+), 1070 deletions(-) create mode 100644 crates/cli-support/src/multivalue.rs delete mode 100644 crates/cli-support/src/wit/adapters.rs create mode 100644 crates/cli-support/src/wit/section.rs diff --git a/crates/cli-support/src/lib.rs b/crates/cli-support/src/lib.rs index 6475aa6cf44..5ddb4559d55 100755 --- a/crates/cli-support/src/lib.rs +++ b/crates/cli-support/src/lib.rs @@ -12,6 +12,7 @@ use walrus::Module; use wasm_bindgen_wasm_conventions as wasm_conventions; // mod anyref; +mod multivalue; mod decode; mod descriptor; mod descriptors; @@ -349,17 +350,16 @@ impl Bindgen { // if self.anyref { // anyref::process(&mut module, self.wasm_interface_types)?; // } - // - // let aux = module - // .customs - // .delete_typed::() - // .expect("aux section should be present"); - // let mut bindings = module - // .customs - // .delete_typed::() - // .unwrap(); - panic!() + let aux = module + .customs + .delete_typed::() + .expect("aux section should be present"); + let mut adapters = module + .customs + .delete_typed::() + .unwrap(); + // // Now that our module is massaged and good to go, feed it into the JS // // shim generation which will actually generate JS for all this. // let (npm_dependencies, (js, ts)) = { @@ -368,34 +368,32 @@ impl Bindgen { // let npm_dependencies = cx.npm_dependencies.clone(); // (npm_dependencies, cx.finalize(stem)?) // }; - // - // if self.wasm_interface_types { - // if self.multi_value { - // webidl::standard::add_multi_value(&mut module, &mut bindings) - // .context("failed to transform return pointers into multi-value Wasm")?; - // } - // webidl::standard::add_section(&mut module, &aux, &bindings) - // .with_context(|| "failed to generate a standard wasm bindings custom section")?; - // } else { - // if self.multi_value { - // anyhow::bail!( - // "Wasm multi-value is currently only available when \ - // Wasm interface types is also enabled" - // ); - // } - // } - // - // // If we exported the shadow stack pointer earlier, remove it from the - // // export set now. - // if exported_shadow_stack_pointer { - // wasm_conventions::unexport_shadow_stack_pointer(&mut module)?; - // // The shadow stack pointer is potentially unused now, but since it - // // most likely _is_ in use, we don't pay the cost of a full GC here - // // just to remove one potentially unnecessary global. - // // - // // walrus::passes::gc::run(&mut module); - // } - // + + if self.wasm_interface_types { + multivalue::run(&mut module, &mut adapters) + .context("failed to transform return pointers into multi-value Wasm")?; + wit::section::add(&mut module, &aux, &adapters) + .context("failed to generate a standard wasm bindings custom section")?; + } else { + if self.multi_value { + anyhow::bail!( + "Wasm multi-value is currently only available when \ + Wasm interface types is also enabled" + ); + } + } + + // If we exported the shadow stack pointer earlier, remove it from the + // export set now. + if exported_shadow_stack_pointer { + wasm_conventions::unexport_shadow_stack_pointer(&mut module)?; + // The shadow stack pointer is potentially unused now, but since it + // most likely _is_ in use, we don't pay the cost of a full GC here + // just to remove one potentially unnecessary global. + // + // walrus::passes::gc::run(&mut module); + } + // Ok(Output { // module, // stem: stem.to_string(), @@ -408,6 +406,8 @@ impl Bindgen { // typescript: self.typescript, // wasm_interface_types: self.wasm_interface_types, // }) + + panic!() } fn local_module_name(&self, module: &str) -> String { diff --git a/crates/cli-support/src/multivalue.rs b/crates/cli-support/src/multivalue.rs new file mode 100644 index 00000000000..d373da60e2f --- /dev/null +++ b/crates/cli-support/src/multivalue.rs @@ -0,0 +1,82 @@ +use crate::descriptor::VectorKind; +use crate::wit::{Adapter, NonstandardWitSection, WasmBindgenAux}; +use crate::wit::{AdapterKind, Instruction}; +use crate::wit::{AuxExportKind, AuxImport, AuxValue, JsImport, JsImportName}; +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; + +pub fn run( + module: &mut Module, + adapters: &mut NonstandardWitSection, +) -> Result<(), Error> { + let mut to_xform = Vec::new(); + let mut slots = Vec::new(); + + for (_, adapter) in adapters.adapters.iter_mut() { + extract_xform(adapter, &mut to_xform, &mut slots); + } + 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)?; + let wrappers = multi_value_xform::run(module, memory, shadow_stack_pointer, &to_xform)?; + + for (slot, id) in slots.into_iter().zip(wrappers) { + *slot = id; + } + + Ok(()) +} + +fn extract_xform<'a>( + adapter: &'a mut Adapter, + to_xform: &mut Vec<(walrus::FunctionId, usize, Vec)>, + slots: &mut Vec<&'a mut walrus::FunctionId>, +) { + let instructions = match &mut adapter.kind { + AdapterKind::Local { instructions } => instructions, + AdapterKind::Import { .. } => return, + }; + + // If the first instruction is a `Retptr`, then this must be an exported + // adapter which calls a wasm-defined function. Something we'd like to + // adapt to multi-value! + if let Some(Instruction::Retptr) = instructions.first() { + instructions.remove(0); + let mut types = Vec::new(); + instructions.retain(|instruction| match instruction { + Instruction::LoadRetptr { ty, .. } => { + types.push(ty.to_wasm().unwrap()); + false + } + _ => true, + }); + let id = instructions + .iter_mut() + .filter_map(|i| match i { + Instruction::Standard(wit_walrus::Instruction::CallCore(f)) => Some(f), + _ => None, + }) + .next() + .expect("should have found call-core"); + + // LLVM currently always uses the first parameter for the return + // pointer. We hard code that here, since we have no better option. + to_xform.push((*id, 0, types)); + slots.push(id); + return; + } + + // If the last instruction is a `StoreRetptr`, then this must be an adapter + // which is calling + // + // TODO: handle this case + if let Some(Instruction::StoreRetptr { .. }) = instructions.last() {} +} diff --git a/crates/cli-support/src/wit/adapters.rs b/crates/cli-support/src/wit/adapters.rs deleted file mode 100644 index bca40bc2e40..00000000000 --- a/crates/cli-support/src/wit/adapters.rs +++ /dev/null @@ -1,192 +0,0 @@ -//! Location where `Adapter` functions are actually created. -//! -//! This module is tasked with converting `Descriptor::Function` instances to -//! `Adapter` functions. It uses the incoming/outgoing modules/builders to do -//! most of the heavy lifting, and then this is the glue around the edges to -//! make sure everything is processed, hooked up in the second, and then -//! inserted into the right map. -//! -//! This module is called from `src/wit/mod.rs` exclusively to populate the -//! imports/exports/elements of the bindings section. Most of this module is -//! largely just connecting the dots! - -use crate::descriptor::Function; -use crate::wit::incoming::IncomingBuilder; -use crate::wit::outgoing::OutgoingBuilder; -use crate::wit::{Adapter, AdapterId, AdapterJsImportKind, NonstandardWitSection}; -use crate::wit::{AdapterKind, AdapterType, Instruction}; -use anyhow::{format_err, Error}; -use walrus::{FunctionId, Module, ValType}; - -/// Adds an element to the `bindings.imports` map for the `import` specified -/// that is supposed to have the signature specified in `binding`. This also -/// expects that the imported item is called as `kind`. -pub fn import( - module: &mut Module, - adapters: &mut NonstandardWitSection, - import: walrus::ImportId, - signature: Function, - kind: AdapterJsImportKind, -) -> Result { - let import = module.imports.get(import); - let (import_module, import_name) = (import.module.clone(), import.name.clone()); - let id = match import.kind { - walrus::ImportKind::Function(f) => f, - _ => unreachable!(), - }; - let import_id = import.id(); - - // Process the returned type first to see if it needs an out-pointer. This - // happens if the results of the incoming arguments translated to wasm take - // up more than one type. - let mut incoming = IncomingBuilder::default(); - incoming.process(&signature.ret)?; - let uses_retptr = incoming.output.len() > 1; - - // Process the argument next, allocating space of the return value if one - // was present. Additionally configure the `module` and `adapters` to allow - // usage of closures going out to the import. - let mut outgoing = OutgoingBuilder::default(); - outgoing.module = Some(module); - outgoing.adapters = Some(adapters); - if uses_retptr { - outgoing.input.push(AdapterType::I32); - } - for arg in signature.arguments.iter() { - outgoing.process(arg)?; - } - - // A bit of destructuring to kill the borrow that the outgoing builder has - // on the module/bindings. - let OutgoingBuilder { - input: outgoing_input, - output: outgoing_output, - instructions: outgoing_instructions, - .. - } = outgoing; - - // Build up the list of instructions for our adapter function. We start out - // with all the outgoing instructions which convert all wasm params to the - // desired types to call our import... - let mut instructions = outgoing_instructions; - - // ... and then we actually call our import. We synthesize an adapter - // definition for it with the appropriate types here on the fly. - let f = adapters.append( - outgoing_output, - incoming.input, - AdapterKind::Import { - module: import_module, - name: import_name, - kind, - }, - ); - instructions.push(Instruction::CallAdapter(f)); - - // ... and then we follow up with a conversion of the incoming type back to - // wasm. - instructions.extend(incoming.instructions); - - // ... and if a return pointer is in use then we need to store the types on - // the stack into the wasm return pointer. Note that we iterate in reverse - // here because the last result is the top value on the stack. - let results = if uses_retptr { - for (i, ty) in incoming.output.into_iter().enumerate().rev() { - instructions.push(Instruction::StoreRetptr { offset: i, ty }); - } - Vec::new() - } else { - incoming.output - }; - let id = adapters.append(outgoing_input, results, AdapterKind::Local { instructions }); - adapters.implements.push((import_id, id)); - Ok(id) -} - -/// Adds an element to `bindings.exports` for the `export` specified to have the -/// `binding` given. -pub fn export( - module: &mut Module, - adapters: &mut NonstandardWitSection, - export: walrus::ExportId, - signature: Function, -) -> Result { - let export = module.exports.get(export); - let name = export.name.clone(); - let id = match export.item { - walrus::ExportItem::Function(f) => f, - _ => unreachable!(), - }; - let export_id = export.id(); - // Do the actual heavy lifting elsewhere to generate the `binding`. - let id = register_wasm_export(module, adapters, id, signature)?; - adapters.exports.push((name, id)); - Ok(id) -} - -/// Like `export` except registers an adapter for a table element. In -/// this case ensures that the table element `idx` is specified to have the -/// `signature` specified. -pub fn table_element( - module: &mut Module, - adapters: &mut NonstandardWitSection, - idx: u32, - signature: Function, -) -> Result { - let table = module - .tables - .main_function_table()? - .ok_or_else(|| format_err!("no function table found"))?; - let table = module.tables.get(table); - let functions = match &table.kind { - walrus::TableKind::Function(f) => f, - _ => unreachable!(), - }; - let id = functions.elements[idx as usize].unwrap(); - // like above, largely just defer the work elsewhere - Ok(register_wasm_export(module, adapters, id, signature)?) -} - -fn register_wasm_export( - module: &mut Module, - adapters: &mut NonstandardWitSection, - id: walrus::FunctionId, - signature: Function, -) -> Result { - // Figure out how to translate all the incoming arguments ... - let mut incoming = IncomingBuilder::default(); - for arg in signature.arguments.iter() { - incoming.process(arg)?; - } - - // ... then the returned value being translated back - let mut outgoing = OutgoingBuilder::default(); - outgoing.process(&signature.ret)?; - let uses_retptr = outgoing.input.len() > 1; - - // Our instruction stream starts out with the return pointer as the first - // argument to the wasm function, if one is in use. Then we convert - // everything to wasm types. - // - // After calling the core wasm function we need to load all the return - // pointer arguments if there were any, otherwise we simply convert - // everything into the outgoing arguments. - let mut instructions = Vec::new(); - if uses_retptr { - instructions.push(Instruction::Retptr); - } - instructions.extend(incoming.instructions); - instructions.push(Instruction::Standard(wit_walrus::Instruction::CallCore(id))); - if uses_retptr { - for (i, ty) in incoming.output.into_iter().enumerate() { - instructions.push(Instruction::LoadRetptr { offset: i, ty }); - } - } - instructions.extend(outgoing.instructions); - - Ok(adapters.append( - incoming.input, - outgoing.output, - AdapterKind::Local { instructions }, - )) -} diff --git a/crates/cli-support/src/wit/incoming.rs b/crates/cli-support/src/wit/incoming.rs index 0435e126fce..0fd0d5ddd6a 100644 --- a/crates/cli-support/src/wit/incoming.rs +++ b/crates/cli-support/src/wit/incoming.rs @@ -8,38 +8,31 @@ //! the `outgoing.rs` module. use crate::descriptor::{Descriptor, VectorKind}; -use crate::wit::{AdapterType, Instruction}; +use crate::wit::{AdapterType, Instruction, InstructionBuilder}; use anyhow::{bail, format_err, Error}; -use walrus::ValType; +use walrus::{FunctionId, MemoryId, ValType}; -#[derive(Default)] -pub struct IncomingBuilder { - pub input: Vec, - pub output: Vec, - pub instructions: Vec, -} - -impl IncomingBuilder { +impl InstructionBuilder<'_, '_> { /// Process a `Descriptor` as if it's being passed from JS to Rust. This /// will skip `Unit` and otherwise internally add instructions necessary to /// convert the foreign type into the Rust bits. - pub fn process(&mut self, arg: &Descriptor) -> Result<(), Error> { + pub fn incoming(&mut self, arg: &Descriptor) -> Result<(), Error> { if let Descriptor::Unit = arg { return Ok(()); } - // This is a wrapper around `_process` to have a number of sanity checks + // This is a wrapper around `_incoming` to have a number of sanity checks // that we don't forget things. We should always produce at least one // wasm arge and exactly one webidl arg. Additionally the number of // bindings should always match the number of webidl types for now. let input_before = self.input.len(); let output_before = self.output.len(); - self._process(arg)?; + self._incoming(arg)?; assert_eq!(output_before + 1, self.output.len()); assert!(input_before < self.input.len()); Ok(()) } - fn _process(&mut self, arg: &Descriptor) -> Result<(), Error> { + fn _incoming(&mut self, arg: &Descriptor) -> Result<(), Error> { use walrus::ValType as WasmVT; use wit_walrus::ValType as WitVT; match arg { @@ -82,17 +75,33 @@ impl IncomingBuilder { self.output.push(AdapterType::F64); } Descriptor::Enum { .. } => self.number(WitVT::U32, WasmVT::I32), - Descriptor::Ref(d) => self.process_ref(false, d)?, - Descriptor::RefMut(d) => self.process_ref(true, d)?, - Descriptor::Option(d) => self.process_option(d)?, + Descriptor::Ref(d) => self.incoming_ref(false, d)?, + Descriptor::RefMut(d) => self.incoming_ref(true, d)?, + Descriptor::Option(d) => self.incoming_option(d)?, - Descriptor::String | Descriptor::CachedString | Descriptor::Vector(_) => { - panic!() - // let kind = arg.vector_kind().ok_or_else(|| { - // format_err!("unsupported argument type for calling Rust function from JS {:?}", arg) - // })? ; - // self.wasm.extend(&[ValType::I32; 2]); - // self.alloc_copy_kind(kind) + Descriptor::String | Descriptor::CachedString => { + self.get(AdapterType::String); + let std = wit_walrus::Instruction::StringToMemory { + malloc: self.cx.malloc()?, + mem: self.cx.memory()?, + }; + self.instructions.push(Instruction::Standard(std)); + self.output.push(AdapterType::I32); + self.output.push(AdapterType::I32); + } + + Descriptor::Vector(_) => { + let kind = arg.vector_kind().ok_or_else(|| { + format_err!("unsupported argument type for calling Rust function from JS {:?}", arg) + })?; + self.get(AdapterType::Vector(kind)); + self.instructions.push(Instruction::VectorToMemory { + kind, + malloc: self.cx.malloc()?, + mem: self.cx.memory()?, + }); + self.output.push(AdapterType::I32); + self.output.push(AdapterType::I32); } // Can't be passed from JS to Rust yet @@ -114,7 +123,7 @@ impl IncomingBuilder { Ok(()) } - fn process_ref(&mut self, mutable: bool, arg: &Descriptor) -> Result<(), Error> { + fn incoming_ref(&mut self, mutable: bool, arg: &Descriptor) -> Result<(), Error> { match arg { Descriptor::RustStruct(class) => { self.get(AdapterType::Anyref); @@ -129,24 +138,34 @@ impl IncomingBuilder { self.instructions.push(Instruction::I32FromAnyrefBorrow); self.output.push(AdapterType::I32); } - Descriptor::String | Descriptor::CachedString | Descriptor::Slice(_) => { - panic!() - // let kind = arg.vector_kind().ok_or_else(|| { - // format_err!( - // "unsupported slice type for calling Rust function from JS {:?}", - // arg - // ) - // })?; - // self.wasm.extend(&[ValType::I32; 2]); - // if mutable { - // self.bindings.push(NonstandardIncoming::MutableSlice { - // kind, - // val: self.expr_get(), - // }); - // self.webidl.push(ast::WebidlScalarType::Any); - // } else { - // self.alloc_copy_kind(kind) - // } + Descriptor::String | Descriptor::CachedString => { + // This allocation is cleaned up once it's received in Rust. + self.get(AdapterType::String); + let std = wit_walrus::Instruction::StringToMemory { + malloc: self.cx.malloc()?, + mem: self.cx.memory()?, + }; + self.instructions.push(Instruction::Standard(std)); + self.output.push(AdapterType::I32); + self.output.push(AdapterType::I32); + } + Descriptor::Slice(_) => { + // like strings, this allocation is cleaned up after being + // received in Rust. + let kind = arg.vector_kind().ok_or_else(|| { + format_err!( + "unsupported argument type for calling Rust function from JS {:?}", + arg + ) + })?; + self.get(AdapterType::Vector(kind)); + self.instructions.push(Instruction::VectorToMemory { + kind, + malloc: self.cx.malloc()?, + mem: self.cx.memory()?, + }); + self.output.push(AdapterType::I32); + self.output.push(AdapterType::I32); } _ => bail!( "unsupported reference argument type for calling Rust function from JS: {:?}", @@ -156,7 +175,7 @@ impl IncomingBuilder { Ok(()) } - fn process_option(&mut self, arg: &Descriptor) -> Result<(), Error> { + fn incoming_option(&mut self, arg: &Descriptor) -> Result<(), Error> { match arg { Descriptor::Anyref => { self.get(AdapterType::Anyref); @@ -205,40 +224,37 @@ impl IncomingBuilder { self.output.push(AdapterType::I32); } - // Descriptor::Ref(_) | Descriptor::RefMut(_) => { - // let mutable = match arg { - // Descriptor::Ref(_) => false, - // _ => true, - // }; - // let kind = arg.vector_kind().ok_or_else(|| { - // format_err!( - // "unsupported optional slice type for calling Rust function from JS {:?}", - // arg - // ) - // })?; - // self.bindings.push(NonstandardIncoming::OptionSlice { - // kind, - // val: self.expr_get(), - // mutable, - // }); - // self.wasm.extend(&[ValType::I32; 2]); - // self.webidl.push(ast::WebidlScalarType::Any); - // } - // - // Descriptor::String | Descriptor::CachedString | Descriptor::Vector(_) => { - // let kind = arg.vector_kind().ok_or_else(|| { - // format_err!( - // "unsupported optional slice type for calling Rust function from JS {:?}", - // arg - // ) - // })?; - // self.bindings.push(NonstandardIncoming::OptionVector { - // kind, - // val: self.expr_get(), - // }); - // self.wasm.extend(&[ValType::I32; 2]); - // self.webidl.push(ast::WebidlScalarType::Any); - // } + Descriptor::Ref(_) | Descriptor::RefMut(_) => { + let mutable = match arg { + Descriptor::Ref(_) => false, + _ => true, + }; + let kind = arg.vector_kind().ok_or_else(|| { + format_err!( + "unsupported optional slice type for calling Rust function from JS {:?}", + arg + ) + })?; + self.get(AdapterType::Anyref); + self.instructions + .push(Instruction::OptionSlice { kind, mutable }); + self.output.push(AdapterType::I32); + self.output.push(AdapterType::I32); + } + + Descriptor::String | Descriptor::CachedString | Descriptor::Vector(_) => { + let kind = arg.vector_kind().ok_or_else(|| { + format_err!( + "unsupported optional slice type for calling Rust function from JS {:?}", + arg + ) + })?; + self.get(AdapterType::Anyref); + self.instructions.push(Instruction::OptionVector { kind }); + self.output.push(AdapterType::I32); + self.output.push(AdapterType::I32); + } + _ => bail!( "unsupported optional argument type for calling Rust function from JS: {:?}", arg @@ -247,71 +263,18 @@ impl IncomingBuilder { Ok(()) } - fn get(&mut self, ty: AdapterType) { - let idx = self.input.len() as u32 - 1; + pub fn get(&mut self, ty: AdapterType) { self.input.push(ty); - let std = wit_walrus::Instruction::ArgGet(idx); - self.instructions.push(Instruction::Standard(std)); - } - // fn alloc_func_name(&self) -> String { - // "__wbindgen_malloc".to_string() - // } - // - // fn alloc_copy_kind(&mut self, kind: VectorKind) { - // use wasm_webidl_bindings::ast::WebidlScalarType::*; - // - // match kind { - // VectorKind::I8 => self.alloc_copy(Int8Array), - // VectorKind::U8 => self.alloc_copy(Uint8Array), - // VectorKind::ClampedU8 => self.alloc_copy(Uint8ClampedArray), - // VectorKind::I16 => self.alloc_copy(Int16Array), - // VectorKind::U16 => self.alloc_copy(Uint16Array), - // VectorKind::I32 => self.alloc_copy(Int32Array), - // VectorKind::U32 => self.alloc_copy(Uint32Array), - // VectorKind::F32 => self.alloc_copy(Float32Array), - // VectorKind::F64 => self.alloc_copy(Float64Array), - // VectorKind::String => { - // let expr = ast::IncomingBindingExpressionAllocUtf8Str { - // alloc_func_name: self.alloc_func_name(), - // expr: Box::new(self.expr_get()), - // }; - // self.webidl.push(DomString); - // self.bindings - // .push(NonstandardIncoming::Standard(expr.into())); - // } - // VectorKind::I64 | VectorKind::U64 => { - // let signed = match kind { - // VectorKind::I64 => true, - // _ => false, - // }; - // self.bindings.push(NonstandardIncoming::AllocCopyInt64 { - // alloc_func_name: self.alloc_func_name(), - // expr: Box::new(self.expr_get()), - // signed, - // }); - // self.webidl.push(Any); - // } - // VectorKind::Anyref => { - // self.bindings - // .push(NonstandardIncoming::AllocCopyAnyrefArray { - // alloc_func_name: self.alloc_func_name(), - // expr: Box::new(self.expr_get()), - // }); - // self.webidl.push(Any); - // } - // } - // } - // - // fn alloc_copy(&mut self, webidl: ast::WebidlScalarType) { - // let expr = ast::IncomingBindingExpressionAllocCopy { - // alloc_func_name: self.alloc_func_name(), - // expr: Box::new(self.expr_get()), - // }; - // self.webidl.push(webidl); - // self.bindings - // .push(NonstandardIncoming::Standard(expr.into())); - // } + // If we're generating instructions in the return position then the + // arguments are already on the stack to consume, otherwise we need to + // fetch them from the parameters. + if !self.return_position { + let idx = self.input.len() as u32 - 1; + let std = wit_walrus::Instruction::ArgGet(idx); + self.instructions.push(Instruction::Standard(std)); + } + } fn number(&mut self, input: wit_walrus::ValType, output: walrus::ValType) { self.get(AdapterType::from_wit(input)); diff --git a/crates/cli-support/src/wit/mod.rs b/crates/cli-support/src/wit/mod.rs index 36cd9de2b6c..1cf51ddba89 100644 --- a/crates/cli-support/src/wit/mod.rs +++ b/crates/cli-support/src/wit/mod.rs @@ -7,15 +7,16 @@ use std::borrow::Cow; use std::collections::{HashMap, HashSet}; use std::path::PathBuf; use std::str; +use walrus::MemoryId; use walrus::{ExportId, FunctionId, ImportId, Module, TypedCustomSectionId}; use wasm_bindgen_shared::struct_function_export_name; const PLACEHOLDER_MODULE: &str = "__wbindgen_placeholder__"; -mod adapters; mod incoming; mod nonstandard; mod outgoing; +pub mod section; mod standard; pub use self::nonstandard::*; pub use self::standard::*; @@ -27,6 +28,7 @@ struct Context<'a> { aux: WasmBindgenAux, function_exports: HashMap, function_imports: HashMap, + memory: Option, vendor_prefixes: HashMap>, unique_crate_identifier: &'a str, descriptors: HashMap, @@ -35,6 +37,14 @@ struct Context<'a> { support_start: bool, } +struct InstructionBuilder<'a, 'b> { + input: Vec, + output: Vec, + instructions: Vec, + cx: &'a mut Context<'b>, + return_position: bool, +} + pub fn process( module: &mut Module, anyref_enabled: bool, @@ -52,6 +62,7 @@ pub fn process( vendor_prefixes: Default::default(), descriptors: Default::default(), unique_crate_identifier: "", + memory: wasm_bindgen_wasm_conventions::get_memory(module).ok(), module, start_found: false, anyref_enabled, @@ -144,13 +155,7 @@ impl<'a> Context<'a> { arguments: vec![Descriptor::I32; 3], ret: Descriptor::Anyref, }; - adapters::import( - self.module, - &mut self.adapters, - id, - signature, - AdapterJsImportKind::Normal, - )?; + self.import_adapter(id, signature, AdapterJsImportKind::Normal)?; // Synthesize the two integer pointers we pass through which // aren't present in the signature but are present in the wasm // signature. @@ -158,12 +163,7 @@ impl<'a> Context<'a> { let nargs = function.arguments.len(); function.arguments.insert(0, Descriptor::I32); function.arguments.insert(0, Descriptor::I32); - let id = adapters::table_element( - self.module, - &mut self.adapters, - descriptor.shim_idx, - function, - )?; + let id = self.table_element_adapter(descriptor.shim_idx, function)?; self.aux.import_map.insert( id, AuxImport::Closure { @@ -248,13 +248,7 @@ impl<'a> Context<'a> { } fn bind_intrinsic(&mut self, id: ImportId, intrinsic: Intrinsic) -> Result<(), Error> { - let id = adapters::import( - self.module, - &mut self.adapters, - id, - intrinsic.signature(), - AdapterJsImportKind::Normal, - )?; + let id = self.import_adapter(id, intrinsic.signature(), AdapterJsImportKind::Normal)?; self.aux .import_map .insert(id, AuxImport::Intrinsic(intrinsic)); @@ -382,7 +376,7 @@ impl<'a> Context<'a> { None => AuxExportKind::Function(export.function.name.to_string()), }; - let id = adapters::export(self.module, &mut self.adapters, export_id, descriptor)?; + let id = self.export_adapter(export_id, descriptor)?; self.aux.export_map.insert( id, AuxExport { @@ -470,9 +464,7 @@ impl<'a> Context<'a> { // NB: `structural` is ignored for constructors since the // js type isn't expected to change anyway. decode::MethodKind::Constructor => { - let id = adapters::import( - self.module, - &mut self.adapters, + let id = self.import_adapter( import_id, descriptor, AdapterJsImportKind::Constructor, @@ -487,16 +479,7 @@ impl<'a> Context<'a> { } else { AdapterJsImportKind::Normal }; - ( - adapters::import( - self.module, - &mut self.adapters, - import_id, - descriptor, - kind, - )?, - import, - ) + (self.import_adapter(import_id, descriptor, kind)?, import) } } } @@ -504,13 +487,7 @@ impl<'a> Context<'a> { // NB: `structural` is ignored for free functions since it's // expected that the binding isn't changing anyway. None => { - let id = adapters::import( - self.module, - &mut self.adapters, - import_id, - descriptor, - AdapterJsImportKind::Normal, - )?; + let id = self.import_adapter(import_id, descriptor, AdapterJsImportKind::Normal)?; let name = self.determine_import(import, function.name)?; (id, AuxImport::Value(AuxValue::Bare(name))) } @@ -652,9 +629,7 @@ impl<'a> Context<'a> { }; // Register the signature of this imported shim - let id = adapters::import( - self.module, - &mut self.adapters, + let id = self.import_adapter( import_id, Function { arguments: Vec::new(), @@ -682,9 +657,7 @@ impl<'a> Context<'a> { }; // Register the signature of this imported shim - let id = adapters::import( - self.module, - &mut self.adapters, + let id = self.import_adapter( import_id, Function { arguments: vec![Descriptor::Ref(Box::new(Descriptor::Anyref))], @@ -733,12 +706,7 @@ impl<'a> Context<'a> { shim_idx: 0, ret: descriptor.clone(), }; - let getter_id = adapters::export( - self.module, - &mut self.adapters, - getter_id, - getter_descriptor, - )?; + let getter_id = self.export_adapter(getter_id, getter_descriptor)?; self.aux.export_map.insert( getter_id, AuxExport { @@ -763,12 +731,7 @@ impl<'a> Context<'a> { shim_idx: 0, ret: Descriptor::Unit, }; - let setter_id = adapters::export( - self.module, - &mut self.adapters, - setter_id, - setter_descriptor, - )?; + let setter_id = self.export_adapter(setter_id, setter_descriptor)?; self.aux.export_map.insert( setter_id, AuxExport { @@ -789,19 +752,13 @@ impl<'a> Context<'a> { self.aux.structs.push(aux); let wrap_constructor = wasm_bindgen_shared::new_function(struct_.name); - if let Some((import_id, _id)) = self.function_imports.get(&wrap_constructor) { + if let Some((import_id, _id)) = self.function_imports.get(&wrap_constructor).cloned() { let signature = Function { shim_idx: 0, arguments: vec![Descriptor::I32], ret: Descriptor::Anyref, }; - let id = adapters::import( - self.module, - &mut self.adapters, - *import_id, - signature, - AdapterJsImportKind::Normal, - )?; + let id = self.import_adapter(import_id, signature, AdapterJsImportKind::Normal)?; self.aux .import_map .insert(id, AuxImport::WrapInExportedClass(struct_.name.to_string())); @@ -1080,6 +1037,184 @@ impl<'a> Context<'a> { Ok(()) } + + /// Creates an import adapter for the `import` which will have the given + /// `signature`. + /// + /// Note that the JS function imported will be invoked as `kind`. + fn import_adapter( + &mut self, + import: ImportId, + signature: Function, + kind: AdapterJsImportKind, + ) -> Result { + let import = self.module.imports.get(import); + let (import_module, import_name) = (import.module.clone(), import.name.clone()); + let id = match import.kind { + walrus::ImportKind::Function(f) => f, + _ => unreachable!(), + }; + let import_id = import.id(); + + // Process the returned type first to see if it needs an out-pointer. This + // happens if the results of the incoming arguments translated to wasm take + // up more than one type. + let mut ret = self.instruction_builder(true); + ret.incoming(&signature.ret)?; + let uses_retptr = ret.output.len() > 1; + + // Process the argument next, allocating space of the return value if one + // was present. Additionally configure the `module` and `adapters` to allow + // usage of closures going out to the import. + let mut args = ret.cx.instruction_builder(false); + if uses_retptr { + args.input.push(AdapterType::I32); + } + for arg in signature.arguments.iter() { + args.outgoing(arg)?; + } + + // Build up the list of instructions for our adapter function. We start out + // with all the outgoing instructions which convert all wasm params to the + // desired types to call our import... + let mut instructions = args.instructions; + + // ... and then we actually call our import. We synthesize an adapter + // definition for it with the appropriate types here on the fly. + let f = args.cx.adapters.append( + args.output, + ret.input, + AdapterKind::Import { + module: import_module, + name: import_name, + kind, + }, + ); + instructions.push(Instruction::CallAdapter(f)); + + // ... and then we follow up with a conversion of the incoming type + // back to wasm. + instructions.extend(ret.instructions); + + // ... and if a return pointer is in use then we need to store the types on + // the stack into the wasm return pointer. Note that we iterate in reverse + // here because the last result is the top value on the stack. + let results = if uses_retptr { + for (i, ty) in ret.output.into_iter().enumerate().rev() { + instructions.push(Instruction::StoreRetptr { offset: i, ty }); + } + Vec::new() + } else { + ret.output + }; + let id = args + .cx + .adapters + .append(args.input, results, AdapterKind::Local { instructions }); + args.cx.adapters.implements.push((import_id, id)); + Ok(id) + } + + /// Creates an adapter function for the `export` given to have the + /// `signature` specified. + fn export_adapter( + &mut self, + export: ExportId, + signature: Function, + ) -> Result { + let export = self.module.exports.get(export); + let name = export.name.clone(); + let id = match export.item { + walrus::ExportItem::Function(f) => f, + _ => unreachable!(), + }; + let export_id = export.id(); + // Do the actual heavy lifting elsewhere to generate the `binding`. + let id = self.register_export_adapter(id, signature)?; + self.adapters.exports.push((name, id)); + Ok(id) + } + + fn table_element_adapter(&mut self, idx: u32, signature: Function) -> Result { + let table = self + .module + .tables + .main_function_table()? + .ok_or_else(|| anyhow!("no function table found"))?; + let table = self.module.tables.get(table); + let functions = match &table.kind { + walrus::TableKind::Function(f) => f, + _ => unreachable!(), + }; + let id = functions.elements[idx as usize].unwrap(); + // like above, largely just defer the work elsewhere + Ok(self.register_export_adapter(id, signature)?) + } + + fn register_export_adapter( + &mut self, + id: walrus::FunctionId, + signature: Function, + ) -> Result { + // Figure out how to translate all the incoming arguments ... + let mut args = self.instruction_builder(false); + for arg in signature.arguments.iter() { + args.incoming(arg)?; + } + + // ... then the returned value being translated back + let mut ret = args.cx.instruction_builder(true,); + ret.outgoing(&signature.ret)?; + let uses_retptr = ret.input.len() > 1; + + // Our instruction stream starts out with the return pointer as the first + // argument to the wasm function, if one is in use. Then we convert + // everything to wasm types. + // + // After calling the core wasm function we need to load all the return + // pointer arguments if there were any, otherwise we simply convert + // everything into the outgoing arguments. + let mut instructions = Vec::new(); + if uses_retptr { + instructions.push(Instruction::Retptr); + } + instructions.extend(args.instructions); + instructions.push(Instruction::Standard(wit_walrus::Instruction::CallCore(id))); + if uses_retptr { + for (i, ty) in ret.input.into_iter().enumerate() { + instructions.push(Instruction::LoadRetptr { offset: i, ty }); + } + } + instructions.extend(ret.instructions); + + Ok(ret + .cx + .adapters + .append(args.input, ret.output, AdapterKind::Local { instructions })) + } + + fn instruction_builder<'b>(&'b mut self, return_position: bool) -> InstructionBuilder<'b, 'a> { + InstructionBuilder { + cx: self, + input: Vec::new(), + output: Vec::new(), + instructions: Vec::new(), + return_position, + } + } + + fn malloc(&self) -> Result { + self.function_exports + .get("__wbindgen_malloc") + .cloned() + .map(|p| p.1) + .ok_or_else(|| anyhow!("failed to find declaration of `__wbindgen_malloc` in module")) + } + + fn memory(&self) -> Result { + self.memory + .ok_or_else(|| anyhow!("failed to find memory declaration in module")) + } } fn extract_programs<'a>( diff --git a/crates/cli-support/src/wit/outgoing.rs b/crates/cli-support/src/wit/outgoing.rs index ddb302229d6..67b8e642462 100644 --- a/crates/cli-support/src/wit/outgoing.rs +++ b/crates/cli-support/src/wit/outgoing.rs @@ -14,7 +14,7 @@ // //! Rust type to an outgoing binding. use crate::descriptor::{Descriptor, VectorKind}; -use crate::wit::{AdapterType, Instruction, NonstandardWitSection}; +use crate::wit::{AdapterType, Instruction, NonstandardWitSection, InstructionBuilder}; use anyhow::{bail, format_err, Error}; use walrus::{Module, ValType}; @@ -153,50 +153,33 @@ use walrus::{Module, ValType}; // }, // } // -#[derive(Default)] -pub struct OutgoingBuilder<'a> { - pub input: Vec, - pub output: Vec, - pub instructions: Vec, - - // These two arguments are optional and, if set, will enable creating - // `StackClosure` bindings. They're not present for return values from - // exported Rust functions, but they are available for the arguments of - // calling imported functions. - pub module: Option<&'a mut Module>, - pub adapters: Option<&'a mut NonstandardWitSection>, -} - -impl OutgoingBuilder<'_> { - // /// Adds a dummy first argument which is passed through as an integer - // /// representing the return pointer. - // pub fn process_retptr(&mut self) { - // self.standard_as(ValType::I32, ast::WebidlScalarType::Long); - // } - // +impl InstructionBuilder<'_, '_> { /// Processes one more `Descriptor` as an argument to a JS function that /// wasm is calling. /// /// This will internally skip `Unit` and otherwise build up the `bindings` /// map and ensure that it's correctly mapped from wasm to JS. - pub fn process(&mut self, arg: &Descriptor) -> Result<(), Error> { + pub fn outgoing(&mut self, arg: &Descriptor) -> Result<(), Error> { if let Descriptor::Unit = arg { return Ok(()); } - // assert_eq!(self.webidl.len(), self.bindings.len()); - // let wasm_before = self.wasm.len(); - // let webidl_before = self.webidl.len(); - // self._process(arg)?; - // assert_eq!(self.webidl.len(), self.bindings.len()); - // assert_eq!(webidl_before + 1, self.webidl.len()); - // assert!(wasm_before < self.wasm.len()); + // Similar rationale to `incoming.rs` around these sanity checks. + let input_before = self.input.len(); + let output_before = self.output.len(); + self._outgoing(arg)?; + assert_eq!(output_before + 1, self.output.len()); + assert!(input_before < self.input.len()); Ok(()) } - // fn _process(&mut self, arg: &Descriptor) -> Result<(), Error> { - // match arg { - // Descriptor::Boolean => self.standard_as(ValType::I32, ast::WebidlScalarType::Boolean), - // Descriptor::Anyref => self.standard_as(ValType::Anyref, ast::WebidlScalarType::Any), + fn _outgoing(&mut self, arg: &Descriptor) -> Result<(), Error> { + match arg { + Descriptor::Boolean => { + self.get(AdapterType::I32); + self.instructions.push(Instruction::BoolFromI32); + self.output.push(AdapterType::Bool); + } + Descriptor::Anyref => self.standard_as(ValType::Anyref, ast::WebidlScalarType::Any), // Descriptor::I8 => self.standard_as(ValType::I32, ast::WebidlScalarType::Byte), // Descriptor::U8 => self.standard_as(ValType::I32, ast::WebidlScalarType::Octet), // Descriptor::I16 => self.standard_as(ValType::I32, ast::WebidlScalarType::Short), @@ -268,16 +251,17 @@ impl OutgoingBuilder<'_> { // "unsupported argument type for calling JS function from Rust: {:?}", // arg // ), - // - // // nothing to do - // Descriptor::Unit => {} - // - // // Largely synthetic and can't show up - // Descriptor::ClampedU8 => unreachable!(), - // } - // Ok(()) - // } - // + + // nothing to do + Descriptor::Unit => {} + + // Largely synthetic and can't show up + Descriptor::ClampedU8 => unreachable!(), + _ => {} + } + Ok(()) + } + // fn process_ref(&mut self, mutable: bool, arg: &Descriptor) -> Result<(), Error> { // match arg { // Descriptor::Anyref => { diff --git a/crates/cli-support/src/wit/section.rs b/crates/cli-support/src/wit/section.rs new file mode 100644 index 00000000000..5c1bcd973c4 --- /dev/null +++ b/crates/cli-support/src/wit/section.rs @@ -0,0 +1,408 @@ +//! Support for generating a standard wasm interface types +//! +//! This module has all the necessary support for generating a full-fledged +//! standard wasm interface types section as defined by the `wit_walrus` +//! 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::wit::{AuxExportKind, AuxImport, AuxValue, JsImport, JsImportName}; +use crate::wit::{NonstandardWitSection, 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; + +pub fn add( + module: &mut Module, + aux: &WasmBindgenAux, + nonstandard: &NonstandardWitSection, +) -> Result<(), Error> { + let mut section = wit_walrus::WasmInterfaceTypes::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, + ); + } + + module.customs.add(section); + if true { panic!() } + 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::WitScalarType::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) +// } + +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 + ); +} diff --git a/crates/cli-support/src/wit/standard.rs b/crates/cli-support/src/wit/standard.rs index 3a235ded43a..c5b230c3cf1 100644 --- a/crates/cli-support/src/wit/standard.rs +++ b/crates/cli-support/src/wit/standard.rs @@ -1,3 +1,4 @@ +use crate::descriptor::VectorKind; use std::borrow::Cow; use std::collections::HashMap; use walrus::{ImportId, TypedCustomSectionId}; @@ -70,6 +71,7 @@ pub enum AdapterType { Bool, I32, I64, + Vector(VectorKind), } #[derive(Debug, Clone)] @@ -133,6 +135,25 @@ pub enum Instruction { /// 0/1 if it's none/some and second is `ty` value if it was there or 0 if /// it wasn't there. OptionNative { ty: walrus::ValType }, + + /// Pops a vector value of `kind` from the stack, allocates memory with + /// `malloc`, and then copies all the data into `mem`. Pushes the pointer + /// and length as i32. + VectorToMemory { + kind: VectorKind, + malloc: walrus::FunctionId, + mem: walrus::MemoryId, + }, + + /// Pops an anyref, pushes pointer/length or all zeros. Will update original + /// view if mutable. + OptionSlice { kind: VectorKind, mutable: bool }, + + /// Pops an anyref, pushes pointer/length or all zeros + OptionVector { kind: VectorKind }, + + /// pops a `bool`, pushes `i32` + BoolFromI32, } impl AdapterType { @@ -157,14 +178,45 @@ impl AdapterType { pub fn from_wasm(wasm: walrus::ValType) -> Option { Some(match wasm { - walrus::ValType::I32 => AdapterType::S32, - walrus::ValType::I64 => AdapterType::S64, + walrus::ValType::I32 => AdapterType::I32, + walrus::ValType::I64 => AdapterType::I64, walrus::ValType::F32 => AdapterType::F32, walrus::ValType::F64 => AdapterType::F64, walrus::ValType::Anyref => AdapterType::Anyref, walrus::ValType::V128 => return None, }) } + + pub fn to_wasm(&self) -> Option { + Some(match self { + AdapterType::I32 => walrus::ValType::I32, + AdapterType::I64 => walrus::ValType::I64, + AdapterType::F32 => walrus::ValType::F32, + AdapterType::F64 => walrus::ValType::F64, + AdapterType::Anyref => walrus::ValType::Anyref, + _ => return None, + }) + } + + pub fn to_wit(&self) -> Option { + Some(match self { + AdapterType::S8 => wit_walrus::ValType::S8, + AdapterType::S16 => wit_walrus::ValType::S16, + AdapterType::S32 => wit_walrus::ValType::S32, + AdapterType::S64 => wit_walrus::ValType::S64, + AdapterType::U8 => wit_walrus::ValType::U8, + AdapterType::U16 => wit_walrus::ValType::U16, + AdapterType::U32 => wit_walrus::ValType::U32, + AdapterType::U64 => wit_walrus::ValType::U64, + AdapterType::F32 => wit_walrus::ValType::F32, + AdapterType::F64 => wit_walrus::ValType::F64, + AdapterType::String => wit_walrus::ValType::String, + AdapterType::Anyref => wit_walrus::ValType::Anyref, + AdapterType::I32 => wit_walrus::ValType::I32, + AdapterType::I64 => wit_walrus::ValType::I64, + AdapterType::Bool | AdapterType::Vector(_) => return None, + }) + } } impl NonstandardWitSection { @@ -197,566 +249,3 @@ impl walrus::CustomSection for NonstandardWitSection { panic!("shouldn't emit custom sections just yet"); } } - -// //! 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 -// ); -// } diff --git a/crates/multi-value-xform/src/lib.rs b/crates/multi-value-xform/src/lib.rs index 2bcd974ab6f..75e7c262d25 100644 --- a/crates/multi-value-xform/src/lib.rs +++ b/crates/multi-value-xform/src/lib.rs @@ -108,23 +108,27 @@ /// return pointer parameter that will be removed. The `Vec` /// is the new result type that will be returned directly instead of via the /// return pointer. +/// +/// Returns a list of wrappers which have multi value signatures and call the +/// corresponding element in the `to_xform` list. pub fn run( module: &mut walrus::Module, memory: walrus::MemoryId, shadow_stack_pointer: walrus::GlobalId, - to_xform: &[(walrus::ExportId, usize, &[walrus::ValType])], -) -> Result<(), anyhow::Error> { - for &(export, return_pointer_index, results) in to_xform { - xform_one( + to_xform: &[(walrus::FunctionId, usize, Vec)], +) -> Result, anyhow::Error> { + let mut wrappers = Vec::new(); + for (func, return_pointer_index, results) in to_xform { + wrappers.push(xform_one( module, memory, shadow_stack_pointer, - export, - return_pointer_index, + *func, + *return_pointer_index, results, - )?; + )?); } - Ok(()) + Ok(wrappers) } // Ensure that `n` is aligned to `align`, rounding up as necessary. @@ -137,19 +141,14 @@ fn xform_one( module: &mut walrus::Module, memory: walrus::MemoryId, shadow_stack_pointer: walrus::GlobalId, - export: walrus::ExportId, + func: walrus::FunctionId, return_pointer_index: usize, results: &[walrus::ValType], -) -> Result<(), anyhow::Error> { +) -> Result { if module.globals.get(shadow_stack_pointer).ty != walrus::ValType::I32 { anyhow::bail!("shadow stack pointer global does not have type `i32`"); } - let func = match module.exports.get(export).item { - walrus::ExportItem::Function(f) => f, - _ => anyhow::bail!("can only multi-value transform exported functions, found non-function"), - }; - // Compute the total size of all results, potentially with padding to ensure // that each result is aligned. let mut results_size = 0; @@ -300,13 +299,7 @@ fn xform_one( module.funcs.get_mut(wrapper).name = Some(format!("{} multivalue shim", name)); } - // Replace the old export with our new multi-value wrapper for it! - match module.exports.get_mut(export).item { - walrus::ExportItem::Function(ref mut f) => *f = wrapper, - _ => unreachable!(), - } - - Ok(()) + Ok(wrapper) } #[cfg(test)] From 8a102fea7a6b7126ad48c7f4e921dd1d76fe79d5 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 25 Nov 2019 14:28:43 -0800 Subject: [PATCH 06/35] Get a lot more pieces up and at least compiling --- crates/cli-support/src/anyref.rs | 384 ++++++++---- crates/cli-support/src/lib.rs | 22 +- crates/cli-support/src/multivalue.rs | 17 +- crates/cli-support/src/wit/incoming.rs | 281 +++++---- crates/cli-support/src/wit/mod.rs | 74 ++- crates/cli-support/src/wit/outgoing.rs | 816 ++++++++++--------------- crates/cli-support/src/wit/section.rs | 488 +++++++-------- crates/cli-support/src/wit/standard.rs | 128 +++- 8 files changed, 1184 insertions(+), 1026 deletions(-) diff --git a/crates/cli-support/src/anyref.rs b/crates/cli-support/src/anyref.rs index a1e6c6361d1..5dd29988786 100644 --- a/crates/cli-support/src/anyref.rs +++ b/crates/cli-support/src/anyref.rs @@ -1,160 +1,296 @@ -use crate::wit::NonstandardInstr; -use crate::wit::{NonstandardWitSection, WasmBindgenAux}; +use crate::wit::{AdapterKind, Instruction, NonstandardWitSection, WasmBindgenAux}; +use crate::wit::{AdapterType, InstructionData, StackChange}; use anyhow::Error; -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use walrus::Module; use wasm_bindgen_anyref_xform::Context; -use wasm_webidl_bindings::ast; pub fn process(module: &mut Module, wasm_interface_types: bool) -> Result<(), Error> { let mut cfg = Context::default(); cfg.prepare(module)?; - let bindings = module + let section = module .customs .get_typed_mut::() .expect("wit custom section should exist"); + let implements = section + .implements + .iter() + .cloned() + .map(|(core, adapter)| (adapter, core)) + .collect::>(); + // Transform all exported functions in the module, using the bindings listed // for each exported function. - for (export, binding) in bindings.exports.iter_mut() { - let ty = module.types.get(binding.wasm_ty); - let args = Arguments::Incoming(&mut binding.incoming); - let (args, ret) = extract_anyrefs(ty, args); - cfg.export_xform(*export, &args, ret); + for (id, adapter) in section.adapters.iter_mut() { + let instructions = match &mut adapter.kind { + AdapterKind::Local { instructions } => instructions, + AdapterKind::Import { .. } => continue, + }; + if let Some(id) = implements.get(&id) { + import_xform( + &mut cfg, + *id, + instructions, + &mut adapter.params, + &mut adapter.results, + ); + continue; + } + if let Some(id) = find_call_export(instructions) { + export_xform(&mut cfg, id, instructions); + continue; + } } - // Transform all imported functions in the module, using the bindings listed - // for each imported function. - for (import, binding) in bindings.imports.iter_mut() { - let ty = module.types.get(binding.wasm_ty); - let args = Arguments::Outgoing(&mut binding.outgoing); - let (args, ret) = extract_anyrefs(ty, args); - cfg.import_xform(*import, &args, ret); - } + cfg.run(module)?; + + // TODO: still needed? + // // 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); + // + // // The GC pass above may end up removing some imported intrinsics. For + // // example `__wbindgen_object_clone_ref` is no longer needed after the + // // anyref pass. Make sure to delete the associated metadata for those + // // intrinsics so we don't try to access stale intrinsics later on. + // let remaining_imports = module + // .imports + // .iter() + // .map(|i| i.id()) + // .collect::>(); + // module + // .customs + // .get_typed_mut::() + // .expect("wit custom section should exist") + // .implements + // .retain(|(id, _)| remaining_imports.contains(id)); + // module + // .customs + // .get_typed_mut::() + // .expect("wasm-bindgen aux section should exist") + // .import_map + // .retain(|id, _| remaining_imports.contains(id)); + Ok(()) +} + +fn find_call_export(instrs: &[InstructionData]) -> Option { + instrs + .iter() + .enumerate() + .filter_map(|(i, instr)| match instr.instr { + Instruction::CallExport(e) => Some(Export::Export(e)), + Instruction::CallTableElement(e) => Some(Export::TableElement { + idx: e, + call_idx: i, + }), + _ => None, + }) + .next() +} + +enum Export { + Export(walrus::ExportId), + TableElement { + /// Table element that we're calling + idx: u32, + /// Index in the instruction stream where the call instruction is found + call_idx: usize, + }, +} - // And finally transform all table elements that are used as function - // pointers for closures and such. - for (idx, binding) in bindings.elems.iter_mut() { - let ty = module.types.get(binding.wasm_ty); - let args = Arguments::Incoming(&mut binding.incoming); - let (args, ret) = extract_anyrefs(ty, args); - if let Some(new) = cfg.table_element_xform(*idx, &args, ret) { - *idx = new; +/// Adapts the `instrs` given which are an implementation of the import of `id`. +/// +/// This function will pattern match outgoing arguments and update the +/// instruction stream to remove any anyref-management instructions since +/// we'll be sinking those into the WebAssembly module. +fn import_xform( + cx: &mut Context, + id: walrus::ImportId, + instrs: &mut Vec, + params: &mut [AdapterType], + results: &mut [AdapterType], +) { + let mut to_delete = Vec::new(); + let mut iter = instrs.iter().enumerate(); + let mut args = Vec::new(); + + let mut prev = None; + while let Some((i, instr)) = iter.next() { + match instr.instr { + Instruction::CallAdapter(_) => break, + Instruction::AnyrefLoadOwned | Instruction::TableGet => { + let owned = match instr.instr { + Instruction::TableGet => false, + _ => true, + }; + let prev = match prev.take() { + Some(prev) => prev as usize, + None => panic!("previous instruction must be `arg.get`"), + }; + args.pop(); + args.push(Some(owned)); + match params[prev] { + AdapterType::I32 => {} + _ => panic!("must be `i32` type"), + } + params[prev] = AdapterType::Anyref; + to_delete.push(i); + } + Instruction::Standard(wit_walrus::Instruction::ArgGet(n)) => { + prev = Some(n); + args.push(None); + continue; + } + _ => match instr.stack_change { + StackChange::Modified { pushed, popped } => { + for _ in 0..popped { + args.pop(); + } + for _ in 0..pushed { + args.push(None); + } + } + StackChange::Unknown => { + panic!("must have stack change data"); + } + }, } + prev = None; } - cfg.run(module)?; - - // 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); + let mut ret_anyref = false; + while let Some((i, instr)) = iter.next() { + match instr.instr { + Instruction::AnyrefLoadOwned => { + ret_anyref = true; + to_delete.push(i); + } + _ => {} + } } - // Clean up now-unused intrinsics and shims and such - walrus::passes::gc::run(module); + // Delete all unnecessary anyref management insructions + for idx in to_delete.into_iter().rev() { + instrs.remove(idx); + } - // The GC pass above may end up removing some imported intrinsics. For - // example `__wbindgen_object_clone_ref` is no longer needed after the - // anyref pass. Make sure to delete the associated metadata for those - // intrinsics so we don't try to access stale intrinsics later on. - let remaining_imports = module - .imports + // Filter down our list of arguments to just the ones that are anyref + // values. + let args = args .iter() - .map(|i| i.id()) - .collect::>(); - module - .customs - .get_typed_mut::() - .expect("wit custom section should exist") - .imports - .retain(|id, _| remaining_imports.contains(id)); - module - .customs - .get_typed_mut::() - .expect("wasm-bindgen aux section should exist") - .import_map - .retain(|id, _| remaining_imports.contains(id)); - Ok(()) -} + .enumerate() + .filter_map(|(i, owned)| owned.map(|owned| (i, owned))) + .collect::>(); -enum Arguments<'a> { - Incoming(&'a mut [NonstandardIncoming]), - Outgoing(&'a mut [NonstandardOutgoing]), + // ... and register this entire transformation with the anyref + // transformation pass. + cx.import_xform(id, &args, ret_anyref); } -/// Extract a description of the anyref arguments from the function signature -/// described by `f`. -/// -/// The returned values are expected to be passed to the anyref transformation -/// pass, and indicate which arguments (by index) in the wasm signature should -/// be transformed from `i32` to `anyref` as well as whether the returned value -/// is an `anyref` or not. +/// Adapts the `instrs` of an adapter function that calls an export. /// -/// The `offset` argument here is typically 0 and indicates the offset at which -/// the wasm abi arguments described by `f` start at. For closures this is 2 -/// because two synthetic arguments are injected into the wasm signature which -/// aren't present in the `Function` signature. -fn extract_anyrefs(ty: &walrus::Type, args: Arguments<'_>) -> (Vec<(usize, bool)>, bool) { - let mut ret = Vec::new(); - - // First find all the `anyref` arguments in the input type, and we'll - // assume that they're owned anyref arguments for now (the `true`) - for (i, arg) in ty.params().iter().enumerate() { - if *arg == walrus::ValType::Anyref { - ret.push((i, true)); +/// The `instrus` must be generated by wasm-bindgen itself and follow the +/// pattern matched below to pass off to the anyref transformation pass. The +/// signature of the adapter doesn't change (it remains as anyref-aware) but the +/// signature of the export we're calling will change during the transformation. +fn export_xform(cx: &mut Context, export: Export, instrs: &mut Vec) { + let mut to_delete = Vec::new(); + let mut iter = instrs.iter().enumerate(); + let mut args = Vec::new(); + + // Mutate instructions leading up to the `CallExport` instruction. We + // maintain a stack of indicators whether the element at that stack slot is + // unknown (`None`) or whether it's an owned/borrowed anyref + // (`Some(owned)`). + // + // Note that we're going to delete the `I32FromAnyref*` instructions, so we + // also maintain indices of the instructions to delete. + while let Some((i, instr)) = iter.next() { + match instr.instr { + Instruction::CallExport(_) | Instruction::CallTableElement(_) => break, + Instruction::I32FromAnyrefOwned => { + args.pop(); + args.push(Some(true)); + to_delete.push(i); + } + Instruction::I32FromAnyrefBorrow => { + args.pop(); + args.push(Some(false)); + to_delete.push(i); + } + _ => match instr.stack_change { + StackChange::Modified { pushed, popped } => { + for _ in 0..popped { + args.pop(); + } + for _ in 0..pushed { + args.push(None); + } + } + StackChange::Unknown => { + panic!("must have stack change data"); + } + }, } } - // Afterwards look through the argument list (specified with various - // bindings) to find any borrowed anyref values and update our - // transformation metadata accordingly. if we find one then the binding no - // longer needs to remember its borrowed but rather it's just a simple cast - // from wasm anyref to JS any. - match args { - Arguments::Incoming(incoming) => { - for binding in incoming { - let expr = match binding { - NonstandardIncoming::BorrowedAnyref { - val: ast::IncomingBindingExpression::Get(expr), - } => expr.clone(), - _ => continue, - }; - ret.iter_mut().find(|p| p.0 == expr.idx as usize).unwrap().1 = false; - let new_binding = ast::IncomingBindingExpressionAs { - ty: walrus::ValType::Anyref, - expr: Box::new(expr.into()), - }; - *binding = NonstandardIncoming::Standard(new_binding.into()); + // If one of the instructions after the call is an `AnyrefLoadOwned` then we + // know that the function returned an anyref. Currently `&'static Anyref` + // can't be done as a return value, so this is the only case we handle here. + let mut ret_anyref = false; + while let Some((i, instr)) = iter.next() { + match instr.instr { + Instruction::AnyrefLoadOwned => { + ret_anyref = true; + to_delete.push(i); } + _ => {} } - Arguments::Outgoing(outgoing) => { - for binding in outgoing { - let idx = match binding { - NonstandardOutgoing::BorrowedAnyref { idx } => *idx, - _ => continue, - }; - ret.iter_mut().find(|p| p.0 == idx as usize).unwrap().1 = false; - let new_binding = ast::OutgoingBindingExpressionAs { - idx, - ty: ast::WebidlScalarType::Any.into(), - }; - *binding = NonstandardOutgoing::Standard(new_binding.into()); + } + + // Filter down our list of arguments to just the ones that are anyref + // values. + let args = args + .iter() + .enumerate() + .filter_map(|(i, owned)| owned.map(|owned| (i, owned))) + .collect::>(); + + // ... and register this entire transformation with the anyref + // transformation pass. + match export { + Export::Export(id) => { + cx.export_xform(id, &args, ret_anyref); + } + Export::TableElement { idx, call_idx } => { + if let Some(new_idx) = cx.table_element_xform(idx, &args, ret_anyref) { + instrs[call_idx].instr = Instruction::CallTableElement(new_idx); } } } - (ret, ty.results() == &[walrus::ValType::Anyref]) + + // Delete all unnecessary anyref management instructions. We're going to + // sink these instructions into the wasm module itself. + for idx in to_delete.into_iter().rev() { + instrs.remove(idx); + } } diff --git a/crates/cli-support/src/lib.rs b/crates/cli-support/src/lib.rs index 5ddb4559d55..6a46346e5ae 100755 --- a/crates/cli-support/src/lib.rs +++ b/crates/cli-support/src/lib.rs @@ -11,12 +11,12 @@ use std::str; use walrus::Module; use wasm_bindgen_wasm_conventions as wasm_conventions; -// mod anyref; -mod multivalue; +mod anyref; mod decode; mod descriptor; mod descriptors; mod intrinsic; +mod multivalue; // mod js; pub mod wasm2es6js; mod wit; @@ -342,14 +342,14 @@ impl Bindgen { 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. - // // This is only done if the anyref pass is enabled, which it's - // // currently off-by-default since `anyref` is still in development in - // // engines. - // if self.anyref { - // anyref::process(&mut module, self.wasm_interface_types)?; - // } + // Now that we've got type information from the webidl processing pass, + // touch up the output of rustc to insert anyref shims where necessary. + // This is only done if the anyref pass is enabled, which it's + // currently off-by-default since `anyref` is still in development in + // engines. + if self.anyref { + anyref::process(&mut module, self.wasm_interface_types)?; + } let aux = module .customs @@ -364,7 +364,7 @@ impl Bindgen { // // shim generation which will actually generate JS for all this. // let (npm_dependencies, (js, ts)) = { // let mut cx = js::Context::new(&mut module, self)?; - // cx.generate(&aux, &bindings)?; + // cx.generate(&aux, &adapters)?; // let npm_dependencies = cx.npm_dependencies.clone(); // (npm_dependencies, cx.finalize(stem)?) // }; diff --git a/crates/cli-support/src/multivalue.rs b/crates/cli-support/src/multivalue.rs index d373da60e2f..589aceae7af 100644 --- a/crates/cli-support/src/multivalue.rs +++ b/crates/cli-support/src/multivalue.rs @@ -7,10 +7,7 @@ use walrus::Module; use wasm_bindgen_multi_value_xform as multi_value_xform; use wasm_bindgen_wasm_conventions as wasm_conventions; -pub fn run( - module: &mut Module, - adapters: &mut NonstandardWitSection, -) -> Result<(), Error> { +pub fn run(module: &mut Module, adapters: &mut NonstandardWitSection) -> Result<(), Error> { let mut to_xform = Vec::new(); let mut slots = Vec::new(); @@ -48,10 +45,10 @@ fn extract_xform<'a>( // If the first instruction is a `Retptr`, then this must be an exported // adapter which calls a wasm-defined function. Something we'd like to // adapt to multi-value! - if let Some(Instruction::Retptr) = instructions.first() { + if let Some(Instruction::Retptr) = instructions.first().map(|e| &e.instr) { instructions.remove(0); let mut types = Vec::new(); - instructions.retain(|instruction| match instruction { + instructions.retain(|instruction| match instruction.instr { Instruction::LoadRetptr { ty, .. } => { types.push(ty.to_wasm().unwrap()); false @@ -60,7 +57,7 @@ fn extract_xform<'a>( }); let id = instructions .iter_mut() - .filter_map(|i| match i { + .filter_map(|i| match &mut i.instr { Instruction::Standard(wit_walrus::Instruction::CallCore(f)) => Some(f), _ => None, }) @@ -75,8 +72,8 @@ fn extract_xform<'a>( } // If the last instruction is a `StoreRetptr`, then this must be an adapter - // which is calling + // which calls an imported function. // - // TODO: handle this case - if let Some(Instruction::StoreRetptr { .. }) = instructions.last() {} + // FIXME(#1872) handle this + // if let Some(Instruction::StoreRetptr { .. }) = instructions.last() {} } diff --git a/crates/cli-support/src/wit/incoming.rs b/crates/cli-support/src/wit/incoming.rs index 0fd0d5ddd6a..a45f192c14a 100644 --- a/crates/cli-support/src/wit/incoming.rs +++ b/crates/cli-support/src/wit/incoming.rs @@ -8,7 +8,8 @@ //! the `outgoing.rs` module. use crate::descriptor::{Descriptor, VectorKind}; -use crate::wit::{AdapterType, Instruction, InstructionBuilder}; +use crate::wit::InstructionData; +use crate::wit::{AdapterType, Instruction, InstructionBuilder, StackChange}; use anyhow::{bail, format_err, Error}; use walrus::{FunctionId, MemoryId, ValType}; @@ -37,26 +38,34 @@ impl InstructionBuilder<'_, '_> { use wit_walrus::ValType as WitVT; match arg { Descriptor::Boolean => { - self.get(AdapterType::Bool); - self.instructions.push(Instruction::I32FromBool); - self.output.push(AdapterType::I32); + self.instruction( + &[AdapterType::Bool], + Instruction::I32FromBool, + &[AdapterType::I32], + ); } Descriptor::Char => { - self.get(AdapterType::String); - self.instructions.push(Instruction::I32FromStringFirstChar); - self.output.push(AdapterType::I32); + self.instruction( + &[AdapterType::String], + Instruction::I32FromStringFirstChar, + &[AdapterType::I32], + ); } Descriptor::Anyref => { - self.get(AdapterType::Anyref); - self.instructions.push(Instruction::I32FromAnyrefOwned); - self.output.push(AdapterType::I32); + self.instruction( + &[AdapterType::Anyref], + Instruction::I32FromAnyrefOwned, + &[AdapterType::I32], + ); } Descriptor::RustStruct(class) => { - self.get(AdapterType::Anyref); - self.instructions.push(Instruction::I32FromAnyrefRustOwned { - class: class.clone(), - }); - self.output.push(AdapterType::I32); + self.instruction( + &[AdapterType::Anyref], + Instruction::I32FromAnyrefRustOwned { + class: class.clone(), + }, + &[AdapterType::I32], + ); } Descriptor::I8 => self.number(WitVT::S8, WasmVT::I32), Descriptor::U8 => self.number(WitVT::U8, WasmVT::I32), @@ -80,28 +89,30 @@ impl InstructionBuilder<'_, '_> { Descriptor::Option(d) => self.incoming_option(d)?, Descriptor::String | Descriptor::CachedString => { - self.get(AdapterType::String); let std = wit_walrus::Instruction::StringToMemory { malloc: self.cx.malloc()?, mem: self.cx.memory()?, }; - self.instructions.push(Instruction::Standard(std)); - self.output.push(AdapterType::I32); - self.output.push(AdapterType::I32); + self.instruction( + &[AdapterType::String], + Instruction::Standard(std), + &[AdapterType::I32, AdapterType::I32], + ); } Descriptor::Vector(_) => { let kind = arg.vector_kind().ok_or_else(|| { format_err!("unsupported argument type for calling Rust function from JS {:?}", arg) })?; - self.get(AdapterType::Vector(kind)); - self.instructions.push(Instruction::VectorToMemory { - kind, - malloc: self.cx.malloc()?, - mem: self.cx.memory()?, - }); - self.output.push(AdapterType::I32); - self.output.push(AdapterType::I32); + self.instruction( + &[AdapterType::Vector(kind)], + Instruction::VectorToMemory { + kind, + malloc: self.cx.malloc()?, + mem: self.cx.memory()?, + }, + &[AdapterType::I32, AdapterType::I32], + ); } // Can't be passed from JS to Rust yet @@ -126,28 +137,32 @@ impl InstructionBuilder<'_, '_> { fn incoming_ref(&mut self, mutable: bool, arg: &Descriptor) -> Result<(), Error> { match arg { Descriptor::RustStruct(class) => { - self.get(AdapterType::Anyref); - self.instructions - .push(Instruction::I32FromAnyrefRustBorrow { + self.instruction( + &[AdapterType::Anyref], + Instruction::I32FromAnyrefRustBorrow { class: class.clone(), - }); - self.output.push(AdapterType::I32); + }, + &[AdapterType::I32], + ); } Descriptor::Anyref => { - self.get(AdapterType::Anyref); - self.instructions.push(Instruction::I32FromAnyrefBorrow); - self.output.push(AdapterType::I32); + self.instruction( + &[AdapterType::Anyref], + Instruction::I32FromAnyrefBorrow, + &[AdapterType::I32], + ); } Descriptor::String | Descriptor::CachedString => { // This allocation is cleaned up once it's received in Rust. - self.get(AdapterType::String); let std = wit_walrus::Instruction::StringToMemory { malloc: self.cx.malloc()?, mem: self.cx.memory()?, }; - self.instructions.push(Instruction::Standard(std)); - self.output.push(AdapterType::I32); - self.output.push(AdapterType::I32); + self.instruction( + &[AdapterType::String], + Instruction::Standard(std), + &[AdapterType::I32, AdapterType::I32], + ); } Descriptor::Slice(_) => { // like strings, this allocation is cleaned up after being @@ -158,14 +173,15 @@ impl InstructionBuilder<'_, '_> { arg ) })?; - self.get(AdapterType::Vector(kind)); - self.instructions.push(Instruction::VectorToMemory { - kind, - malloc: self.cx.malloc()?, - mem: self.cx.memory()?, - }); - self.output.push(AdapterType::I32); - self.output.push(AdapterType::I32); + self.instruction( + &[AdapterType::Vector(kind)], + Instruction::VectorToMemory { + kind, + malloc: self.cx.malloc()?, + mem: self.cx.memory()?, + }, + &[AdapterType::I32, AdapterType::I32], + ); } _ => bail!( "unsupported reference argument type for calling Rust function from JS: {:?}", @@ -178,50 +194,60 @@ impl InstructionBuilder<'_, '_> { fn incoming_option(&mut self, arg: &Descriptor) -> Result<(), Error> { match arg { Descriptor::Anyref => { - self.get(AdapterType::Anyref); - self.instructions.push(Instruction::I32FromOptionAnyref); - self.output.push(AdapterType::I32); + self.instruction( + &[AdapterType::Anyref], + Instruction::I32FromOptionAnyref, + &[AdapterType::I32], + ); } - Descriptor::I8 => self.option_sentinel(), - Descriptor::U8 => self.option_sentinel(), - Descriptor::I16 => self.option_sentinel(), - Descriptor::U16 => self.option_sentinel(), - Descriptor::I32 => self.option_native(ValType::I32), - Descriptor::U32 => self.option_native(ValType::I32), - Descriptor::F32 => self.option_native(ValType::F32), - Descriptor::F64 => self.option_native(ValType::F64), + Descriptor::I8 => self.in_option_sentinel(), + Descriptor::U8 => self.in_option_sentinel(), + Descriptor::I16 => self.in_option_sentinel(), + Descriptor::U16 => self.in_option_sentinel(), + Descriptor::I32 => self.in_option_native(ValType::I32), + Descriptor::U32 => self.in_option_native(ValType::I32), + Descriptor::F32 => self.in_option_native(ValType::F32), + Descriptor::F64 => self.in_option_native(ValType::F64), Descriptor::I64 | Descriptor::U64 => { - self.get(AdapterType::Anyref); let signed = match arg { Descriptor::I64 => true, _ => false, }; - self.instructions - .push(Instruction::I32SplitOption64 { signed }); - self.output.extend(&[AdapterType::I32; 4]); + self.instruction( + &[AdapterType::Anyref], + Instruction::I32SplitOption64 { signed }, + &[AdapterType::I32; 3], + ); } Descriptor::Boolean => { - self.get(AdapterType::Anyref); - self.instructions.push(Instruction::I32FromOptionBool); - self.output.push(AdapterType::I32); + self.instruction( + &[AdapterType::Anyref], + Instruction::I32FromOptionBool, + &[AdapterType::I32], + ); } Descriptor::Char => { - self.get(AdapterType::Anyref); - self.instructions.push(Instruction::I32FromOptionChar); - self.output.push(AdapterType::I32); + self.instruction( + &[AdapterType::Anyref], + Instruction::I32FromOptionChar, + &[AdapterType::I32], + ); } Descriptor::Enum { hole } => { - self.get(AdapterType::Anyref); - self.instructions - .push(Instruction::I32FromOptionEnum { hole: *hole }); - self.output.push(AdapterType::I32); + self.instruction( + &[AdapterType::Anyref], + Instruction::I32FromOptionEnum { hole: *hole }, + &[AdapterType::I32], + ); } Descriptor::RustStruct(name) => { - self.get(AdapterType::Anyref); - self.instructions.push(Instruction::I32FromOptionRust { - class: name.to_string(), - }); - self.output.push(AdapterType::I32); + self.instruction( + &[AdapterType::Anyref], + Instruction::I32FromOptionRust { + class: name.to_string(), + }, + &[AdapterType::I32], + ); } Descriptor::Ref(_) | Descriptor::RefMut(_) => { @@ -235,11 +261,11 @@ impl InstructionBuilder<'_, '_> { arg ) })?; - self.get(AdapterType::Anyref); - self.instructions - .push(Instruction::OptionSlice { kind, mutable }); - self.output.push(AdapterType::I32); - self.output.push(AdapterType::I32); + self.instruction( + &[AdapterType::Anyref], + Instruction::OptionSlice { kind, mutable }, + &[AdapterType::I32; 2], + ); } Descriptor::String | Descriptor::CachedString | Descriptor::Vector(_) => { @@ -249,10 +275,11 @@ impl InstructionBuilder<'_, '_> { arg ) })?; - self.get(AdapterType::Anyref); - self.instructions.push(Instruction::OptionVector { kind }); - self.output.push(AdapterType::I32); - self.output.push(AdapterType::I32); + self.instruction( + &[AdapterType::Anyref], + Instruction::OptionVector { kind }, + &[AdapterType::I32; 2], + ); } _ => bail!( @@ -272,44 +299,80 @@ impl InstructionBuilder<'_, '_> { if !self.return_position { let idx = self.input.len() as u32 - 1; let std = wit_walrus::Instruction::ArgGet(idx); - self.instructions.push(Instruction::Standard(std)); + self.instructions.push(InstructionData { + instr: Instruction::Standard(std), + stack_change: StackChange::Modified { + pushed: 1, + popped: 0, + }, + }); + } + } + + pub fn instruction( + &mut self, + inputs: &[AdapterType], + instr: Instruction, + outputs: &[AdapterType], + ) { + // If we're generating instructions in the return position then the + // arguments are already on the stack to consume, otherwise we need to + // fetch them from the parameters. + if !self.return_position { + for input in inputs { + self.get(*input); + } } + + self.instructions.push(InstructionData { + instr, + stack_change: StackChange::Modified { + popped: inputs.len(), + pushed: outputs.len(), + }, + }); + self.input.extend_from_slice(inputs); + self.output.extend_from_slice(outputs); } fn number(&mut self, input: wit_walrus::ValType, output: walrus::ValType) { - self.get(AdapterType::from_wit(input)); let std = wit_walrus::Instruction::IntToWasm { input, output, trap: false, }; - self.instructions.push(Instruction::Standard(std)); - self.output.push(AdapterType::from_wasm(output).unwrap()); + self.instruction( + &[AdapterType::from_wit(input)], + Instruction::Standard(std), + &[AdapterType::from_wasm(output).unwrap()], + ); } fn number64(&mut self, signed: bool) { - self.get(if signed { - AdapterType::S64 - } else { - AdapterType::U64 - }); - self.instructions.push(Instruction::I32Split64 { signed }); - self.output.push(AdapterType::I32); - self.output.push(AdapterType::I32); + self.instruction( + &[if signed { + AdapterType::S64 + } else { + AdapterType::U64 + }], + Instruction::I32Split64 { signed }, + &[AdapterType::I32, AdapterType::I32], + ); } - fn option_native(&mut self, wasm: ValType) { - self.get(AdapterType::Anyref); - self.instructions - .push(Instruction::OptionNative { ty: wasm }); - self.output.push(AdapterType::I32); - self.output.push(AdapterType::from_wasm(wasm).unwrap()); + fn in_option_native(&mut self, wasm: ValType) { + self.instruction( + &[AdapterType::Anyref], + Instruction::FromOptionNative { ty: wasm }, + &[AdapterType::I32, AdapterType::from_wasm(wasm).unwrap()], + ); } - fn option_sentinel(&mut self) { - self.get(AdapterType::Anyref); - self.instructions - .push(Instruction::I32FromOptionU32Sentinel); - self.output.push(AdapterType::I32); + fn in_option_sentinel(&mut self) { + self.instruction( + &[AdapterType::Anyref], + Instruction::I32FromOptionU32Sentinel, + &[AdapterType::I32], + ); } } diff --git a/crates/cli-support/src/wit/mod.rs b/crates/cli-support/src/wit/mod.rs index 1cf51ddba89..729e4555372 100644 --- a/crates/cli-support/src/wit/mod.rs +++ b/crates/cli-support/src/wit/mod.rs @@ -40,7 +40,7 @@ struct Context<'a> { struct InstructionBuilder<'a, 'b> { input: Vec, output: Vec, - instructions: Vec, + instructions: Vec, cx: &'a mut Context<'b>, return_position: bool, } @@ -935,6 +935,10 @@ impl<'a> Context<'a> { } other => Instruction::Standard(other.clone()), }) + .map(|instr| InstructionData { + instr, + stack_change: StackChange::Unknown, + }) .collect::>(); // Store the instrs into the adapter function directly. @@ -1090,7 +1094,10 @@ impl<'a> Context<'a> { kind, }, ); - instructions.push(Instruction::CallAdapter(f)); + instructions.push(InstructionData { + instr: Instruction::CallAdapter(f), + stack_change: StackChange::Unknown, + }); // ... and then we follow up with a conversion of the incoming type // back to wasm. @@ -1101,7 +1108,13 @@ impl<'a> Context<'a> { // here because the last result is the top value on the stack. let results = if uses_retptr { for (i, ty) in ret.output.into_iter().enumerate().rev() { - instructions.push(Instruction::StoreRetptr { offset: i, ty }); + instructions.push(InstructionData { + instr: Instruction::StoreRetptr { offset: i, ty }, + stack_change: StackChange::Modified { + pushed: 0, + popped: 1, + }, + }); } Vec::new() } else { @@ -1124,36 +1137,22 @@ impl<'a> Context<'a> { ) -> Result { let export = self.module.exports.get(export); let name = export.name.clone(); - let id = match export.item { - walrus::ExportItem::Function(f) => f, - _ => unreachable!(), - }; - let export_id = export.id(); // Do the actual heavy lifting elsewhere to generate the `binding`. - let id = self.register_export_adapter(id, signature)?; + let call = Instruction::CallExport(export.id()); + let id = self.register_export_adapter(call, signature)?; self.adapters.exports.push((name, id)); Ok(id) } fn table_element_adapter(&mut self, idx: u32, signature: Function) -> Result { - let table = self - .module - .tables - .main_function_table()? - .ok_or_else(|| anyhow!("no function table found"))?; - let table = self.module.tables.get(table); - let functions = match &table.kind { - walrus::TableKind::Function(f) => f, - _ => unreachable!(), - }; - let id = functions.elements[idx as usize].unwrap(); + let call = Instruction::CallTableElement(idx); // like above, largely just defer the work elsewhere - Ok(self.register_export_adapter(id, signature)?) + Ok(self.register_export_adapter(call, signature)?) } fn register_export_adapter( &mut self, - id: walrus::FunctionId, + call: Instruction, signature: Function, ) -> Result { // Figure out how to translate all the incoming arguments ... @@ -1163,7 +1162,7 @@ impl<'a> Context<'a> { } // ... then the returned value being translated back - let mut ret = args.cx.instruction_builder(true,); + let mut ret = args.cx.instruction_builder(true); ret.outgoing(&signature.ret)?; let uses_retptr = ret.input.len() > 1; @@ -1176,13 +1175,28 @@ impl<'a> Context<'a> { // everything into the outgoing arguments. let mut instructions = Vec::new(); if uses_retptr { - instructions.push(Instruction::Retptr); + instructions.push(InstructionData { + instr: Instruction::Retptr, + stack_change: StackChange::Modified { + pushed: 1, + popped: 0, + }, + }); } instructions.extend(args.instructions); - instructions.push(Instruction::Standard(wit_walrus::Instruction::CallCore(id))); + instructions.push(InstructionData { + instr: call, + stack_change: StackChange::Unknown, + }); if uses_retptr { for (i, ty) in ret.input.into_iter().enumerate() { - instructions.push(Instruction::LoadRetptr { offset: i, ty }); + instructions.push(InstructionData { + instr: Instruction::LoadRetptr { offset: i, ty }, + stack_change: StackChange::Modified { + pushed: 1, + popped: 0, + }, + }); } } instructions.extend(ret.instructions); @@ -1211,6 +1225,14 @@ impl<'a> Context<'a> { .ok_or_else(|| anyhow!("failed to find declaration of `__wbindgen_malloc` in module")) } + fn free(&self) -> Result { + self.function_exports + .get("__wbindgen_free") + .cloned() + .map(|p| p.1) + .ok_or_else(|| anyhow!("failed to find declaration of `__wbindgen_free` in module")) + } + fn memory(&self) -> Result { self.memory .ok_or_else(|| anyhow!("failed to find memory declaration in module")) diff --git a/crates/cli-support/src/wit/outgoing.rs b/crates/cli-support/src/wit/outgoing.rs index 67b8e642462..f2d2164d110 100644 --- a/crates/cli-support/src/wit/outgoing.rs +++ b/crates/cli-support/src/wit/outgoing.rs @@ -1,158 +1,9 @@ -// //! This module is used to define `NonstandardOutgoing`, a list of possible ways -// //! values in Rust can be passed to JS. -// //! -// //! Like the `NonstandardIncoming` list we attempt to use a standard -// //! `OutgoingBindingExpression` wherever possible but we naturally have a lot of -// //! features in `wasm-bindgen` which haven't been upstreamed into the WebIDL -// //! bindings standard yet (nor which are likely to ever get standardized). We -// //! attempt to use standard bindings aggressively and wherever possible, but -// //! sometimes we need to resort to our own custom bindings with our own custom -// //! JS shims for now. -// //! -// //! This module also houses the definition of converting a `Descriptor` to a -// //! `NonstandardOutgoing` binding, effectively defining how to translate from a -// //! Rust type to an outgoing binding. - use crate::descriptor::{Descriptor, VectorKind}; -use crate::wit::{AdapterType, Instruction, NonstandardWitSection, InstructionBuilder}; +use crate::wit::{AdapterType, Instruction, InstructionBuilder, NonstandardWitSection}; +use crate::wit::{InstructionData, StackChange}; use anyhow::{bail, format_err, Error}; use walrus::{Module, ValType}; -// /// A list of all possible outgoing bindings which can be used when converting -// /// Rust types to JS. This is predominantly used when calling an imported JS -// /// function. -// #[derive(Debug, Clone)] -// pub enum NonstandardOutgoing { -// /// This is a standard upstream WebIDL outgoing binding expression. Where -// /// possible we can actually leave this in the wasm file and generate even -// /// less JS shim code. -// Standard(ast::OutgoingBindingExpression), -// -// /// We're returning a pointer from Rust to JS to get wrapped in a JS class -// /// which has memory management around it. -// RustType { class: String, idx: u32 }, -// -// /// A single rust `char` value which is converted to a `string` in JS. -// Char { idx: u32 }, -// -// /// An `i64` or `u64` in Rust converted to a `BigInt` in JS -// Number64 { -// lo_idx: u32, -// hi_idx: u32, -// signed: bool, -// }, -// -// /// A *borrowed* anyref value which has special meanings about ownership, -// /// namely Rust is still using the underlying value after the call returns. -// BorrowedAnyref { idx: u32 }, -// -// /// An owned vector is passed from Rust to JS. Note that this is currently a -// /// special binding because it requires memory management via deallocation -// /// in the JS shim. -// /// -// /// TODO: we should strive to not have this nonstandard binding and instead -// /// do all the memory management in Rust. Ideally we'd use `AllocCopy` in -// /// place of this. -// Vector { -// offset: u32, -// length: u32, -// kind: VectorKind, -// }, -// -// /// A Rust String (or &str) which might be cached, or might be `None`. -// /// -// /// If `offset` is 0 then it is cached, and the cached JsValue's index is in `length`. -// /// -// /// If `offset` and `length` are both 0, then it is `None`. -// CachedString { -// offset: u32, -// length: u32, -// owned: bool, -// optional: bool, -// }, -// -// /// A `&[u64]` or `&[i64]` is being passed to JS, and the 64-bit sizes here -// /// aren't supported by WebIDL bindings yet. -// View64 { -// offset: u32, -// length: u32, -// signed: bool, -// }, -// -// /// A list of `anyref` is being passed to JS, and it's got a somewhat -// /// magical representation with indics which doesn't map to WebIDL bindings. -// ViewAnyref { offset: u32, length: u32 }, -// -// /// An optional owned vector of data is being passed to JS. -// /// -// /// TODO: with some cleverness this could probably use `AllocCopy`. -// OptionVector { -// offset: u32, -// length: u32, -// kind: VectorKind, -// }, -// -// /// An optional slice of data is being passed into JS. -// /// -// /// TODO: with some cleverness this could probably use `AllocCopy`. -// OptionSlice { -// kind: VectorKind, -// offset: u32, -// length: u32, -// }, -// -// /// An optional "native type" like i32/u32/f32/f64 is being passed to JS, -// /// and this requires a discriminant in the ABI. -// OptionNative { -// present: u32, -// val: u32, -// signed: bool, -// }, -// -// /// An optional number is being passed to JS where the number uses a -// /// sentinel value to represent `None` -// OptionU32Sentinel { idx: u32 }, -// -// /// An optional boolean with a special value for `None` -// OptionBool { idx: u32 }, -// -// /// An optional character with a special value for `None` -// OptionChar { idx: u32 }, -// -// /// An optional integral enum value with the specified `hole` being used for -// /// `None`. -// OptionIntegerEnum { idx: u32, hole: u32 }, -// -// /// An optional 64-bit integer being used. -// OptionInt64 { -// present: u32, -// _ignored: u32, -// lo: u32, -// hi: u32, -// signed: bool, -// }, -// -// /// An optional owned Rust type being transferred from Rust to JS. -// OptionRustType { class: String, idx: u32 }, -// -// /// A temporary stack closure being passed from Rust to JS. A JS function is -// /// manufactured and then neutered just before the call returns. -// StackClosure { -// /// Argument index of the first data pointer Rust needs -// a: u32, -// /// Argument index of the second data pointer Rust needs -// b: u32, -// /// The index of the shim in the element bindings section that we're -// /// going to be invoking. -// binding_idx: u32, -// /// Number of arguments to the closure -// nargs: usize, -// /// Whether or not this is a mutable closure (affects codegen and how -// /// it's called recursively) -// mutable: bool, -// }, -// } -// impl InstructionBuilder<'_, '_> { /// Processes one more `Descriptor` as an argument to a JS function that /// wasm is calling. @@ -175,357 +26,338 @@ impl InstructionBuilder<'_, '_> { fn _outgoing(&mut self, arg: &Descriptor) -> Result<(), Error> { match arg { Descriptor::Boolean => { + self.instruction( + &[AdapterType::I32], + Instruction::BoolFromI32, + &[AdapterType::Bool], + ); + } + Descriptor::Anyref => { + self.instruction( + &[AdapterType::I32], + Instruction::AnyrefLoadOwned, + &[AdapterType::Anyref], + ); + } + Descriptor::I8 => self.outgoing_i32(AdapterType::S8), + Descriptor::U8 => self.outgoing_i32(AdapterType::U8), + Descriptor::I16 => self.outgoing_i32(AdapterType::S16), + Descriptor::U16 => self.outgoing_i32(AdapterType::U16), + Descriptor::I32 => self.outgoing_i32(AdapterType::S32), + Descriptor::U32 => self.outgoing_i32(AdapterType::U32), + Descriptor::F32 => { + self.get(AdapterType::F32); + self.output.push(AdapterType::F32); + } + Descriptor::F64 => { + self.get(AdapterType::F64); + self.output.push(AdapterType::F64); + } + Descriptor::Enum { .. } => self.outgoing_i32(AdapterType::U32), + + Descriptor::Char => { + self.instruction( + &[AdapterType::I32], + Instruction::StringFromChar, + &[AdapterType::String], + ); + } + + Descriptor::I64 | Descriptor::U64 => { + let signed = match arg { + Descriptor::I64 => true, + _ => false, + }; + self.instruction( + &[AdapterType::I32; 2], + Instruction::I64FromLoHi { signed }, + &[if signed { + AdapterType::S64 + } else { + AdapterType::U64 + }], + ); + } + + Descriptor::RustStruct(class) => { + self.instruction( + &[AdapterType::I32], + Instruction::RustFromI32 { + class: class.to_string(), + }, + &[AdapterType::Anyref], + ); + } + Descriptor::Ref(d) => self.outgoing_ref(false, d)?, + Descriptor::RefMut(d) => self.outgoing_ref(true, d)?, + + Descriptor::CachedString => self.cached_string(false, true), + + Descriptor::String => { + // fetch the ptr/length ... + self.get(AdapterType::I32); self.get(AdapterType::I32); - self.instructions.push(Instruction::BoolFromI32); - self.output.push(AdapterType::Bool); + + // ... then defer a call to `free` to happen later + let free = self.cx.free()?; + let std = wit_walrus::Instruction::DeferCallCore(free); + self.instructions.push(InstructionData { + instr: Instruction::Standard(std), + stack_change: StackChange::Modified { + popped: 2, + pushed: 2, + }, + }); + + // ... and then convert it to a string type + let std = wit_walrus::Instruction::MemoryToString(self.cx.memory()?); + self.instructions.push(InstructionData { + instr: Instruction::Standard(std), + stack_change: StackChange::Modified { + popped: 2, + pushed: 1, + }, + }); + self.output.push(AdapterType::String); } - Descriptor::Anyref => self.standard_as(ValType::Anyref, ast::WebidlScalarType::Any), - // Descriptor::I8 => self.standard_as(ValType::I32, ast::WebidlScalarType::Byte), - // Descriptor::U8 => self.standard_as(ValType::I32, ast::WebidlScalarType::Octet), - // Descriptor::I16 => self.standard_as(ValType::I32, ast::WebidlScalarType::Short), - // Descriptor::U16 => self.standard_as(ValType::I32, ast::WebidlScalarType::UnsignedShort), - // Descriptor::I32 => self.standard_as(ValType::I32, ast::WebidlScalarType::Long), - // Descriptor::U32 => self.standard_as(ValType::I32, ast::WebidlScalarType::UnsignedLong), - // Descriptor::F32 => { - // self.standard_as(ValType::F32, ast::WebidlScalarType::UnrestrictedFloat) - // } - // Descriptor::F64 => { - // self.standard_as(ValType::F64, ast::WebidlScalarType::UnrestrictedDouble) - // } - // Descriptor::Enum { .. } => self.standard_as(ValType::I32, ast::WebidlScalarType::Long), - // - // Descriptor::Char => { - // let idx = self.push_wasm(ValType::I32); - // self.webidl.push(ast::WebidlScalarType::DomString); - // self.bindings.push(NonstandardOutgoing::Char { idx }); - // } - // - // Descriptor::I64 | Descriptor::U64 => { - // let signed = match arg { - // Descriptor::I64 => true, - // _ => false, - // }; - // let lo_idx = self.push_wasm(ValType::I32); - // let hi_idx = self.push_wasm(ValType::I32); - // self.webidl.push(ast::WebidlScalarType::Any); - // self.bindings.push(NonstandardOutgoing::Number64 { - // lo_idx, - // hi_idx, - // signed, - // }); - // } - // - // Descriptor::RustStruct(class) => { - // let idx = self.push_wasm(ValType::I32); - // self.webidl.push(ast::WebidlScalarType::Any); - // self.bindings.push(NonstandardOutgoing::RustType { - // idx, - // class: class.to_string(), - // }); - // } - // Descriptor::Ref(d) => self.process_ref(false, d)?, - // Descriptor::RefMut(d) => self.process_ref(true, d)?, - // - // Descriptor::CachedString => self.cached_string(false, true), - // - // Descriptor::Vector(_) | Descriptor::String => { - // let kind = arg.vector_kind().ok_or_else(|| { - // format_err!( - // "unsupported argument type for calling JS function from Rust {:?}", - // arg - // ) - // })?; - // let offset = self.push_wasm(ValType::I32); - // let length = self.push_wasm(ValType::I32); - // self.webidl.push(ast::WebidlScalarType::Any); - // self.bindings.push(NonstandardOutgoing::Vector { - // offset, - // kind, - // length, - // }) - // } - // - // Descriptor::Option(d) => self.process_option(d)?, - // - // Descriptor::Function(_) | Descriptor::Closure(_) | Descriptor::Slice(_) => bail!( - // "unsupported argument type for calling JS function from Rust: {:?}", - // arg - // ), + + Descriptor::Vector(_) => { + let kind = arg.vector_kind().ok_or_else(|| { + format_err!( + "unsupported argument type for calling JS function from Rust {:?}", + arg + ) + })?; + self.instruction( + &[AdapterType::I32; 2], + Instruction::VectorLoad { kind }, + &[AdapterType::Vector(kind)], + ); + } + + Descriptor::Option(d) => self.outgoing_option(d)?, + + Descriptor::Function(_) | Descriptor::Closure(_) | Descriptor::Slice(_) => bail!( + "unsupported argument type for calling JS function from Rust: {:?}", + arg + ), // nothing to do Descriptor::Unit => {} // Largely synthetic and can't show up Descriptor::ClampedU8 => unreachable!(), - _ => {} } Ok(()) } - // fn process_ref(&mut self, mutable: bool, arg: &Descriptor) -> Result<(), Error> { - // match arg { - // Descriptor::Anyref => { - // let idx = self.push_wasm(ValType::Anyref); - // self.webidl.push(ast::WebidlScalarType::Any); - // self.bindings - // .push(NonstandardOutgoing::BorrowedAnyref { idx }); - // } - // Descriptor::CachedString => self.cached_string(false, false), - // Descriptor::Slice(_) | Descriptor::String => { - // use wasm_webidl_bindings::ast::WebidlScalarType::*; - // - // let kind = arg.vector_kind().ok_or_else(|| { - // format_err!( - // "unsupported argument type for calling JS function from Rust {:?}", - // arg - // ) - // })?; - // let offset = self.push_wasm(ValType::I32); - // let length = self.push_wasm(ValType::I32); - // match kind { - // VectorKind::I8 => self.standard_view(offset, length, Int8Array), - // VectorKind::U8 => self.standard_view(offset, length, Uint8Array), - // VectorKind::ClampedU8 => self.standard_view(offset, length, Uint8ClampedArray), - // VectorKind::I16 => self.standard_view(offset, length, Int16Array), - // VectorKind::U16 => self.standard_view(offset, length, Uint16Array), - // VectorKind::I32 => self.standard_view(offset, length, Int32Array), - // VectorKind::U32 => self.standard_view(offset, length, Uint32Array), - // VectorKind::F32 => self.standard_view(offset, length, Float32Array), - // VectorKind::F64 => self.standard_view(offset, length, Float64Array), - // VectorKind::String => { - // self.webidl.push(DomString); - // let binding = ast::OutgoingBindingExpressionUtf8Str { - // ty: ast::WebidlScalarType::DomString.into(), - // offset, - // length, - // }; - // self.bindings - // .push(NonstandardOutgoing::Standard(binding.into())); - // } - // VectorKind::I64 | VectorKind::U64 => { - // let signed = match kind { - // VectorKind::I64 => true, - // _ => false, - // }; - // self.webidl.push(Any); - // self.bindings.push(NonstandardOutgoing::View64 { - // offset, - // length, - // signed, - // }); - // } - // VectorKind::Anyref => { - // self.webidl.push(Any); - // self.bindings - // .push(NonstandardOutgoing::ViewAnyref { offset, length }); - // } - // } - // } - // - // Descriptor::Function(descriptor) => { - // let module = self - // .module - // .as_mut() - // .ok_or_else(|| format_err!("cannot return a closure from Rust"))?; - // let section = self.bindings_section.as_mut().unwrap(); - // // synthesize the a/b arguments that aren't present in the - // // signature from wasm-bindgen but are present in the wasm file. - // let mut descriptor = (**descriptor).clone(); - // let nargs = descriptor.arguments.len(); - // descriptor.arguments.insert(0, Descriptor::I32); - // descriptor.arguments.insert(0, Descriptor::I32); - // let binding_idx = super::bindings::register_table_element( - // module, - // section, - // descriptor.shim_idx, - // descriptor, - // )?; - // let a = self.push_wasm(ValType::I32); - // let b = self.push_wasm(ValType::I32); - // self.webidl.push(ast::WebidlScalarType::Any); - // self.bindings.push(NonstandardOutgoing::StackClosure { - // a, - // b, - // binding_idx, - // nargs, - // mutable, - // }); - // } - // - // _ => bail!( - // "unsupported reference argument type for calling JS function from Rust: {:?}", - // arg - // ), - // } - // Ok(()) - // } - // - // fn process_option(&mut self, arg: &Descriptor) -> Result<(), Error> { - // match arg { - // Descriptor::Anyref => self.standard_as(ValType::Anyref, ast::WebidlScalarType::Any), - // Descriptor::I8 => self.option_sentinel(), - // Descriptor::U8 => self.option_sentinel(), - // Descriptor::I16 => self.option_sentinel(), - // Descriptor::U16 => self.option_sentinel(), - // Descriptor::I32 => self.option_native(true, ValType::I32), - // Descriptor::U32 => self.option_native(false, ValType::I32), - // Descriptor::F32 => self.option_native(true, ValType::F32), - // Descriptor::F64 => self.option_native(true, ValType::F64), - // Descriptor::I64 | Descriptor::U64 => { - // let signed = match arg { - // Descriptor::I64 => true, - // _ => false, - // }; - // let binding = NonstandardOutgoing::OptionInt64 { - // present: self.push_wasm(ValType::I32), - // _ignored: self.push_wasm(ValType::I32), - // lo: self.push_wasm(ValType::I32), - // hi: self.push_wasm(ValType::I32), - // signed, - // }; - // self.webidl.push(ast::WebidlScalarType::Any); - // self.bindings.push(binding); - // } - // Descriptor::Boolean => { - // let idx = self.push_wasm(ValType::I32); - // self.webidl.push(ast::WebidlScalarType::Any); - // self.bindings.push(NonstandardOutgoing::OptionBool { idx }); - // } - // Descriptor::Char => { - // let idx = self.push_wasm(ValType::I32); - // self.webidl.push(ast::WebidlScalarType::Any); - // self.bindings.push(NonstandardOutgoing::OptionChar { idx }); - // } - // Descriptor::Enum { hole } => { - // let idx = self.push_wasm(ValType::I32); - // self.webidl.push(ast::WebidlScalarType::Long); - // self.bindings - // .push(NonstandardOutgoing::OptionIntegerEnum { idx, hole: *hole }); - // } - // Descriptor::RustStruct(name) => { - // let idx = self.push_wasm(ValType::I32); - // self.webidl.push(ast::WebidlScalarType::Any); - // self.bindings.push(NonstandardOutgoing::OptionRustType { - // idx, - // class: name.to_string(), - // }); - // } - // Descriptor::Ref(d) => self.process_option_ref(false, d)?, - // Descriptor::RefMut(d) => self.process_option_ref(true, d)?, - // - // Descriptor::CachedString => self.cached_string(true, true), - // - // Descriptor::String | Descriptor::Vector(_) => { - // let kind = arg.vector_kind().ok_or_else(|| { - // format_err!( - // "unsupported optional slice type for calling JS function from Rust {:?}", - // arg - // ) - // })?; - // let offset = self.push_wasm(ValType::I32); - // let length = self.push_wasm(ValType::I32); - // self.webidl.push(ast::WebidlScalarType::Any); - // self.bindings.push(NonstandardOutgoing::OptionVector { - // kind, - // offset, - // length, - // }) - // } - // - // _ => bail!( - // "unsupported optional argument type for calling JS function from Rust: {:?}", - // arg - // ), - // } - // Ok(()) - // } - // - // fn process_option_ref(&mut self, _mutable: bool, arg: &Descriptor) -> Result<(), Error> { - // match arg { - // Descriptor::Anyref => { - // let idx = self.push_wasm(ValType::Anyref); - // self.webidl.push(ast::WebidlScalarType::Any); - // self.bindings - // .push(NonstandardOutgoing::BorrowedAnyref { idx }); - // } - // Descriptor::CachedString => self.cached_string(true, false), - // Descriptor::String | Descriptor::Slice(_) => { - // let kind = arg.vector_kind().ok_or_else(|| { - // format_err!( - // "unsupported optional slice type for calling JS function from Rust {:?}", - // arg - // ) - // })?; - // let offset = self.push_wasm(ValType::I32); - // let length = self.push_wasm(ValType::I32); - // self.webidl.push(ast::WebidlScalarType::Any); - // self.bindings.push(NonstandardOutgoing::OptionSlice { - // kind, - // offset, - // length, - // }); - // } - // _ => bail!( - // "unsupported optional ref argument type for calling JS function from Rust: {:?}", - // arg - // ), - // } - // Ok(()) - // } - // - // fn push_wasm(&mut self, ty: ValType) -> u32 { - // self.wasm.push(ty); - // self.wasm.len() as u32 - 1 - // } - // - // fn standard_as(&mut self, wasm: ValType, webidl: ast::WebidlScalarType) { - // let binding = ast::OutgoingBindingExpressionAs { - // ty: webidl.into(), - // idx: self.push_wasm(wasm), - // }; - // self.webidl.push(webidl); - // self.bindings - // .push(NonstandardOutgoing::Standard(binding.into())); - // } - // - // fn standard_view(&mut self, offset: u32, length: u32, ty: ast::WebidlScalarType) { - // let binding = ast::OutgoingBindingExpressionView { - // ty: ty.into(), - // offset, - // length, - // }; - // self.webidl.push(ty); - // self.bindings - // .push(NonstandardOutgoing::Standard(binding.into())); - // } - // - // fn cached_string(&mut self, optional: bool, owned: bool) { - // let offset = self.push_wasm(ValType::I32); - // let length = self.push_wasm(ValType::I32); - // self.webidl.push(ast::WebidlScalarType::DomString); - // self.bindings.push(NonstandardOutgoing::CachedString { - // offset, - // length, - // owned, - // optional, - // }) - // } - // - // fn option_native(&mut self, signed: bool, ty: ValType) { - // let present = self.push_wasm(ValType::I32); - // let val = self.push_wasm(ty); - // self.webidl.push(ast::WebidlScalarType::Any); - // self.bindings.push(NonstandardOutgoing::OptionNative { - // signed, - // present, - // val, - // }); - // } - // - // fn option_sentinel(&mut self) { - // let idx = self.push_wasm(ValType::I32); - // self.webidl.push(ast::WebidlScalarType::Any); - // self.bindings - // .push(NonstandardOutgoing::OptionU32Sentinel { idx }); - // } + fn outgoing_ref(&mut self, mutable: bool, arg: &Descriptor) -> Result<(), Error> { + match arg { + Descriptor::Anyref => { + self.instruction( + &[AdapterType::I32], + Instruction::TableGet, + &[AdapterType::Anyref], + ); + } + Descriptor::CachedString => self.cached_string(false, false), + + Descriptor::String => { + let std = wit_walrus::Instruction::MemoryToString(self.cx.memory()?); + self.instruction( + &[AdapterType::I32; 2], + Instruction::Standard(std), + &[AdapterType::String], + ); + } + Descriptor::Slice(_) => { + let kind = arg.vector_kind().ok_or_else(|| { + format_err!( + "unsupported argument type for calling JS function from Rust {:?}", + arg + ) + })?; + self.instruction( + &[AdapterType::I32; 2], + Instruction::View { kind }, + &[AdapterType::Vector(kind)], + ); + } + + Descriptor::Function(descriptor) => { + // synthesize the a/b arguments that aren't present in the + // signature from wasm-bindgen but are present in the wasm file. + let mut descriptor = (**descriptor).clone(); + let nargs = descriptor.arguments.len(); + descriptor.arguments.insert(0, Descriptor::I32); + descriptor.arguments.insert(0, Descriptor::I32); + let adapter = self + .cx + .table_element_adapter(descriptor.shim_idx, descriptor)?; + self.instruction( + &[AdapterType::I32; 2], + Instruction::StackClosure { + adapter, + nargs, + mutable, + }, + &[AdapterType::Anyref], + ); + } + + _ => bail!( + "unsupported reference argument type for calling JS function from Rust: {:?}", + arg + ), + } + Ok(()) + } + + fn outgoing_option(&mut self, arg: &Descriptor) -> Result<(), Error> { + match arg { + Descriptor::Anyref => { + self.instruction( + &[AdapterType::I32], + Instruction::AnyrefLoadOptionOwned, + &[AdapterType::Anyref], + ); + } + Descriptor::I8 => self.out_option_sentinel(), + Descriptor::U8 => self.out_option_sentinel(), + Descriptor::I16 => self.out_option_sentinel(), + Descriptor::U16 => self.out_option_sentinel(), + Descriptor::I32 => self.option_native(true, ValType::I32), + Descriptor::U32 => self.option_native(false, ValType::I32), + Descriptor::F32 => self.option_native(true, ValType::F32), + Descriptor::F64 => self.option_native(true, ValType::F64), + Descriptor::I64 | Descriptor::U64 => { + let signed = match arg { + Descriptor::I64 => true, + _ => false, + }; + self.instruction( + &[AdapterType::I32; 3], + Instruction::Option64FromI32 { signed }, + &[AdapterType::Anyref], + ); + } + Descriptor::Boolean => { + self.instruction( + &[AdapterType::I32], + Instruction::OptionBoolFromI32, + &[AdapterType::Anyref], + ); + } + Descriptor::Char => { + self.instruction( + &[AdapterType::I32], + Instruction::OptionCharFromI32, + &[AdapterType::Anyref], + ); + } + Descriptor::Enum { hole } => { + self.instruction( + &[AdapterType::I32], + Instruction::OptionEnumFromI32 { hole: *hole }, + &[AdapterType::Anyref], + ); + } + Descriptor::RustStruct(name) => { + self.instruction( + &[AdapterType::I32], + Instruction::OptionRustFromI32 { + class: name.to_string(), + }, + &[AdapterType::Anyref], + ); + } + Descriptor::Ref(d) => self.outgoing_option_ref(false, d)?, + Descriptor::RefMut(d) => self.outgoing_option_ref(true, d)?, + + // Descriptor::CachedString => self.cached_string(true, true), + Descriptor::String | Descriptor::Vector(_) => { + let kind = arg.vector_kind().ok_or_else(|| { + format_err!( + "unsupported optional slice type for calling JS function from Rust {:?}", + arg + ) + })?; + self.instruction( + &[AdapterType::I32; 2], + Instruction::OptionVectorLoad { kind }, + &[AdapterType::Anyref], + ); + } + + _ => bail!( + "unsupported optional argument type for calling JS function from Rust: {:?}", + arg + ), + } + Ok(()) + } + + fn outgoing_option_ref(&mut self, _mutable: bool, arg: &Descriptor) -> Result<(), Error> { + match arg { + Descriptor::Anyref => { + self.instruction( + &[AdapterType::I32], + Instruction::TableGet, + &[AdapterType::Anyref], + ); + } + Descriptor::CachedString => self.cached_string(true, false), + Descriptor::String | Descriptor::Slice(_) => { + let kind = arg.vector_kind().ok_or_else(|| { + format_err!( + "unsupported optional slice type for calling JS function from Rust {:?}", + arg + ) + })?; + self.instruction( + &[AdapterType::I32; 2], + Instruction::OptionView { kind }, + &[AdapterType::Anyref], + ); + } + _ => bail!( + "unsupported optional ref argument type for calling JS function from Rust: {:?}", + arg + ), + } + Ok(()) + } + + fn outgoing_i32(&mut self, output: AdapterType) { + let std = wit_walrus::Instruction::WasmToInt { + input: walrus::ValType::I32, + output: output.to_wit().unwrap(), + trap: false, + }; + self.instruction(&[AdapterType::I32], Instruction::Standard(std), &[output]); + } + + fn cached_string(&mut self, optional: bool, owned: bool) { + self.instruction( + &[AdapterType::I32; 2], + Instruction::CachedStringLoad { owned, optional }, + &[AdapterType::String], + ); + } + + fn option_native(&mut self, signed: bool, ty: ValType) { + self.instruction( + &[AdapterType::I32, AdapterType::from_wasm(ty).unwrap()], + Instruction::ToOptionNative { signed, ty }, + &[AdapterType::Anyref], + ); + } + + fn out_option_sentinel(&mut self) { + self.instruction( + &[AdapterType::I32], + Instruction::OptionU32Sentinel, + &[AdapterType::Anyref], + ); + } } diff --git a/crates/cli-support/src/wit/section.rs b/crates/cli-support/src/wit/section.rs index 5c1bcd973c4..14e3097c9e5 100644 --- a/crates/cli-support/src/wit/section.rs +++ b/crates/cli-support/src/wit/section.rs @@ -17,9 +17,12 @@ //! the module contains the wasm bindings section and it's actually respected. use crate::descriptor::VectorKind; +use crate::wit::{AdapterId, AdapterJsImportKind, AdapterType, Instruction}; +use crate::wit::{AdapterKind, NonstandardWitSection, WasmBindgenAux}; +use crate::wit::{AuxExport, InstructionData}; use crate::wit::{AuxExportKind, AuxImport, AuxValue, JsImport, JsImportName}; -use crate::wit::{NonstandardWitSection, WasmBindgenAux}; -use anyhow::{bail, Context, Error}; +use anyhow::{anyhow, bail, Context, Error}; +use std::collections::HashMap; use walrus::Module; use wasm_bindgen_multi_value_xform as multi_value_xform; use wasm_bindgen_wasm_conventions as wasm_conventions; @@ -44,131 +47,93 @@ pub fn add( 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(), - // }); - // } + let adapter_context = |id: AdapterId| { + if let Some((name, _)) = nonstandard.exports.iter().find(|p| p.1 == id) { + return format!("in function export `{}`", name); + } + if let Some((core, _)) = nonstandard.implements.iter().find(|p| p.1 == id) { + let import = module.imports.get(*core); + return format!( + "in function import from `{}::{}`", + import.module, import.name + ); + } + unreachable!() + }; + + let mut us2walrus = HashMap::new(); + for (us, func) in nonstandard.adapters.iter() { + if let Some(export) = export_map.get(us) { + check_standard_export(export).context(adapter_context(*us))?; + } + if let Some(import) = import_map.get(us) { + check_standard_import(import).context(adapter_context(*us))?; + } + let params = translate_tys(&func.params).context(adapter_context(*us))?; + let results = translate_tys(&func.results).context(adapter_context(*us))?; + let ty = section.types.add(params, results); + let walrus = match &func.kind { + AdapterKind::Local { .. } => section.funcs.add_local(ty, Vec::new()), + AdapterKind::Import { + module, + name, + kind: AdapterJsImportKind::Normal, + } => section.add_import_func(module, name, ty).0, + AdapterKind::Import { + module, + name, + kind: AdapterJsImportKind::Constructor, + } => { + bail!( + "interfaces types doesn't support import of `{}::{}` \ + as a constructor", + module, + name + ); + } + AdapterKind::Import { + module, + name, + kind: AdapterJsImportKind::Method, + } => { + bail!( + "interfaces types doesn't support import of `{}::{}` \ + as a method", + module, + name + ); + } + }; + us2walrus.insert(*us, walrus); + } + + for (core, adapter) in nonstandard.implements.iter() { + let core = match module.imports.get(*core).kind { + walrus::ImportKind::Function(f) => f, + _ => bail!("cannot implement a non-function"), + }; + section.implements.add(us2walrus[adapter], core); + } + + for (name, adapter) in nonstandard.exports.iter() { + section.exports.add(name, us2walrus[adapter]); + } + + for (id, func) in nonstandard.adapters.iter() { + let instructions = match &func.kind { + AdapterKind::Local { instructions } => instructions, + AdapterKind::Import { .. } => continue, + }; + let result = match &mut section.funcs.get_mut(us2walrus[id]).kind { + wit_walrus::FuncKind::Local(i) => i, + _ => unreachable!(), + }; + + for instruction in instructions { + translate_instruction(instruction, &us2walrus, module) + .with_context(|| adapter_context(*id))?; + } + } if let Some((name, _)) = local_modules.iter().next() { bail!( @@ -197,27 +162,21 @@ pub fn add( ); } - // 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(id) = imports_with_catch.iter().next() { + bail!( + "{}\ngenerating a bindings section is currently incompatible with \ + `#[wasm_bindgen(catch)]`", + adapter_context(*id), + ); + } + + if let Some(id) = imports_with_variadic.iter().next() { + bail!( + "{}\ngenerating a bindings section is currently incompatible with \ + `#[wasm_bindgen(variadic)]`", + adapter_context(*id), + ); + } if let Some(enum_) = enums.iter().next() { bail!( @@ -236,102 +195,91 @@ pub fn add( } module.customs.add(section); - if true { panic!() } 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::WitScalarType::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) -// } +fn translate_instruction( + instr: &InstructionData, + us2walrus: &HashMap, + module: &Module, +) -> Result { + use Instruction::*; + + match &instr.instr { + Standard(s) => Ok(s.clone()), + CallAdapter(id) => { + let id = us2walrus[id]; + Ok(wit_walrus::Instruction::CallAdapter(id)) + } + CallExport(e) => match module.exports.get(*e).item { + walrus::ExportItem::Function(f) => Ok(wit_walrus::Instruction::CallCore(f)), + _ => bail!("can only call exported functions"), + }, + CallTableElement(e) => { + let table = module + .tables + .main_function_table()? + .ok_or_else(|| anyhow!("no function table found in module"))?; + let functions = match &module.tables.get(table).kind { + walrus::TableKind::Function(f) => f, + _ => unreachable!(), + }; + match functions.elements.get(*e as usize) { + Some(Some(f)) => Ok(wit_walrus::Instruction::CallCore(*f)), + _ => bail!("expected to find an element of the function table"), + } + } + StoreRetptr { .. } | LoadRetptr { .. } | Retptr => { + bail!("return pointers aren't supported in wasm interface types"); + } + I32FromBool | BoolFromI32 => { + bail!("booleans aren't supported in wasm interface types"); + } + I32FromStringFirstChar | StringFromChar => { + bail!("chars aren't supported in wasm interface types"); + } + I32FromAnyrefOwned | I32FromAnyrefBorrow | AnyrefLoadOwned | TableGet => { + bail!("anyref pass failed to sink into wasm module"); + } + I32FromAnyrefRustOwned { .. } | I32FromAnyrefRustBorrow { .. } | RustFromI32 { .. } => { + bail!("rust types aren't supported in wasm interface types"); + } + I32Split64 { .. } | I64FromLoHi { .. } => { + bail!("64-bit integers aren't supported in wasm-bindgen"); + } + I32SplitOption64 { .. } + | I32FromOptionAnyref + | I32FromOptionU32Sentinel + | I32FromOptionRust { .. } + | I32FromOptionBool + | I32FromOptionChar + | I32FromOptionEnum { .. } + | FromOptionNative { .. } + | OptionSlice { .. } + | OptionVector { .. } + | AnyrefLoadOptionOwned + | OptionRustFromI32 { .. } + | OptionVectorLoad { .. } + | OptionView { .. } + | OptionU32Sentinel + | ToOptionNative { .. } + | OptionBoolFromI32 + | OptionCharFromI32 + | OptionEnumFromI32 { .. } + | Option64FromI32 { .. } => { + bail!("optional types aren't supported in wasm bindgen"); + } + VectorToMemory { .. } | VectorLoad { .. } | View { .. } => { + bail!("vector slices aren't supported in wasm interface types yet"); + } + CachedStringLoad { .. } => { + bail!("cached strings aren't supported in wasm interface types"); + } + StackClosure { .. } => { + bail!("closures aren't supported in wasm interface types"); + } + } +} fn check_standard_import(import: &AuxImport) -> Result<(), Error> { let desc_js = |js: &JsImport| { @@ -406,3 +354,61 @@ fn check_standard_import(import: &AuxImport) -> Result<(), Error> { item ); } + +fn check_standard_export(export: &AuxExport) -> Result<(), Error> { + // First up make sure this is something that's actually valid to export + // form a vanilla WebAssembly module with WebIDL bindings. + match &export.kind { + AuxExportKind::Function(_) => Ok(()), + 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 + ); + } + } +} + +fn translate_tys(tys: &[AdapterType]) -> Result, Error> { + tys.iter() + .map(|ty| { + ty.to_wit() + .ok_or_else(|| anyhow!("type {:?} isn't supported in standard interface types", ty)) + }) + .collect() +} diff --git a/crates/cli-support/src/wit/standard.rs b/crates/cli-support/src/wit/standard.rs index c5b230c3cf1..9d5a450fb6b 100644 --- a/crates/cli-support/src/wit/standard.rs +++ b/crates/cli-support/src/wit/standard.rs @@ -34,7 +34,7 @@ pub struct Adapter { #[derive(Debug, Clone)] pub enum AdapterKind { Local { - instructions: Vec, + instructions: Vec, }, Import { module: String, @@ -43,6 +43,18 @@ pub enum AdapterKind { }, } +#[derive(Debug, Clone)] +pub struct InstructionData { + pub instr: Instruction, + pub stack_change: StackChange, +} + +#[derive(Debug, Clone)] +pub enum StackChange { + Modified { pushed: usize, popped: usize }, + Unknown, +} + #[derive(Debug, Clone)] pub enum AdapterJsImportKind { /// The first argument is an `anyref` which is the `this` of the function @@ -82,11 +94,21 @@ pub enum Instruction { /// A call to one of our own defined adapters, similar to the standard /// call-adapter instruction CallAdapter(AdapterId), + /// Call an exported function in the core module + CallExport(walrus::ExportId), + /// Call an element in the function table of the core module + CallTableElement(u32), /// An instruction to store `ty` at the `offset` index in the return pointer - StoreRetptr { ty: AdapterType, offset: usize }, + StoreRetptr { + ty: AdapterType, + offset: usize, + }, /// An instruction to load `ty` at the `offset` index from the return pointer - LoadRetptr { ty: AdapterType, offset: usize }, + LoadRetptr { + ty: AdapterType, + offset: usize, + }, /// An instruction which pushes the return pointer onto the stack. Retptr, @@ -102,20 +124,30 @@ pub enum Instruction { I32FromAnyrefBorrow, /// Pops an `anyref` from the stack, assumes it's a Rust class given, and /// deallocates the JS object and returns the i32 Rust pointer. - I32FromAnyrefRustOwned { class: String }, + I32FromAnyrefRustOwned { + class: String, + }, /// Pops an `anyref` from the stack, assumes it's a Rust class given, and /// passes the pointer to Rust which will be borrowed for the duration of a /// call - I32FromAnyrefRustBorrow { class: String }, + I32FromAnyrefRustBorrow { + class: String, + }, /// Pops an `anyref` from the stack, pushes 0 if it's "none" or the /// consumed pointer value if it's "some". - I32FromOptionRust { class: String }, + I32FromOptionRust { + class: String, + }, /// Pops an `s64` or `u64` from the stack, pushing two `i32` values. - I32Split64 { signed: bool }, + I32Split64 { + signed: bool, + }, /// Pops an `s64` or `u64` from the stack, pushing three `i32` values. /// First is the "some/none" bit, and the next is the low bits, and the /// next is the high bits. - I32SplitOption64 { signed: bool }, + I32SplitOption64 { + signed: bool, + }, /// Pops an `anyref` from the stack, pushes either 0 if it's "none" or and /// index into the owned wasm table it was stored at if it's "some" I32FromOptionAnyref, @@ -130,11 +162,15 @@ pub enum Instruction { I32FromOptionChar, /// Pops an `anyref` from the stack, pushes `hole` for "none" or the /// value if it's "some" - I32FromOptionEnum { hole: u32 }, + I32FromOptionEnum { + hole: u32, + }, /// Pops any anyref from the stack and then pushes two values. First is a /// 0/1 if it's none/some and second is `ty` value if it was there or 0 if /// it wasn't there. - OptionNative { ty: walrus::ValType }, + FromOptionNative { + ty: walrus::ValType, + }, /// Pops a vector value of `kind` from the stack, allocates memory with /// `malloc`, and then copies all the data into `mem`. Pushes the pointer @@ -147,13 +183,79 @@ pub enum Instruction { /// Pops an anyref, pushes pointer/length or all zeros. Will update original /// view if mutable. - OptionSlice { kind: VectorKind, mutable: bool }, + OptionSlice { + kind: VectorKind, + mutable: bool, + }, /// Pops an anyref, pushes pointer/length or all zeros - OptionVector { kind: VectorKind }, + OptionVector { + kind: VectorKind, + }, - /// pops a `bool`, pushes `i32` + /// pops a `i32`, pushes `bool` BoolFromI32, + /// pops `i32`, loads anyref at that slot, dealloates anyref, pushes `anyref` + AnyrefLoadOwned, + /// pops `i32`, loads anyref at that slot, dealloates anyref, pushes `anyref` + AnyrefLoadOptionOwned, + /// pops `i32`, pushes string from that `char` + StringFromChar, + /// pops two `i32`, pushes a 64-bit number + I64FromLoHi { + signed: bool, + }, + /// pops `i32`, pushes an anyref for the wrapped rust class + RustFromI32 { + class: String, + }, + OptionRustFromI32 { + class: String, + }, + /// pops ptr/length i32, loads string from cache + CachedStringLoad { + owned: bool, + optional: bool, + }, + /// pops ptr/length, pushes a vector, frees the original data + VectorLoad { + kind: VectorKind, + }, + /// pops ptr/length, pushes a vector, frees the original data + OptionVectorLoad { + kind: VectorKind, + }, + /// pops i32, loads anyref from anyref table + TableGet, + /// pops two i32 data pointers, pushes an anyref closure + StackClosure { + adapter: AdapterId, + nargs: usize, + mutable: bool, + }, + /// pops two i32 data pointers, pushes a vector view + View { + kind: VectorKind, + }, + /// pops two i32 data pointers, pushes a vector view + OptionView { + kind: VectorKind, + }, + /// pops i32, pushes it viewed as an optional value with a known sentinel + OptionU32Sentinel, + /// pops an i32, then `ty`, then pushes anyref + ToOptionNative { + ty: walrus::ValType, + signed: bool, + }, + OptionBoolFromI32, + OptionCharFromI32, + OptionEnumFromI32 { + hole: u32, + }, + Option64FromI32 { + signed: bool, + }, } impl AdapterType { From c019a032f395dcdaf64ea7a18e5e90c462af4744 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 26 Nov 2019 09:49:08 -0800 Subject: [PATCH 07/35] More work in progress Next up figuring out how to juggle malloc functions for strings and vectors and such. --- crates/cli-support/src/anyref.rs | 10 +- crates/cli-support/src/js/binding.rs | 972 ++++++++++++++++------ crates/cli-support/src/js/incoming.rs | 197 ----- crates/cli-support/src/js/mod.rs | 423 +++++----- crates/cli-support/src/lib.rs | 46 +- crates/cli-support/src/multivalue.rs | 6 +- crates/cli-support/src/wit/incoming.rs | 7 +- crates/cli-support/src/wit/mod.rs | 11 +- crates/cli-support/src/wit/nonstandard.rs | 1 + crates/cli-support/src/wit/outgoing.rs | 6 +- crates/cli-support/src/wit/section.rs | 11 +- crates/cli-support/src/wit/standard.rs | 10 +- 12 files changed, 1014 insertions(+), 686 deletions(-) diff --git a/crates/cli-support/src/anyref.rs b/crates/cli-support/src/anyref.rs index 5dd29988786..9250bcd264f 100644 --- a/crates/cli-support/src/anyref.rs +++ b/crates/cli-support/src/anyref.rs @@ -1,7 +1,7 @@ -use crate::wit::{AdapterKind, Instruction, NonstandardWitSection, WasmBindgenAux}; +use crate::wit::{AdapterKind, Instruction, NonstandardWitSection}; use crate::wit::{AdapterType, InstructionData, StackChange}; use anyhow::Error; -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; use walrus::Module; use wasm_bindgen_anyref_xform::Context; @@ -181,6 +181,12 @@ fn import_xform( while let Some((i, instr)) = iter.next() { match instr.instr { Instruction::AnyrefLoadOwned => { + assert_eq!(results.len(), 1); + match results[0] { + AdapterType::I32 => {} + _ => panic!("must be `i32` type"), + } + results[0] = AdapterType::Anyref; ret_anyref = true; to_delete.push(i); } diff --git a/crates/cli-support/src/js/binding.rs b/crates/cli-support/src/js/binding.rs index 2154d7618a9..615fc28c484 100644 --- a/crates/cli-support/src/js/binding.rs +++ b/crates/cli-support/src/js/binding.rs @@ -4,13 +4,11 @@ //! exported functions, table elements, imports, etc. All function shims //! generated by `wasm-bindgen` run through this type. -use crate::js::incoming; -use crate::js::outgoing; use crate::js::Context; -use crate::webidl::Binding; -use anyhow::{bail, Error}; +use crate::wit::{Adapter, AdapterId, AdapterKind, AdapterType, Instruction}; +use anyhow::{anyhow, bail, Error}; use std::collections::HashSet; -use wasm_webidl_bindings::ast; +use walrus::Module; /// A one-size-fits-all builder for processing WebIDL bindings and generating /// JS. @@ -51,12 +49,14 @@ pub struct Builder<'a, 'b> { } /// Helper struct used in incoming/outgoing to generate JS. -pub struct JsBuilder { +pub struct JsBuilder<'a, 'b> { + cx: &'a mut Context<'b>, typescript: Vec, prelude: String, finally: String, tmp: usize, args: Vec, + stack: Vec, } pub struct TypescriptArg { @@ -109,251 +109,267 @@ impl<'a, 'b> Builder<'a, 'b> { pub fn process( &mut self, - binding: &Binding, - webidl: &ast::WebidlFunction, - incoming_args: bool, + adapter: &Adapter, + // webidl: &ast::WebidlFunction, + // incoming_args: bool, explicit_arg_names: &Option>, - invoke: &mut dyn FnMut(&mut Context, &mut String, &[String]) -> Result, + // invoke: &mut dyn FnMut(&mut Context, &mut String, &[String]) -> Result, ) -> Result { // used in `finalize` below if self.log_error { self.cx.expose_log_error(); } - // First up we handle all the arguments. Depending on whether incoming - // or outgoing ar the arguments this is pretty different. - let mut arg_names = Vec::new(); - let mut js; - if incoming_args { - let mut webidl_params = webidl.params.iter(); - - // If we're returning via an out pointer then it's guaranteed to be - // the first argument. This isn't an argument of the function shim - // we're generating so synthesize the parameter and its value. - // - // For the actual value of the return pointer we just pick the first - // properly aligned nonzero address. We use the address for a - // BigInt64Array sometimes which means it needs to be 8-byte - // aligned. Otherwise valid code is unlikely to ever be working - // around address 8, so this should be a safe address to use for - // returning data through. - if binding.return_via_outptr.is_some() { - drop(webidl_params.next()); - self.args_prelude.push_str("const retptr = 8;\n"); - arg_names.push("retptr".to_string()); - } + let mut params = adapter.params.iter(); + let mut adapter_params = Vec::new(); - // If this is a method then we're generating this as part of a class - // method, so the leading parameter is the this pointer stored on - // the JS object, so synthesize that here. - match self.method { - Some(consumes_self) => { - drop(webidl_params.next()); - if self.cx.config.debug { - self.args_prelude.push_str( - "if (this.ptr == 0) throw new Error('Attempt to use a moved value');\n", - ); - } - if consumes_self { - self.args_prelude.push_str("const ptr = this.ptr;\n"); - self.args_prelude.push_str("this.ptr = 0;\n"); - arg_names.push("ptr".to_string()); - } else { - arg_names.push("this.ptr".to_string()); - } + // If this is a method then we're generating this as part of a class + // method, so the leading parameter is the this pointer stored on + // the JS object, so synthesize that here. + match self.method { + Some(consumes_self) => { + drop(params.next()); + if self.cx.config.debug { + self.args_prelude.push_str( + "if (this.ptr == 0) throw new Error('Attempt to use a moved value');\n", + ); + } + if consumes_self { + self.args_prelude.push_str("const ptr = this.ptr;\n"); + self.args_prelude.push_str("this.ptr = 0;\n"); + adapter_params.push("ptr".to_string()); + } else { + adapter_params.push("this.ptr".to_string()); } - None => {} - } - - // And now take the rest of the parameters and generate a name for them. - for (i, _) in webidl_params.enumerate() { - let arg = match explicit_arg_names { - Some(list) => list[i].clone(), - None => format!("arg{}", i), - }; - self.function_args.push(arg.clone()); - arg_names.push(arg); - } - js = JsBuilder::new(arg_names); - let mut args = incoming::Incoming::new(self.cx, &webidl.params, &mut js); - for argument in binding.incoming.iter() { - self.invoc_args.extend(args.process(argument)?); - } - } else { - // If we're getting arguments from outgoing values then the ret ptr - // is actually an argument of the function itself. That means that - // `arg0` we generate below is the ret ptr, and we shouldn't - // generate a JS binding for it and instead skip the first binding - // listed. - let mut skip = 0; - if binding.return_via_outptr.is_some() { - skip = 1; - } - - // And now take the rest of the parameters and generate a name for them. - for i in 0..self.cx.module.types.get(binding.wasm_ty).params().len() { - let arg = format!("arg{}", i); - self.function_args.push(arg.clone()); - arg_names.push(arg); - } - js = JsBuilder::new(arg_names); - let mut args = outgoing::Outgoing::new(self.cx, &mut js); - for argument in binding.outgoing.iter().skip(skip) { - self.invoc_args.push(args.process(argument)?); } + None => {} } - - // Save off the results of JS generation for the arguments. - self.args_prelude.push_str(&js.prelude); - self.finally.push_str(&js.finally); - self.ts_args.extend(js.typescript); - - // Remove extraneous typescript args which were synthesized and aren't - // part of our function shim. - while self.ts_args.len() > self.function_args.len() { - self.ts_args.remove(0); + for (i, _param) in params.enumerate() { + let arg = match explicit_arg_names { + Some(list) => list[i].clone(), + None => format!("arg{}", i), + }; + adapter_params.push(arg.clone()); + self.function_args.push(arg); } + let mut js = JsBuilder::new(self.cx, adapter_params); - // Handle the special case where there is no return value. In this case - // we can skip all the logic below and go straight to the end. - if incoming_args { - if binding.outgoing.len() == 0 { - assert!(binding.return_via_outptr.is_none()); - assert!(self.constructor.is_none()); - let invoc = invoke(self.cx, &mut self.args_prelude, &self.invoc_args)?; - return Ok(self.finalize(&invoc)); - } - assert_eq!(binding.outgoing.len(), 1); - } else { - if binding.incoming.len() == 0 { - assert!(binding.return_via_outptr.is_none()); - assert!(self.constructor.is_none()); - let invoc = invoke(self.cx, &mut self.args_prelude, &self.invoc_args)?; - return Ok(self.finalize(&invoc)); - } - assert_eq!(binding.incoming.len(), 1); - } - - // Like above handling the return value is quite different based on - // whether it's an outgoing argument or an incoming argument. - let mut ret_args = Vec::new(); - let mut js; - if incoming_args { - match &binding.return_via_outptr { - // If we have an outgoing value that requires multiple - // aggregates then we're passing a return pointer (a global one) - // to a wasm function, and then afterwards we're going to read - // the results of that return pointer. Here we generate an - // expression effectively which represents reading each value of - // the return pointer that was filled in. These values are then - // used by the outgoing builder as inputs to generate the final - // actual return value. - Some(list) => { - let mut exposed = HashSet::new(); - for (i, ty) in list.iter().enumerate() { - let (mem, size) = match ty { - walrus::ValType::I32 => { - if exposed.insert(*ty) { - self.cx.expose_int32_memory(); - self.ret_prelude - .push_str("const memi32 = getInt32Memory();\n"); - } - ("memi32", 4) - } - walrus::ValType::F32 => { - if exposed.insert(*ty) { - self.cx.expose_f32_memory(); - self.ret_prelude - .push_str("const memf32 = getFloat32Memory();\n"); - } - ("memf32", 4) - } - walrus::ValType::F64 => { - if exposed.insert(*ty) { - self.cx.expose_f64_memory(); - self.ret_prelude - .push_str("const memf64 = getFloat64Memory();\n"); - } - ("memf64", 8) - } - _ => bail!("invalid aggregate return type"), - }; - ret_args.push(format!("{}[retptr / {} + {}]", mem, size, i)); - } - } + // for instr in adapter.instrs.iter() { + // } - // No return pointer? That's much easier! - // - // If there's one return value we just have one input of `ret` - // which is created in the JS shim below. If there's multiple - // return values (a multi-value module) then we'll pull results - // from the returned array. - None => { - let amt = self.cx.module.types.get(binding.wasm_ty).results().len(); - if amt == 1 { - ret_args.push("ret".to_string()); - } else { - for i in 0..amt { - ret_args.push(format!("ret[{}]", i)); - } - } - } - } - js = JsBuilder::new(ret_args); - let mut ret = outgoing::Outgoing::new(self.cx, &mut js); - let ret_js = ret.process(&binding.outgoing[0])?; - self.ret_js.push_str(&ret_js); - } else { - // If there's an out ptr for an incoming argument then it means that - // the first argument to our function is the return pointer, and we - // need to fill that in. After we process the value we then write - // each result of the processed value into the corresponding typed - // array. - js = JsBuilder::new(vec!["ret".to_string()]); - let results = match &webidl.result { - Some(ptr) => std::slice::from_ref(ptr), - None => &[], - }; - let mut ret = incoming::Incoming::new(self.cx, results, &mut js); - let ret_js = ret.process(&binding.incoming[0])?; - match &binding.return_via_outptr { - Some(list) => { - assert_eq!(list.len(), ret_js.len()); - for (i, js) in ret_js.iter().enumerate() { - self.ret_finally - .push_str(&format!("const ret{} = {};\n", i, js)); - } - for (i, ty) in list.iter().enumerate() { - let (mem, size) = match ty { - walrus::ValType::I32 => { - self.cx.expose_int32_memory(); - ("getInt32Memory()", 4) - } - walrus::ValType::F32 => { - self.cx.expose_f32_memory(); - ("getFloat32Memory()", 4) - } - walrus::ValType::F64 => { - self.cx.expose_f64_memory(); - ("getFloat64Memory()", 8) - } - _ => bail!("invalid aggregate return type"), - }; - self.ret_finally - .push_str(&format!("{}[arg0 / {} + {}] = ret{};\n", mem, size, i, i)); - } - } - None => { - assert_eq!(ret_js.len(), 1); - self.ret_js.push_str(&ret_js[0]); - } - } - } - self.ret_finally.push_str(&js.finally); - self.ret_prelude.push_str(&js.prelude); - self.ts_ret = Some(js.typescript.remove(0)); - let invoc = invoke(self.cx, &mut self.args_prelude, &self.invoc_args)?; - Ok(self.finalize(&invoc)) + // // First up we handle all the arguments. Depending on whether incoming + // // or outgoing ar the arguments this is pretty different. + // let mut arg_names = Vec::new(); + // let mut js; + // if incoming_args { + // let mut webidl_params = webidl.params.iter(); + // + // // If we're returning via an out pointer then it's guaranteed to be + // // the first argument. This isn't an argument of the function shim + // // we're generating so synthesize the parameter and its value. + // // + // // For the actual value of the return pointer we just pick the first + // // properly aligned nonzero address. We use the address for a + // // BigInt64Array sometimes which means it needs to be 8-byte + // // aligned. Otherwise valid code is unlikely to ever be working + // // around address 8, so this should be a safe address to use for + // // returning data through. + // if binding.return_via_outptr.is_some() { + // drop(webidl_params.next()); + // self.args_prelude.push_str("const retptr = 8;\n"); + // arg_names.push("retptr".to_string()); + // } + // + // // And now take the rest of the parameters and generate a name for them. + // for (i, _) in webidl_params.enumerate() { + // let arg = match explicit_arg_names { + // Some(list) => list[i].clone(), + // None => format!("arg{}", i), + // }; + // self.function_args.push(arg.clone()); + // arg_names.push(arg); + // } + // js = JsBuilder::new(arg_names); + // let mut args = incoming::Incoming::new(self.cx, &webidl.params, &mut js); + // for argument in binding.incoming.iter() { + // self.invoc_args.extend(args.process(argument)?); + // } + // } else { + // // If we're getting arguments from outgoing values then the ret ptr + // // is actually an argument of the function itself. That means that + // // `arg0` we generate below is the ret ptr, and we shouldn't + // // generate a JS binding for it and instead skip the first binding + // // listed. + // let mut skip = 0; + // if binding.return_via_outptr.is_some() { + // skip = 1; + // } + // + // // And now take the rest of the parameters and generate a name for them. + // for i in 0..self.cx.module.types.get(binding.wasm_ty).params().len() { + // let arg = format!("arg{}", i); + // self.function_args.push(arg.clone()); + // arg_names.push(arg); + // } + // js = JsBuilder::new(arg_names); + // let mut args = outgoing::Outgoing::new(self.cx, &mut js); + // for argument in binding.outgoing.iter().skip(skip) { + // self.invoc_args.push(args.process(argument)?); + // } + // } + // + // // Save off the results of JS generation for the arguments. + // self.args_prelude.push_str(&js.prelude); + // self.finally.push_str(&js.finally); + // self.ts_args.extend(js.typescript); + // + // // Remove extraneous typescript args which were synthesized and aren't + // // part of our function shim. + // while self.ts_args.len() > self.function_args.len() { + // self.ts_args.remove(0); + // } + // + // // Handle the special case where there is no return value. In this case + // // we can skip all the logic below and go straight to the end. + // if incoming_args { + // if binding.outgoing.len() == 0 { + // assert!(binding.return_via_outptr.is_none()); + // assert!(self.constructor.is_none()); + // let invoc = invoke(self.cx, &mut self.args_prelude, &self.invoc_args)?; + // return Ok(self.finalize(&invoc)); + // } + // assert_eq!(binding.outgoing.len(), 1); + // } else { + // if binding.incoming.len() == 0 { + // assert!(binding.return_via_outptr.is_none()); + // assert!(self.constructor.is_none()); + // let invoc = invoke(self.cx, &mut self.args_prelude, &self.invoc_args)?; + // return Ok(self.finalize(&invoc)); + // } + // assert_eq!(binding.incoming.len(), 1); + // } + // + // // Like above handling the return value is quite different based on + // // whether it's an outgoing argument or an incoming argument. + // let mut ret_args = Vec::new(); + // let mut js; + // if incoming_args { + // match &binding.return_via_outptr { + // // If we have an outgoing value that requires multiple + // // aggregates then we're passing a return pointer (a global one) + // // to a wasm function, and then afterwards we're going to read + // // the results of that return pointer. Here we generate an + // // expression effectively which represents reading each value of + // // the return pointer that was filled in. These values are then + // // used by the outgoing builder as inputs to generate the final + // // actual return value. + // Some(list) => { + // let mut exposed = HashSet::new(); + // for (i, ty) in list.iter().enumerate() { + // let (mem, size) = match ty { + // walrus::ValType::I32 => { + // if exposed.insert(*ty) { + // self.cx.expose_int32_memory(); + // self.ret_prelude + // .push_str("const memi32 = getInt32Memory();\n"); + // } + // ("memi32", 4) + // } + // walrus::ValType::F32 => { + // if exposed.insert(*ty) { + // self.cx.expose_f32_memory(); + // self.ret_prelude + // .push_str("const memf32 = getFloat32Memory();\n"); + // } + // ("memf32", 4) + // } + // walrus::ValType::F64 => { + // if exposed.insert(*ty) { + // self.cx.expose_f64_memory(); + // self.ret_prelude + // .push_str("const memf64 = getFloat64Memory();\n"); + // } + // ("memf64", 8) + // } + // _ => bail!("invalid aggregate return type"), + // }; + // ret_args.push(format!("{}[retptr / {} + {}]", mem, size, i)); + // } + // } + // + // // No return pointer? That's much easier! + // // + // // If there's one return value we just have one input of `ret` + // // which is created in the JS shim below. If there's multiple + // // return values (a multi-value module) then we'll pull results + // // from the returned array. + // None => { + // let amt = self.cx.module.types.get(binding.wasm_ty).results().len(); + // if amt == 1 { + // ret_args.push("ret".to_string()); + // } else { + // for i in 0..amt { + // ret_args.push(format!("ret[{}]", i)); + // } + // } + // } + // } + // js = JsBuilder::new(ret_args); + // let mut ret = outgoing::Outgoing::new(self.cx, &mut js); + // let ret_js = ret.process(&binding.outgoing[0])?; + // self.ret_js.push_str(&ret_js); + // } else { + // // If there's an out ptr for an incoming argument then it means that + // // the first argument to our function is the return pointer, and we + // // need to fill that in. After we process the value we then write + // // each result of the processed value into the corresponding typed + // // array. + // js = JsBuilder::new(vec!["ret".to_string()]); + // let results = match &webidl.result { + // Some(ptr) => std::slice::from_ref(ptr), + // None => &[], + // }; + // let mut ret = incoming::Incoming::new(self.cx, results, &mut js); + // let ret_js = ret.process(&binding.incoming[0])?; + // match &binding.return_via_outptr { + // Some(list) => { + // assert_eq!(list.len(), ret_js.len()); + // for (i, js) in ret_js.iter().enumerate() { + // self.ret_finally + // .push_str(&format!("const ret{} = {};\n", i, js)); + // } + // for (i, ty) in list.iter().enumerate() { + // let (mem, size) = match ty { + // walrus::ValType::I32 => { + // self.cx.expose_int32_memory(); + // ("getInt32Memory()", 4) + // } + // walrus::ValType::F32 => { + // self.cx.expose_f32_memory(); + // ("getFloat32Memory()", 4) + // } + // walrus::ValType::F64 => { + // self.cx.expose_f64_memory(); + // ("getFloat64Memory()", 8) + // } + // _ => bail!("invalid aggregate return type"), + // }; + // self.ret_finally + // .push_str(&format!("{}[arg0 / {} + {}] = ret{};\n", mem, size, i, i)); + // } + // } + // None => { + // assert_eq!(ret_js.len(), 1); + // self.ret_js.push_str(&ret_js[0]); + // } + // } + // } + // self.ret_finally.push_str(&js.finally); + // self.ret_prelude.push_str(&js.prelude); + // self.ts_ret = Some(js.typescript.remove(0)); + // let invoc = invoke(self.cx, &mut self.args_prelude, &self.invoc_args)?; + // Ok(self.finalize(&invoc)) + Ok(self.finalize("x")) } // This method... is a mess. Refactorings and improvements are more than @@ -480,14 +496,16 @@ impl<'a, 'b> Builder<'a, 'b> { } } -impl JsBuilder { - pub fn new(args: Vec) -> JsBuilder { +impl<'a, 'b> JsBuilder<'a, 'b> { + pub fn new(cx: &'a mut Context<'b>, args: Vec) -> JsBuilder<'a, 'b> { JsBuilder { + cx, args, tmp: 0, finally: String::new(), prelude: String::new(), typescript: Vec::new(), + stack: Vec::new(), } } @@ -536,4 +554,480 @@ impl JsBuilder { self.tmp += 1; return ret; } + + fn pop(&mut self) -> String { + self.stack.pop().unwrap() + } + + fn push(&mut self, arg: String) { + self.stack.push(arg); + } + + fn assert_class(&mut self, arg: &str, class: &str) { + self.cx.expose_assert_class(); + self.prelude(&format!("_assertClass({}, {});", arg, class)); + } + + fn assert_number(&mut self, arg: &str) { + if !self.cx.config.debug { + return; + } + self.cx.expose_assert_num(); + self.prelude(&format!("_assertNum({});", arg)); + } + + fn assert_bool(&mut self, arg: &str) { + if !self.cx.config.debug { + return; + } + self.cx.expose_assert_bool(); + self.prelude(&format!("_assertBoolean({});", arg)); + } + + fn assert_optional_number(&mut self, arg: &str) { + if !self.cx.config.debug { + return; + } + self.cx.expose_is_like_none(); + self.prelude(&format!("if (!isLikeNone({})) {{", arg)); + self.assert_number(arg); + self.prelude("}"); + } + + fn assert_optional_bool(&mut self, arg: &str) { + if !self.cx.config.debug { + return; + } + self.cx.expose_is_like_none(); + self.prelude(&format!("if (!isLikeNone({})) {{", arg)); + self.assert_bool(arg); + self.prelude("}"); + } + + fn assert_not_moved(&mut self, arg: &str) { + if !self.cx.config.debug { + return; + } + self.prelude(&format!( + "\ + if ({0}.ptr === 0) {{ + throw new Error('Attempt to use a moved value'); + }} + ", + arg, + )); + } + + fn assert_mem_named_memory(&mut self, mem: walrus::MemoryId) -> Result<(), Error> { + if self.cx.export_name_of(mem) == "memory" { + return Ok(()); + } + bail!("exported memory must be called `memory` for now"); + } +} + +fn instruction(js: &mut JsBuilder, instr: &Instruction) -> Result<(), Error> { + // Here first properly aligned nonzero address is chosen to be the + // out-pointer. We use the address for a BigInt64Array sometimes which + // means it needs to be 8-byte aligned. Otherwise valid code is + // unlikely to ever be working around address 8, so this should be a + // safe address to use for returning data through. + let retptr_val = 8; + + match instr { + Instruction::Standard(wit_walrus::Instruction::ArgGet(n)) => { + let arg = js.arg(*n).to_string(); + js.push(arg); + } + + Instruction::Standard(wit_walrus::Instruction::CallAdapter(_)) => { + panic!("standard call adapter functions should be mapped to our adapters"); + } + + Instruction::Standard(wit_walrus::Instruction::CallCore(_)) + | Instruction::CallExport(_) + | Instruction::CallAdapter(_) + | Instruction::CallTableElement(_) + | Instruction::Standard(wit_walrus::Instruction::DeferCallCore(_)) => { + let invoc = Invocation::from(instr, js.cx.module)?; + let (params, results) = invoc.params_results(js.cx); + + // Pop off the number of parameters for the function we're calling + let mut args = Vec::new(); + for _ in 0..params { + args.push(js.pop()); + } + args.reverse(); + + // Call the function through an export of the underlying module. + let call = invoc.invoke(js.cx, &args, &mut js.prelude)?; + + // And then figure out how to actually handle where the call + // happens. This is pretty conditional depending on the number of + // return values of the function. + match (invoc.defer(), results) { + (true, 0) => js.finally(&format!("{};", call)), + (true, _) => panic!("deferred calls must have no results"), + (false, 0) => js.prelude(&format!("{};", call)), + (false, 1) => js.push(call), + (false, n) => { + js.prelude(&format!("const ret = {};", call)); + for i in 0..n { + js.push(format!("ret[{}]", i)); + } + } + } + } + + Instruction::Standard(wit_walrus::Instruction::WasmToInt { trap: false, .. }) + | Instruction::Standard(wit_walrus::Instruction::IntToWasm { trap: false, .. }) => { + // we assume that the JS bindings to wasm standard handles all this + } + + Instruction::Standard(wit_walrus::Instruction::WasmToInt { trap: true, .. }) + | Instruction::Standard(wit_walrus::Instruction::IntToWasm { trap: true, .. }) => { + bail!("trapping wasm-to-int and int-to-wasm instructions not supported") + } + + Instruction::Standard(wit_walrus::Instruction::MemoryToString(mem)) => { + js.typescript_required("string"); + let len = js.pop(); + let ptr = js.pop(); + js.cx.expose_get_string_from_wasm()?; + js.assert_mem_named_memory(*mem)?; + js.push(format!("getStringFromWasm({}, {})", ptr, len)); + } + + Instruction::Standard(wit_walrus::Instruction::StringToMemory { mem, malloc }) => { + js.typescript_required("string"); + js.cx.expose_pass_string_to_wasm()?; + js.assert_mem_named_memory(*mem)?; + let val = js.pop(); + let malloc = js.cx.export_name_of(*malloc); + js.push(format!("passStringToWasm({}, wasm.{})", val, malloc)); + js.push("WASM_VECTOR_LEN".to_string()); + } + + Instruction::Retptr => js.stack.push(retptr_val.to_string()), + + Instruction::StoreRetptr { ty, offset } => { + let (mem, size) = match ty { + AdapterType::I32 => { + js.cx.expose_int32_memory(); + ("getInt32Memory()", 4) + } + AdapterType::F32 => { + js.cx.expose_f32_memory(); + ("getFloat32Memory()", 4) + } + AdapterType::F64 => { + js.cx.expose_f64_memory(); + ("getFloat64Memory()", 8) + } + other => bail!("invalid aggregate return type {:?}", other), + }; + // Note that we always assume the return pointer is argument 0, + // which is currently the case for LLVM. + let val = js.pop(); + let expr = format!("{}[{} / {} + {}] = {};", mem, js.arg(0), size, offset, val,); + js.prelude(&expr); + } + + Instruction::LoadRetptr { ty, offset } => { + let (mem, size) = match ty { + AdapterType::I32 => { + js.cx.expose_int32_memory(); + ("getInt32Memory()", 4) + } + AdapterType::F32 => { + js.cx.expose_f32_memory(); + ("getFloat32Memory()", 4) + } + AdapterType::F64 => { + js.cx.expose_f64_memory(); + ("getFloat64Memory()", 8) + } + other => bail!("invalid aggregate return type {:?}", other), + }; + // If we're loading from the return pointer then we must have pushed + // it earlier, and we always push the same value, so load that value + // here + let expr = format!("{}[{} / {} + {}]", mem, retptr_val, size, offset,); + js.push(expr); + } + + Instruction::I32FromBool => { + js.typescript_required("boolean"); + let val = js.pop(); + js.assert_bool(&val); + // JS will already coerce booleans into numbers for us + js.push(val); + } + + Instruction::I32FromStringFirstChar => { + js.typescript_required("string"); + let val = js.pop(); + js.push(format!("{}.codePointAt(0)", val)); + } + + Instruction::I32FromAnyrefOwned => { + js.typescript_required("any"); + js.cx.expose_add_heap_object(); + let val = js.pop(); + js.push(format!("addHeapObject({})", val)); + } + + Instruction::I32FromAnyrefBorrow => { + js.typescript_required("any"); + js.cx.expose_borrowed_objects(); + js.cx.expose_global_stack_pointer(); + let val = js.pop(); + js.push(format!("addBorrowedObject({})", val)); + js.finally("heap[stack_pointer++] = undefined;"); + } + + Instruction::I32FromAnyrefRustOwned { class } => { + js.typescript_required(class); + let val = js.pop(); + js.assert_class(&val, &class); + js.assert_not_moved(&val); + let i = js.tmp(); + js.prelude(&format!("const ptr{} = {}.ptr;", i, val)); + js.prelude(&format!("{}.ptr = 0;", val)); + js.push(format!("ptr{}", i)); + } + + Instruction::I32FromAnyrefRustBorrow { class } => { + js.typescript_required(class); + let val = js.pop(); + js.assert_class(&val, &class); + js.assert_not_moved(&val); + js.push(format!("{}.ptr", val)); + } + + Instruction::I32FromOptionRust { class } => { + js.typescript_optional(class); + let val = js.pop(); + js.cx.expose_is_like_none(); + let i = js.tmp(); + js.prelude(&format!("let ptr{} = 0;", i)); + js.prelude(&format!("if (!isLikeNone({0})) {{", val)); + js.assert_class(&val, class); + js.assert_not_moved(&val); + js.prelude(&format!("ptr{} = {}.ptr;", i, val)); + js.prelude(&format!("{}.ptr = 0;", val)); + js.prelude("}"); + js.push(format!("ptr{}", i)); + } + + Instruction::I32Split64 { signed } => { + js.typescript_required("BigInt"); + let val = js.pop(); + let f = if *signed { + js.cx.expose_int64_cvt_shim() + } else { + js.cx.expose_uint64_cvt_shim() + }; + js.cx.expose_uint32_memory(); + let i = js.tmp(); + js.prelude(&format!( + " + {f}[0] = {val}; + const low{i} = u32CvtShim[0]; + const high{i} = u32CvtShim[1]; + ", + i = i, + f = f, + val = val, + )); + js.push(format!("low{}", i)); + js.push(format!("high{}", i)); + } + + Instruction::I32SplitOption64 { signed } => { + js.typescript_optional("BigInt"); + let val = js.pop(); + js.cx.expose_is_like_none(); + let f = if *signed { + js.cx.expose_int64_cvt_shim() + } else { + js.cx.expose_uint64_cvt_shim() + }; + js.cx.expose_uint32_memory(); + let i = js.tmp(); + js.prelude(&format!( + "\ + {f}[0] = isLikeNone({val}) ? BigInt(0) : {val}; + const low{i} = u32CvtShim[0]; + const high{i} = u32CvtShim[1]; + ", + i = i, + f = f, + val = val, + )); + js.push(format!("!isLikeNone({0})", val)); + js.push(format!("low{}", i)); + js.push(format!("high{}", i)); + } + + Instruction::I32FromOptionAnyref => { + js.typescript_optional("any"); + let val = js.pop(); + js.cx.expose_is_like_none(); + // TODO: would be great to handle this in the anyref pass so we + // don't have to worry about it here, shouldn't have an extra + // switch. + if js.cx.config.anyref { + js.cx.expose_add_to_anyref_table()?; + js.push(format!("isLikeNone({0}) ? 0 : addToAnyrefTable({0})", val)); + } else { + js.cx.expose_add_heap_object(); + js.push(format!("isLikeNone({0}) ? 0 : addHeapObject({0})", val)); + } + } + + Instruction::I32FromOptionU32Sentinel => { + js.typescript_optional("number"); + let val = js.pop(); + js.cx.expose_is_like_none(); + js.assert_optional_number(&val); + js.push(format!("isLikeNone({0}) ? 0xFFFFFF : {0}", val)); + } + + Instruction::I32FromOptionBool => { + js.typescript_optional("boolean"); + let val = js.pop(); + js.cx.expose_is_like_none(); + js.assert_optional_bool(&val); + js.push(format!("isLikeNone({0}) ? 0xFFFFFF : {0} ? 1 : 0", val)); + } + + Instruction::I32FromOptionChar => { + js.typescript_optional("string"); + let val = js.pop(); + js.cx.expose_is_like_none(); + js.push(format!( + "isLikeNone({0}) ? 0xFFFFFF : {0}.codePointAt(0)", + val + )); + } + + Instruction::I32FromOptionEnum { hole } => { + js.typescript_optional("number"); + let val = js.pop(); + js.cx.expose_is_like_none(); + js.assert_optional_number(&val); + js.push(format!("isLikeNone({0}) ? {1} : {0}", val, hole)); + } + + Instruction::FromOptionNative { .. } => { + js.typescript_optional("number"); + let val = js.pop(); + js.cx.expose_is_like_none(); + js.assert_optional_number(&val); + js.push(format!("!isLikeNone({0})", val)); + js.push(format!("isLikeNone({0}) ? 0 : {0}", val)); + } + + _ => panic!(), + } + Ok(()) +} + +enum Invocation { + Core { id: walrus::FunctionId, defer: bool }, + Adapter(AdapterId), +} + +impl Invocation { + fn from(instr: &Instruction, module: &Module) -> Result { + use Instruction::*; + Ok(match instr { + Standard(wit_walrus::Instruction::CallCore(f)) => Invocation::Core { + id: *f, + defer: false, + }, + + Standard(wit_walrus::Instruction::DeferCallCore(f)) => Invocation::Core { + id: *f, + defer: true, + }, + + CallExport(e) => match module.exports.get(*e).item { + walrus::ExportItem::Function(id) => Invocation::Core { id, defer: false }, + _ => panic!("can only call exported function"), + }, + + // The function table never changes right now, so we can statically + // look up the desired function. + CallTableElement(idx) => { + let table = module + .tables + .main_function_table()? + .ok_or_else(|| anyhow!("no function table found"))?; + let functions = match &module.tables.get(table).kind { + walrus::TableKind::Function(f) => f, + _ => bail!("should have found a function table"), + }; + let id = functions + .elements + .get(*idx as usize) + .and_then(|id| *id) + .ok_or_else(|| anyhow!("function table wasn't filled in a {}", idx))?; + Invocation::Core { id, defer: false } + } + + CallAdapter(id) => Invocation::Adapter(*id), + + // this function is only called for the above instructions + _ => unreachable!(), + }) + } + + fn params_results(&self, cx: &Context) -> (usize, usize) { + match self { + Invocation::Core { id, .. } => { + let ty = cx.module.funcs.get(*id).ty(); + let ty = cx.module.types.get(ty); + (ty.params().len(), ty.results().len()) + } + Invocation::Adapter(id) => { + let adapter = &cx.wit.adapters[id]; + (adapter.params.len(), adapter.results.len()) + } + } + } + + fn invoke( + &self, + cx: &mut Context, + args: &[String], + prelude: &mut String, + ) -> Result { + match self { + Invocation::Core { id, .. } => { + let name = cx.export_name_of(*id); + Ok(format!("wasm.{}({})", name, args.join(", "))) + } + Invocation::Adapter(id) => { + let adapter = &cx.wit.adapters[id]; + let kind = match adapter.kind { + AdapterKind::Import { kind, .. } => kind, + AdapterKind::Local { .. } => { + bail!("adapter-to-adapter calls not supported yet"); + } + }; + let import = &cx.aux.import_map[id]; + let variadic = cx.aux.imports_with_variadic.contains(id); + cx.invoke_import(import, kind, args, variadic, prelude) + } + } + } + + fn defer(&self) -> bool { + match self { + Invocation::Core { defer, .. } => *defer, + _ => false, + } + } } diff --git a/crates/cli-support/src/js/incoming.rs b/crates/cli-support/src/js/incoming.rs index e5592318071..b0c922b72b1 100644 --- a/crates/cli-support/src/js/incoming.rs +++ b/crates/cli-support/src/js/incoming.rs @@ -37,33 +37,6 @@ impl<'a, 'b> Incoming<'a, 'b> { let single = match incoming { NonstandardIncoming::Standard(val) => return self.standard(val), - // Evaluate the `val` binding, store it into a one-element `BigInt` - // array (appropriately typed) and then use a 32-bit view into the - // `BigInt` array to extract the high/low bits and pass them through - // in the ABI. - NonstandardIncoming::Int64 { val, signed } => { - self.js.typescript_required("BigInt"); - let (expr, ty) = self.standard_typed(val)?; - assert_eq!(ty, ast::WebidlScalarType::Any.into()); - let f = if *signed { - self.cx.expose_int64_cvt_shim() - } else { - self.cx.expose_uint64_cvt_shim() - }; - self.cx.expose_uint32_memory(); - let i = self.js.tmp(); - self.js.prelude(&format!( - " - {f}[0] = {expr}; - const low{i} = u32CvtShim[0]; - const high{i} = u32CvtShim[1]; - ", - i = i, - f = f, - expr = expr, - )); - return Ok(vec![format!("low{}", i), format!("high{}", i)]); - } // Same as `IncomingBindingExpressionAllocCopy`, except we use a // different `VectorKind` @@ -103,54 +76,6 @@ impl<'a, 'b> Incoming<'a, 'b> { ]); } - // There's no `char` in JS, so we take a string instead and just - // forward along the first code point to Rust. - NonstandardIncoming::Char { val } => { - let (expr, ty) = self.standard_typed(val)?; - assert_eq!(ty, ast::WebidlScalarType::DomString.into()); - self.js.typescript_required("string"); - format!("{}.codePointAt(0)", expr) - } - - // When moving a type back into Rust we need to clear out the - // internal pointer in JS to prevent it from being reused again in - // the future. - NonstandardIncoming::RustType { class, val } => { - let (expr, ty) = self.standard_typed(val)?; - assert_eq!(ty, ast::WebidlScalarType::Any.into()); - self.assert_class(&expr, &class); - self.assert_not_moved(&expr); - let i = self.js.tmp(); - self.js.prelude(&format!("const ptr{} = {}.ptr;", i, expr)); - self.js.prelude(&format!("{}.ptr = 0;", expr)); - self.js.typescript_required(class); - format!("ptr{}", i) - } - - // Here we can simply pass along the pointer with no extra fluff - // needed. - NonstandardIncoming::RustTypeRef { class, val } => { - let (expr, ty) = self.standard_typed(val)?; - assert_eq!(ty, ast::WebidlScalarType::Any.into()); - self.assert_class(&expr, &class); - self.assert_not_moved(&expr); - self.js.typescript_required(class); - format!("{}.ptr", expr) - } - - // the "stack-ful" nature means that we're always popping from the - // stack, and make sure that we actually clear our reference to - // allow stale values to get GC'd - NonstandardIncoming::BorrowedAnyref { val } => { - let (expr, ty) = self.standard_typed(val)?; - assert_eq!(ty, ast::WebidlScalarType::Any.into()); - self.cx.expose_borrowed_objects(); - self.cx.expose_global_stack_pointer(); - self.js.finally("heap[stack_pointer++] = undefined;"); - self.js.typescript_required("any"); - format!("addBorrowedObject({})", expr) - } - // Similar to `AllocCopy`, except that we deallocate in a finally // block. NonstandardIncoming::MutableSlice { kind, val } => { @@ -167,128 +92,6 @@ impl<'a, 'b> Incoming<'a, 'b> { return Ok(vec![format!("ptr{}", i), format!("len{}", i)]); } - // Pass `None` as a sentinel value that `val` will never take on. - // This is only manufactured for specific underlying types. - NonstandardIncoming::OptionU32Sentinel { val } => { - let (expr, ty) = self.standard_typed(val)?; - assert_eq!(ty, ast::WebidlScalarType::Any.into()); - self.cx.expose_is_like_none(); - self.js.typescript_optional("number"); - self.assert_optional_number(&expr); - format!("isLikeNone({0}) ? 0xFFFFFF : {0}", expr) - } - - // Pass `true` as 1, `false` as 0, and `None` as a sentinel value. - NonstandardIncoming::OptionBool { val } => { - let (expr, ty) = self.standard_typed(val)?; - assert_eq!(ty, ast::WebidlScalarType::Any.into()); - self.cx.expose_is_like_none(); - self.js.typescript_optional("boolean"); - self.assert_optional_bool(&expr); - format!("isLikeNone({0}) ? 0xFFFFFF : {0} ? 1 : 0", expr) - } - - // Pass `None` as a sentinel value a character can never have - NonstandardIncoming::OptionChar { val } => { - let (expr, ty) = self.standard_typed(val)?; - assert_eq!(ty, ast::WebidlScalarType::Any.into()); - self.cx.expose_is_like_none(); - self.js.typescript_optional("string"); - format!("isLikeNone({0}) ? 0xFFFFFF : {0}.codePointAt(0)", expr) - } - - // Pass `None` as the hole in the enum which no valid value can ever - // take - NonstandardIncoming::OptionIntegerEnum { val, hole } => { - let (expr, ty) = self.standard_typed(val)?; - assert_eq!(ty, ast::WebidlScalarType::Any.into()); - self.cx.expose_is_like_none(); - self.js.typescript_optional("number"); - self.assert_optional_number(&expr); - format!("isLikeNone({0}) ? {1} : {0}", expr, hole) - } - - // `None` here is zero, but if `Some` then we need to clear out the - // internal pointer because the value is being moved. - NonstandardIncoming::OptionRustType { class, val } => { - let (expr, ty) = self.standard_typed(val)?; - assert_eq!(ty, ast::WebidlScalarType::Any.into()); - self.cx.expose_is_like_none(); - let i = self.js.tmp(); - self.js.prelude(&format!("let ptr{} = 0;", i)); - self.js.prelude(&format!("if (!isLikeNone({0})) {{", expr)); - self.assert_class(&expr, class); - self.assert_not_moved(&expr); - self.js.prelude(&format!("ptr{} = {}.ptr;", i, expr)); - self.js.prelude(&format!("{}.ptr = 0;", expr)); - self.js.prelude("}"); - self.js.typescript_optional(class); - format!("ptr{}", i) - } - - // The ABI produces four values here, all zero for `None` and 1 in - // the first for the last two being the low/high bits - NonstandardIncoming::OptionInt64 { val, signed } => { - let (expr, ty) = self.standard_typed(val)?; - assert_eq!(ty, ast::WebidlScalarType::Any.into()); - self.cx.expose_is_like_none(); - let f = if *signed { - self.cx.expose_int64_cvt_shim() - } else { - self.cx.expose_uint64_cvt_shim() - }; - self.cx.expose_uint32_memory(); - let i = self.js.tmp(); - self.js.prelude(&format!( - "\ - {f}[0] = isLikeNone({expr}) ? BigInt(0) : {expr}; - const low{i} = isLikeNone({expr}) ? 0 : u32CvtShim[0]; - const high{i} = isLikeNone({expr}) ? 0 : u32CvtShim[1]; - ", - i = i, - f = f, - expr = expr, - )); - self.js.typescript_optional("BigInt"); - return Ok(vec![ - format!("!isLikeNone({0})", expr), - "0".to_string(), - format!("low{}", i), - format!("high{}", i), - ]); - } - - // The ABI here is always an integral index into the anyref table, - // and the anyref table just differs based on whether we ran the - // anyref pass or not. - NonstandardIncoming::OptionAnyref { val } => { - let (expr, ty) = self.standard_typed(val)?; - assert_eq!(ty, ast::WebidlScalarType::Any.into()); - self.cx.expose_is_like_none(); - self.js.typescript_optional("any"); - if self.cx.config.anyref { - self.cx.expose_add_to_anyref_table()?; - format!("isLikeNone({0}) ? 0 : addToAnyrefTable({0})", expr) - } else { - self.cx.expose_add_heap_object(); - format!("isLikeNone({0}) ? 0 : addHeapObject({0})", expr) - } - } - - // Native types of wasm take a leading discriminant to indicate - // whether the next value is valid or not. - NonstandardIncoming::OptionNative { val } => { - let (expr, ty) = self.standard_typed(val)?; - assert_eq!(ty, ast::WebidlScalarType::Any.into()); - self.cx.expose_is_like_none(); - self.js.typescript_optional("number"); - self.assert_optional_number(&expr); - return Ok(vec![ - format!("!isLikeNone({0})", expr), - format!("isLikeNone({0}) ? 0 : {0}", expr), - ]); - } - // Similar to `AllocCopy`, except we're handling the undefined case // and passing null for the pointer value. NonstandardIncoming::OptionVector { kind, val } => { diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 8e30530b19a..38789c303a0 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -1,20 +1,18 @@ use crate::descriptor::VectorKind; use crate::intrinsic::Intrinsic; -use crate::webidl; -use crate::webidl::{AuxEnum, AuxExport, AuxExportKind, AuxImport, AuxStruct}; -use crate::webidl::{AuxValue, Binding}; -use crate::webidl::{JsImport, JsImportName, NonstandardWebidlSection, WasmBindgenAux}; +use crate::wit::{Adapter, AuxValue, AdapterJsImportKind}; +use crate::wit::{AuxEnum, AuxExport, AuxExportKind, AuxImport, AuxStruct}; +use crate::wit::{JsImport, JsImportName, NonstandardWitSection, WasmBindgenAux}; use crate::{Bindgen, EncodeInto, OutputMode}; use anyhow::{bail, Context as _, Error}; use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::fs; use std::path::{Path, PathBuf}; use walrus::{ExportId, ImportId, MemoryId, Module}; -use wasm_webidl_bindings::ast; mod binding; -mod incoming; -mod outgoing; +// mod incoming; +// mod outgoing; pub struct Context<'a> { globals: String, @@ -22,8 +20,11 @@ pub struct Context<'a> { typescript: String, exposed_globals: Option>, required_internal_exports: HashSet<&'static str>, + next_export_idx: usize, config: &'a Bindgen, pub module: &'a mut Module, + aux: &'a WasmBindgenAux, + wit: &'a NonstandardWitSection, /// A map representing the `import` statements we'll be generating in the JS /// glue. The key is the module we're importing from and the value is the @@ -66,7 +67,12 @@ const INITIAL_HEAP_VALUES: &[&str] = &["undefined", "null", "true", "false"]; const INITIAL_HEAP_OFFSET: usize = 32; impl<'a> Context<'a> { - pub fn new(module: &'a mut Module, config: &'a Bindgen) -> Result, Error> { + pub fn new( + module: &'a mut Module, + config: &'a Bindgen, + wit: &'a NonstandardWitSection, + aux: &'a WasmBindgenAux, + ) -> Result, Error> { // Find the single memory, if there is one, and for ease of use in our // binding generation just inject one if there's not one already (and // we'll clean it up later if we end up not using it). @@ -94,6 +100,9 @@ impl<'a> Context<'a> { module, memory, npm_dependencies: Default::default(), + next_export_idx: 0, + wit, + aux, }) } @@ -465,10 +474,12 @@ impl<'a> Context<'a> { }; let default_module_path = match self.config.mode { - OutputMode::Web => "\ + OutputMode::Web => { + "\ if (typeof module === 'undefined') { module = import.meta.url.replace(/\\.js$/, '_bg.wasm'); - }", + }" + } _ => "", }; @@ -828,10 +839,10 @@ impl<'a> Context<'a> { self.global(&format!( " - function passStringToWasm(arg) {{ + function passStringToWasm(arg, malloc) {{ {} const len = Buffer.byteLength(arg); - const ptr = wasm.__wbindgen_malloc(len); + const ptr = malloc(len); getNodeBufferMemory().write(arg, ptr, len); WASM_VECTOR_LEN = len; return ptr; @@ -911,7 +922,7 @@ impl<'a> Context<'a> { // charCodeAt on ASCII strings is usually optimised to raw bytes. let encode_as_ascii = "\ let len = arg.length; - let ptr = wasm.__wbindgen_malloc(len); + let ptr = malloc(len); const mem = getUint8Memory(); @@ -930,7 +941,7 @@ impl<'a> Context<'a> { // looping over the string to calculate the precise size, or perhaps using // `shrink_to_fit` on the Rust side. self.global(&format!( - "function passStringToWasm(arg) {{ + "function passStringToWasm(arg, malloc) {{ {} {} if (offset !== len) {{ @@ -1874,100 +1885,100 @@ impl<'a> Context<'a> { Ok(()) } - pub fn generate( - &mut self, - aux: &WasmBindgenAux, - bindings: &NonstandardWebidlSection, - ) -> Result<(), Error> { - for (i, (idx, binding)) in bindings.elems.iter().enumerate() { - self.generate_elem_binding(i, *idx, binding, bindings)?; - } - - let mut pairs = aux.export_map.iter().collect::>(); - pairs.sort_by_key(|(k, _)| *k); - check_duplicated_getter_and_setter_names(&pairs)?; - for (id, export) in pairs { - self.generate_export(*id, export, bindings) - .with_context(|| { - format!( - "failed to generate bindings for Rust export `{}`", - export.debug_name, - ) - })?; - } - - for (id, import) in sorted_iter(&aux.import_map) { - let variadic = aux.imports_with_variadic.contains(&id); - let catch = aux.imports_with_catch.contains(&id); - let assert_no_shim = aux.imports_with_assert_no_shim.contains(&id); - self.generate_import(*id, import, bindings, variadic, catch, assert_no_shim) - .with_context(|| { - format!("failed to generate bindings for import `{:?}`", import,) - })?; + pub fn generate(&mut self) -> Result<(), Error> { + for (id, adapter) in self.wit.adapters.iter() { + if let Some(export) = self.aux.export_map.get(id) { + self.generate_export(export, adapter)?; + continue; + } + if let Some(import) = self.aux.import_map.get(id) { + } + // self.generate_adapter(id, adapter); } - for e in aux.enums.iter() { + // for (i, (idx, binding)) in bindings.elems.iter().enumerate() { + // self.generate_elem_binding(i, *idx, binding, bindings)?; + // } + // + // let mut pairs = aux.export_map.iter().collect::>(); + // pairs.sort_by_key(|(k, _)| *k); + // check_duplicated_getter_and_setter_names(&pairs)?; + // for (id, export) in pairs { + // self.generate_export(*id, export, bindings) + // .with_context(|| { + // format!( + // "failed to generate bindings for Rust export `{}`", + // export.debug_name, + // ) + // })?; + // } + // + // for (id, import) in sorted_iter(&aux.import_map) { + // let variadic = aux.imports_with_variadic.contains(&id); + // let catch = aux.imports_with_catch.contains(&id); + // let assert_no_shim = aux.imports_with_assert_no_shim.contains(&id); + // self.generate_import(*id, import, bindings, variadic, catch, assert_no_shim) + // .with_context(|| { + // format!("failed to generate bindings for import `{:?}`", import,) + // })?; + // } + for e in self.aux.enums.iter() { self.generate_enum(e)?; } - for s in aux.structs.iter() { + for s in self.aux.structs.iter() { self.generate_struct(s)?; } - self.typescript.push_str(&aux.extra_typescript); + self.typescript.push_str(&self.aux.extra_typescript); - for path in aux.package_jsons.iter() { + for path in self.aux.package_jsons.iter() { self.process_package_json(path)?; } Ok(()) } - /// Generates a wrapper function for each bound element of the function - /// table. These wrapper functions have the expected WebIDL signature we'd - /// like them to have. This currently isn't part of the WebIDL bindings - /// proposal, but the thinking is that it'd look something like this if - /// added. - /// - /// Note that this is just an internal function shim used by closures and - /// such, so we're not actually exporting anything here. - fn generate_elem_binding( - &mut self, - idx: usize, - elem_idx: u32, - binding: &Binding, - bindings: &NonstandardWebidlSection, - ) -> Result<(), Error> { - let webidl = bindings - .types - .get::(binding.webidl_ty) - .unwrap(); - self.export_function_table()?; - let mut builder = binding::Builder::new(self); - builder.disable_log_error(true); - let js = builder.process(&binding, &webidl, true, &None, &mut |_, _, args| { - Ok(format!( - "wasm.__wbg_function_table.get({})({})", - elem_idx, - args.join(", ") - )) - })?; - self.globals - .push_str(&format!("function __wbg_elem_binding{}{}\n", idx, js)); - Ok(()) - } - - fn generate_export( - &mut self, - id: ExportId, - export: &AuxExport, - bindings: &NonstandardWebidlSection, - ) -> Result<(), Error> { - let wasm_name = self.module.exports.get(id).name.clone(); - let binding = &bindings.exports[&id]; - let webidl = bindings - .types - .get::(binding.webidl_ty) - .unwrap(); + // /// Generates a wrapper function for each bound element of the function + // /// table. These wrapper functions have the expected WebIDL signature we'd + // /// like them to have. This currently isn't part of the WebIDL bindings + // /// proposal, but the thinking is that it'd look something like this if + // /// added. + // /// + // /// Note that this is just an internal function shim used by closures and + // /// such, so we're not actually exporting anything here. + // fn generate_elem_binding( + // &mut self, + // idx: usize, + // elem_idx: u32, + // binding: &Binding, + // bindings: &NonstandardWitSection, + // ) -> Result<(), Error> { + // let webidl = bindings + // .types + // .get::(binding.webidl_ty) + // .unwrap(); + // self.export_function_table()?; + // let mut builder = binding::Builder::new(self); + // builder.disable_log_error(true); + // let js = builder.process(&binding, &webidl, true, &None, &mut |_, _, args| { + // Ok(format!( + // "wasm.__wbg_function_table.get({})({})", + // elem_idx, + // args.join(", ") + // )) + // })?; + // self.globals + // .push_str(&format!("function __wbg_elem_binding{}{}\n", idx, js)); + // Ok(()) + // } + + fn generate_export(&mut self, export: &AuxExport, adapter: &Adapter) -> Result<(), Error> { + // let wasm_name = self.module.exports.get(id).name.clone(); + // let binding = &bindings.exports[&id]; + // let webidl = bindings + // .types + // .get::(binding.webidl_ty) + // .unwrap(); // Construct a JS shim builder, and configure it based on the kind of // export that we're generating. @@ -1983,12 +1994,11 @@ impl<'a> Context<'a> { // Process the `binding` and generate a bunch of JS/TypeScript/etc. let js = builder.process( - &binding, - &webidl, - true, + &adapter, &export.arg_names, - &mut |_, _, args| Ok(format!("wasm.{}({})", wasm_name, args.join(", "))), + // &mut |_, _, args| Ok(format!("wasm.{}({})", wasm_name, args.join(", "))), )?; + let js = String::new(); let ts = builder.typescript_signature(); let js_doc = builder.js_doc_comments(); let docs = format_doc_comments(&export.comments, Some(js_doc)); @@ -2034,57 +2044,57 @@ impl<'a> Context<'a> { Ok(()) } - fn generate_import( - &mut self, - id: ImportId, - import: &AuxImport, - bindings: &NonstandardWebidlSection, - variadic: bool, - catch: bool, - assert_no_shim: bool, - ) -> Result<(), Error> { - let binding = &bindings.imports[&id]; - let webidl = bindings - .types - .get::(binding.webidl_ty) - .unwrap(); - match import { - AuxImport::Value(AuxValue::Bare(js)) - if !variadic && !catch && self.import_does_not_require_glue(binding, webidl) => - { - self.direct_import(id, js) - } - _ => { - if assert_no_shim { - panic!( - "imported function was annotated with `#[wasm_bindgen(assert_no_shim)]` \ - but we need to generate a JS shim for it:\n\n\ - \timport = {:?}\n\n\ - \tbinding = {:?}\n\n\ - \twebidl = {:?}", - import, binding, webidl, - ); - } - - let disable_log_error = self.import_never_log_error(import); - let mut builder = binding::Builder::new(self); - builder.catch(catch)?; - builder.disable_log_error(disable_log_error); - let js = builder.process( - &binding, - &webidl, - false, - &None, - &mut |cx, prelude, args| { - cx.invoke_import(&binding, import, bindings, args, variadic, prelude) - }, - )?; - self.wasm_import_definitions - .insert(id, format!("function{}", js)); - Ok(()) - } - } - } + // fn generate_import( + // &mut self, + // id: ImportId, + // import: &AuxImport, + // bindings: &NonstandardWitSection, + // variadic: bool, + // catch: bool, + // assert_no_shim: bool, + // ) -> Result<(), Error> { + // let binding = &bindings.imports[&id]; + // let webidl = bindings + // .types + // .get::(binding.webidl_ty) + // .unwrap(); + // match import { + // AuxImport::Value(AuxValue::Bare(js)) + // if !variadic && !catch && self.import_does_not_require_glue(binding, webidl) => + // { + // self.direct_import(id, js) + // } + // _ => { + // if assert_no_shim { + // panic!( + // "imported function was annotated with `#[wasm_bindgen(assert_no_shim)]` \ + // but we need to generate a JS shim for it:\n\n\ + // \timport = {:?}\n\n\ + // \tbinding = {:?}\n\n\ + // \twebidl = {:?}", + // import, binding, webidl, + // ); + // } + // + // let disable_log_error = self.import_never_log_error(import); + // let mut builder = binding::Builder::new(self); + // builder.catch(catch)?; + // builder.disable_log_error(disable_log_error); + // let js = builder.process( + // &binding, + // &webidl, + // false, + // &None, + // &mut |cx, prelude, args| { + // cx.invoke_import(&binding, import, bindings, args, variadic, prelude) + // }, + // )?; + // self.wasm_import_definitions + // .insert(id, format!("function{}", js)); + // Ok(()) + // } + // } + // } /// Returns whether we should disable the logic, in debug mode, to catch an /// error, log it, and rethrow it. This is only intended for user-defined @@ -2102,30 +2112,26 @@ impl<'a> Context<'a> { } } - fn import_does_not_require_glue( - &self, - binding: &Binding, - webidl: &ast::WebidlFunction, - ) -> bool { - if !self.config.anyref && binding.contains_anyref(self.module) { - return false; - } - - let wasm_ty = self.module.types.get(binding.wasm_ty); - webidl.kind == ast::WebidlFunctionKind::Static - && webidl::outgoing_do_not_require_glue( - &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::>(), - wasm_ty.results(), - self.config.wasm_interface_types, - ) - } + // fn import_does_not_require_glue(&self, binding: &Binding, webidl: &ast::WitFunction) -> bool { + // if !self.config.anyref && binding.contains_anyref(self.module) { + // return false; + // } + // + // let wasm_ty = self.module.types.get(binding.wasm_ty); + // webidl.kind == AdapterJsImportKind::Normal + // && webidl::outgoing_do_not_require_glue( + // &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::>(), + // wasm_ty.results(), + // self.config.wasm_interface_types, + // ) + // } /// Emit a direct import directive that hooks up the `js` value specified to /// the wasm import `id`. @@ -2202,14 +2208,12 @@ impl<'a> Context<'a> { /// purpose of `AuxImport`, which depends on the kind of import. fn invoke_import( &mut self, - binding: &Binding, import: &AuxImport, - bindings: &NonstandardWebidlSection, + kind: AdapterJsImportKind, args: &[String], variadic: bool, prelude: &mut String, ) -> Result { - let webidl_ty: &ast::WebidlFunction = bindings.types.get(binding.webidl_ty).unwrap(); let variadic_args = |js_arguments: &[String]| { Ok(if !variadic { format!("{}", js_arguments.join(", ")) @@ -2226,15 +2230,15 @@ impl<'a> Context<'a> { }) }; match import { - AuxImport::Value(val) => match webidl_ty.kind { - ast::WebidlFunctionKind::Constructor => { + AuxImport::Value(val) => match kind { + AdapterJsImportKind::Constructor => { let js = match val { AuxValue::Bare(js) => self.import_name(js)?, _ => bail!("invalid import set for constructor"), }; Ok(format!("new {}({})", js, variadic_args(&args)?)) } - ast::WebidlFunctionKind::Method(_) => { + AdapterJsImportKind::Method => { let descriptor = |anchor: &str, extra: &str, field: &str, which: &str| { format!( "GetOwnOrInheritedPropertyDescriptor({}{}, '{}').{}", @@ -2266,7 +2270,7 @@ impl<'a> Context<'a> { }; Ok(format!("{}.call({})", js, variadic_args(&args)?)) } - ast::WebidlFunctionKind::Static => { + AdapterJsImportKind::Normal => { let js = match val { AuxValue::Bare(js) => self.import_name(js)?, _ => bail!("invalid import set for free function"), @@ -2281,7 +2285,7 @@ impl<'a> Context<'a> { } AuxImport::Instanceof(js) => { - assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static); + assert!(kind == AdapterJsImportKind::Normal); assert!(!variadic); assert_eq!(args.len(), 1); let js = self.import_name(js)?; @@ -2289,7 +2293,7 @@ impl<'a> Context<'a> { } AuxImport::Static(js) => { - assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static); + assert!(kind == AdapterJsImportKind::Normal); assert!(!variadic); assert_eq!(args.len(), 0); self.import_name(js) @@ -2298,10 +2302,10 @@ impl<'a> Context<'a> { AuxImport::Closure { dtor, mutable, - binding_idx, + adapter, nargs, } => { - assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static); + assert!(kind == AdapterJsImportKind::Normal); assert!(!variadic); assert_eq!(args.len(), 3); let arg_names = (0..*nargs) @@ -2316,7 +2320,8 @@ impl<'a> Context<'a> { self.export_function_table()?; let dtor = format!("wasm.__wbg_function_table.get({})", dtor); - let call = format!("__wbg_elem_binding{}", binding_idx); + // TODO: refactor this name generation to be somewhere else + let call = format!("__wbg_adapter_{}", adapter.0); if *mutable { // For mutable closures they can't be invoked recursively. @@ -2369,7 +2374,7 @@ impl<'a> Context<'a> { } AuxImport::StructuralMethod(name) => { - assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static); + assert!(kind == AdapterJsImportKind::Normal); let (receiver, args) = match args.split_first() { Some(pair) => pair, None => bail!("structural method calls must have at least one argument"), @@ -2378,14 +2383,14 @@ impl<'a> Context<'a> { } AuxImport::StructuralGetter(field) => { - assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static); + assert!(kind == AdapterJsImportKind::Normal); assert!(!variadic); assert_eq!(args.len(), 1); Ok(format!("{}.{}", args[0], field)) } AuxImport::StructuralClassGetter(class, field) => { - assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static); + assert!(kind == AdapterJsImportKind::Normal); assert!(!variadic); assert_eq!(args.len(), 0); let class = self.import_name(class)?; @@ -2393,14 +2398,14 @@ impl<'a> Context<'a> { } AuxImport::StructuralSetter(field) => { - assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static); + assert!(kind == AdapterJsImportKind::Normal); assert!(!variadic); assert_eq!(args.len(), 2); Ok(format!("{}.{} = {}", args[0], field, args[1])) } AuxImport::StructuralClassSetter(class, field) => { - assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static); + assert!(kind == AdapterJsImportKind::Normal); assert!(!variadic); assert_eq!(args.len(), 1); let class = self.import_name(class)?; @@ -2408,7 +2413,7 @@ impl<'a> Context<'a> { } AuxImport::IndexingGetterOfClass(class) => { - assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static); + assert!(kind == AdapterJsImportKind::Normal); assert!(!variadic); assert_eq!(args.len(), 1); let class = self.import_name(class)?; @@ -2416,14 +2421,14 @@ impl<'a> Context<'a> { } AuxImport::IndexingGetterOfObject => { - assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static); + assert!(kind == AdapterJsImportKind::Normal); assert!(!variadic); assert_eq!(args.len(), 2); Ok(format!("{}[{}]", args[0], args[1])) } AuxImport::IndexingSetterOfClass(class) => { - assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static); + assert!(kind == AdapterJsImportKind::Normal); assert!(!variadic); assert_eq!(args.len(), 2); let class = self.import_name(class)?; @@ -2431,14 +2436,14 @@ impl<'a> Context<'a> { } AuxImport::IndexingSetterOfObject => { - assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static); + assert!(kind == AdapterJsImportKind::Normal); assert!(!variadic); assert_eq!(args.len(), 3); Ok(format!("{}[{}] = {}", args[0], args[1], args[2])) } AuxImport::IndexingDeleterOfClass(class) => { - assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static); + assert!(kind == AdapterJsImportKind::Normal); assert!(!variadic); assert_eq!(args.len(), 1); let class = self.import_name(class)?; @@ -2446,14 +2451,14 @@ impl<'a> Context<'a> { } AuxImport::IndexingDeleterOfObject => { - assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static); + assert!(kind == AdapterJsImportKind::Normal); assert!(!variadic); assert_eq!(args.len(), 2); Ok(format!("delete {}[{}]", args[0], args[1])) } AuxImport::WrapInExportedClass(class) => { - assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static); + assert!(kind == AdapterJsImportKind::Normal); assert!(!variadic); assert_eq!(args.len(), 1); self.require_class_wrap(class); @@ -2461,7 +2466,7 @@ impl<'a> Context<'a> { } AuxImport::Intrinsic(intrinsic) => { - assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static); + assert!(kind == AdapterJsImportKind::Normal); assert!(!variadic); self.invoke_intrinsic(intrinsic, args, prelude) } @@ -2874,6 +2879,28 @@ impl<'a> Context<'a> { self.module.exports.add("__wbg_function_table", id); Ok(()) } + + fn export_name_of(&mut self, id: impl Into) -> String { + let id = id.into(); + let export = self.module.exports.iter().find(|e| { + use walrus::ExportItem::*; + + match (e.item, id) { + (Function(a), Function(b)) => a == b, + (Table(a), Table(b)) => a == b, + (Memory(a), Memory(b)) => a == b, + (Global(a), Global(b)) => a == b, + _ => false, + } + }); + if let Some(export) = export { + return export.name.clone(); + } + let mut name = format!("__wbindgen_export_{}", self.next_export_idx); + self.next_export_idx += 1; + self.module.exports.add(&name, id); + return name; + } } fn check_duplicated_getter_and_setter_names( diff --git a/crates/cli-support/src/lib.rs b/crates/cli-support/src/lib.rs index 6a46346e5ae..665f54cf6cc 100755 --- a/crates/cli-support/src/lib.rs +++ b/crates/cli-support/src/lib.rs @@ -1,5 +1,4 @@ #![doc(html_root_url = "https://docs.rs/wasm-bindgen-cli-support/0.2")] -#![allow(dead_code, unused)] use anyhow::{bail, Context, Error}; use std::collections::{BTreeMap, BTreeSet, HashMap}; @@ -16,8 +15,9 @@ mod decode; mod descriptor; mod descriptors; mod intrinsic; +#[allow(dead_code, unused)] +mod js; mod multivalue; -// mod js; pub mod wasm2es6js; mod wit; @@ -360,14 +360,14 @@ impl Bindgen { .delete_typed::() .unwrap(); - // // Now that our module is massaged and good to go, feed it into the JS - // // shim generation which will actually generate JS for all this. - // let (npm_dependencies, (js, ts)) = { - // let mut cx = js::Context::new(&mut module, self)?; - // cx.generate(&aux, &adapters)?; - // let npm_dependencies = cx.npm_dependencies.clone(); - // (npm_dependencies, cx.finalize(stem)?) - // }; + // Now that our module is massaged and good to go, feed it into the JS + // shim generation which will actually generate JS for all this. + let (npm_dependencies, (js, ts)) = { + let mut cx = js::Context::new(&mut module, self, &adapters, &aux)?; + cx.generate()?; + let npm_dependencies = cx.npm_dependencies.clone(); + (npm_dependencies, cx.finalize(stem)?) + }; if self.wasm_interface_types { multivalue::run(&mut module, &mut adapters) @@ -394,20 +394,18 @@ impl Bindgen { // walrus::passes::gc::run(&mut module); } - // Ok(Output { - // module, - // stem: stem.to_string(), - // snippets: aux.snippets.clone(), - // local_modules: aux.local_modules.clone(), - // npm_dependencies, - // js, - // ts, - // mode: self.mode.clone(), - // typescript: self.typescript, - // wasm_interface_types: self.wasm_interface_types, - // }) - - panic!() + Ok(Output { + module, + stem: stem.to_string(), + snippets: aux.snippets.clone(), + local_modules: aux.local_modules.clone(), + npm_dependencies, + js, + ts, + mode: self.mode.clone(), + typescript: self.typescript, + wasm_interface_types: self.wasm_interface_types, + }) } fn local_module_name(&self, module: &str) -> String { diff --git a/crates/cli-support/src/multivalue.rs b/crates/cli-support/src/multivalue.rs index 589aceae7af..6e2f866c595 100644 --- a/crates/cli-support/src/multivalue.rs +++ b/crates/cli-support/src/multivalue.rs @@ -1,8 +1,6 @@ -use crate::descriptor::VectorKind; -use crate::wit::{Adapter, NonstandardWitSection, WasmBindgenAux}; +use crate::wit::{Adapter, NonstandardWitSection}; use crate::wit::{AdapterKind, Instruction}; -use crate::wit::{AuxExportKind, AuxImport, AuxValue, JsImport, JsImportName}; -use anyhow::{bail, Context, Error}; +use anyhow::Error; use walrus::Module; use wasm_bindgen_multi_value_xform as multi_value_xform; use wasm_bindgen_wasm_conventions as wasm_conventions; diff --git a/crates/cli-support/src/wit/incoming.rs b/crates/cli-support/src/wit/incoming.rs index a45f192c14a..be9d0802751 100644 --- a/crates/cli-support/src/wit/incoming.rs +++ b/crates/cli-support/src/wit/incoming.rs @@ -7,11 +7,11 @@ //! Note that the mirror operation, going from WebAssembly to JS, is found in //! the `outgoing.rs` module. -use crate::descriptor::{Descriptor, VectorKind}; +use crate::descriptor::Descriptor; use crate::wit::InstructionData; use crate::wit::{AdapterType, Instruction, InstructionBuilder, StackChange}; use anyhow::{bail, format_err, Error}; -use walrus::{FunctionId, MemoryId, ValType}; +use walrus::ValType; impl InstructionBuilder<'_, '_> { /// Process a `Descriptor` as if it's being passed from JS to Rust. This @@ -175,10 +175,11 @@ impl InstructionBuilder<'_, '_> { })?; self.instruction( &[AdapterType::Vector(kind)], - Instruction::VectorToMemory { + Instruction::SliceToMemory { kind, malloc: self.cx.malloc()?, mem: self.cx.memory()?, + mutable, }, &[AdapterType::I32, AdapterType::I32], ); diff --git a/crates/cli-support/src/wit/mod.rs b/crates/cli-support/src/wit/mod.rs index 729e4555372..a03d787d8d7 100644 --- a/crates/cli-support/src/wit/mod.rs +++ b/crates/cli-support/src/wit/mod.rs @@ -3,12 +3,10 @@ use crate::descriptor::{Descriptor, Function}; use crate::descriptors::WasmBindgenDescriptorsSection; use crate::intrinsic::Intrinsic; use anyhow::{anyhow, bail, Error}; -use std::borrow::Cow; -use std::collections::{HashMap, HashSet}; -use std::path::PathBuf; +use std::collections::HashMap; use std::str; use walrus::MemoryId; -use walrus::{ExportId, FunctionId, ImportId, Module, TypedCustomSectionId}; +use walrus::{ExportId, FunctionId, ImportId, Module}; use wasm_bindgen_shared::struct_function_export_name; const PLACEHOLDER_MODULE: &str = "__wbindgen_placeholder__"; @@ -170,6 +168,7 @@ impl<'a> Context<'a> { dtor: descriptor.dtor_idx, mutable: descriptor.mutable, nargs, + adapter: id, }, ); } @@ -1054,10 +1053,6 @@ impl<'a> Context<'a> { ) -> Result { let import = self.module.imports.get(import); let (import_module, import_name) = (import.module.clone(), import.name.clone()); - let id = match import.kind { - walrus::ImportKind::Function(f) => f, - _ => unreachable!(), - }; let import_id = import.id(); // Process the returned type first to see if it needs an out-pointer. This diff --git a/crates/cli-support/src/wit/nonstandard.rs b/crates/cli-support/src/wit/nonstandard.rs index ca355713af9..79901e2190c 100644 --- a/crates/cli-support/src/wit/nonstandard.rs +++ b/crates/cli-support/src/wit/nonstandard.rs @@ -172,6 +172,7 @@ pub enum AuxImport { Closure { mutable: bool, // whether or not this was a `FnMut` closure dtor: u32, // table element index of the destructor function + adapter: AdapterId, // the adapter which translates the types for this closure nargs: usize, }, diff --git a/crates/cli-support/src/wit/outgoing.rs b/crates/cli-support/src/wit/outgoing.rs index f2d2164d110..cbd3e8d00b9 100644 --- a/crates/cli-support/src/wit/outgoing.rs +++ b/crates/cli-support/src/wit/outgoing.rs @@ -1,8 +1,8 @@ -use crate::descriptor::{Descriptor, VectorKind}; -use crate::wit::{AdapterType, Instruction, InstructionBuilder, NonstandardWitSection}; +use crate::descriptor::Descriptor; +use crate::wit::{AdapterType, Instruction, InstructionBuilder}; use crate::wit::{InstructionData, StackChange}; use anyhow::{bail, format_err, Error}; -use walrus::{Module, ValType}; +use walrus::ValType; impl InstructionBuilder<'_, '_> { /// Processes one more `Descriptor` as an argument to a JS function that diff --git a/crates/cli-support/src/wit/section.rs b/crates/cli-support/src/wit/section.rs index 14e3097c9e5..a49a04a7769 100644 --- a/crates/cli-support/src/wit/section.rs +++ b/crates/cli-support/src/wit/section.rs @@ -16,7 +16,6 @@ //! 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::wit::{AdapterId, AdapterJsImportKind, AdapterType, Instruction}; use crate::wit::{AdapterKind, NonstandardWitSection, WasmBindgenAux}; use crate::wit::{AuxExport, InstructionData}; @@ -24,8 +23,6 @@ use crate::wit::{AuxExportKind, AuxImport, AuxValue, JsImport, JsImportName}; use anyhow::{anyhow, bail, Context, Error}; use std::collections::HashMap; use walrus::Module; -use wasm_bindgen_multi_value_xform as multi_value_xform; -use wasm_bindgen_wasm_conventions as wasm_conventions; pub fn add( module: &mut Module, @@ -130,8 +127,10 @@ pub fn add( }; for instruction in instructions { - translate_instruction(instruction, &us2walrus, module) - .with_context(|| adapter_context(*id))?; + result.push( + translate_instruction(instruction, &us2walrus, module) + .with_context(|| adapter_context(*id))?, + ); } } @@ -269,7 +268,7 @@ fn translate_instruction( | Option64FromI32 { .. } => { bail!("optional types aren't supported in wasm bindgen"); } - VectorToMemory { .. } | VectorLoad { .. } | View { .. } => { + SliceToMemory { .. } | VectorToMemory { .. } | VectorLoad { .. } | View { .. } => { bail!("vector slices aren't supported in wasm interface types yet"); } CachedStringLoad { .. } => { diff --git a/crates/cli-support/src/wit/standard.rs b/crates/cli-support/src/wit/standard.rs index 9d5a450fb6b..8bdd0e8edc9 100644 --- a/crates/cli-support/src/wit/standard.rs +++ b/crates/cli-support/src/wit/standard.rs @@ -21,7 +21,7 @@ pub struct NonstandardWitSection { pub type NonstandardWitSectionId = TypedCustomSectionId; #[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)] -pub struct AdapterId(usize); +pub struct AdapterId(pub usize); #[derive(Debug, Clone)] pub struct Adapter { @@ -55,7 +55,7 @@ pub enum StackChange { Unknown, } -#[derive(Debug, Clone)] +#[derive(Debug, Copy, Clone, PartialEq)] pub enum AdapterJsImportKind { /// The first argument is an `anyref` which is the `this` of the function /// call @@ -180,6 +180,12 @@ pub enum Instruction { malloc: walrus::FunctionId, mem: walrus::MemoryId, }, + SliceToMemory { + kind: VectorKind, + malloc: walrus::FunctionId, + mem: walrus::MemoryId, + mutable: bool, + }, /// Pops an anyref, pushes pointer/length or all zeros. Will update original /// view if mutable. From 0f8c16c7715a99a1d1f2d0ceb0bf1e4d37bd66ab Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 26 Nov 2019 14:35:38 -0800 Subject: [PATCH 08/35] Another round of attempts On to actually running the test suite! --- crates/cli-support/src/intrinsic.rs | 8 +- crates/cli-support/src/js/binding.rs | 523 +++++++-- crates/cli-support/src/js/incoming.rs | 359 ------- crates/cli-support/src/js/mod.rs | 1177 ++++++++++----------- crates/cli-support/src/js/outgoing.rs | 451 -------- crates/cli-support/src/wit/incoming.rs | 80 +- crates/cli-support/src/wit/mod.rs | 50 +- crates/cli-support/src/wit/nonstandard.rs | 4 +- crates/cli-support/src/wit/outgoing.rs | 35 +- crates/cli-support/src/wit/section.rs | 4 +- crates/cli-support/src/wit/standard.rs | 24 +- crates/cli/tests/wasm-bindgen/main.rs | 2 +- crates/futures/src/task/multithread.rs | 2 +- src/lib.rs | 36 +- 14 files changed, 1161 insertions(+), 1594 deletions(-) delete mode 100644 crates/cli-support/src/js/incoming.rs delete mode 100644 crates/cli-support/src/js/outgoing.rs diff --git a/crates/cli-support/src/intrinsic.rs b/crates/cli-support/src/intrinsic.rs index 654f10607b0..4532adbdbee 100644 --- a/crates/cli-support/src/intrinsic.rs +++ b/crates/cli-support/src/intrinsic.rs @@ -13,7 +13,7 @@ macro_rules! intrinsics { (pub enum Intrinsic { $( #[symbol = $sym:tt] - #[signature = fn($($arg:expr),*) -> $ret:ident] + #[signature = fn($($arg:expr),*) -> $ret:expr] $name:ident, )* }) => { @@ -71,6 +71,10 @@ fn ref_string() -> Descriptor { Descriptor::Ref(Box::new(Descriptor::String)) } +fn opt_string() -> Descriptor { + Descriptor::Option(Box::new(Descriptor::String)) +} + intrinsics! { pub enum Intrinsic { #[symbol = "__wbindgen_jsval_eq"] @@ -125,7 +129,7 @@ intrinsics! { #[signature = fn(ref_anyref(), I32) -> F64] NumberGet, #[symbol = "__wbindgen_string_get"] - #[signature = fn(ref_anyref(), I32) -> I32] + #[signature = fn(ref_anyref()) -> opt_string()] StringGet, #[symbol = "__wbindgen_boolean_get"] #[signature = fn(ref_anyref()) -> I32] diff --git a/crates/cli-support/src/js/binding.rs b/crates/cli-support/src/js/binding.rs index 615fc28c484..cda1b50756b 100644 --- a/crates/cli-support/src/js/binding.rs +++ b/crates/cli-support/src/js/binding.rs @@ -5,6 +5,7 @@ //! generated by `wasm-bindgen` run through this type. use crate::js::Context; +use crate::wit::InstructionData; use crate::wit::{Adapter, AdapterId, AdapterKind, AdapterType, Instruction}; use anyhow::{anyhow, bail, Error}; use std::collections::HashSet; @@ -14,7 +15,7 @@ use walrus::Module; /// JS. pub struct Builder<'a, 'b> { /// Parent context used to expose helper functions and such. - cx: &'a mut Context<'b>, + pub cx: &'a mut Context<'b>, /// Prelude JS which is present before the main invocation to prepare /// arguments. args_prelude: String, @@ -110,10 +111,8 @@ impl<'a, 'b> Builder<'a, 'b> { pub fn process( &mut self, adapter: &Adapter, - // webidl: &ast::WebidlFunction, - // incoming_args: bool, + instructions: &[InstructionData], explicit_arg_names: &Option>, - // invoke: &mut dyn FnMut(&mut Context, &mut String, &[String]) -> Result, ) -> Result { // used in `finalize` below if self.log_error { @@ -154,70 +153,12 @@ impl<'a, 'b> Builder<'a, 'b> { } let mut js = JsBuilder::new(self.cx, adapter_params); - // for instr in adapter.instrs.iter() { - // } + for instr in instructions { + instruction(&mut js, &instr.instr)?; + } + + assert_eq!(js.stack.len(), adapter.results.len()); - // // First up we handle all the arguments. Depending on whether incoming - // // or outgoing ar the arguments this is pretty different. - // let mut arg_names = Vec::new(); - // let mut js; - // if incoming_args { - // let mut webidl_params = webidl.params.iter(); - // - // // If we're returning via an out pointer then it's guaranteed to be - // // the first argument. This isn't an argument of the function shim - // // we're generating so synthesize the parameter and its value. - // // - // // For the actual value of the return pointer we just pick the first - // // properly aligned nonzero address. We use the address for a - // // BigInt64Array sometimes which means it needs to be 8-byte - // // aligned. Otherwise valid code is unlikely to ever be working - // // around address 8, so this should be a safe address to use for - // // returning data through. - // if binding.return_via_outptr.is_some() { - // drop(webidl_params.next()); - // self.args_prelude.push_str("const retptr = 8;\n"); - // arg_names.push("retptr".to_string()); - // } - // - // // And now take the rest of the parameters and generate a name for them. - // for (i, _) in webidl_params.enumerate() { - // let arg = match explicit_arg_names { - // Some(list) => list[i].clone(), - // None => format!("arg{}", i), - // }; - // self.function_args.push(arg.clone()); - // arg_names.push(arg); - // } - // js = JsBuilder::new(arg_names); - // let mut args = incoming::Incoming::new(self.cx, &webidl.params, &mut js); - // for argument in binding.incoming.iter() { - // self.invoc_args.extend(args.process(argument)?); - // } - // } else { - // // If we're getting arguments from outgoing values then the ret ptr - // // is actually an argument of the function itself. That means that - // // `arg0` we generate below is the ret ptr, and we shouldn't - // // generate a JS binding for it and instead skip the first binding - // // listed. - // let mut skip = 0; - // if binding.return_via_outptr.is_some() { - // skip = 1; - // } - // - // // And now take the rest of the parameters and generate a name for them. - // for i in 0..self.cx.module.types.get(binding.wasm_ty).params().len() { - // let arg = format!("arg{}", i); - // self.function_args.push(arg.clone()); - // arg_names.push(arg); - // } - // js = JsBuilder::new(arg_names); - // let mut args = outgoing::Outgoing::new(self.cx, &mut js); - // for argument in binding.outgoing.iter().skip(skip) { - // self.invoc_args.push(args.process(argument)?); - // } - // } - // // // Save off the results of JS generation for the arguments. // self.args_prelude.push_str(&js.prelude); // self.finally.push_str(&js.finally); @@ -518,7 +459,7 @@ impl<'a, 'b> JsBuilder<'a, 'b> { } pub fn typescript_required(&mut self, ty: &str) { - let name = self.args[self.typescript.len()].clone(); + let name = self.arg_name(); self.typescript.push(TypescriptArg { ty: ty.to_string(), optional: false, @@ -527,7 +468,7 @@ impl<'a, 'b> JsBuilder<'a, 'b> { } pub fn typescript_optional(&mut self, ty: &str) { - let name = self.args[self.typescript.len()].clone(); + let name = self.arg_name(); self.typescript.push(TypescriptArg { ty: ty.to_string(), optional: true, @@ -535,6 +476,13 @@ impl<'a, 'b> JsBuilder<'a, 'b> { }); } + fn arg_name(&self) -> String { + self.args + .get(self.typescript.len()) + .cloned() + .unwrap_or_else(|| format!("arg{}", self.typescript.len())) + } + pub fn prelude(&mut self, prelude: &str) { for line in prelude.trim().lines() { self.prelude.push_str(line); @@ -618,12 +566,38 @@ impl<'a, 'b> JsBuilder<'a, 'b> { )); } - fn assert_mem_named_memory(&mut self, mem: walrus::MemoryId) -> Result<(), Error> { - if self.cx.export_name_of(mem) == "memory" { - return Ok(()); - } - bail!("exported memory must be called `memory` for now"); - } + // fn finally_free_slice( + // &mut self, + // expr: &str, + // i: usize, + // kind: VectorKind, + // mutable: bool, + // ) -> Result<(), Error> { + // // If the slice was mutable it's currently a feature that we + // // mirror back updates to the original slice. This... is + // // arguably a misfeature of wasm-bindgen... + // if mutable { + // let get = self.cx.memview_function(kind); + // self.js.finally(&format!( + // "\ + // {arg}.set({get}().subarray(\ + // ptr{i} / {size}, \ + // ptr{i} / {size} + len{i}\ + // ));\ + // ", + // i = i, + // arg = expr, + // get = get, + // size = kind.size() + // )); + // } + // self.js.finally(&format!( + // "wasm.__wbindgen_free(ptr{i}, len{i} * {size});", + // i = i, + // size = kind.size(), + // )); + // self.cx.require_internal_export("__wbindgen_free") + // } } fn instruction(js: &mut JsBuilder, instr: &Instruction) -> Result<(), Error> { @@ -679,9 +653,21 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction) -> Result<(), Error> { } } - Instruction::Standard(wit_walrus::Instruction::WasmToInt { trap: false, .. }) - | Instruction::Standard(wit_walrus::Instruction::IntToWasm { trap: false, .. }) => { - // we assume that the JS bindings to wasm standard handles all this + Instruction::Standard(wit_walrus::Instruction::IntToWasm { trap: false, .. }) => { + let val = js.pop(); + js.assert_number(&val); + js.push(val); + } + + // When converting to a JS number we need to specially handle the `u32` + // case because if the high bit is set then it comes out as a negative + // number, but we want to switch that to an unsigned representation. + Instruction::Standard(wit_walrus::Instruction::WasmToInt { trap: false, output, .. }) => { + let val = js.pop(); + match output { + wit_walrus::ValType::U32 => js.push(format!("{} >>> 0", val)), + _ => js.push(val), + } } Instruction::Standard(wit_walrus::Instruction::WasmToInt { trap: true, .. }) @@ -693,58 +679,54 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction) -> Result<(), Error> { js.typescript_required("string"); let len = js.pop(); let ptr = js.pop(); - js.cx.expose_get_string_from_wasm()?; - js.assert_mem_named_memory(*mem)?; + js.cx.expose_get_string_from_wasm(*mem)?; js.push(format!("getStringFromWasm({}, {})", ptr, len)); } Instruction::Standard(wit_walrus::Instruction::StringToMemory { mem, malloc }) => { js.typescript_required("string"); - js.cx.expose_pass_string_to_wasm()?; - js.assert_mem_named_memory(*mem)?; + let pass = js.cx.expose_pass_string_to_wasm(*mem)?; let val = js.pop(); let malloc = js.cx.export_name_of(*malloc); - js.push(format!("passStringToWasm({}, wasm.{})", val, malloc)); + js.push(format!("{}({}, wasm.{})", pass, val, malloc)); js.push("WASM_VECTOR_LEN".to_string()); } Instruction::Retptr => js.stack.push(retptr_val.to_string()), - Instruction::StoreRetptr { ty, offset } => { + Instruction::StoreRetptr { ty, offset, mem } => { let (mem, size) = match ty { - AdapterType::I32 => { - js.cx.expose_int32_memory(); - ("getInt32Memory()", 4) - } - AdapterType::F32 => { - js.cx.expose_f32_memory(); - ("getFloat32Memory()", 4) - } - AdapterType::F64 => { - js.cx.expose_f64_memory(); - ("getFloat64Memory()", 8) - } + AdapterType::I32 => (js.cx.expose_int32_memory(*mem), 4), + AdapterType::F32 => (js.cx.expose_f32_memory(*mem), 4), + AdapterType::F64 => (js.cx.expose_f64_memory(*mem), 8), other => bail!("invalid aggregate return type {:?}", other), }; // Note that we always assume the return pointer is argument 0, // which is currently the case for LLVM. let val = js.pop(); - let expr = format!("{}[{} / {} + {}] = {};", mem, js.arg(0), size, offset, val,); + let expr = format!( + "{}[{}() / {} + {}] = {};", + mem, + js.arg(0), + size, + offset, + val, + ); js.prelude(&expr); } - Instruction::LoadRetptr { ty, offset } => { + Instruction::LoadRetptr { ty, offset, mem } => { let (mem, size) = match ty { AdapterType::I32 => { - js.cx.expose_int32_memory(); + js.cx.expose_int32_memory(*mem); ("getInt32Memory()", 4) } AdapterType::F32 => { - js.cx.expose_f32_memory(); + js.cx.expose_f32_memory(*mem); ("getFloat32Memory()", 4) } AdapterType::F64 => { - js.cx.expose_f64_memory(); + js.cx.expose_f64_memory(*mem); ("getFloat64Memory()", 8) } other => bail!("invalid aggregate return type {:?}", other), @@ -828,7 +810,6 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction) -> Result<(), Error> { } else { js.cx.expose_uint64_cvt_shim() }; - js.cx.expose_uint32_memory(); let i = js.tmp(); js.prelude(&format!( " @@ -853,7 +834,6 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction) -> Result<(), Error> { } else { js.cx.expose_uint64_cvt_shim() }; - js.cx.expose_uint32_memory(); let i = js.tmp(); js.prelude(&format!( "\ @@ -929,7 +909,342 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction) -> Result<(), Error> { js.push(format!("isLikeNone({0}) ? 0 : {0}", val)); } - _ => panic!(), + Instruction::VectorToMemory { kind, malloc, mem } => { + js.typescript_required(kind.js_ty()); + let val = js.pop(); + let func = js.cx.pass_to_wasm_function(*kind, *mem)?; + let malloc = js.cx.export_name_of(*malloc); + js.push(format!("{}({}, wasm.{})", func, val, malloc)); + js.push("WASM_VECTOR_LEN".to_string()); + } + + Instruction::OptionVector { kind, mem, malloc } => { + js.typescript_optional(kind.js_ty()); + let func = js.cx.pass_to_wasm_function(*kind, *mem)?; + js.cx.expose_is_like_none(); + let i = js.tmp(); + let malloc = js.cx.export_name_of(*malloc); + let val = js.pop(); + js.prelude(&format!( + "const ptr{i} = isLikeNone({0}) ? 0 : {f}({0}, wasm.{malloc});", + val, + i = i, + f = func, + malloc = malloc, + )); + js.prelude(&format!("const len{} = WASM_VECTOR_LEN;", i)); + js.push(format!("ptr{}", i)); + js.push(format!("len{}", i)); + } + + Instruction::OptionMutableSlice { .. } | + Instruction::MutableSliceToMemory { + .. + // kind, + // malloc, + // mem, + // free, + } => { + panic!() + // js.typescript_required(kind.js_ty()); + // let val = js.pop(); + // let func = js.cx.pass_to_wasm_function(*kind, *mem)?; + // let malloc = js.cx.export_name_of(*malloc); + // let i = js.tmp(); + // js.prelude(&format!( + // "const ptr{i} = {f}({val}, wasm.{malloc});", + // val = val, + // i = i, + // f = func, + // malloc = malloc, + // )); + // js.prelude(&format!("const len{} = WASM_VECTOR_LEN;", i)); + // js.push(format!("ptr{}", i)); + // js.push(format!("len{}", i)); + // let free = js.cx.export_name_of(*free); + // js.finally_free_slice(&val, i, *kind, + // js.finally(&format!( + // "wasm.{free}(ptr{i}, len{i} * {size});", + // free = free, + // size = kind.size(), + // i = i + // )); + } + + Instruction::BoolFromI32 => { + js.typescript_required("bool"); + let val = js.pop(); + js.push(format!("{} !== 0", val)); + } + + Instruction::AnyrefLoadOwned => { + js.typescript_required("any"); + js.cx.expose_take_object(); + let val = js.pop(); + js.push(format!("takeObject({})", val)); + } + + Instruction::AnyrefLoadOptionOwned => { + js.typescript_optional("any"); + js.cx.expose_take_object(); + let val = js.pop(); + js.push(format!("{0} === 0 ? undefined : takeObject({0})", val)); + } + + Instruction::StringFromChar => { + js.typescript_required("string"); + let val = js.pop(); + js.push(format!("String.fromCodePoint({})", val)); + } + + Instruction::I64FromLoHi { signed } => { + js.typescript_required("BigInt"); + let f = if *signed { + js.cx.expose_int64_cvt_shim() + } else { + js.cx.expose_uint64_cvt_shim() + }; + let i = js.tmp(); + let high = js.pop(); + let low = js.pop(); + js.prelude(&format!( + "\ + u32CvtShim[0] = {low}; + u32CvtShim[1] = {high}; + const n{i} = {f}[0]; + ", + low = low, + high = high, + f = f, + i = i, + )); + js.push(format!("n{}", i)) + } + + Instruction::RustFromI32 { class } => { + js.typescript_required(class); + js.cx.require_class_wrap(class); + let val = js.pop(); + js.push(format!("{}.__wrap({})", class, val)); + } + + Instruction::OptionRustFromI32 { class } => { + js.typescript_optional(class); + js.cx.require_class_wrap(class); + let val = js.pop(); + js.push(format!( + "{0} === 0 ? undefined : {1}.__wrap({0})", + val, + class, + )) + } + + Instruction::CachedStringLoad { owned, optional, mem, free } => { + if *optional { + js.typescript_optional("string"); + } else { + js.typescript_required("string"); + } + + let len = js.pop(); + let ptr = js.pop(); + let tmp = js.tmp(); + + let get = js.cx.expose_get_cached_string_from_wasm(*mem)?; + + js.prelude(&format!( + "const v{} = {}({}, {});", + tmp, get, ptr, len + )); + + if *owned { + let free = js.cx.export_name_of(*free); + js.prelude(&format!( + "if ({ptr} !== 0) {{ wasm.{}({ptr}, {len}); }}", + free, + ptr = ptr, + len = len, + )); + } + + js.push(format!("v{}", tmp)); + } + + Instruction::TableGet => { + js.typescript_required("any"); + let val = js.pop(); + js.cx.expose_get_object(); + js.push(format!("getObject({})", val)); + } + + Instruction::StackClosure { adapter, nargs, mutable } => { + js.typescript_optional("any"); + let i = js.tmp(); + let b = js.pop(); + let a = js.pop(); + js.prelude(&format!( + "const state{} = {{a: {}, b: {}}};", + i, + a, + b, + )); + let args = (0..*nargs) + .map(|i| format!("arg{}", i)) + .collect::>() + .join(", "); + let wrapper = js.cx.adapter_name(*adapter); + if *mutable { + // Mutable closures need protection against being called + // recursively, so ensure that we clear out one of the + // internal pointers while it's being invoked. + js.prelude(&format!( + "const cb{i} = ({args}) => {{ + const a = state{i}.a; + state{i}.a = 0; + try {{ + return {name}(a, state{i}.b, {args}); + }} finally {{ + state{i}.a = a; + }} + }};", + i = i, + args = args, + name = wrapper, + )); + } else { + js.prelude(&format!( + "const cb{i} = ({args}) => {wrapper}(state{i}.a, state{i}.b, {args});", + i = i, + args = args, + wrapper = wrapper, + )); + } + + // Make sure to null out our internal pointers when we return + // back to Rust to ensure that any lingering references to the + // closure will fail immediately due to null pointers passed in + // to Rust. + js.finally(&format!("state{}.a = state{0}.b = 0;", i)); + js.push(format!("cb{}", i)); + } + + Instruction::VectorLoad { kind, mem, free } => { + js.typescript_required(kind.js_ty()); + let len = js.pop(); + let ptr = js.pop(); + let f = js.cx.expose_get_vector_from_wasm(*kind, *mem)?; + let i = js.tmp(); + let free = js.cx.export_name_of(*free); + js.prelude(&format!("const v{} = {}({}, {}).slice();", i, f, ptr, len)); + js.prelude(&format!("wasm.{}({}, {} * {});", free, ptr, len, kind.size())); + js.push(format!("v{}", i)) + } + + Instruction::OptionVectorLoad { kind, mem, free } => { + js.typescript_optional(kind.js_ty()); + let len = js.pop(); + let ptr = js.pop(); + let f = js.cx.expose_get_vector_from_wasm(*kind, *mem)?; + let i = js.tmp(); + let free = js.cx.export_name_of(*free); + js.prelude(&format!("let v{};", i)); + js.prelude(&format!("if ({} !== 0) {{", ptr)); + js.prelude(&format!("v{} = {}({}, {}).slice();", i, f, ptr, len)); + js.prelude(&format!("wasm.{}({}, {} * {});", free, ptr, len, kind.size())); + js.prelude("}"); + js.push(format!("v{}", i)); + } + + Instruction::View { kind, mem } => { + js.typescript_required(kind.js_ty()); + let len = js.pop(); + let ptr = js.pop(); + let f = js.cx.expose_get_vector_from_wasm(*kind, *mem)?; + js.push(format!( + "{f}({ptr}, {len})", + ptr = ptr, + len = len, + f = f + )); + } + + Instruction::OptionView { kind, mem } => { + js.typescript_optional(kind.js_ty()); + let len = js.pop(); + let ptr = js.pop(); + let f = js.cx.expose_get_vector_from_wasm(*kind, *mem)?; + js.push(format!( + "{ptr} === 0 ? undefined : {f}({ptr}, {len})", + ptr = ptr, + len = len, + f = f + )); + } + + Instruction::OptionU32Sentinel => { + js.typescript_optional("number"); + let val = js.pop(); + js.push(format!("{0} === 0xFFFFFF ? undefined : {0}", val)); + } + + Instruction::ToOptionNative { ty: _, signed } => { + js.typescript_optional("number"); + let val = js.pop(); + let present = js.pop(); + js.push(format!( + "{} === 0 ? undefined : {}{}", + present, + val, + if *signed { "" } else { " >>> 0" }, + )); + } + + Instruction::OptionBoolFromI32 => { + js.typescript_optional("boolean"); + let val = js.pop(); + js.push(format!("{0} === 0xFFFFFF ? undefined : {0} !== 0", val)); + } + + Instruction::OptionCharFromI32 => { + js.typescript_optional("string"); + let val = js.pop(); + js.push(format!( + "{0} === 0xFFFFFF ? undefined : String.fromCodePoint({0})", + val, + )); + } + + Instruction::OptionEnumFromI32 { hole } => { + js.typescript_optional("number"); + let val = js.pop(); + js.push(format!("{0} === {1} ? undefined : {0}", val, hole)); + } + + Instruction::Option64FromI32 { signed } => { + js.typescript_optional("BigInt"); + let f = if *signed { + js.cx.expose_int64_cvt_shim() + } else { + js.cx.expose_uint64_cvt_shim() + }; + let i = js.tmp(); + let high = js.pop(); + let low = js.pop(); + let present = js.pop(); + js.prelude(&format!( + " + u32CvtShim[0] = {low}; + u32CvtShim[1] = {high}; + const n{i} = {present} === 0 ? undefined : {f}[0]; + ", + present = present, + low = low, + high = high, + f = f, + i = i, + )); + js.push(format!("n{}", i)); + } } Ok(()) } diff --git a/crates/cli-support/src/js/incoming.rs b/crates/cli-support/src/js/incoming.rs deleted file mode 100644 index b0c922b72b1..00000000000 --- a/crates/cli-support/src/js/incoming.rs +++ /dev/null @@ -1,359 +0,0 @@ -//! Implementation of taking a `NonstandardIncoming` binding and generating JS -//! which represents it and executes it for what we need. -//! -//! This module is used to generate JS for all our incoming bindings which -//! includes arguments going into exports or return values from imports. - -use crate::descriptor::VectorKind; -use crate::js::binding::JsBuilder; -use crate::js::Context; -use crate::webidl::NonstandardIncoming; -use anyhow::{bail, Error}; -use wasm_webidl_bindings::ast; - -pub struct Incoming<'a, 'b> { - cx: &'a mut Context<'b>, - types: &'a [ast::WebidlTypeRef], - js: &'a mut JsBuilder, -} - -impl<'a, 'b> Incoming<'a, 'b> { - pub fn new( - cx: &'a mut Context<'b>, - types: &'a [ast::WebidlTypeRef], - js: &'a mut JsBuilder, - ) -> Incoming<'a, 'b> { - Incoming { cx, types, js } - } - - pub fn process(&mut self, incoming: &NonstandardIncoming) -> Result, Error> { - let before = self.js.typescript_len(); - let ret = self.nonstandard(incoming)?; - assert_eq!(before + 1, self.js.typescript_len()); - Ok(ret) - } - - fn nonstandard(&mut self, incoming: &NonstandardIncoming) -> Result, Error> { - let single = match incoming { - NonstandardIncoming::Standard(val) => return self.standard(val), - - - // Same as `IncomingBindingExpressionAllocCopy`, except we use a - // different `VectorKind` - NonstandardIncoming::AllocCopyInt64 { - alloc_func_name: _, - expr, - signed, - } => { - let (expr, ty) = self.standard_typed(expr)?; - assert_eq!(ty, ast::WebidlScalarType::Any.into()); - let kind = if *signed { - VectorKind::I64 - } else { - VectorKind::U64 - }; - let func = self.cx.pass_to_wasm_function(kind)?; - self.js.typescript_required(kind.js_ty()); - return Ok(vec![ - format!("{}({})", func, expr), - "WASM_VECTOR_LEN".to_string(), - ]); - } - - // Same as `IncomingBindingExpressionAllocCopy`, except we use a - // different `VectorKind` - NonstandardIncoming::AllocCopyAnyrefArray { - alloc_func_name: _, - expr, - } => { - let (expr, ty) = self.standard_typed(expr)?; - assert_eq!(ty, ast::WebidlScalarType::Any.into()); - let func = self.cx.pass_to_wasm_function(VectorKind::Anyref)?; - self.js.typescript_required(VectorKind::Anyref.js_ty()); - return Ok(vec![ - format!("{}({})", func, expr), - "WASM_VECTOR_LEN".to_string(), - ]); - } - - // Similar to `AllocCopy`, except that we deallocate in a finally - // block. - NonstandardIncoming::MutableSlice { kind, val } => { - let (expr, ty) = self.standard_typed(val)?; - assert_eq!(ty, ast::WebidlScalarType::Any.into()); - let func = self.cx.pass_to_wasm_function(*kind)?; - let i = self.js.tmp(); - self.js - .prelude(&format!("const ptr{} = {}({});", i, func, expr)); - self.js - .prelude(&format!("const len{} = WASM_VECTOR_LEN;", i)); - self.finally_free_slice(&expr, i, *kind, true)?; - self.js.typescript_required(kind.js_ty()); - return Ok(vec![format!("ptr{}", i), format!("len{}", i)]); - } - - // Similar to `AllocCopy`, except we're handling the undefined case - // and passing null for the pointer value. - NonstandardIncoming::OptionVector { kind, val } => { - let (expr, ty) = self.standard_typed(val)?; - assert_eq!(ty, ast::WebidlScalarType::Any.into()); - let func = self.cx.pass_to_wasm_function(*kind)?; - self.cx.expose_is_like_none(); - let i = self.js.tmp(); - self.js.prelude(&format!( - "const ptr{i} = isLikeNone({0}) ? 0 : {f}({0});", - expr, - i = i, - f = func, - )); - self.js - .prelude(&format!("const len{} = WASM_VECTOR_LEN;", i)); - self.js.typescript_optional(kind.js_ty()); - return Ok(vec![format!("ptr{}", i), format!("len{}", i)]); - } - - // An unfortunate smorgasboard of handling slices, transfers if - // mutable, etc. Not the prettiest binding option here, and of - // course never going to be standardized. - NonstandardIncoming::OptionSlice { kind, val, mutable } => { - let (expr, ty) = self.standard_typed(val)?; - assert_eq!(ty, ast::WebidlScalarType::Any.into()); - let func = self.cx.pass_to_wasm_function(*kind)?; - self.cx.expose_is_like_none(); - let i = self.js.tmp(); - self.js.prelude(&format!( - "const ptr{i} = isLikeNone({0}) ? 0 : {f}({0});", - expr, - i = i, - f = func, - )); - self.js - .prelude(&format!("const len{} = WASM_VECTOR_LEN;", i)); - self.js.finally(&format!("if (ptr{} !== 0) {{", i)); - self.finally_free_slice(&expr, i, *kind, *mutable)?; - self.js.finally("}"); - self.js.typescript_optional(kind.js_ty()); - return Ok(vec![format!("ptr{}", i), format!("len{}", i)]); - } - }; - Ok(vec![single]) - } - - /// Evaluates the `standard` binding expression, returning the JS expression - /// needed to evaluate the binding. - fn standard( - &mut self, - standard: &ast::IncomingBindingExpression, - ) -> Result, Error> { - let single = match standard { - ast::IncomingBindingExpression::As(as_) => { - let (expr, ty) = self.standard_typed(&as_.expr)?; - match ty { - ast::WebidlTypeRef::Scalar(ast::WebidlScalarType::Any) => { - self.js.typescript_required("any"); - - // If the type here is anyref but we didn't run the - // anyref pass that means we have to instead actually - // pass in an index - // - // TODO: we should ideally move this `addHeapObject` - // into a nonstanard binding whenever the anyref pass - // doesn't already run rather than implicitly picking - // it up here - if self.cx.config.anyref { - expr - } else { - self.cx.expose_add_heap_object(); - format!("addHeapObject({})", expr) - } - } - ast::WebidlTypeRef::Scalar(ast::WebidlScalarType::Boolean) => { - self.js.typescript_required("boolean"); - self.assert_bool(&expr); - // JS will already coerce booleans into numbers for us - expr - } - _ => { - self.js.typescript_required("number"); - self.assert_number(&expr); - expr - } - } - } - ast::IncomingBindingExpression::Get(_) => { - bail!("unsupported bare `get` in webidl bindings"); - } - ast::IncomingBindingExpression::AllocUtf8Str(expr) => { - let (expr, ty) = self.standard_typed(&expr.expr)?; - assert_eq!(ty, ast::WebidlScalarType::DomString.into()); - self.js.typescript_required("string"); - self.cx.expose_pass_string_to_wasm()?; - return Ok(vec![ - format!("passStringToWasm({})", expr), - "WASM_VECTOR_LEN".to_string(), - ]); - } - ast::IncomingBindingExpression::AllocCopy(expr) => { - let (expr, ty) = self.standard_typed(&expr.expr)?; - let scalar = match ty { - ast::WebidlTypeRef::Scalar(s) => s, - ast::WebidlTypeRef::Id(_) => { - bail!("unsupported type passed to `alloc-copy` in webidl binding") - } - }; - let kind = match scalar { - ast::WebidlScalarType::Int8Array => VectorKind::I8, - ast::WebidlScalarType::Uint8Array => VectorKind::U8, - ast::WebidlScalarType::Uint8ClampedArray => VectorKind::ClampedU8, - ast::WebidlScalarType::Int16Array => VectorKind::I16, - ast::WebidlScalarType::Uint16Array => VectorKind::U16, - ast::WebidlScalarType::Int32Array => VectorKind::I32, - ast::WebidlScalarType::Uint32Array => VectorKind::U32, - ast::WebidlScalarType::Float32Array => VectorKind::F32, - ast::WebidlScalarType::Float64Array => VectorKind::F64, - _ => bail!("unsupported type passed to alloc-copy: {:?}", scalar), - }; - self.js.typescript_required(kind.js_ty()); - let func = self.cx.pass_to_wasm_function(kind)?; - return Ok(vec![ - format!("{}({})", func, expr), - "WASM_VECTOR_LEN".to_string(), - ]); - } - ast::IncomingBindingExpression::EnumToI32(_) => { - bail!("unsupported enum-to-i32 conversion in webidl binding"); - } - ast::IncomingBindingExpression::Field(_) => { - bail!("unsupported field accessor in webidl binding"); - } - ast::IncomingBindingExpression::BindImport(_) => { - bail!("unsupported import binding in webidl binding"); - } - }; - Ok(vec![single]) - } - - /// Evaluates the `standard` binding expression, returning both the - /// JS expression to evaluate along with the WebIDL type of the expression. - /// - /// Currently only supports `Get`. - fn standard_typed( - &mut self, - standard: &ast::IncomingBindingExpression, - ) -> Result<(String, ast::WebidlTypeRef), Error> { - match standard { - ast::IncomingBindingExpression::As(_) => { - bail!("unsupported as in webidl binding"); - } - ast::IncomingBindingExpression::Get(expr) => { - let arg = self.js.arg(expr.idx).to_string(); - let ty = self.types[expr.idx as usize]; - Ok((arg, ty)) - } - ast::IncomingBindingExpression::AllocUtf8Str(_) => { - bail!("unsupported alloc-utf8-str in webidl binding"); - } - ast::IncomingBindingExpression::AllocCopy(_) => { - bail!("unsupported alloc-copy in webidl binding"); - } - ast::IncomingBindingExpression::EnumToI32(_) => { - bail!("unsupported enum-to-i32 in webidl binding"); - } - ast::IncomingBindingExpression::Field(_) => { - bail!("unsupported field accessor in webidl binding"); - } - ast::IncomingBindingExpression::BindImport(_) => { - bail!("unsupported import binding in webidl binding"); - } - } - } - - fn assert_class(&mut self, arg: &str, class: &str) { - self.cx.expose_assert_class(); - self.js - .prelude(&format!("_assertClass({}, {});", arg, class)); - } - - fn assert_number(&mut self, arg: &str) { - if !self.cx.config.debug { - return; - } - self.cx.expose_assert_num(); - self.js.prelude(&format!("_assertNum({});", arg)); - } - - fn assert_bool(&mut self, arg: &str) { - if !self.cx.config.debug { - return; - } - self.cx.expose_assert_bool(); - self.js.prelude(&format!("_assertBoolean({});", arg)); - } - - fn assert_optional_number(&mut self, arg: &str) { - if !self.cx.config.debug { - return; - } - self.cx.expose_is_like_none(); - self.js.prelude(&format!("if (!isLikeNone({})) {{", arg)); - self.assert_number(arg); - self.js.prelude("}"); - } - - fn assert_optional_bool(&mut self, arg: &str) { - if !self.cx.config.debug { - return; - } - self.cx.expose_is_like_none(); - self.js.prelude(&format!("if (!isLikeNone({})) {{", arg)); - self.assert_bool(arg); - self.js.prelude("}"); - } - - fn assert_not_moved(&mut self, arg: &str) { - if !self.cx.config.debug { - return; - } - self.js.prelude(&format!( - "\ - if ({0}.ptr === 0) {{ - throw new Error('Attempt to use a moved value'); - }} - ", - arg, - )); - } - - fn finally_free_slice( - &mut self, - expr: &str, - i: usize, - kind: VectorKind, - mutable: bool, - ) -> Result<(), Error> { - // If the slice was mutable it's currently a feature that we - // mirror back updates to the original slice. This... is - // arguably a misfeature of wasm-bindgen... - if mutable { - let get = self.cx.memview_function(kind); - self.js.finally(&format!( - "\ - {arg}.set({get}().subarray(\ - ptr{i} / {size}, \ - ptr{i} / {size} + len{i}\ - ));\ - ", - i = i, - arg = expr, - get = get, - size = kind.size() - )); - } - self.js.finally(&format!( - "wasm.__wbindgen_free(ptr{i}, len{i} * {size});", - i = i, - size = kind.size(), - )); - self.cx.require_internal_export("__wbindgen_free") - } -} diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 38789c303a0..c47af4fb512 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -1,11 +1,14 @@ use crate::descriptor::VectorKind; use crate::intrinsic::Intrinsic; -use crate::wit::{Adapter, AuxValue, AdapterJsImportKind}; +use crate::wit::{Adapter, AdapterId, AdapterJsImportKind, AuxValue}; +use crate::wit::{AdapterKind, InstructionData}; use crate::wit::{AuxEnum, AuxExport, AuxExportKind, AuxImport, AuxStruct}; use crate::wit::{JsImport, JsImportName, NonstandardWitSection, WasmBindgenAux}; use crate::{Bindgen, EncodeInto, OutputMode}; -use anyhow::{bail, Context as _, Error}; +use anyhow::{anyhow, bail, Context as _, Error}; +use std::borrow::Cow; use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; +use std::fmt; use std::fs; use std::path::{Path, PathBuf}; use walrus::{ExportId, ImportId, MemoryId, Module}; @@ -18,7 +21,7 @@ pub struct Context<'a> { globals: String, imports_post: String, typescript: String, - exposed_globals: Option>, + exposed_globals: Option>>, required_internal_exports: HashSet<&'static str>, next_export_idx: usize, config: &'a Bindgen, @@ -44,11 +47,14 @@ pub struct Context<'a> { defined_identifiers: HashMap, exported_classes: Option>, - memory: MemoryId, /// A map of the name of npm dependencies we've loaded so far to the path /// they're defined in as well as their version specification. pub npm_dependencies: HashMap, + + /// A mapping of a index for memories as we see them. Used in function + /// names. + memory_indices: HashMap, } #[derive(Default)] @@ -73,18 +79,6 @@ impl<'a> Context<'a> { wit: &'a NonstandardWitSection, aux: &'a WasmBindgenAux, ) -> Result, Error> { - // Find the single memory, if there is one, and for ease of use in our - // binding generation just inject one if there's not one already (and - // we'll clean it up later if we end up not using it). - let mut memories = module.memories.iter().map(|m| m.id()); - let memory = memories.next(); - if memories.next().is_some() { - bail!("multiple memories currently not supported"); - } - drop(memories); - let memory = memory.unwrap_or_else(|| module.memories.add_local(false, 1, None)); - - // And then we're good to go! Ok(Context { globals: String::new(), imports_post: String::new(), @@ -98,16 +92,16 @@ impl<'a> Context<'a> { exported_classes: Some(Default::default()), config, module, - memory, npm_dependencies: Default::default(), next_export_idx: 0, wit, aux, + memory_indices: Default::default(), }) } - fn should_write_global(&mut self, name: &'static str) -> bool { - self.exposed_globals.as_mut().unwrap().insert(name) + fn should_write_global(&mut self, name: impl Into>) -> bool { + self.exposed_globals.as_mut().unwrap().insert(name.into()) } fn export( @@ -446,157 +440,158 @@ impl<'a> Context<'a> { needs_manual_start: bool, mut imports: Option<&mut String>, ) -> Result<(String, String), Error> { - let module_name = "wbg"; - let mem = self.module.memories.get(self.memory); - let (init_memory1, init_memory2) = if let Some(id) = mem.import { - self.module.imports.get_mut(id).module = module_name.to_string(); - let mut memory = String::from("new WebAssembly.Memory({"); - memory.push_str(&format!("initial:{}", mem.initial)); - if let Some(max) = mem.maximum { - memory.push_str(&format!(",maximum:{}", max)); - } - if mem.shared { - memory.push_str(",shared:true"); - } - memory.push_str("})"); - self.imports_post.push_str("let memory;\n"); - ( - format!("memory = imports.{}.memory = maybe_memory;", module_name), - format!("memory = imports.{}.memory = {};", module_name, memory), - ) - } else { - (String::new(), String::new()) - }; - let init_memory_arg = if mem.import.is_some() { - ", maybe_memory" - } else { - "" - }; - - let default_module_path = match self.config.mode { - OutputMode::Web => { - "\ - if (typeof module === 'undefined') { - module = import.meta.url.replace(/\\.js$/, '_bg.wasm'); - }" - } - _ => "", - }; - - let ts = Self::ts_for_init_fn(mem.import.is_some(), !default_module_path.is_empty()); - - // Initialize the `imports` object for all import definitions that we're - // directed to wire up. - let mut imports_init = String::new(); - if self.wasm_import_definitions.len() > 0 { - imports_init.push_str("imports."); - imports_init.push_str(module_name); - imports_init.push_str(" = {};\n"); - } - for (id, js) in sorted_iter(&self.wasm_import_definitions) { - let import = self.module.imports.get_mut(*id); - import.module = module_name.to_string(); - imports_init.push_str("imports."); - imports_init.push_str(module_name); - imports_init.push_str("."); - imports_init.push_str(&import.name); - imports_init.push_str(" = "); - imports_init.push_str(js.trim()); - imports_init.push_str(";\n"); - } - - let extra_modules = self - .module - .imports - .iter() - .filter(|i| !self.wasm_import_definitions.contains_key(&i.id())) - .filter(|i| { - // Importing memory is handled specially in this area, so don't - // consider this a candidate for importing from extra modules. - match i.kind { - walrus::ImportKind::Memory(_) => false, - _ => true, - } - }) - .map(|i| &i.module) - .collect::>(); - for (i, extra) in extra_modules.iter().enumerate() { - let imports = match &mut imports { - Some(list) => list, - None => bail!( - "cannot import from modules (`{}`) with `--no-modules`", - extra - ), - }; - imports.push_str(&format!("import * as __wbg_star{} from '{}';\n", i, extra)); - imports_init.push_str(&format!("imports['{}'] = __wbg_star{};\n", extra, i)); - } - - let js = format!( - "\ - function init(module{init_memory_arg}) {{ - {default_module_path} - let result; - const imports = {{}}; - {imports_init} - if ((typeof URL === 'function' && module instanceof URL) || typeof module === 'string' || (typeof Request === 'function' && module instanceof Request)) {{ - {init_memory2} - const response = fetch(module); - if (typeof WebAssembly.instantiateStreaming === 'function') {{ - result = WebAssembly.instantiateStreaming(response, imports) - .catch(e => {{ - return response - .then(r => {{ - if (r.headers.get('Content-Type') != 'application/wasm') {{ - console.warn(\"`WebAssembly.instantiateStreaming` failed \ - because your server does not serve wasm with \ - `application/wasm` MIME type. Falling back to \ - `WebAssembly.instantiate` which is slower. Original \ - error:\\n\", e); - return r.arrayBuffer(); - }} else {{ - throw e; - }} - }}) - .then(bytes => WebAssembly.instantiate(bytes, imports)); - }}); - }} else {{ - result = response - .then(r => r.arrayBuffer()) - .then(bytes => WebAssembly.instantiate(bytes, imports)); - }} - }} else {{ - {init_memory1} - result = WebAssembly.instantiate(module, imports) - .then(result => {{ - if (result instanceof WebAssembly.Instance) {{ - return {{ instance: result, module }}; - }} else {{ - return result; - }} - }}); - }} - return result.then(({{instance, module}}) => {{ - wasm = instance.exports; - init.__wbindgen_wasm_module = module; - {start} - return wasm; - }}); - }} - ", - init_memory_arg = init_memory_arg, - default_module_path = default_module_path, - init_memory1 = init_memory1, - init_memory2 = init_memory2, - start = if needs_manual_start { - "wasm.__wbindgen_start();" - } else { - "" - }, - imports_init = imports_init, - ); - - Ok((js, ts)) + panic!() + // let module_name = "wbg"; + // let mem = self.module.memories.get(self.memory); + // let (init_memory1, init_memory2) = if let Some(id) = mem.import { + // self.module.imports.get_mut(id).module = module_name.to_string(); + // let mut memory = String::from("new WebAssembly.Memory({"); + // memory.push_str(&format!("initial:{}", mem.initial)); + // if let Some(max) = mem.maximum { + // memory.push_str(&format!(",maximum:{}", max)); + // } + // if mem.shared { + // memory.push_str(",shared:true"); + // } + // memory.push_str("})"); + // self.imports_post.push_str("let memory;\n"); + // ( + // format!("memory = imports.{}.memory = maybe_memory;", module_name), + // format!("memory = imports.{}.memory = {};", module_name, memory), + // ) + // } else { + // (String::new(), String::new()) + // }; + // let init_memory_arg = if mem.import.is_some() { + // ", maybe_memory" + // } else { + // "" + // }; + // + // let default_module_path = match self.config.mode { + // OutputMode::Web => { + // "\ + // if (typeof module === 'undefined') { + // module = import.meta.url.replace(/\\.js$/, '_bg.wasm'); + // }" + // } + // _ => "", + // }; + // + // let ts = Self::ts_for_init_fn(mem.import.is_some(), !default_module_path.is_empty()); + // + // // Initialize the `imports` object for all import definitions that we're + // // directed to wire up. + // let mut imports_init = String::new(); + // if self.wasm_import_definitions.len() > 0 { + // imports_init.push_str("imports."); + // imports_init.push_str(module_name); + // imports_init.push_str(" = {};\n"); + // } + // for (id, js) in sorted_iter(&self.wasm_import_definitions) { + // let import = self.module.imports.get_mut(*id); + // import.module = module_name.to_string(); + // imports_init.push_str("imports."); + // imports_init.push_str(module_name); + // imports_init.push_str("."); + // imports_init.push_str(&import.name); + // imports_init.push_str(" = "); + // imports_init.push_str(js.trim()); + // imports_init.push_str(";\n"); + // } + // + // let extra_modules = self + // .module + // .imports + // .iter() + // .filter(|i| !self.wasm_import_definitions.contains_key(&i.id())) + // .filter(|i| { + // // Importing memory is handled specially in this area, so don't + // // consider this a candidate for importing from extra modules. + // match i.kind { + // walrus::ImportKind::Memory(_) => false, + // _ => true, + // } + // }) + // .map(|i| &i.module) + // .collect::>(); + // for (i, extra) in extra_modules.iter().enumerate() { + // let imports = match &mut imports { + // Some(list) => list, + // None => bail!( + // "cannot import from modules (`{}`) with `--no-modules`", + // extra + // ), + // }; + // imports.push_str(&format!("import * as __wbg_star{} from '{}';\n", i, extra)); + // imports_init.push_str(&format!("imports['{}'] = __wbg_star{};\n", extra, i)); + // } + // + // let js = format!( + // "\ + // function init(module{init_memory_arg}) {{ + // {default_module_path} + // let result; + // const imports = {{}}; + // {imports_init} + // if ((typeof URL === 'function' && module instanceof URL) || typeof module === 'string' || (typeof Request === 'function' && module instanceof Request)) {{ + // {init_memory2} + // const response = fetch(module); + // if (typeof WebAssembly.instantiateStreaming === 'function') {{ + // result = WebAssembly.instantiateStreaming(response, imports) + // .catch(e => {{ + // return response + // .then(r => {{ + // if (r.headers.get('Content-Type') != 'application/wasm') {{ + // console.warn(\"`WebAssembly.instantiateStreaming` failed \ + // because your server does not serve wasm with \ + // `application/wasm` MIME type. Falling back to \ + // `WebAssembly.instantiate` which is slower. Original \ + // error:\\n\", e); + // return r.arrayBuffer(); + // }} else {{ + // throw e; + // }} + // }}) + // .then(bytes => WebAssembly.instantiate(bytes, imports)); + // }}); + // }} else {{ + // result = response + // .then(r => r.arrayBuffer()) + // .then(bytes => WebAssembly.instantiate(bytes, imports)); + // }} + // }} else {{ + // {init_memory1} + // result = WebAssembly.instantiate(module, imports) + // .then(result => {{ + // if (result instanceof WebAssembly.Instance) {{ + // return {{ instance: result, module }}; + // }} else {{ + // return result; + // }} + // }}); + // }} + // return result.then(({{instance, module}}) => {{ + // wasm = instance.exports; + // init.__wbindgen_wasm_module = module; + // {start} + // return wasm; + // }}); + // }} + // ", + // init_memory_arg = init_memory_arg, + // default_module_path = default_module_path, + // init_memory1 = init_memory1, + // init_memory2 = init_memory2, + // start = if needs_manual_start { + // "wasm.__wbindgen_start();" + // } else { + // "" + // }, + // imports_init = imports_init, + // ); + // + // Ok((js, ts)) } fn write_classes(&mut self) -> Result<(), Error> { @@ -814,12 +809,7 @@ impl<'a> Context<'a> { self.global("let WASM_VECTOR_LEN = 0;"); } - fn expose_pass_string_to_wasm(&mut self) -> Result<(), Error> { - if !self.should_write_global("pass_string_to_wasm") { - return Ok(()); - } - - self.require_internal_export("__wbindgen_malloc")?; + fn expose_pass_string_to_wasm(&mut self, memory: MemoryId) -> Result { self.expose_wasm_vector_len(); let debug = if self.config.debug { @@ -835,25 +825,40 @@ impl<'a> Context<'a> { // doesn't require creating intermediate view using `subarray` // and also has `Buffer::byteLength` to calculate size upfront. if self.config.mode.nodejs() { - self.expose_node_buffer_memory(); + let get_buf = self.expose_node_buffer_memory(memory); + let ret = MemView { + name: "passStringToWasm", + num: get_buf.num, + }; + if !self.should_write_global(ret.to_string()) { + return Ok(ret); + } self.global(&format!( " - function passStringToWasm(arg, malloc) {{ + function {}(arg, malloc) {{ {} const len = Buffer.byteLength(arg); const ptr = malloc(len); - getNodeBufferMemory().write(arg, ptr, len); + {}().write(arg, ptr, len); WASM_VECTOR_LEN = len; return ptr; }} ", - debug, + ret, debug, get_buf, )); - return Ok(()); + return Ok(ret); } + let mem = self.expose_uint8_memory(memory); + let ret = MemView { + name: "passStringToWasm", + num: mem.num, + }; + if !self.should_write_global(ret.to_string()) { + return Ok(ret); + } self.expose_text_encoder()?; // The first implementation we have for this is to use @@ -877,7 +882,7 @@ impl<'a> Context<'a> { // Looks like `encodeInto` doesn't currently work when the memory passed // in is backed by a `SharedArrayBuffer`, so force usage of `encode` if // a `SharedArrayBuffer` is in use. - let shared = self.module.memories.get(self.memory).shared; + let shared = self.module.memories.get(memory).shared; match self.config.encode_into { EncodeInto::Always if !shared => { @@ -908,7 +913,6 @@ impl<'a> Context<'a> { } } - self.expose_uint8_memory(); self.require_internal_export("__wbindgen_realloc")?; // A fast path that directly writes char codes into WASM memory as long @@ -941,134 +945,143 @@ impl<'a> Context<'a> { // looping over the string to calculate the precise size, or perhaps using // `shrink_to_fit` on the Rust side. self.global(&format!( - "function passStringToWasm(arg, malloc) {{ - {} - {} + "function {name}(arg, malloc) {{ + {debug} + {ascii} if (offset !== len) {{ if (offset !== 0) {{ arg = arg.slice(offset); }} ptr = wasm.__wbindgen_realloc(ptr, len, len = offset + arg.length * 3); - const view = getUint8Memory().subarray(ptr + offset, ptr + len); + const view = {mem}().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - {} + {debug_end} offset += ret.written; }} WASM_VECTOR_LEN = offset; return ptr; }}", - debug, - encode_as_ascii, - if self.config.debug { + name = ret, + debug = debug, + ascii = encode_as_ascii, + mem = mem, + debug_end = if self.config.debug { "if (ret.read !== arg.length) throw new Error('failed to pass whole string');" } else { "" }, )); - Ok(()) + Ok(ret) } - fn expose_pass_array8_to_wasm(&mut self) -> Result<(), Error> { - self.expose_uint8_memory(); - self.pass_array_to_wasm("passArray8ToWasm", "getUint8Memory", 1) + fn expose_pass_array8_to_wasm(&mut self, memory: MemoryId) -> Result { + let view = self.expose_uint8_memory(memory); + self.pass_array_to_wasm("passArray8ToWasm", view, 1) } - fn expose_pass_array16_to_wasm(&mut self) -> Result<(), Error> { - self.expose_uint16_memory(); - self.pass_array_to_wasm("passArray16ToWasm", "getUint16Memory", 2) + fn expose_pass_array16_to_wasm(&mut self, memory: MemoryId) -> Result { + let view = self.expose_uint16_memory(memory); + self.pass_array_to_wasm("passArray16ToWasm", view, 2) } - fn expose_pass_array32_to_wasm(&mut self) -> Result<(), Error> { - self.expose_uint32_memory(); - self.pass_array_to_wasm("passArray32ToWasm", "getUint32Memory", 4) + fn expose_pass_array32_to_wasm(&mut self, memory: MemoryId) -> Result { + let view = self.expose_uint32_memory(memory); + self.pass_array_to_wasm("passArray32ToWasm", view, 4) } - fn expose_pass_array64_to_wasm(&mut self) -> Result<(), Error> { - self.expose_uint64_memory(); - self.pass_array_to_wasm("passArray64ToWasm", "getUint64Memory", 8) + fn expose_pass_array64_to_wasm(&mut self, memory: MemoryId) -> Result { + let view = self.expose_uint64_memory(memory); + self.pass_array_to_wasm("passArray64ToWasm", view, 8) } - fn expose_pass_array_f32_to_wasm(&mut self) -> Result<(), Error> { - self.expose_f32_memory(); - self.pass_array_to_wasm("passArrayF32ToWasm", "getFloat32Memory", 4) + fn expose_pass_array_f32_to_wasm(&mut self, memory: MemoryId) -> Result { + let view = self.expose_f32_memory(memory); + self.pass_array_to_wasm("passArrayF32ToWasm", view, 4) } - fn expose_pass_array_f64_to_wasm(&mut self) -> Result<(), Error> { - self.expose_f64_memory(); - self.pass_array_to_wasm("passArrayF64ToWasm", "getFloat64Memory", 8) + fn expose_pass_array_f64_to_wasm(&mut self, memory: MemoryId) -> Result { + let view = self.expose_f64_memory(memory); + self.pass_array_to_wasm("passArrayF64ToWasm", view, 8) } - fn expose_pass_array_jsvalue_to_wasm(&mut self) -> Result<(), Error> { - if !self.should_write_global("pass_array_jsvalue") { - return Ok(()); + fn expose_pass_array_jsvalue_to_wasm(&mut self, memory: MemoryId) -> Result { + let mem = self.expose_uint32_memory(memory); + let ret = MemView { + name: "passArrayJsValueToWasm", + num: mem.num, + }; + if !self.should_write_global(ret.to_string()) { + return Ok(ret); } - self.require_internal_export("__wbindgen_malloc")?; - self.expose_uint32_memory(); self.expose_wasm_vector_len(); if self.config.anyref { // TODO: using `addToAnyrefTable` goes back and forth between wasm // and JS a lot, we should have a bulk operation for this. self.expose_add_to_anyref_table()?; - self.global( + self.global(&format!( " - function passArrayJsValueToWasm(array) { - const ptr = wasm.__wbindgen_malloc(array.length * 4); - const mem = getUint32Memory(); - for (let i = 0; i < array.length; i++) { + function {}(array, malloc) {{ + const ptr = malloc(array.length * 4); + const mem = {}(); + for (let i = 0; i < array.length; i++) {{ mem[ptr / 4 + i] = addToAnyrefTable(array[i]); - } + }} WASM_VECTOR_LEN = array.length; return ptr; - } - ", - ); + }} + ", + ret, mem, + )); } else { self.expose_add_heap_object(); - self.global( + self.global(&format!( " - function passArrayJsValueToWasm(array) { - const ptr = wasm.__wbindgen_malloc(array.length * 4); - const mem = getUint32Memory(); - for (let i = 0; i < array.length; i++) { + function {}(array, malloc) {{ + const ptr = malloc(array.length * 4); + const mem = {}(); + for (let i = 0; i < array.length; i++) {{ mem[ptr / 4 + i] = addHeapObject(array[i]); - } + }} WASM_VECTOR_LEN = array.length; return ptr; - } - - ", - ); + }} + ", + ret, mem, + )); } - Ok(()) + Ok(ret) } fn pass_array_to_wasm( &mut self, name: &'static str, - delegate: &str, + view: MemView, size: usize, - ) -> Result<(), Error> { - if !self.should_write_global(name) { - return Ok(()); + ) -> Result { + let ret = MemView { + name, + num: view.num, + }; + if !self.should_write_global(ret.to_string()) { + return Ok(ret); } - self.require_internal_export("__wbindgen_malloc")?; self.expose_wasm_vector_len(); self.global(&format!( " - function {}(arg) {{ - const ptr = wasm.__wbindgen_malloc(arg.length * {size}); + function {}(arg, malloc) {{ + const ptr = malloc(arg.length * {size}); {}().set(arg, ptr / {size}); WASM_VECTOR_LEN = arg.length; return ptr; }} ", - name, - delegate, + view, + view, size = size )); - Ok(()) + Ok(ret) } fn expose_text_encoder(&mut self) -> Result<(), Error> { @@ -1120,12 +1133,17 @@ impl<'a> Context<'a> { Ok(()) } - fn expose_get_string_from_wasm(&mut self) -> Result<(), Error> { - if !self.should_write_global("get_string_from_wasm") { - return Ok(()); - } + fn expose_get_string_from_wasm(&mut self, memory: MemoryId) -> Result { self.expose_text_decoder()?; - self.expose_uint8_memory(); + let mem = self.expose_uint8_memory(memory); + let ret = MemView { + name: "getStringFromWasm", + num: mem.num, + }; + + if !self.should_write_global(ret.to_string()) { + return Ok(ret); + } // Typically we try to give a raw view of memory out to `TextDecoder` to // avoid copying too much data. If, however, a `SharedArrayBuffer` is @@ -1135,27 +1153,31 @@ impl<'a> Context<'a> { // creates just a view. That way in shared mode we copy more data but in // non-shared mode there's no need to copy the data except for the // string itself. - let is_shared = self.module.memories.get(self.memory).shared; + let is_shared = self.module.memories.get(memory).shared; let method = if is_shared { "slice" } else { "subarray" }; self.global(&format!( " - function getStringFromWasm(ptr, len) {{ - return cachedTextDecoder.decode(getUint8Memory().{}(ptr, ptr + len)); + function {}(ptr, len) {{ + return cachedTextDecoder.decode({}().{}(ptr, ptr + len)); }} - ", - method + ", + ret, mem, method )); - Ok(()) + Ok(ret) } - fn expose_get_cached_string_from_wasm(&mut self) -> Result<(), Error> { - if !self.should_write_global("get_cached_string_from_wasm") { - return Ok(()); - } - + fn expose_get_cached_string_from_wasm(&mut self, memory: MemoryId) -> Result { self.expose_get_object(); - self.expose_get_string_from_wasm()?; + let get = self.expose_get_string_from_wasm(memory)?; + let ret = MemView { + name: "getCachedStringFromWasm", + num: get.num, + }; + + if !self.should_write_global(ret.to_string()) { + return Ok(ret); + } // This has support for both `&str` and `Option<&str>`. // @@ -1165,118 +1187,129 @@ impl<'a> Context<'a> { // // If `ptr` and `len` are both `0` then that means it's `None`, in that case we rely upon // the fact that `getObject(0)` is guaranteed to be `undefined`. - self.global( + self.global(&format!( " - function getCachedStringFromWasm(ptr, len) { - if (ptr === 0) { + function {}(ptr, len) {{ + if (ptr === 0) {{ return getObject(len); - } else { - return getStringFromWasm(ptr, len); - } - } - ", - ); - Ok(()) + }} else {{ + return {}(ptr, len); + }} + }} + ", + ret, get, + )); + Ok(ret) } - fn expose_get_array_js_value_from_wasm(&mut self) -> Result<(), Error> { - if !self.should_write_global("get_array_js_value_from_wasm") { - return Ok(()); + fn expose_get_array_js_value_from_wasm(&mut self, memory: MemoryId) -> Result { + let mem = self.expose_uint32_memory(memory); + let ret = MemView { + name: "getArrayJsValueFromWasm", + num: mem.num, + }; + if !self.should_write_global(ret.to_string()) { + return Ok(ret); } - self.expose_uint32_memory(); if self.config.anyref { - self.global( + self.global(&format!( " - function getArrayJsValueFromWasm(ptr, len) { - const mem = getUint32Memory(); + function {}(ptr, len) {{ + const mem = {}(); const slice = mem.subarray(ptr / 4, ptr / 4 + len); const result = []; - for (let i = 0; i < slice.length; i++) { + for (let i = 0; i < slice.length; i++) {{ result.push(wasm.__wbg_anyref_table.get(slice[i])); - } + }} wasm.__wbindgen_drop_anyref_slice(ptr, len); return result; - } + }} ", - ); + ret, mem, + )); self.require_internal_export("__wbindgen_drop_anyref_slice")?; } else { self.expose_take_object(); - self.global( + self.global(&format!( " - function getArrayJsValueFromWasm(ptr, len) { - const mem = getUint32Memory(); + function {}(ptr, len) {{ + const mem = {}(); const slice = mem.subarray(ptr / 4, ptr / 4 + len); const result = []; - for (let i = 0; i < slice.length; i++) { + for (let i = 0; i < slice.length; i++) {{ result.push(takeObject(slice[i])); - } + }} return result; - } + }} ", - ); + ret, mem, + )); } - Ok(()) + Ok(ret) } - fn expose_get_array_i8_from_wasm(&mut self) { - self.expose_int8_memory(); - self.arrayget("getArrayI8FromWasm", "getInt8Memory", 1); + fn expose_get_array_i8_from_wasm(&mut self, memory: MemoryId) -> MemView { + let view = self.expose_int8_memory(memory); + self.arrayget("getArrayI8FromWasm", view, 1) } - fn expose_get_array_u8_from_wasm(&mut self) { - self.expose_uint8_memory(); - self.arrayget("getArrayU8FromWasm", "getUint8Memory", 1); + fn expose_get_array_u8_from_wasm(&mut self, memory: MemoryId) -> MemView { + let view = self.expose_uint8_memory(memory); + self.arrayget("getArrayU8FromWasm", view, 1) } - fn expose_get_clamped_array_u8_from_wasm(&mut self) { - self.expose_clamped_uint8_memory(); - self.arrayget("getClampedArrayU8FromWasm", "getUint8ClampedMemory", 1); + fn expose_get_clamped_array_u8_from_wasm(&mut self, memory: MemoryId) -> MemView { + let view = self.expose_clamped_uint8_memory(memory); + self.arrayget("getClampedArrayU8FromWasm", view, 1) } - fn expose_get_array_i16_from_wasm(&mut self) { - self.expose_int16_memory(); - self.arrayget("getArrayI16FromWasm", "getInt16Memory", 2); + fn expose_get_array_i16_from_wasm(&mut self, memory: MemoryId) -> MemView { + let view = self.expose_int16_memory(memory); + self.arrayget("getArrayI16FromWasm", view, 2) } - fn expose_get_array_u16_from_wasm(&mut self) { - self.expose_uint16_memory(); - self.arrayget("getArrayU16FromWasm", "getUint16Memory", 2); + fn expose_get_array_u16_from_wasm(&mut self, memory: MemoryId) -> MemView { + let view = self.expose_uint16_memory(memory); + self.arrayget("getArrayU16FromWasm", view, 2) } - fn expose_get_array_i32_from_wasm(&mut self) { - self.expose_int32_memory(); - self.arrayget("getArrayI32FromWasm", "getInt32Memory", 4); + fn expose_get_array_i32_from_wasm(&mut self, memory: MemoryId) -> MemView { + let view = self.expose_int32_memory(memory); + self.arrayget("getArrayI32FromWasm", view, 4) } - fn expose_get_array_u32_from_wasm(&mut self) { - self.expose_uint32_memory(); - self.arrayget("getArrayU32FromWasm", "getUint32Memory", 4); + fn expose_get_array_u32_from_wasm(&mut self, memory: MemoryId) -> MemView { + let view = self.expose_uint32_memory(memory); + self.arrayget("getArrayU32FromWasm", view, 4) } - fn expose_get_array_i64_from_wasm(&mut self) { - self.expose_int64_memory(); - self.arrayget("getArrayI64FromWasm", "getInt64Memory", 8); + fn expose_get_array_i64_from_wasm(&mut self, memory: MemoryId) -> MemView { + let view = self.expose_int64_memory(memory); + self.arrayget("getArrayI64FromWasm", view, 8) } - fn expose_get_array_u64_from_wasm(&mut self) { - self.expose_uint64_memory(); - self.arrayget("getArrayU64FromWasm", "getUint64Memory", 8); + fn expose_get_array_u64_from_wasm(&mut self, memory: MemoryId) -> MemView { + let view = self.expose_uint64_memory(memory); + self.arrayget("getArrayU64FromWasm", view, 8) } - fn expose_get_array_f32_from_wasm(&mut self) { - self.expose_f32_memory(); - self.arrayget("getArrayF32FromWasm", "getFloat32Memory", 4); + fn expose_get_array_f32_from_wasm(&mut self, memory: MemoryId) -> MemView { + let view = self.expose_f32_memory(memory); + self.arrayget("getArrayF32FromWasm", view, 4) } - fn expose_get_array_f64_from_wasm(&mut self) { - self.expose_f64_memory(); - self.arrayget("getArrayF64FromWasm", "getFloat64Memory", 8); + fn expose_get_array_f64_from_wasm(&mut self, memory: MemoryId) -> MemView { + let view = self.expose_f64_memory(memory); + self.arrayget("getArrayF64FromWasm", view, 8) } - fn arrayget(&mut self, name: &'static str, mem: &'static str, size: usize) { + fn arrayget(&mut self, name: &'static str, view: MemView, size: usize) -> MemView { + let ret = MemView { + name, + num: view.num, + }; if !self.should_write_global(name) { - return; + return ret; } self.global(&format!( " @@ -1285,121 +1318,86 @@ impl<'a> Context<'a> { }} ", name = name, - mem = mem, + mem = view, size = size, )); + return ret; } - fn expose_node_buffer_memory(&mut self) { - self.memview("getNodeBufferMemory", "Buffer.from"); + fn expose_node_buffer_memory(&mut self, memory: MemoryId) -> MemView { + self.memview("getNodeBufferMemory", "Buffer.from", memory) } - fn expose_int8_memory(&mut self) { - self.memview("getInt8Memory", "new Int8Array"); + fn expose_int8_memory(&mut self, memory: MemoryId) -> MemView { + self.memview("getInt8Memory", "new Int8Array", memory) } - fn expose_uint8_memory(&mut self) { - self.memview("getUint8Memory", "new Uint8Array"); + fn expose_uint8_memory(&mut self, memory: MemoryId) -> MemView { + self.memview("getUint8Memory", "new Uint8Array", memory) } - fn expose_clamped_uint8_memory(&mut self) { - self.memview("getUint8ClampedMemory", "new Uint8ClampedArray"); + fn expose_clamped_uint8_memory(&mut self, memory: MemoryId) -> MemView { + self.memview("getUint8ClampedMemory", "new Uint8ClampedArray", memory) } - fn expose_int16_memory(&mut self) { - self.memview("getInt16Memory", "new Int16Array"); + fn expose_int16_memory(&mut self, memory: MemoryId) -> MemView { + self.memview("getInt16Memory", "new Int16Array", memory) } - fn expose_uint16_memory(&mut self) { - self.memview("getUint16Memory", "new Uint16Array"); + fn expose_uint16_memory(&mut self, memory: MemoryId) -> MemView { + self.memview("getUint16Memory", "new Uint16Array", memory) } - fn expose_int32_memory(&mut self) { - self.memview("getInt32Memory", "new Int32Array"); + fn expose_int32_memory(&mut self, memory: MemoryId) -> MemView { + self.memview("getInt32Memory", "new Int32Array", memory) } - fn expose_uint32_memory(&mut self) { - self.memview("getUint32Memory", "new Uint32Array"); + fn expose_uint32_memory(&mut self, memory: MemoryId) -> MemView { + self.memview("getUint32Memory", "new Uint32Array", memory) } - fn expose_int64_memory(&mut self) { - self.memview("getInt64Memory", "new BigInt64Array"); + fn expose_int64_memory(&mut self, memory: MemoryId) -> MemView { + self.memview("getInt64Memory", "new BigInt64Array", memory) } - fn expose_uint64_memory(&mut self) { - self.memview("getUint64Memory", "new BigUint64Array"); + fn expose_uint64_memory(&mut self, memory: MemoryId) -> MemView { + self.memview("getUint64Memory", "new BigUint64Array", memory) } - fn expose_f32_memory(&mut self) { - self.memview("getFloat32Memory", "new Float32Array"); + fn expose_f32_memory(&mut self, memory: MemoryId) -> MemView { + self.memview("getFloat32Memory", "new Float32Array", memory) } - fn expose_f64_memory(&mut self) { - self.memview("getFloat64Memory", "new Float64Array"); + fn expose_f64_memory(&mut self, memory: MemoryId) -> MemView { + self.memview("getFloat64Memory", "new Float64Array", memory) } - fn memview_function(&mut self, t: VectorKind) -> &'static str { + fn memview_function(&mut self, t: VectorKind, memory: MemoryId) -> MemView { match t { - VectorKind::String => { - self.expose_uint8_memory(); - "getUint8Memory" - } - VectorKind::I8 => { - self.expose_int8_memory(); - "getInt8Memory" - } - VectorKind::U8 => { - self.expose_uint8_memory(); - "getUint8Memory" - } - VectorKind::ClampedU8 => { - self.expose_clamped_uint8_memory(); - "getUint8ClampedMemory" - } - VectorKind::I16 => { - self.expose_int16_memory(); - "getInt16Memory" - } - VectorKind::U16 => { - self.expose_uint16_memory(); - "getUint16Memory" - } - VectorKind::I32 => { - self.expose_int32_memory(); - "getInt32Memory" - } - VectorKind::U32 => { - self.expose_uint32_memory(); - "getUint32Memory" - } - VectorKind::I64 => { - self.expose_int64_memory(); - "getInt64Memory" - } - VectorKind::U64 => { - self.expose_uint64_memory(); - "getUint64Memory" - } - VectorKind::F32 => { - self.expose_f32_memory(); - "getFloat32Memory" - } - VectorKind::F64 => { - self.expose_f64_memory(); - "getFloat64Memory" - } - VectorKind::Anyref => { - self.expose_uint32_memory(); - "getUint32Memory" - } - } - } - - fn memview(&mut self, name: &'static str, js: &str) { - if !self.should_write_global(name) { - return; - } - let mem = self.memory(); + VectorKind::String => self.expose_uint8_memory(memory), + VectorKind::I8 => self.expose_int8_memory(memory), + VectorKind::U8 => self.expose_uint8_memory(memory), + VectorKind::ClampedU8 => self.expose_clamped_uint8_memory(memory), + VectorKind::I16 => self.expose_int16_memory(memory), + VectorKind::U16 => self.expose_uint16_memory(memory), + VectorKind::I32 => self.expose_int32_memory(memory), + VectorKind::U32 => self.expose_uint32_memory(memory), + VectorKind::I64 => self.expose_int64_memory(memory), + VectorKind::U64 => self.expose_uint64_memory(memory), + VectorKind::F32 => self.expose_f32_memory(memory), + VectorKind::F64 => self.expose_f64_memory(memory), + VectorKind::Anyref => self.expose_uint32_memory(memory), + } + } + + fn memview(&mut self, name: &'static str, js: &str, memory: walrus::MemoryId) -> MemView { + let next = self.memory_indices.len(); + let num = *self.memory_indices.entry(memory).or_insert(next); + let view = MemView { name, num }; + if !self.should_write_global(name.to_string()) { + return view; + } + let mem = self.export_name_of(memory); self.global(&format!( " let cache{name} = null; @@ -1410,10 +1408,11 @@ impl<'a> Context<'a> { return cache{name}; }} ", - name = name, + name = view, js = js, mem = mem, )); + return view; } fn expose_assert_class(&mut self) { @@ -1566,98 +1565,40 @@ impl<'a> Context<'a> { ); } - fn pass_to_wasm_function(&mut self, t: VectorKind) -> Result<&'static str, Error> { - let s = match t { - VectorKind::String => { - self.expose_pass_string_to_wasm()?; - "passStringToWasm" - } + fn pass_to_wasm_function(&mut self, t: VectorKind, memory: MemoryId) -> Result { + match t { + VectorKind::String => self.expose_pass_string_to_wasm(memory), VectorKind::I8 | VectorKind::U8 | VectorKind::ClampedU8 => { - self.expose_pass_array8_to_wasm()?; - "passArray8ToWasm" - } - VectorKind::U16 | VectorKind::I16 => { - self.expose_pass_array16_to_wasm()?; - "passArray16ToWasm" - } - VectorKind::I32 | VectorKind::U32 => { - self.expose_pass_array32_to_wasm()?; - "passArray32ToWasm" + self.expose_pass_array8_to_wasm(memory) } - VectorKind::I64 | VectorKind::U64 => { - self.expose_pass_array64_to_wasm()?; - "passArray64ToWasm" - } - VectorKind::F32 => { - self.expose_pass_array_f32_to_wasm()?; - "passArrayF32ToWasm" - } - VectorKind::F64 => { - self.expose_pass_array_f64_to_wasm()?; - "passArrayF64ToWasm" - } - VectorKind::Anyref => { - self.expose_pass_array_jsvalue_to_wasm()?; - "passArrayJsValueToWasm" - } - }; - Ok(s) + VectorKind::U16 | VectorKind::I16 => self.expose_pass_array16_to_wasm(memory), + VectorKind::I32 | VectorKind::U32 => self.expose_pass_array32_to_wasm(memory), + VectorKind::I64 | VectorKind::U64 => self.expose_pass_array64_to_wasm(memory), + VectorKind::F32 => self.expose_pass_array_f32_to_wasm(memory), + VectorKind::F64 => self.expose_pass_array_f64_to_wasm(memory), + VectorKind::Anyref => self.expose_pass_array_jsvalue_to_wasm(memory), + } } - fn expose_get_vector_from_wasm(&mut self, ty: VectorKind) -> Result<&'static str, Error> { + fn expose_get_vector_from_wasm( + &mut self, + ty: VectorKind, + memory: MemoryId, + ) -> Result { Ok(match ty { - VectorKind::String => { - self.expose_get_string_from_wasm()?; - "getStringFromWasm" - } - VectorKind::I8 => { - self.expose_get_array_i8_from_wasm(); - "getArrayI8FromWasm" - } - VectorKind::U8 => { - self.expose_get_array_u8_from_wasm(); - "getArrayU8FromWasm" - } - VectorKind::ClampedU8 => { - self.expose_get_clamped_array_u8_from_wasm(); - "getClampedArrayU8FromWasm" - } - VectorKind::I16 => { - self.expose_get_array_i16_from_wasm(); - "getArrayI16FromWasm" - } - VectorKind::U16 => { - self.expose_get_array_u16_from_wasm(); - "getArrayU16FromWasm" - } - VectorKind::I32 => { - self.expose_get_array_i32_from_wasm(); - "getArrayI32FromWasm" - } - VectorKind::U32 => { - self.expose_get_array_u32_from_wasm(); - "getArrayU32FromWasm" - } - VectorKind::I64 => { - self.expose_get_array_i64_from_wasm(); - "getArrayI64FromWasm" - } - VectorKind::U64 => { - self.expose_get_array_u64_from_wasm(); - "getArrayU64FromWasm" - } - VectorKind::F32 => { - self.expose_get_array_f32_from_wasm(); - "getArrayF32FromWasm" - } - VectorKind::F64 => { - self.expose_get_array_f64_from_wasm(); - "getArrayF64FromWasm" - } - VectorKind::Anyref => { - self.expose_get_array_js_value_from_wasm()?; - "getArrayJsValueFromWasm" - } + VectorKind::String => self.expose_get_string_from_wasm(memory)?, + VectorKind::I8 => self.expose_get_array_i8_from_wasm(memory), + VectorKind::U8 => self.expose_get_array_u8_from_wasm(memory), + VectorKind::ClampedU8 => self.expose_get_clamped_array_u8_from_wasm(memory), + VectorKind::I16 => self.expose_get_array_i16_from_wasm(memory), + VectorKind::U16 => self.expose_get_array_u16_from_wasm(memory), + VectorKind::I32 => self.expose_get_array_i32_from_wasm(memory), + VectorKind::U32 => self.expose_get_array_u32_from_wasm(memory), + VectorKind::I64 => self.expose_get_array_i64_from_wasm(memory), + VectorKind::U64 => self.expose_get_array_u64_from_wasm(memory), + VectorKind::F32 => self.expose_get_array_f32_from_wasm(memory), + VectorKind::F64 => self.expose_get_array_f64_from_wasm(memory), + VectorKind::Anyref => self.expose_get_array_js_value_from_wasm(memory)?, }) } @@ -1748,14 +1689,6 @@ impl<'a> Context<'a> { self.globals.push_str("\n"); } - fn memory(&mut self) -> &'static str { - if self.module.memories.get(self.memory).import.is_some() { - "memory" - } else { - "wasm.memory" - } - } - fn require_class_wrap(&mut self, name: &str) { require_class(&mut self.exported_classes, name).wrap_needed = true; } @@ -1886,14 +1819,12 @@ impl<'a> Context<'a> { } pub fn generate(&mut self) -> Result<(), Error> { - for (id, adapter) in self.wit.adapters.iter() { - if let Some(export) = self.aux.export_map.get(id) { - self.generate_export(export, adapter)?; - continue; - } - if let Some(import) = self.aux.import_map.get(id) { - } - // self.generate_adapter(id, adapter); + for (id, adapter) in sorted_iter(&self.wit.adapters) { + let instrs = match &adapter.kind { + AdapterKind::Import { .. } => continue, + AdapterKind::Local { instructions } => instructions, + }; + self.generate_adapter(*id, adapter, instrs)?; } // for (i, (idx, binding)) in bindings.elems.iter().enumerate() { // self.generate_elem_binding(i, *idx, binding, bindings)?; @@ -1972,73 +1903,123 @@ impl<'a> Context<'a> { // Ok(()) // } - fn generate_export(&mut self, export: &AuxExport, adapter: &Adapter) -> Result<(), Error> { - // let wasm_name = self.module.exports.get(id).name.clone(); - // let binding = &bindings.exports[&id]; - // let webidl = bindings - // .types - // .get::(binding.webidl_ty) - // .unwrap(); + fn generate_adapter( + &mut self, + id: AdapterId, + adapter: &Adapter, + instrs: &[InstructionData], + ) -> Result<(), Error> { + enum Kind<'a> { + Import(&'a AuxImport), + Export(&'a AuxExport), + Other, + } + + let mut disable_log_error = true; + let kind = match self.aux.export_map.get(&id) { + Some(export) => Kind::Export(export), + None => match self.aux.import_map.get(&id) { + Some(import) => { + if true { + panic!() + } + disable_log_error = self.import_never_log_error(import); + Kind::Import(import) + } + None => Kind::Other, + }, + }; // Construct a JS shim builder, and configure it based on the kind of // export that we're generating. let mut builder = binding::Builder::new(self); - builder.disable_log_error(true); - match &export.kind { - AuxExportKind::Function(_) => {} - AuxExportKind::StaticFunction { .. } => {} - AuxExportKind::Constructor(class) => builder.constructor(class), - AuxExportKind::Getter { .. } | AuxExportKind::Setter { .. } => builder.method(false), - AuxExportKind::Method { consumed, .. } => builder.method(*consumed), + builder.disable_log_error(disable_log_error); + builder.catch(builder.cx.aux.imports_with_catch.contains(&id))?; + let mut arg_names = &None; + match kind { + Kind::Export(export) => { + arg_names = &export.arg_names; + match &export.kind { + AuxExportKind::Function(_) => {} + AuxExportKind::StaticFunction { .. } => {} + AuxExportKind::Constructor(class) => builder.constructor(class), + AuxExportKind::Getter { .. } | AuxExportKind::Setter { .. } => { + builder.method(false) + } + AuxExportKind::Method { consumed, .. } => builder.method(*consumed), + } + } + Kind::Import(import) => {} + Kind::Other => {} } // Process the `binding` and generate a bunch of JS/TypeScript/etc. - let js = builder.process( - &adapter, - &export.arg_names, - // &mut |_, _, args| Ok(format!("wasm.{}({})", wasm_name, args.join(", "))), - )?; - let js = String::new(); + let js = builder + .process(&adapter, instrs, arg_names) + .with_context(|| match kind { + Kind::Export(e) => format!("failed to generate bindings for `{}`", e.debug_name), + Kind::Import(i) => format!("failed to generate bindings for import {:?}", i), + Kind::Other => format!("failed to generates bindings for adapter"), + })?; let ts = builder.typescript_signature(); let js_doc = builder.js_doc_comments(); - let docs = format_doc_comments(&export.comments, Some(js_doc)); // Once we've got all the JS then put it in the right location depending // on what's being exported. - match &export.kind { - AuxExportKind::Function(name) => { - self.export(&name, &format!("function{}", js), Some(docs))?; - self.globals.push_str("\n"); - self.typescript.push_str("export function "); - self.typescript.push_str(&name); - self.typescript.push_str(&ts); - self.typescript.push_str(";\n"); - } - AuxExportKind::Constructor(class) => { - let exported = require_class(&mut self.exported_classes, class); - if exported.has_constructor { - bail!("found duplicate constructor for class `{}`", class); + match kind { + Kind::Export(export) => { + let docs = format_doc_comments(&export.comments, Some(js_doc)); + match &export.kind { + AuxExportKind::Function(name) => { + self.export(&name, &format!("function{}", js), Some(docs))?; + self.globals.push_str("\n"); + self.typescript.push_str("export function "); + self.typescript.push_str(&name); + self.typescript.push_str(&ts); + self.typescript.push_str(";\n"); + } + AuxExportKind::Constructor(class) => { + let exported = require_class(&mut self.exported_classes, class); + if exported.has_constructor { + bail!("found duplicate constructor for class `{}`", class); + } + exported.has_constructor = true; + exported.push(&docs, "constructor", "", &js, &ts); + } + AuxExportKind::Getter { class, field } => { + let ret_ty = builder.ts_ret.as_ref().unwrap().ty.clone(); + let exported = require_class(&mut self.exported_classes, class); + exported.push_getter(&docs, field, &js, &ret_ty); + } + AuxExportKind::Setter { class, field } => { + let arg_ty = builder.ts_args[0].ty.clone(); + let exported = require_class(&mut self.exported_classes, class); + exported.push_setter(&docs, field, &js, &arg_ty); + } + AuxExportKind::StaticFunction { class, name } => { + let exported = require_class(&mut self.exported_classes, class); + exported.push(&docs, name, "static ", &js, &ts); + } + AuxExportKind::Method { class, name, .. } => { + let exported = require_class(&mut self.exported_classes, class); + exported.push(&docs, name, "", &js, &ts); + } } - exported.has_constructor = true; - exported.push(&docs, "constructor", "", &js, &ts); - } - AuxExportKind::Getter { class, field } => { - let ret_ty = builder.ts_ret.as_ref().unwrap().ty.clone(); - let exported = require_class(&mut self.exported_classes, class); - exported.push_getter(&docs, field, &js, &ret_ty); - } - AuxExportKind::Setter { class, field } => { - let arg_ty = builder.ts_args[0].ty.clone(); - let exported = require_class(&mut self.exported_classes, class); - exported.push_setter(&docs, field, &js, &arg_ty); } - AuxExportKind::StaticFunction { class, name } => { - let exported = require_class(&mut self.exported_classes, class); - exported.push(&docs, name, "static ", &js, &ts); - } - AuxExportKind::Method { class, name, .. } => { - let exported = require_class(&mut self.exported_classes, class); - exported.push(&docs, name, "", &js, &ts); + Kind::Import(_) => {} + Kind::Other => { + let core = self.wit.implements.iter().find(|pair| pair.1 == id); + match core { + Some((core, _)) => { + self.wasm_import_definitions + .insert(*core, format!("function{}", js)); + } + None => { + self.globals.push_str("function "); + self.globals.push_str(&self.adapter_name(id)); + self.globals.push_str(&js); + } + } } } Ok(()) @@ -2053,11 +2034,6 @@ impl<'a> Context<'a> { // catch: bool, // assert_no_shim: bool, // ) -> Result<(), Error> { - // let binding = &bindings.imports[&id]; - // let webidl = bindings - // .types - // .get::(binding.webidl_ty) - // .unwrap(); // match import { // AuxImport::Value(AuxValue::Bare(js)) // if !variadic && !catch && self.import_does_not_require_glue(binding, webidl) => @@ -2089,8 +2065,6 @@ impl<'a> Context<'a> { // cx.invoke_import(&binding, import, bindings, args, variadic, prelude) // }, // )?; - // self.wasm_import_definitions - // .insert(id, format!("function{}", js)); // Ok(()) // } // } @@ -2320,8 +2294,7 @@ impl<'a> Context<'a> { self.export_function_table()?; let dtor = format!("wasm.__wbg_function_table.get({})", dtor); - // TODO: refactor this name generation to be somewhere else - let call = format!("__wbg_adapter_{}", adapter.0); + let call = self.adapter_name(*adapter); if *mutable { // For mutable closures they can't be invoked recursively. @@ -2569,26 +2542,15 @@ impl<'a> Context<'a> { } Intrinsic::NumberGet => { - assert_eq!(args.len(), 2); - self.expose_uint8_memory(); + assert_eq!(args.len(), 1); prelude.push_str(&format!("const obj = {};\n", args[0])); - prelude.push_str("if (typeof(obj) === 'number') return obj;\n"); - prelude.push_str(&format!("getUint8Memory()[{}] = 1;\n", args[1])); - "0".to_string() + format!("typeof(obj) === 'number' ? obj : undefined") } Intrinsic::StringGet => { - self.expose_pass_string_to_wasm()?; - self.expose_uint32_memory(); - assert_eq!(args.len(), 2); + assert_eq!(args.len(), 1); prelude.push_str(&format!("const obj = {};\n", args[0])); - prelude.push_str("if (typeof(obj) !== 'string') return 0;\n"); - prelude.push_str("const ptr = passStringToWasm(obj);\n"); - prelude.push_str(&format!( - "getUint32Memory()[{} / 4] = WASM_VECTOR_LEN;\n", - args[1], - )); - "ptr".to_string() + format!("typeof(obj) === 'string' ? obj : undefined") } Intrinsic::BooleanGet => { @@ -2620,7 +2582,19 @@ impl<'a> Context<'a> { Intrinsic::Memory => { assert_eq!(args.len(), 0); - self.memory().to_string() + let mut memories = self.module.memories.iter(); + let memory = memories + .next() + .ok_or_else(|| anyhow!("no memory found to return in memory intrinsic"))? + .id(); + if memories.next().is_some() { + bail!( + "multiple memories found, unsure which to return \ + from memory intrinsic" + ); + } + drop(memories); + format!("wasm.{}", self.export_name_of(memory)) } Intrinsic::FunctionTable => { @@ -2901,6 +2875,10 @@ impl<'a> Context<'a> { self.module.exports.add(&name, id); return name; } + + fn adapter_name(&self, id: AdapterId) -> String { + format!("__wbg_adapter_{}", id.0) + } } fn check_duplicated_getter_and_setter_names( @@ -3066,3 +3044,14 @@ fn test_generate_identifier() { "default2".to_string() ); } + +struct MemView { + name: &'static str, + num: usize, +} + +impl fmt::Display for MemView { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}{}", self.name, self.num) + } +} diff --git a/crates/cli-support/src/js/outgoing.rs b/crates/cli-support/src/js/outgoing.rs deleted file mode 100644 index eceece77e05..00000000000 --- a/crates/cli-support/src/js/outgoing.rs +++ /dev/null @@ -1,451 +0,0 @@ -//! Implementation of translating a `NonstandardOutgoing` expression to an -//! actual JS shim and code snippet which ensures that bindings behave as we'd -//! expect. - -use crate::descriptor::VectorKind; -use crate::js::binding::JsBuilder; -use crate::js::Context; -use crate::webidl::NonstandardOutgoing; -use anyhow::{bail, Error}; -use wasm_webidl_bindings::ast; - -pub struct Outgoing<'a, 'b> { - cx: &'a mut Context<'b>, - js: &'a mut JsBuilder, -} - -impl<'a, 'b> Outgoing<'a, 'b> { - pub fn new(cx: &'a mut Context<'b>, js: &'a mut JsBuilder) -> Outgoing<'a, 'b> { - Outgoing { cx, js } - } - - pub fn process(&mut self, outgoing: &NonstandardOutgoing) -> Result { - let before = self.js.typescript_len(); - let ret = self.nonstandard(outgoing)?; - assert_eq!(before + 1, self.js.typescript_len()); - Ok(ret) - } - - fn nonstandard(&mut self, outgoing: &NonstandardOutgoing) -> Result { - match outgoing { - NonstandardOutgoing::Standard(expr) => self.standard(expr), - - // Converts the wasm argument, a single code unit, to a string. - NonstandardOutgoing::Char { idx } => { - self.js.typescript_required("string"); - Ok(format!("String.fromCodePoint({})", self.arg(*idx))) - } - - // Just need to wrap up the pointer we get from Rust into a JS type - // and then we can pass that along - NonstandardOutgoing::RustType { class, idx } => { - self.js.typescript_required(class); - self.cx.require_class_wrap(class); - Ok(format!("{}.__wrap({})", class, self.arg(*idx))) - } - - // Just a small wrapper around `getObject` - NonstandardOutgoing::BorrowedAnyref { idx } => { - self.js.typescript_required("any"); - self.cx.expose_get_object(); - Ok(format!("getObject({})", self.arg(*idx))) - } - - // given the low/high bits we get from Rust, store them into a - // temporary 64-bit conversion array and then load the BigInt out of - // it. - NonstandardOutgoing::Number64 { - lo_idx, - hi_idx, - signed, - } => { - self.js.typescript_required("BigInt"); - let f = if *signed { - self.cx.expose_int64_cvt_shim() - } else { - self.cx.expose_uint64_cvt_shim() - }; - let i = self.js.tmp(); - self.js.prelude(&format!( - "\ - u32CvtShim[0] = {low}; - u32CvtShim[1] = {high}; - const n{i} = {f}[0]; - ", - low = self.arg(*lo_idx), - high = self.arg(*hi_idx), - f = f, - i = i, - )); - Ok(format!("n{}", i)) - } - - // Similar to `View` below, except using 64-bit types which don't - // fit into webidl scalar types right now. - NonstandardOutgoing::View64 { - offset, - length, - signed, - } => { - let ptr = self.arg(*offset); - let len = self.arg(*length); - let kind = if *signed { - VectorKind::I64 - } else { - VectorKind::U64 - }; - self.js.typescript_required(kind.js_ty()); - let f = self.cx.expose_get_vector_from_wasm(kind)?; - Ok(format!("{}({}, {})", f, ptr, len)) - } - - // Similar to `View` below, except using anyref types which have - // fancy conversion functions on our end. - NonstandardOutgoing::ViewAnyref { offset, length } => { - let ptr = self.arg(*offset); - let len = self.arg(*length); - self.js.typescript_required(VectorKind::Anyref.js_ty()); - let f = self.cx.expose_get_vector_from_wasm(VectorKind::Anyref)?; - Ok(format!("{}({}, {})", f, ptr, len)) - } - - // Similar to `View` below, except we free the memory in JS right - // now. - // - // TODO: we should free the memory in Rust to allow using standard - // webidl bindings. - NonstandardOutgoing::Vector { - offset, - length, - kind, - } => { - let ptr = self.arg(*offset); - let len = self.arg(*length); - self.js.typescript_required(kind.js_ty()); - let f = self.cx.expose_get_vector_from_wasm(*kind)?; - let i = self.js.tmp(); - self.js - .prelude(&format!("const v{} = {}({}, {}).slice();", i, f, ptr, len)); - self.prelude_free_vector(*offset, *length, *kind)?; - Ok(format!("v{}", i)) - } - - NonstandardOutgoing::CachedString { - offset, - length, - owned, - optional, - } => { - let ptr = self.arg(*offset); - let len = self.arg(*length); - let tmp = self.js.tmp(); - - if *optional { - self.js.typescript_optional("string"); - } else { - self.js.typescript_required("string"); - } - - self.cx.expose_get_cached_string_from_wasm()?; - - self.js.prelude(&format!( - "const v{} = getCachedStringFromWasm({}, {});", - tmp, ptr, len - )); - - if *owned { - self.prelude_free_cached_string(&ptr, &len)?; - } - - Ok(format!("v{}", tmp)) - } - - NonstandardOutgoing::StackClosure { - a, - b, - binding_idx, - nargs, - mutable, - } => { - self.js.typescript_optional("any"); - let i = self.js.tmp(); - self.js.prelude(&format!( - "const state{} = {{a: {}, b: {}}};", - i, - self.arg(*a), - self.arg(*b), - )); - let args = (0..*nargs) - .map(|i| format!("arg{}", i)) - .collect::>() - .join(", "); - if *mutable { - // Mutable closures need protection against being called - // recursively, so ensure that we clear out one of the - // internal pointers while it's being invoked. - self.js.prelude(&format!( - "const cb{i} = ({args}) => {{ - const a = state{i}.a; - state{i}.a = 0; - try {{ - return __wbg_elem_binding{idx}(a, state{i}.b, {args}); - }} finally {{ - state{i}.a = a; - }} - }};", - i = i, - args = args, - idx = binding_idx, - )); - } else { - self.js.prelude(&format!( - "const cb{i} = ({args}) => __wbg_elem_binding{idx}(state{i}.a, state{i}.b, {args});", - i = i, - args = args, - idx = binding_idx, - )); - } - - // Make sure to null out our internal pointers when we return - // back to Rust to ensure that any lingering references to the - // closure will fail immediately due to null pointers passed in - // to Rust. - self.js.finally(&format!("state{}.a = state{0}.b = 0;", i)); - Ok(format!("cb{}", i)) - } - - NonstandardOutgoing::OptionBool { idx } => { - self.js.typescript_optional("boolean"); - Ok(format!( - "{0} === 0xFFFFFF ? undefined : {0} !== 0", - self.arg(*idx) - )) - } - - NonstandardOutgoing::OptionChar { idx } => { - self.js.typescript_optional("string"); - Ok(format!( - "{0} === 0xFFFFFF ? undefined : String.fromCodePoint({0})", - self.arg(*idx) - )) - } - - NonstandardOutgoing::OptionIntegerEnum { idx, hole } => { - self.js.typescript_optional("number"); - Ok(format!( - "{0} === {1} ? undefined : {0}", - self.arg(*idx), - hole - )) - } - - NonstandardOutgoing::OptionRustType { class, idx } => { - self.cx.require_class_wrap(class); - self.js.typescript_optional(class); - Ok(format!( - "{0} === 0 ? undefined : {1}.__wrap({0})", - self.arg(*idx), - class, - )) - } - - NonstandardOutgoing::OptionU32Sentinel { idx } => { - self.js.typescript_optional("number"); - Ok(format!( - "{0} === 0xFFFFFF ? undefined : {0}", - self.arg(*idx) - )) - } - - NonstandardOutgoing::OptionNative { - signed, - present, - val, - } => { - self.js.typescript_optional("number"); - Ok(format!( - "{} === 0 ? undefined : {}{}", - self.arg(*present), - self.arg(*val), - if *signed { "" } else { " >>> 0" }, - )) - } - - NonstandardOutgoing::OptionInt64 { - present, - _ignored, - lo, - hi, - signed, - } => { - self.js.typescript_optional("BigInt"); - let f = if *signed { - self.cx.expose_int64_cvt_shim() - } else { - self.cx.expose_uint64_cvt_shim() - }; - let i = self.js.tmp(); - self.js.prelude(&format!( - " - u32CvtShim[0] = {low}; - u32CvtShim[1] = {high}; - const n{i} = {present} === 0 ? undefined : {f}[0]; - ", - present = self.arg(*present), - low = self.arg(*lo), - high = self.arg(*hi), - f = f, - i = i, - )); - Ok(format!("n{}", i)) - } - - NonstandardOutgoing::OptionSlice { - kind, - offset, - length, - } => { - let ptr = self.arg(*offset); - let len = self.arg(*length); - self.js.typescript_optional(kind.js_ty()); - let f = self.cx.expose_get_vector_from_wasm(*kind)?; - Ok(format!( - "{ptr} === 0 ? undefined : {f}({ptr}, {len})", - ptr = ptr, - len = len, - f = f - )) - } - - NonstandardOutgoing::OptionVector { - offset, - length, - kind, - } => { - let ptr = self.arg(*offset); - let len = self.arg(*length); - self.js.typescript_optional(kind.js_ty()); - let f = self.cx.expose_get_vector_from_wasm(*kind)?; - let i = self.js.tmp(); - self.js.prelude(&format!("let v{};", i)); - self.js.prelude(&format!("if ({} !== 0) {{", ptr)); - self.js - .prelude(&format!("v{} = {}({}, {}).slice();", i, f, ptr, len)); - self.prelude_free_vector(*offset, *length, *kind)?; - self.js.prelude("}"); - Ok(format!("v{}", i)) - } - } - } - - /// Evaluates the `standard` binding expression, returning the JS expression - /// needed to evaluate the binding. - fn standard(&mut self, standard: &ast::OutgoingBindingExpression) -> Result { - match standard { - ast::OutgoingBindingExpression::As(expr) => match expr.ty { - ast::WebidlTypeRef::Scalar(ast::WebidlScalarType::Any) => { - self.js.typescript_required("any"); - if self.cx.config.anyref { - Ok(self.arg(expr.idx)) - } else { - self.cx.expose_take_object(); - Ok(format!("takeObject({})", self.arg(expr.idx))) - } - } - ast::WebidlTypeRef::Scalar(ast::WebidlScalarType::Boolean) => { - self.js.typescript_required("boolean"); - Ok(format!("{} !== 0", self.arg(expr.idx))) - } - ast::WebidlTypeRef::Scalar(ast::WebidlScalarType::UnsignedLong) => { - self.js.typescript_required("number"); - Ok(format!("{} >>> 0", self.arg(expr.idx))) - } - _ => { - self.js.typescript_required("number"); - Ok(self.arg(expr.idx)) - } - }, - ast::OutgoingBindingExpression::View(view) => { - // TODO: deduplicate with same match statement in incoming - // bindings - let scalar = match view.ty { - ast::WebidlTypeRef::Scalar(s) => s, - ast::WebidlTypeRef::Id(_) => { - bail!("unsupported type passed to `view` in webidl binding") - } - }; - let kind = match scalar { - ast::WebidlScalarType::Int8Array => VectorKind::I8, - ast::WebidlScalarType::Uint8Array => VectorKind::U8, - ast::WebidlScalarType::Uint8ClampedArray => VectorKind::ClampedU8, - ast::WebidlScalarType::Int16Array => VectorKind::I16, - ast::WebidlScalarType::Uint16Array => VectorKind::U16, - ast::WebidlScalarType::Int32Array => VectorKind::I32, - ast::WebidlScalarType::Uint32Array => VectorKind::U32, - ast::WebidlScalarType::Float32Array => VectorKind::F32, - ast::WebidlScalarType::Float64Array => VectorKind::F64, - _ => bail!("unsupported type passed to `view`: {:?}", scalar), - }; - self.js.typescript_required(kind.js_ty()); - let ptr = self.arg(view.offset); - let len = self.arg(view.length); - let f = self.cx.expose_get_vector_from_wasm(kind)?; - Ok(format!("{}({}, {})", f, ptr, len)) - } - - ast::OutgoingBindingExpression::Utf8Str(expr) => { - assert_eq!(expr.ty, ast::WebidlScalarType::DomString.into()); - self.js.typescript_required("string"); - let ptr = self.arg(expr.offset); - let len = self.arg(expr.length); - self.cx.expose_get_string_from_wasm()?; - Ok(format!("getStringFromWasm({}, {})", ptr, len)) - } - - ast::OutgoingBindingExpression::Utf8CStr(_) => { - bail!("unsupported `utf8-cstr` found in outgoing webidl bindings"); - } - ast::OutgoingBindingExpression::I32ToEnum(_) => { - bail!("unsupported `i32-to-enum` found in outgoing webidl bindings"); - } - ast::OutgoingBindingExpression::Copy(_) => { - bail!("unsupported `copy` found in outgoing webidl bindings"); - } - ast::OutgoingBindingExpression::Dict(_) => { - bail!("unsupported `dict` found in outgoing webidl bindings"); - } - ast::OutgoingBindingExpression::BindExport(_) => { - bail!("unsupported `bind-export` found in outgoing webidl bindings"); - } - } - } - - fn arg(&self, idx: u32) -> String { - self.js.arg(idx).to_string() - } - - fn prelude_free_vector( - &mut self, - offset: u32, - length: u32, - kind: VectorKind, - ) -> Result<(), Error> { - self.js.prelude(&format!( - "wasm.__wbindgen_free({0}, {1} * {size});", - self.arg(offset), - self.arg(length), - size = kind.size(), - )); - self.cx.require_internal_export("__wbindgen_free") - } - - fn prelude_free_cached_string(&mut self, ptr: &str, len: &str) -> Result<(), Error> { - self.js.prelude(&format!( - "if ({ptr} !== 0) {{ wasm.__wbindgen_free({ptr}, {len}); }}", - ptr = ptr, - len = len, - )); - - self.cx.require_internal_export("__wbindgen_free") - } -} diff --git a/crates/cli-support/src/wit/incoming.rs b/crates/cli-support/src/wit/incoming.rs index be9d0802751..f1da6eda79c 100644 --- a/crates/cli-support/src/wit/incoming.rs +++ b/crates/cli-support/src/wit/incoming.rs @@ -28,8 +28,17 @@ impl InstructionBuilder<'_, '_> { let input_before = self.input.len(); let output_before = self.output.len(); self._incoming(arg)?; - assert_eq!(output_before + 1, self.output.len()); - assert!(input_before < self.input.len()); + assert_eq!( + input_before + 1, + self.input.len(), + "didn't push an input {:?}", + arg + ); + assert!( + output_before < self.output.len(), + "didn't push more outputs {:?}", + arg + ); Ok(()) } @@ -173,16 +182,28 @@ impl InstructionBuilder<'_, '_> { arg ) })?; - self.instruction( - &[AdapterType::Vector(kind)], - Instruction::SliceToMemory { - kind, - malloc: self.cx.malloc()?, - mem: self.cx.memory()?, - mutable, - }, - &[AdapterType::I32, AdapterType::I32], - ); + if mutable { + self.instruction( + &[AdapterType::Vector(kind)], + Instruction::MutableSliceToMemory { + kind, + malloc: self.cx.malloc()?, + mem: self.cx.memory()?, + free: self.cx.free()?, + }, + &[AdapterType::I32, AdapterType::I32], + ); + } else { + self.instruction( + &[AdapterType::Vector(kind)], + Instruction::VectorToMemory { + kind, + malloc: self.cx.malloc()?, + mem: self.cx.memory()?, + }, + &[AdapterType::I32, AdapterType::I32], + ); + } } _ => bail!( "unsupported reference argument type for calling Rust function from JS: {:?}", @@ -262,11 +283,29 @@ impl InstructionBuilder<'_, '_> { arg ) })?; - self.instruction( - &[AdapterType::Anyref], - Instruction::OptionSlice { kind, mutable }, - &[AdapterType::I32; 2], - ); + drop((kind, mutable)); + bail!("unsupported slice"); + // let malloc = self.cx.malloc()?; + // let mem = self.cx.memory()?; + // if mutable { + // let free = self.cx.free()?; + // self.instruction( + // &[AdapterType::Anyref], + // Instruction::OptionMutableSlice { + // kind, + // malloc, + // mem, + // free, + // }, + // &[AdapterType::I32; 2], + // ); + // } else { + // self.instruction( + // &[AdapterType::Anyref], + // Instruction::OptionVector { kind, malloc, mem }, + // &[AdapterType::I32; 2], + // ); + // } } Descriptor::String | Descriptor::CachedString | Descriptor::Vector(_) => { @@ -276,9 +315,11 @@ impl InstructionBuilder<'_, '_> { arg ) })?; + let malloc = self.cx.malloc()?; + let mem = self.cx.memory()?; self.instruction( &[AdapterType::Anyref], - Instruction::OptionVector { kind }, + Instruction::OptionVector { kind, malloc, mem }, &[AdapterType::I32; 2], ); } @@ -323,6 +364,8 @@ impl InstructionBuilder<'_, '_> { for input in inputs { self.get(*input); } + } else { + self.input.extend_from_slice(inputs); } self.instructions.push(InstructionData { @@ -332,7 +375,6 @@ impl InstructionBuilder<'_, '_> { pushed: outputs.len(), }, }); - self.input.extend_from_slice(inputs); self.output.extend_from_slice(outputs); } diff --git a/crates/cli-support/src/wit/mod.rs b/crates/cli-support/src/wit/mod.rs index a03d787d8d7..24856ea7252 100644 --- a/crates/cli-support/src/wit/mod.rs +++ b/crates/cli-support/src/wit/mod.rs @@ -153,7 +153,7 @@ impl<'a> Context<'a> { arguments: vec![Descriptor::I32; 3], ret: Descriptor::Anyref, }; - self.import_adapter(id, signature, AdapterJsImportKind::Normal)?; + let id = self.import_adapter(id, signature, AdapterJsImportKind::Normal)?; // Synthesize the two integer pointers we pass through which // aren't present in the signature but are present in the wasm // signature. @@ -161,14 +161,14 @@ impl<'a> Context<'a> { let nargs = function.arguments.len(); function.arguments.insert(0, Descriptor::I32); function.arguments.insert(0, Descriptor::I32); - let id = self.table_element_adapter(descriptor.shim_idx, function)?; + let adapter = self.table_element_adapter(descriptor.shim_idx, function)?; self.aux.import_map.insert( id, AuxImport::Closure { dtor: descriptor.dtor_idx, mutable: descriptor.mutable, nargs, - adapter: id, + adapter, }, ); } @@ -984,7 +984,9 @@ impl<'a> Context<'a> { /// Perform a small verification pass over the module to perform some /// internal sanity checks. fn verify(&self) -> Result<(), Error> { - let mut imports_counted = 0; + // First up verify that all imports in the wasm module from our + // `$PLACEHOLDER_MODULE` are connected to an adapter via the + // `implements` section. let mut implemented = HashMap::new(); for (core, adapter) in self.adapters.implements.iter() { implemented.insert(core, adapter); @@ -997,34 +999,36 @@ impl<'a> Context<'a> { walrus::ImportKind::Function(_) => {} _ => bail!("import from `{}` was not a function", PLACEHOLDER_MODULE), } - let adapter = match implemented.remove(&import.id()) { - Some(id) => id, - None => { - bail!("import of `{}` doesn't have an adapter listed", import.name); - } - }; + if implemented.remove(&import.id()).is_none() { + bail!("import of `{}` doesn't have an adapter listed", import.name); + } + } + if implemented.len() != 0 { + bail!("more implementations listed than imports"); + } - // Ensure that everything imported from the `__wbindgen_placeholder__` - // module has a location listed as to where it's expected to be - // imported from. - if !self.aux.import_map.contains_key(&adapter) { + // Next up verify that all imported adapter functions have a listing of + // where they're imported from. + let mut imports_counted = 0; + for (id, adapter) in self.adapters.adapters.iter() { + let name = match &adapter.kind { + AdapterKind::Import { name, .. } => name, + AdapterKind::Local { .. } => continue, + }; + if !self.aux.import_map.contains_key(id) { bail!( "import of `{}` doesn't have an import map item listed", - import.name + name ); } imports_counted += 1; } - // Make sure there's no extraneous adapters that weren't actually // imported in the module. if self.aux.import_map.len() != imports_counted { bail!("import map is larger than the number of imports"); } - if implemented.len() != 0 { - bail!("more implementations listed than imports"); - } // Make sure the export map and export adapters map contain the same // number of entries. @@ -1102,9 +1106,10 @@ impl<'a> Context<'a> { // the stack into the wasm return pointer. Note that we iterate in reverse // here because the last result is the top value on the stack. let results = if uses_retptr { + let mem = args.cx.memory()?; for (i, ty) in ret.output.into_iter().enumerate().rev() { instructions.push(InstructionData { - instr: Instruction::StoreRetptr { offset: i, ty }, + instr: Instruction::StoreRetptr { offset: i, ty, mem }, stack_change: StackChange::Modified { pushed: 0, popped: 1, @@ -1120,7 +1125,7 @@ impl<'a> Context<'a> { .adapters .append(args.input, results, AdapterKind::Local { instructions }); args.cx.adapters.implements.push((import_id, id)); - Ok(id) + Ok(f) } /// Creates an adapter function for the `export` given to have the @@ -1184,9 +1189,10 @@ impl<'a> Context<'a> { stack_change: StackChange::Unknown, }); if uses_retptr { + let mem = ret.cx.memory()?; for (i, ty) in ret.input.into_iter().enumerate() { instructions.push(InstructionData { - instr: Instruction::LoadRetptr { offset: i, ty }, + instr: Instruction::LoadRetptr { offset: i, ty, mem }, stack_change: StackChange::Modified { pushed: 1, popped: 0, diff --git a/crates/cli-support/src/wit/nonstandard.rs b/crates/cli-support/src/wit/nonstandard.rs index 79901e2190c..b83f0f16635 100644 --- a/crates/cli-support/src/wit/nonstandard.rs +++ b/crates/cli-support/src/wit/nonstandard.rs @@ -170,8 +170,8 @@ pub enum AuxImport { /// This import is intended to manufacture a JS closure with the given /// signature and then return that back to Rust. Closure { - mutable: bool, // whether or not this was a `FnMut` closure - dtor: u32, // table element index of the destructor function + mutable: bool, // whether or not this was a `FnMut` closure + dtor: u32, // table element index of the destructor function adapter: AdapterId, // the adapter which translates the types for this closure nargs: usize, }, diff --git a/crates/cli-support/src/wit/outgoing.rs b/crates/cli-support/src/wit/outgoing.rs index cbd3e8d00b9..46f5967edf1 100644 --- a/crates/cli-support/src/wit/outgoing.rs +++ b/crates/cli-support/src/wit/outgoing.rs @@ -91,7 +91,7 @@ impl InstructionBuilder<'_, '_> { Descriptor::Ref(d) => self.outgoing_ref(false, d)?, Descriptor::RefMut(d) => self.outgoing_ref(true, d)?, - Descriptor::CachedString => self.cached_string(false, true), + Descriptor::CachedString => self.cached_string(false, true)?, Descriptor::String => { // fetch the ptr/length ... @@ -128,9 +128,11 @@ impl InstructionBuilder<'_, '_> { arg ) })?; + let mem = self.cx.memory()?; + let free = self.cx.free()?; self.instruction( &[AdapterType::I32; 2], - Instruction::VectorLoad { kind }, + Instruction::VectorLoad { kind, mem, free }, &[AdapterType::Vector(kind)], ); } @@ -160,7 +162,7 @@ impl InstructionBuilder<'_, '_> { &[AdapterType::Anyref], ); } - Descriptor::CachedString => self.cached_string(false, false), + Descriptor::CachedString => self.cached_string(false, false)?, Descriptor::String => { let std = wit_walrus::Instruction::MemoryToString(self.cx.memory()?); @@ -177,9 +179,10 @@ impl InstructionBuilder<'_, '_> { arg ) })?; + let mem = self.cx.memory()?; self.instruction( &[AdapterType::I32; 2], - Instruction::View { kind }, + Instruction::View { kind, mem }, &[AdapterType::Vector(kind)], ); } @@ -274,7 +277,8 @@ impl InstructionBuilder<'_, '_> { Descriptor::Ref(d) => self.outgoing_option_ref(false, d)?, Descriptor::RefMut(d) => self.outgoing_option_ref(true, d)?, - // Descriptor::CachedString => self.cached_string(true, true), + Descriptor::CachedString => self.cached_string(true, true)?, + Descriptor::String | Descriptor::Vector(_) => { let kind = arg.vector_kind().ok_or_else(|| { format_err!( @@ -282,9 +286,11 @@ impl InstructionBuilder<'_, '_> { arg ) })?; + let mem = self.cx.memory()?; + let free = self.cx.free()?; self.instruction( &[AdapterType::I32; 2], - Instruction::OptionVectorLoad { kind }, + Instruction::OptionVectorLoad { kind, mem, free }, &[AdapterType::Anyref], ); } @@ -306,7 +312,7 @@ impl InstructionBuilder<'_, '_> { &[AdapterType::Anyref], ); } - Descriptor::CachedString => self.cached_string(true, false), + Descriptor::CachedString => self.cached_string(true, false)?, Descriptor::String | Descriptor::Slice(_) => { let kind = arg.vector_kind().ok_or_else(|| { format_err!( @@ -314,9 +320,10 @@ impl InstructionBuilder<'_, '_> { arg ) })?; + let mem = self.cx.memory()?; self.instruction( &[AdapterType::I32; 2], - Instruction::OptionView { kind }, + Instruction::OptionView { kind, mem }, &[AdapterType::Anyref], ); } @@ -337,12 +344,20 @@ impl InstructionBuilder<'_, '_> { self.instruction(&[AdapterType::I32], Instruction::Standard(std), &[output]); } - fn cached_string(&mut self, optional: bool, owned: bool) { + fn cached_string(&mut self, optional: bool, owned: bool) -> Result<(), Error> { + let mem = self.cx.memory()?; + let free = self.cx.free()?; self.instruction( &[AdapterType::I32; 2], - Instruction::CachedStringLoad { owned, optional }, + Instruction::CachedStringLoad { + owned, + optional, + mem, + free, + }, &[AdapterType::String], ); + Ok(()) } fn option_native(&mut self, signed: bool, ty: ValType) { diff --git a/crates/cli-support/src/wit/section.rs b/crates/cli-support/src/wit/section.rs index a49a04a7769..b9bc0c3477e 100644 --- a/crates/cli-support/src/wit/section.rs +++ b/crates/cli-support/src/wit/section.rs @@ -254,7 +254,7 @@ fn translate_instruction( | I32FromOptionChar | I32FromOptionEnum { .. } | FromOptionNative { .. } - | OptionSlice { .. } + | OptionMutableSlice { .. } | OptionVector { .. } | AnyrefLoadOptionOwned | OptionRustFromI32 { .. } @@ -268,7 +268,7 @@ fn translate_instruction( | Option64FromI32 { .. } => { bail!("optional types aren't supported in wasm bindgen"); } - SliceToMemory { .. } | VectorToMemory { .. } | VectorLoad { .. } | View { .. } => { + MutableSliceToMemory { .. } | VectorToMemory { .. } | VectorLoad { .. } | View { .. } => { bail!("vector slices aren't supported in wasm interface types yet"); } CachedStringLoad { .. } => { diff --git a/crates/cli-support/src/wit/standard.rs b/crates/cli-support/src/wit/standard.rs index 8bdd0e8edc9..7252badff2e 100644 --- a/crates/cli-support/src/wit/standard.rs +++ b/crates/cli-support/src/wit/standard.rs @@ -20,7 +20,7 @@ pub struct NonstandardWitSection { pub type NonstandardWitSectionId = TypedCustomSectionId; -#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)] +#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, PartialOrd, Ord)] pub struct AdapterId(pub usize); #[derive(Debug, Clone)] @@ -103,11 +103,13 @@ pub enum Instruction { StoreRetptr { ty: AdapterType, offset: usize, + mem: walrus::MemoryId, }, /// An instruction to load `ty` at the `offset` index from the return pointer LoadRetptr { ty: AdapterType, offset: usize, + mem: walrus::MemoryId, }, /// An instruction which pushes the return pointer onto the stack. Retptr, @@ -180,23 +182,27 @@ pub enum Instruction { malloc: walrus::FunctionId, mem: walrus::MemoryId, }, - SliceToMemory { + MutableSliceToMemory { kind: VectorKind, malloc: walrus::FunctionId, + free: walrus::FunctionId, mem: walrus::MemoryId, - mutable: bool, }, /// Pops an anyref, pushes pointer/length or all zeros. Will update original /// view if mutable. - OptionSlice { + OptionMutableSlice { kind: VectorKind, - mutable: bool, + malloc: walrus::FunctionId, + free: walrus::FunctionId, + mem: walrus::MemoryId, }, /// Pops an anyref, pushes pointer/length or all zeros OptionVector { kind: VectorKind, + malloc: walrus::FunctionId, + mem: walrus::MemoryId, }, /// pops a `i32`, pushes `bool` @@ -222,14 +228,20 @@ pub enum Instruction { CachedStringLoad { owned: bool, optional: bool, + mem: walrus::MemoryId, + free: walrus::FunctionId, }, /// pops ptr/length, pushes a vector, frees the original data VectorLoad { kind: VectorKind, + mem: walrus::MemoryId, + free: walrus::FunctionId, }, /// pops ptr/length, pushes a vector, frees the original data OptionVectorLoad { kind: VectorKind, + mem: walrus::MemoryId, + free: walrus::FunctionId, }, /// pops i32, loads anyref from anyref table TableGet, @@ -242,10 +254,12 @@ pub enum Instruction { /// pops two i32 data pointers, pushes a vector view View { kind: VectorKind, + mem: walrus::MemoryId, }, /// pops two i32 data pointers, pushes a vector view OptionView { kind: VectorKind, + mem: walrus::MemoryId, }, /// pops i32, pushes it viewed as an optional value with a known sentinel OptionU32Sentinel, diff --git a/crates/cli/tests/wasm-bindgen/main.rs b/crates/cli/tests/wasm-bindgen/main.rs index 2f58e2fe7cd..568940d72e6 100644 --- a/crates/cli/tests/wasm-bindgen/main.rs +++ b/crates/cli/tests/wasm-bindgen/main.rs @@ -206,7 +206,7 @@ fn empty_interface_types() { r#" #[no_mangle] pub extern fn foo() {} - "# + "#, ) .file( "Cargo.toml", diff --git a/crates/futures/src/task/multithread.rs b/crates/futures/src/task/multithread.rs index 70f28cf4b66..f2fed5446c6 100644 --- a/crates/futures/src/task/multithread.rs +++ b/crates/futures/src/task/multithread.rs @@ -130,7 +130,7 @@ impl Task { // resources associated with the future ASAP. Poll::Ready(()) => { *borrow = None; - }, + } // Unlike `singlethread.rs` we are responsible for ensuring there's // a closure to handle the notification that a Future is ready. In diff --git a/src/lib.rs b/src/lib.rs index 2d662fd9c79..325f7710997 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,7 +14,7 @@ use core::marker; use core::mem; use core::ops::{Deref, DerefMut}; -use crate::convert::FromWasmAbi; +use crate::convert::{FromWasmAbi, WasmSlice}; macro_rules! if_std { ($($i:item)*) => ($( @@ -240,15 +240,16 @@ impl JsValue { /// If this JS value is not an instance of a number then this returns /// `None`. pub fn as_f64(&self) -> Option { - let mut invalid = 0; - unsafe { - let ret = __wbindgen_number_get(self.idx, &mut invalid); - if invalid == 1 { - None - } else { - Some(ret) - } - } + panic!() + // let mut invalid = 0; + // unsafe { + // let ret = __wbindgen_number_get(self.idx, &mut invalid); + // if invalid == 1 { + // None + // } else { + // Some(ret) + // } + // } } /// Tests whether this JS value is a JS string. @@ -278,16 +279,7 @@ impl JsValue { /// [caveats]: https://rustwasm.github.io/docs/wasm-bindgen/reference/types/str.html #[cfg(feature = "std")] pub fn as_string(&self) -> Option { - unsafe { - let mut len = 0; - let ptr = __wbindgen_string_get(self.idx, &mut len); - if ptr.is_null() { - None - } else { - let data = Vec::from_raw_parts(ptr, len, len); - Some(String::from_utf8_unchecked(data)) - } - } + unsafe { FromWasmAbi::from_abi(__wbindgen_string_get(self.idx)) } } /// Returns the `bool` value of this JS value if it's an instance of a @@ -523,9 +515,9 @@ externs! { fn __wbindgen_is_string(idx: u32) -> u32; fn __wbindgen_is_falsy(idx: u32) -> u32; - fn __wbindgen_number_get(idx: u32, invalid: *mut u8) -> f64; + // fn __wbindgen_number_get(idx: u32, invalid: *mut u8) -> f64; fn __wbindgen_boolean_get(idx: u32) -> u32; - fn __wbindgen_string_get(idx: u32, len: *mut usize) -> *mut u8; + fn __wbindgen_string_get(idx: u32) -> WasmSlice; fn __wbindgen_debug_string(ret: *mut [usize; 2], idx: u32) -> (); From d334ff8045968286b4d49cc36ca818215dc982f0 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 26 Nov 2019 14:54:08 -0800 Subject: [PATCH 09/35] Mliestone: producing parseable JS! --- crates/cli-support/src/js/binding.rs | 415 ++++++++------------------- crates/cli-support/src/js/mod.rs | 2 +- 2 files changed, 123 insertions(+), 294 deletions(-) diff --git a/crates/cli-support/src/js/binding.rs b/crates/cli-support/src/js/binding.rs index cda1b50756b..2267fdd2376 100644 --- a/crates/cli-support/src/js/binding.rs +++ b/crates/cli-support/src/js/binding.rs @@ -16,22 +16,6 @@ use walrus::Module; pub struct Builder<'a, 'b> { /// Parent context used to expose helper functions and such. pub cx: &'a mut Context<'b>, - /// Prelude JS which is present before the main invocation to prepare - /// arguments. - args_prelude: String, - /// Finally block to be executed regardless of the call's status, mostly - /// used for cleanups like free'ing. - finally: String, - /// Code to execute after return value is materialized. - ret_finally: String, - /// Argument names to the JS function shim that we're generating. - function_args: Vec, - /// JS expressions that are arguments to the function that we're calling. - invoc_args: Vec, - /// JS to execute just before the return value is materialized. - ret_prelude: String, - /// The JS expression of the actual return value. - ret_js: String, /// The TypeScript definition for each argument to this function. pub ts_args: Vec, /// The TypeScript return value for this function. @@ -71,13 +55,6 @@ impl<'a, 'b> Builder<'a, 'b> { Builder { log_error: cx.config.debug, cx, - args_prelude: String::new(), - finally: String::new(), - ret_finally: String::new(), - function_args: Vec::new(), - invoc_args: Vec::new(), - ret_prelude: String::new(), - ret_js: String::new(), ts_args: Vec::new(), ts_ret: None, constructor: None, @@ -94,12 +71,8 @@ impl<'a, 'b> Builder<'a, 'b> { self.constructor = Some(class.to_string()); } - pub fn catch(&mut self, catch: bool) -> Result<(), Error> { - if catch { - self.cx.expose_handle_error()?; - } + pub fn catch(&mut self, catch: bool) { self.catch = catch; - Ok(()) } pub fn disable_log_error(&mut self, disable: bool) { @@ -114,31 +87,27 @@ impl<'a, 'b> Builder<'a, 'b> { instructions: &[InstructionData], explicit_arg_names: &Option>, ) -> Result { - // used in `finalize` below - if self.log_error { - self.cx.expose_log_error(); - } - let mut params = adapter.params.iter(); - let mut adapter_params = Vec::new(); + let mut function_args = Vec::new(); // If this is a method then we're generating this as part of a class // method, so the leading parameter is the this pointer stored on // the JS object, so synthesize that here. + let mut js = JsBuilder::new(self.cx); match self.method { Some(consumes_self) => { drop(params.next()); - if self.cx.config.debug { - self.args_prelude.push_str( + if js.cx.config.debug { + js.prelude( "if (this.ptr == 0) throw new Error('Attempt to use a moved value');\n", ); } if consumes_self { - self.args_prelude.push_str("const ptr = this.ptr;\n"); - self.args_prelude.push_str("this.ptr = 0;\n"); - adapter_params.push("ptr".to_string()); + js.prelude("const ptr = this.ptr;"); + js.prelude("this.ptr = 0;"); + js.args.push("ptr".to_string()); } else { - adapter_params.push("this.ptr".to_string()); + js.args.push("this.ptr".to_string()); } } None => {} @@ -148,208 +117,46 @@ impl<'a, 'b> Builder<'a, 'b> { Some(list) => list[i].clone(), None => format!("arg{}", i), }; - adapter_params.push(arg.clone()); - self.function_args.push(arg); + js.args.push(arg.clone()); + function_args.push(arg); } - let mut js = JsBuilder::new(self.cx, adapter_params); for instr in instructions { instruction(&mut js, &instr.instr)?; } assert_eq!(js.stack.len(), adapter.results.len()); - - // // Save off the results of JS generation for the arguments. - // self.args_prelude.push_str(&js.prelude); - // self.finally.push_str(&js.finally); - // self.ts_args.extend(js.typescript); - // - // // Remove extraneous typescript args which were synthesized and aren't - // // part of our function shim. - // while self.ts_args.len() > self.function_args.len() { - // self.ts_args.remove(0); - // } - // - // // Handle the special case where there is no return value. In this case - // // we can skip all the logic below and go straight to the end. - // if incoming_args { - // if binding.outgoing.len() == 0 { - // assert!(binding.return_via_outptr.is_none()); - // assert!(self.constructor.is_none()); - // let invoc = invoke(self.cx, &mut self.args_prelude, &self.invoc_args)?; - // return Ok(self.finalize(&invoc)); - // } - // assert_eq!(binding.outgoing.len(), 1); - // } else { - // if binding.incoming.len() == 0 { - // assert!(binding.return_via_outptr.is_none()); - // assert!(self.constructor.is_none()); - // let invoc = invoke(self.cx, &mut self.args_prelude, &self.invoc_args)?; - // return Ok(self.finalize(&invoc)); - // } - // assert_eq!(binding.incoming.len(), 1); - // } - // - // // Like above handling the return value is quite different based on - // // whether it's an outgoing argument or an incoming argument. - // let mut ret_args = Vec::new(); - // let mut js; - // if incoming_args { - // match &binding.return_via_outptr { - // // If we have an outgoing value that requires multiple - // // aggregates then we're passing a return pointer (a global one) - // // to a wasm function, and then afterwards we're going to read - // // the results of that return pointer. Here we generate an - // // expression effectively which represents reading each value of - // // the return pointer that was filled in. These values are then - // // used by the outgoing builder as inputs to generate the final - // // actual return value. - // Some(list) => { - // let mut exposed = HashSet::new(); - // for (i, ty) in list.iter().enumerate() { - // let (mem, size) = match ty { - // walrus::ValType::I32 => { - // if exposed.insert(*ty) { - // self.cx.expose_int32_memory(); - // self.ret_prelude - // .push_str("const memi32 = getInt32Memory();\n"); - // } - // ("memi32", 4) - // } - // walrus::ValType::F32 => { - // if exposed.insert(*ty) { - // self.cx.expose_f32_memory(); - // self.ret_prelude - // .push_str("const memf32 = getFloat32Memory();\n"); - // } - // ("memf32", 4) - // } - // walrus::ValType::F64 => { - // if exposed.insert(*ty) { - // self.cx.expose_f64_memory(); - // self.ret_prelude - // .push_str("const memf64 = getFloat64Memory();\n"); - // } - // ("memf64", 8) - // } - // _ => bail!("invalid aggregate return type"), - // }; - // ret_args.push(format!("{}[retptr / {} + {}]", mem, size, i)); - // } - // } - // - // // No return pointer? That's much easier! - // // - // // If there's one return value we just have one input of `ret` - // // which is created in the JS shim below. If there's multiple - // // return values (a multi-value module) then we'll pull results - // // from the returned array. - // None => { - // let amt = self.cx.module.types.get(binding.wasm_ty).results().len(); - // if amt == 1 { - // ret_args.push("ret".to_string()); - // } else { - // for i in 0..amt { - // ret_args.push(format!("ret[{}]", i)); - // } - // } - // } - // } - // js = JsBuilder::new(ret_args); - // let mut ret = outgoing::Outgoing::new(self.cx, &mut js); - // let ret_js = ret.process(&binding.outgoing[0])?; - // self.ret_js.push_str(&ret_js); - // } else { - // // If there's an out ptr for an incoming argument then it means that - // // the first argument to our function is the return pointer, and we - // // need to fill that in. After we process the value we then write - // // each result of the processed value into the corresponding typed - // // array. - // js = JsBuilder::new(vec!["ret".to_string()]); - // let results = match &webidl.result { - // Some(ptr) => std::slice::from_ref(ptr), - // None => &[], - // }; - // let mut ret = incoming::Incoming::new(self.cx, results, &mut js); - // let ret_js = ret.process(&binding.incoming[0])?; - // match &binding.return_via_outptr { - // Some(list) => { - // assert_eq!(list.len(), ret_js.len()); - // for (i, js) in ret_js.iter().enumerate() { - // self.ret_finally - // .push_str(&format!("const ret{} = {};\n", i, js)); - // } - // for (i, ty) in list.iter().enumerate() { - // let (mem, size) = match ty { - // walrus::ValType::I32 => { - // self.cx.expose_int32_memory(); - // ("getInt32Memory()", 4) - // } - // walrus::ValType::F32 => { - // self.cx.expose_f32_memory(); - // ("getFloat32Memory()", 4) - // } - // walrus::ValType::F64 => { - // self.cx.expose_f64_memory(); - // ("getFloat64Memory()", 8) - // } - // _ => bail!("invalid aggregate return type"), - // }; - // self.ret_finally - // .push_str(&format!("{}[arg0 / {} + {}] = ret{};\n", mem, size, i, i)); - // } - // } - // None => { - // assert_eq!(ret_js.len(), 1); - // self.ret_js.push_str(&ret_js[0]); - // } - // } - // } - // self.ret_finally.push_str(&js.finally); - // self.ret_prelude.push_str(&js.prelude); - // self.ts_ret = Some(js.typescript.remove(0)); - // let invoc = invoke(self.cx, &mut self.args_prelude, &self.invoc_args)?; - // Ok(self.finalize(&invoc)) - Ok(self.finalize("x")) - } - - // This method... is a mess. Refactorings and improvements are more than - // welcome :) - fn finalize(&self, invoc: &str) -> String { - let mut js = String::new(); - js.push_str("("); - js.push_str(&self.function_args.join(", ")); - js.push_str(") {\n"); - if self.args_prelude.len() > 0 { - js.push_str(self.args_prelude.trim()); - js.push_str("\n"); - } - - let mut call = String::new(); - if self.ts_ret.is_some() { - call.push_str("const ret = "); + match js.stack.len() { + 0 => {} + 1 => { + self.ts_ret = js.typescript.pop(); + let val = js.pop(); + js.prelude(&format!("return {};", val)); + } + _ => { + if true { + panic!() + } + let expr = js.stack.join(", "); + js.stack.truncate(0); + js.prelude(&format!("return [{}];", expr)); + } } - call.push_str(invoc); - call.push_str(";\n"); + assert!(js.stack.is_empty()); + self.ts_args = js.typescript; - if self.ret_prelude.len() > 0 { - call.push_str(self.ret_prelude.trim()); - call.push_str("\n"); - } + let mut ret = String::new(); + ret.push_str("("); + ret.push_str(&function_args.join(", ")); + ret.push_str(") {\n"); - if self.ret_js.len() > 0 { - assert!(self.ts_ret.is_some()); - // Having a this field isn't supported yet, but shouldn't come up - assert!(self.ret_finally.len() == 0); - call.push_str("return "); - call.push_str(&self.ret_js); - call.push_str(";\n"); - } else if self.ret_finally.len() > 0 { - call.push_str(self.ret_finally.trim()); - call.push_str("\n"); + let mut call = js.prelude; + if js.finally.len() != 0 { + call = format!("try {{\n{}}} finally {{\n{}\n}}\n", call, js.finally); } if self.catch { + js.cx.expose_handle_error()?; call = format!("try {{\n{}}} catch (e) {{\n handleError(e)\n}}\n", call); } @@ -358,18 +165,14 @@ impl<'a, 'b> Builder<'a, 'b> { // logs what happened, but keeps the exception being thrown to propagate // elsewhere. if self.log_error { + js.cx.expose_log_error(); call = format!("try {{\n{}}} catch (e) {{\n logError(e)\n}}\n", call); } - let finally = self.finally.trim(); - if finally.len() != 0 { - call = format!("try {{\n{}}} finally {{\n{}\n}}\n", call, finally); - } - - js.push_str(&call); - js.push_str("}"); + ret.push_str(&call); + ret.push_str("}"); - return js; + return Ok(ret); } /// Returns the typescript signature of the binding that this has described. @@ -438,10 +241,10 @@ impl<'a, 'b> Builder<'a, 'b> { } impl<'a, 'b> JsBuilder<'a, 'b> { - pub fn new(cx: &'a mut Context<'b>, args: Vec) -> JsBuilder<'a, 'b> { + pub fn new(cx: &'a mut Context<'b>) -> JsBuilder<'a, 'b> { JsBuilder { cx, - args, + args: Vec::new(), tmp: 0, finally: String::new(), prelude: String::new(), @@ -640,7 +443,10 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction) -> Result<(), Error> { // happens. This is pretty conditional depending on the number of // return values of the function. match (invoc.defer(), results) { - (true, 0) => js.finally(&format!("{};", call)), + (true, 0) => { + js.finally(&format!("{};", call)); + js.stack.extend(args); + } (true, _) => panic!("deferred calls must have no results"), (false, 0) => js.prelude(&format!("{};", call)), (false, 1) => js.push(call), @@ -654,6 +460,7 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction) -> Result<(), Error> { } Instruction::Standard(wit_walrus::Instruction::IntToWasm { trap: false, .. }) => { + js.typescript_required("number"); let val = js.pop(); js.assert_number(&val); js.push(val); @@ -662,7 +469,12 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction) -> Result<(), Error> { // When converting to a JS number we need to specially handle the `u32` // case because if the high bit is set then it comes out as a negative // number, but we want to switch that to an unsigned representation. - Instruction::Standard(wit_walrus::Instruction::WasmToInt { trap: false, output, .. }) => { + Instruction::Standard(wit_walrus::Instruction::WasmToInt { + trap: false, + output, + .. + }) => { + js.typescript_required("number"); let val = js.pop(); match output { wit_walrus::ValType::U32 => js.push(format!("{} >>> 0", val)), @@ -937,38 +749,48 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction) -> Result<(), Error> { js.push(format!("len{}", i)); } - Instruction::OptionMutableSlice { .. } | + Instruction::OptionMutableSlice { .. } => panic!(), Instruction::MutableSliceToMemory { - .. - // kind, - // malloc, - // mem, - // free, + kind, + malloc, + mem, + free, } => { - panic!() - // js.typescript_required(kind.js_ty()); - // let val = js.pop(); - // let func = js.cx.pass_to_wasm_function(*kind, *mem)?; - // let malloc = js.cx.export_name_of(*malloc); - // let i = js.tmp(); - // js.prelude(&format!( - // "const ptr{i} = {f}({val}, wasm.{malloc});", - // val = val, - // i = i, - // f = func, - // malloc = malloc, - // )); - // js.prelude(&format!("const len{} = WASM_VECTOR_LEN;", i)); - // js.push(format!("ptr{}", i)); - // js.push(format!("len{}", i)); - // let free = js.cx.export_name_of(*free); - // js.finally_free_slice(&val, i, *kind, - // js.finally(&format!( - // "wasm.{free}(ptr{i}, len{i} * {size});", - // free = free, - // size = kind.size(), - // i = i - // )); + js.typescript_required(kind.js_ty()); + // First up, pass the JS value into wasm, getting out a pointer and + // a length. These two pointer/length values get pushed onto the + // value stack. + let val = js.pop(); + let func = js.cx.pass_to_wasm_function(*kind, *mem)?; + let malloc = js.cx.export_name_of(*malloc); + let i = js.tmp(); + js.prelude(&format!( + "const ptr{i} = {f}({val}, wasm.{malloc});", + val = val, + i = i, + f = func, + malloc = malloc, + )); + js.prelude(&format!("const len{} = WASM_VECTOR_LEN;", i)); + js.push(format!("ptr{}", i)); + js.push(format!("len{}", i)); + + // Next we set up a `finally` clause which will both update the + // original mutable slice with any modifications, and then free the + // Rust-backed memory. + let free = js.cx.export_name_of(*free); + let get = js.cx.memview_function(*kind, *mem); + js.finally(&format!( + " + {val}.set({get}().subarray(ptr{i} / size, ptr{i} / size + len{i})); + wasm.{free}(ptr{i}, len{i} * {size}); + ", + val = val, + get = get, + free = free, + size = kind.size(), + i = i, + )); } Instruction::BoolFromI32 => { @@ -1034,12 +856,16 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction) -> Result<(), Error> { let val = js.pop(); js.push(format!( "{0} === 0 ? undefined : {1}.__wrap({0})", - val, - class, + val, class, )) } - Instruction::CachedStringLoad { owned, optional, mem, free } => { + Instruction::CachedStringLoad { + owned, + optional, + mem, + free, + } => { if *optional { js.typescript_optional("string"); } else { @@ -1052,10 +878,7 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction) -> Result<(), Error> { let get = js.cx.expose_get_cached_string_from_wasm(*mem)?; - js.prelude(&format!( - "const v{} = {}({}, {});", - tmp, get, ptr, len - )); + js.prelude(&format!("const v{} = {}({}, {});", tmp, get, ptr, len)); if *owned { let free = js.cx.export_name_of(*free); @@ -1077,17 +900,16 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction) -> Result<(), Error> { js.push(format!("getObject({})", val)); } - Instruction::StackClosure { adapter, nargs, mutable } => { + Instruction::StackClosure { + adapter, + nargs, + mutable, + } => { js.typescript_optional("any"); let i = js.tmp(); let b = js.pop(); let a = js.pop(); - js.prelude(&format!( - "const state{} = {{a: {}, b: {}}};", - i, - a, - b, - )); + js.prelude(&format!("const state{} = {{a: {}, b: {}}};", i, a, b,)); let args = (0..*nargs) .map(|i| format!("arg{}", i)) .collect::>() @@ -1136,7 +958,13 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction) -> Result<(), Error> { let i = js.tmp(); let free = js.cx.export_name_of(*free); js.prelude(&format!("const v{} = {}({}, {}).slice();", i, f, ptr, len)); - js.prelude(&format!("wasm.{}({}, {} * {});", free, ptr, len, kind.size())); + js.prelude(&format!( + "wasm.{}({}, {} * {});", + free, + ptr, + len, + kind.size() + )); js.push(format!("v{}", i)) } @@ -1150,7 +978,13 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction) -> Result<(), Error> { js.prelude(&format!("let v{};", i)); js.prelude(&format!("if ({} !== 0) {{", ptr)); js.prelude(&format!("v{} = {}({}, {}).slice();", i, f, ptr, len)); - js.prelude(&format!("wasm.{}({}, {} * {});", free, ptr, len, kind.size())); + js.prelude(&format!( + "wasm.{}({}, {} * {});", + free, + ptr, + len, + kind.size() + )); js.prelude("}"); js.push(format!("v{}", i)); } @@ -1160,12 +994,7 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction) -> Result<(), Error> { let len = js.pop(); let ptr = js.pop(); let f = js.cx.expose_get_vector_from_wasm(*kind, *mem)?; - js.push(format!( - "{f}({ptr}, {len})", - ptr = ptr, - len = len, - f = f - )); + js.push(format!("{f}({ptr}, {len})", ptr = ptr, len = len, f = f)); } Instruction::OptionView { kind, mem } => { diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index c47af4fb512..825f0b75db4 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -1934,7 +1934,7 @@ impl<'a> Context<'a> { // export that we're generating. let mut builder = binding::Builder::new(self); builder.disable_log_error(disable_log_error); - builder.catch(builder.cx.aux.imports_with_catch.contains(&id))?; + builder.catch(builder.cx.aux.imports_with_catch.contains(&id)); let mut arg_names = &None; match kind { Kind::Export(export) => { From cab1b08ebab13d3409fc204d161bea8fba955aef Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 26 Nov 2019 16:02:26 -0800 Subject: [PATCH 10/35] Milestone, main wasm test suite passing! --- crates/cli-support/src/anyref.rs | 2 +- crates/cli-support/src/intrinsic.rs | 6 +- crates/cli-support/src/js/binding.rs | 138 ++++++++++++--------------- crates/cli-support/src/js/mod.rs | 16 ++-- crates/cli-support/src/wit/mod.rs | 7 +- src/convert/mod.rs | 1 + src/lib.rs | 23 ++--- 7 files changed, 90 insertions(+), 103 deletions(-) diff --git a/crates/cli-support/src/anyref.rs b/crates/cli-support/src/anyref.rs index 9250bcd264f..ea5c514dd71 100644 --- a/crates/cli-support/src/anyref.rs +++ b/crates/cli-support/src/anyref.rs @@ -180,7 +180,7 @@ fn import_xform( let mut ret_anyref = false; while let Some((i, instr)) = iter.next() { match instr.instr { - Instruction::AnyrefLoadOwned => { + Instruction::I32FromAnyrefOwned => { assert_eq!(results.len(), 1); match results[0] { AdapterType::I32 => {} diff --git a/crates/cli-support/src/intrinsic.rs b/crates/cli-support/src/intrinsic.rs index 4532adbdbee..d7db4fc94aa 100644 --- a/crates/cli-support/src/intrinsic.rs +++ b/crates/cli-support/src/intrinsic.rs @@ -75,6 +75,10 @@ fn opt_string() -> Descriptor { Descriptor::Option(Box::new(Descriptor::String)) } +fn opt_f64() -> Descriptor { + Descriptor::Option(Box::new(Descriptor::F64)) +} + intrinsics! { pub enum Intrinsic { #[symbol = "__wbindgen_jsval_eq"] @@ -126,7 +130,7 @@ intrinsics! { #[signature = fn(ref_string()) -> Anyref] SymbolNamedNew, #[symbol = "__wbindgen_number_get"] - #[signature = fn(ref_anyref(), I32) -> F64] + #[signature = fn(ref_anyref()) -> opt_f64()] NumberGet, #[symbol = "__wbindgen_string_get"] #[signature = fn(ref_anyref()) -> opt_string()] diff --git a/crates/cli-support/src/js/binding.rs b/crates/cli-support/src/js/binding.rs index 2267fdd2376..e2956b8b005 100644 --- a/crates/cli-support/src/js/binding.rs +++ b/crates/cli-support/src/js/binding.rs @@ -103,7 +103,7 @@ impl<'a, 'b> Builder<'a, 'b> { ); } if consumes_self { - js.prelude("const ptr = this.ptr;"); + js.prelude("var ptr = this.ptr;"); js.prelude("this.ptr = 0;"); js.args.push("ptr".to_string()); } else { @@ -152,7 +152,7 @@ impl<'a, 'b> Builder<'a, 'b> { let mut call = js.prelude; if js.finally.len() != 0 { - call = format!("try {{\n{}}} finally {{\n{}\n}}\n", call, js.finally); + call = format!("try {{\n{}}} finally {{\n{}}}\n", call, js.finally); } if self.catch { @@ -287,16 +287,20 @@ impl<'a, 'b> JsBuilder<'a, 'b> { } pub fn prelude(&mut self, prelude: &str) { - for line in prelude.trim().lines() { - self.prelude.push_str(line); - self.prelude.push_str("\n"); + for line in prelude.trim().lines().map(|l| l.trim()) { + if !line.is_empty() { + self.prelude.push_str(line); + self.prelude.push_str("\n"); + } } } pub fn finally(&mut self, finally: &str) { - for line in finally.trim().lines() { - self.finally.push_str(line); - self.finally.push_str("\n"); + for line in finally.trim().lines().map(|l| l.trim()) { + if !line.is_empty() { + self.finally.push_str(line); + self.finally.push_str("\n"); + } } } @@ -368,39 +372,6 @@ impl<'a, 'b> JsBuilder<'a, 'b> { arg, )); } - - // fn finally_free_slice( - // &mut self, - // expr: &str, - // i: usize, - // kind: VectorKind, - // mutable: bool, - // ) -> Result<(), Error> { - // // If the slice was mutable it's currently a feature that we - // // mirror back updates to the original slice. This... is - // // arguably a misfeature of wasm-bindgen... - // if mutable { - // let get = self.cx.memview_function(kind); - // self.js.finally(&format!( - // "\ - // {arg}.set({get}().subarray(\ - // ptr{i} / {size}, \ - // ptr{i} / {size} + len{i}\ - // ));\ - // ", - // i = i, - // arg = expr, - // get = get, - // size = kind.size() - // )); - // } - // self.js.finally(&format!( - // "wasm.__wbindgen_free(ptr{i}, len{i} * {size});", - // i = i, - // size = kind.size(), - // )); - // self.cx.require_internal_export("__wbindgen_free") - // } } fn instruction(js: &mut JsBuilder, instr: &Instruction) -> Result<(), Error> { @@ -449,11 +420,14 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction) -> Result<(), Error> { } (true, _) => panic!("deferred calls must have no results"), (false, 0) => js.prelude(&format!("{};", call)), - (false, 1) => js.push(call), (false, n) => { - js.prelude(&format!("const ret = {};", call)); - for i in 0..n { - js.push(format!("ret[{}]", i)); + js.prelude(&format!("var ret = {};", call)); + if n == 1 { + js.push("ret".to_string()); + } else { + for i in 0..n { + js.push(format!("ret[{}]", i)); + } } } } @@ -491,8 +465,8 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction) -> Result<(), Error> { js.typescript_required("string"); let len = js.pop(); let ptr = js.pop(); - js.cx.expose_get_string_from_wasm(*mem)?; - js.push(format!("getStringFromWasm({}, {})", ptr, len)); + let get = js.cx.expose_get_string_from_wasm(*mem)?; + js.push(format!("{}({}, {})", get, ptr, len)); } Instruction::Standard(wit_walrus::Instruction::StringToMemory { mem, malloc }) => { @@ -500,8 +474,17 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction) -> Result<(), Error> { let pass = js.cx.expose_pass_string_to_wasm(*mem)?; let val = js.pop(); let malloc = js.cx.export_name_of(*malloc); - js.push(format!("{}({}, wasm.{})", pass, val, malloc)); - js.push("WASM_VECTOR_LEN".to_string()); + let i = js.tmp(); + js.prelude(&format!( + "var ptr{i} = {f}({0}, wasm.{malloc});", + val, + i = i, + f = pass, + malloc = malloc, + )); + js.prelude(&format!("var len{} = WASM_VECTOR_LEN;", i)); + js.push(format!("ptr{}", i)); + js.push(format!("len{}", i)); } Instruction::Retptr => js.stack.push(retptr_val.to_string()), @@ -517,7 +500,7 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction) -> Result<(), Error> { // which is currently the case for LLVM. let val = js.pop(); let expr = format!( - "{}[{}() / {} + {}] = {};", + "{}()[{} / {} + {}] = {};", mem, js.arg(0), size, @@ -529,25 +512,17 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction) -> Result<(), Error> { Instruction::LoadRetptr { ty, offset, mem } => { let (mem, size) = match ty { - AdapterType::I32 => { - js.cx.expose_int32_memory(*mem); - ("getInt32Memory()", 4) - } - AdapterType::F32 => { - js.cx.expose_f32_memory(*mem); - ("getFloat32Memory()", 4) - } - AdapterType::F64 => { - js.cx.expose_f64_memory(*mem); - ("getFloat64Memory()", 8) - } + AdapterType::I32 => (js.cx.expose_int32_memory(*mem), 4), + AdapterType::F32 => (js.cx.expose_f32_memory(*mem), 4), + AdapterType::F64 => (js.cx.expose_f64_memory(*mem), 8), other => bail!("invalid aggregate return type {:?}", other), }; // If we're loading from the return pointer then we must have pushed // it earlier, and we always push the same value, so load that value // here - let expr = format!("{}[{} / {} + {}]", mem, retptr_val, size, offset,); - js.push(expr); + let expr = format!("{}()[{} / {} + {}]", mem, retptr_val, size, offset,); + js.prelude(&format!("var r{} = {};", offset, expr)); + js.push(format!("r{}", offset)); } Instruction::I32FromBool => { @@ -586,7 +561,7 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction) -> Result<(), Error> { js.assert_class(&val, &class); js.assert_not_moved(&val); let i = js.tmp(); - js.prelude(&format!("const ptr{} = {}.ptr;", i, val)); + js.prelude(&format!("var ptr{} = {}.ptr;", i, val)); js.prelude(&format!("{}.ptr = 0;", val)); js.push(format!("ptr{}", i)); } @@ -726,8 +701,17 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction) -> Result<(), Error> { let val = js.pop(); let func = js.cx.pass_to_wasm_function(*kind, *mem)?; let malloc = js.cx.export_name_of(*malloc); - js.push(format!("{}({}, wasm.{})", func, val, malloc)); - js.push("WASM_VECTOR_LEN".to_string()); + let i = js.tmp(); + js.prelude(&format!( + "var ptr{i} = {f}({0}, wasm.{malloc});", + val, + i = i, + f = func, + malloc = malloc, + )); + js.prelude(&format!("var len{} = WASM_VECTOR_LEN;", i)); + js.push(format!("ptr{}", i)); + js.push(format!("len{}", i)); } Instruction::OptionVector { kind, mem, malloc } => { @@ -738,13 +722,13 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction) -> Result<(), Error> { let malloc = js.cx.export_name_of(*malloc); let val = js.pop(); js.prelude(&format!( - "const ptr{i} = isLikeNone({0}) ? 0 : {f}({0}, wasm.{malloc});", + "var ptr{i} = isLikeNone({0}) ? 0 : {f}({0}, wasm.{malloc});", val, i = i, f = func, malloc = malloc, )); - js.prelude(&format!("const len{} = WASM_VECTOR_LEN;", i)); + js.prelude(&format!("var len{} = WASM_VECTOR_LEN;", i)); js.push(format!("ptr{}", i)); js.push(format!("len{}", i)); } @@ -765,13 +749,13 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction) -> Result<(), Error> { let malloc = js.cx.export_name_of(*malloc); let i = js.tmp(); js.prelude(&format!( - "const ptr{i} = {f}({val}, wasm.{malloc});", + "var ptr{i} = {f}({val}, wasm.{malloc});", val = val, i = i, f = func, malloc = malloc, )); - js.prelude(&format!("const len{} = WASM_VECTOR_LEN;", i)); + js.prelude(&format!("var len{} = WASM_VECTOR_LEN;", i)); js.push(format!("ptr{}", i)); js.push(format!("len{}", i)); @@ -782,7 +766,7 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction) -> Result<(), Error> { let get = js.cx.memview_function(*kind, *mem); js.finally(&format!( " - {val}.set({get}().subarray(ptr{i} / size, ptr{i} / size + len{i})); + {val}.set({get}().subarray(ptr{i} / {size}, ptr{i} / {size} + len{i})); wasm.{free}(ptr{i}, len{i} * {size}); ", val = val, @@ -878,7 +862,7 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction) -> Result<(), Error> { let get = js.cx.expose_get_cached_string_from_wasm(*mem)?; - js.prelude(&format!("const v{} = {}({}, {});", tmp, get, ptr, len)); + js.prelude(&format!("var v{} = {}({}, {});", tmp, get, ptr, len)); if *owned { let free = js.cx.export_name_of(*free); @@ -909,7 +893,7 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction) -> Result<(), Error> { let i = js.tmp(); let b = js.pop(); let a = js.pop(); - js.prelude(&format!("const state{} = {{a: {}, b: {}}};", i, a, b,)); + js.prelude(&format!("var state{} = {{a: {}, b: {}}};", i, a, b,)); let args = (0..*nargs) .map(|i| format!("arg{}", i)) .collect::>() @@ -920,7 +904,7 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction) -> Result<(), Error> { // recursively, so ensure that we clear out one of the // internal pointers while it's being invoked. js.prelude(&format!( - "const cb{i} = ({args}) => {{ + "var cb{i} = ({args}) => {{ const a = state{i}.a; state{i}.a = 0; try {{ @@ -935,7 +919,7 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction) -> Result<(), Error> { )); } else { js.prelude(&format!( - "const cb{i} = ({args}) => {wrapper}(state{i}.a, state{i}.b, {args});", + "var cb{i} = ({args}) => {wrapper}(state{i}.a, state{i}.b, {args});", i = i, args = args, wrapper = wrapper, @@ -957,7 +941,7 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction) -> Result<(), Error> { let f = js.cx.expose_get_vector_from_wasm(*kind, *mem)?; let i = js.tmp(); let free = js.cx.export_name_of(*free); - js.prelude(&format!("const v{} = {}({}, {}).slice();", i, f, ptr, len)); + js.prelude(&format!("var v{} = {}({}, {}).slice();", i, f, ptr, len)); js.prelude(&format!( "wasm.{}({}, {} * {});", free, diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 825f0b75db4..1a8924886f3 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -22,7 +22,7 @@ pub struct Context<'a> { imports_post: String, typescript: String, exposed_globals: Option>>, - required_internal_exports: HashSet<&'static str>, + required_internal_exports: HashSet>, next_export_idx: usize, config: &'a Bindgen, pub module: &'a mut Module, @@ -166,7 +166,7 @@ impl<'a> Context<'a> { } fn require_internal_export(&mut self, name: &'static str) -> Result<(), Error> { - if !self.required_internal_exports.insert(name) { + if !self.required_internal_exports.insert(name.into()) { return Ok(()); } @@ -1077,7 +1077,7 @@ impl<'a> Context<'a> { return ptr; }} ", - view, + ret, view, size = size )); @@ -1317,7 +1317,7 @@ impl<'a> Context<'a> { return {mem}().subarray(ptr / {size}, ptr / {size} + len); }} ", - name = name, + name = ret, mem = view, size = size, )); @@ -1402,8 +1402,8 @@ impl<'a> Context<'a> { " let cache{name} = null; function {name}() {{ - if (cache{name} === null || cache{name}.buffer !== {mem}.buffer) {{ - cache{name} = {js}({mem}.buffer); + if (cache{name} === null || cache{name}.buffer !== wasm.{mem}.buffer) {{ + cache{name} = {js}(wasm.{mem}.buffer); }} return cache{name}; }} @@ -2018,6 +2018,7 @@ impl<'a> Context<'a> { self.globals.push_str("function "); self.globals.push_str(&self.adapter_name(id)); self.globals.push_str(&js); + self.globals.push_str("\n\n"); } } } @@ -2868,11 +2869,14 @@ impl<'a> Context<'a> { } }); if let Some(export) = export { + self.required_internal_exports + .insert(export.name.clone().into()); return export.name.clone(); } let mut name = format!("__wbindgen_export_{}", self.next_export_idx); self.next_export_idx += 1; self.module.exports.add(&name, id); + self.required_internal_exports.insert(name.clone().into()); return name; } diff --git a/crates/cli-support/src/wit/mod.rs b/crates/cli-support/src/wit/mod.rs index 24856ea7252..36fb73d95e0 100644 --- a/crates/cli-support/src/wit/mod.rs +++ b/crates/cli-support/src/wit/mod.rs @@ -498,7 +498,12 @@ impl<'a> Context<'a> { self.aux.imports_with_variadic.insert(id); } if *catch { - self.aux.imports_with_catch.insert(id); + // Note that `catch` is applied not to the import itself but to the + // adapter shim we generated, so fetch that shim id and flag it as + // catch here. This basically just needs to be kept in sync with + // `js/mod.rs`. + let adapter = self.adapters.implements.last().unwrap().1; + self.aux.imports_with_catch.insert(adapter); } if *assert_no_shim { self.aux.imports_with_assert_no_shim.insert(id); diff --git a/src/convert/mod.rs b/src/convert/mod.rs index fc24994866c..ce2c0b2c847 100644 --- a/src/convert/mod.rs +++ b/src/convert/mod.rs @@ -6,5 +6,6 @@ mod impls; mod slices; mod traits; +pub use self::impls::*; pub use self::slices::WasmSlice; pub use self::traits::*; diff --git a/src/lib.rs b/src/lib.rs index 325f7710997..05de9cb9fe4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,7 +14,7 @@ use core::marker; use core::mem; use core::ops::{Deref, DerefMut}; -use crate::convert::{FromWasmAbi, WasmSlice}; +use crate::convert::{FromWasmAbi, WasmOptionalF64, WasmSlice}; macro_rules! if_std { ($($i:item)*) => ($( @@ -226,10 +226,8 @@ impl JsValue { T: for<'a> serde::de::Deserialize<'a>, { unsafe { - let mut ret = [0usize; 2]; - __wbindgen_json_serialize(&mut ret, self.idx); - let s = Vec::from_raw_parts(ret[0] as *mut u8, ret[1], ret[1]); - let s = String::from_utf8_unchecked(s); + let ret = __wbindgen_json_serialize(&mut ret, self.idx); + let s = String::from_abi(ret); serde_json::from_str(&s) } } @@ -240,16 +238,7 @@ impl JsValue { /// If this JS value is not an instance of a number then this returns /// `None`. pub fn as_f64(&self) -> Option { - panic!() - // let mut invalid = 0; - // unsafe { - // let ret = __wbindgen_number_get(self.idx, &mut invalid); - // if invalid == 1 { - // None - // } else { - // Some(ret) - // } - // } + unsafe { FromWasmAbi::from_abi(__wbindgen_number_get(self.idx)) } } /// Tests whether this JS value is a JS string. @@ -515,7 +504,7 @@ externs! { fn __wbindgen_is_string(idx: u32) -> u32; fn __wbindgen_is_falsy(idx: u32) -> u32; - // fn __wbindgen_number_get(idx: u32, invalid: *mut u8) -> f64; + fn __wbindgen_number_get(idx: u32) -> WasmOptionalF64; fn __wbindgen_boolean_get(idx: u32) -> u32; fn __wbindgen_string_get(idx: u32) -> WasmSlice; @@ -531,7 +520,7 @@ externs! { fn __wbindgen_describe_closure(a: u32, b: u32, c: u32) -> u32; fn __wbindgen_json_parse(ptr: *const u8, len: usize) -> u32; - fn __wbindgen_json_serialize(ret: *mut [usize; 2], idx: u32) -> (); + fn __wbindgen_json_serialize(idx: u32) -> WasmSlice; fn __wbindgen_jsval_eq(a: u32, b: u32) -> u32; fn __wbindgen_memory() -> u32; From 0f21a8a27eba4af270a5d227f36aea7505eb3f05 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 26 Nov 2019 16:35:00 -0800 Subject: [PATCH 11/35] Now passing the anyref test suite! --- crates/anyref-xform/src/lib.rs | 2 +- crates/backend/src/codegen.rs | 10 +++- crates/cli-support/src/anyref.rs | 70 ++++++++++++++------------ crates/cli-support/src/js/binding.rs | 8 --- crates/cli-support/src/wit/outgoing.rs | 6 ++- crates/cli-support/src/wit/section.rs | 2 - crates/cli-support/src/wit/standard.rs | 11 ---- tests/wasm/imports.js | 4 ++ tests/wasm/imports.rs | 12 +++++ 9 files changed, 67 insertions(+), 58 deletions(-) diff --git a/crates/anyref-xform/src/lib.rs b/crates/anyref-xform/src/lib.rs index bd981519868..0c49d7b085d 100644 --- a/crates/anyref-xform/src/lib.rs +++ b/crates/anyref-xform/src/lib.rs @@ -619,7 +619,7 @@ impl Transform<'_> { // with a fresh type we've been calculating so far. Give the function a // nice name for debugging and then we're good to go! let id = builder.finish(params, funcs); - let name = format!("{}_anyref_shim", name); + let name = format!("{} anyref shim", name); funcs.get_mut(id).name = Some(name); self.shims.insert(id); Ok((id, anyref_ty)) diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs index 442e243f338..82ac71effee 100644 --- a/crates/backend/src/codegen.rs +++ b/crates/backend/src/codegen.rs @@ -618,12 +618,18 @@ impl ToTokens for ast::ImportType { impl OptionIntoWasmAbi for #rust_name { #[inline] - fn none() -> Self::Abi { 0 } + fn none() -> Self::Abi { + 0 + // JsValue::undefined().into_abi() + } } impl<'a> OptionIntoWasmAbi for &'a #rust_name { #[inline] - fn none() -> Self::Abi { 0 } + fn none() -> Self::Abi { + 0 + // JsValue::undefined().into_abi() + } } impl FromWasmAbi for #rust_name { diff --git a/crates/cli-support/src/anyref.rs b/crates/cli-support/src/anyref.rs index ea5c514dd71..41caa469457 100644 --- a/crates/cli-support/src/anyref.rs +++ b/crates/cli-support/src/anyref.rs @@ -45,26 +45,26 @@ pub fn process(module: &mut Module, wasm_interface_types: bool) -> Result<(), Er cfg.run(module)?; + // 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); + } + // TODO: still needed? - // // 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); // @@ -129,11 +129,15 @@ fn import_xform( params: &mut [AdapterType], results: &mut [AdapterType], ) { + struct Arg { + idx: usize, + // Some(false) for a borrowed anyref, Some(true) for an owned one + anyref: Option, + } + let mut to_delete = Vec::new(); let mut iter = instrs.iter().enumerate(); let mut args = Vec::new(); - - let mut prev = None; while let Some((i, instr)) = iter.next() { match instr.instr { Instruction::CallAdapter(_) => break, @@ -142,23 +146,24 @@ fn import_xform( Instruction::TableGet => false, _ => true, }; - let prev = match prev.take() { - Some(prev) => prev as usize, + let mut arg: Arg = match args.pop().unwrap() { + Some(arg) => arg, None => panic!("previous instruction must be `arg.get`"), }; - args.pop(); - args.push(Some(owned)); - match params[prev] { + arg.anyref = Some(owned); + match params[arg.idx] { AdapterType::I32 => {} _ => panic!("must be `i32` type"), } - params[prev] = AdapterType::Anyref; + params[arg.idx] = AdapterType::Anyref; + args.push(Some(arg)); to_delete.push(i); } Instruction::Standard(wit_walrus::Instruction::ArgGet(n)) => { - prev = Some(n); - args.push(None); - continue; + args.push(Some(Arg { + idx: n as usize, + anyref: None, + })); } _ => match instr.stack_change { StackChange::Modified { pushed, popped } => { @@ -174,7 +179,6 @@ fn import_xform( } }, } - prev = None; } let mut ret_anyref = false; @@ -203,8 +207,8 @@ fn import_xform( // values. let args = args .iter() - .enumerate() - .filter_map(|(i, owned)| owned.map(|owned| (i, owned))) + .filter_map(|arg| arg.as_ref()) + .filter_map(|arg| arg.anyref.map(|owned| (arg.idx, owned))) .collect::>(); // ... and register this entire transformation with the anyref diff --git a/crates/cli-support/src/js/binding.rs b/crates/cli-support/src/js/binding.rs index e2956b8b005..523e8cf5da9 100644 --- a/crates/cli-support/src/js/binding.rs +++ b/crates/cli-support/src/js/binding.rs @@ -733,7 +733,6 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction) -> Result<(), Error> { js.push(format!("len{}", i)); } - Instruction::OptionMutableSlice { .. } => panic!(), Instruction::MutableSliceToMemory { kind, malloc, @@ -790,13 +789,6 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction) -> Result<(), Error> { js.push(format!("takeObject({})", val)); } - Instruction::AnyrefLoadOptionOwned => { - js.typescript_optional("any"); - js.cx.expose_take_object(); - let val = js.pop(); - js.push(format!("{0} === 0 ? undefined : takeObject({0})", val)); - } - Instruction::StringFromChar => { js.typescript_required("string"); let val = js.pop(); diff --git a/crates/cli-support/src/wit/outgoing.rs b/crates/cli-support/src/wit/outgoing.rs index 46f5967edf1..364731937f2 100644 --- a/crates/cli-support/src/wit/outgoing.rs +++ b/crates/cli-support/src/wit/outgoing.rs @@ -219,9 +219,11 @@ impl InstructionBuilder<'_, '_> { fn outgoing_option(&mut self, arg: &Descriptor) -> Result<(), Error> { match arg { Descriptor::Anyref => { + // This is set to `undefined` in the `None` case and otherwise + // is the valid owned index. self.instruction( &[AdapterType::I32], - Instruction::AnyrefLoadOptionOwned, + Instruction::AnyrefLoadOwned, &[AdapterType::Anyref], ); } @@ -306,6 +308,8 @@ impl InstructionBuilder<'_, '_> { fn outgoing_option_ref(&mut self, _mutable: bool, arg: &Descriptor) -> Result<(), Error> { match arg { Descriptor::Anyref => { + // If this is `Some` then it's the index, otherwise if it's + // `None` then it's the index pointing to undefined. self.instruction( &[AdapterType::I32], Instruction::TableGet, diff --git a/crates/cli-support/src/wit/section.rs b/crates/cli-support/src/wit/section.rs index b9bc0c3477e..c813d466f88 100644 --- a/crates/cli-support/src/wit/section.rs +++ b/crates/cli-support/src/wit/section.rs @@ -254,9 +254,7 @@ fn translate_instruction( | I32FromOptionChar | I32FromOptionEnum { .. } | FromOptionNative { .. } - | OptionMutableSlice { .. } | OptionVector { .. } - | AnyrefLoadOptionOwned | OptionRustFromI32 { .. } | OptionVectorLoad { .. } | OptionView { .. } diff --git a/crates/cli-support/src/wit/standard.rs b/crates/cli-support/src/wit/standard.rs index 7252badff2e..476b25fd4d5 100644 --- a/crates/cli-support/src/wit/standard.rs +++ b/crates/cli-support/src/wit/standard.rs @@ -189,15 +189,6 @@ pub enum Instruction { mem: walrus::MemoryId, }, - /// Pops an anyref, pushes pointer/length or all zeros. Will update original - /// view if mutable. - OptionMutableSlice { - kind: VectorKind, - malloc: walrus::FunctionId, - free: walrus::FunctionId, - mem: walrus::MemoryId, - }, - /// Pops an anyref, pushes pointer/length or all zeros OptionVector { kind: VectorKind, @@ -209,8 +200,6 @@ pub enum Instruction { BoolFromI32, /// pops `i32`, loads anyref at that slot, dealloates anyref, pushes `anyref` AnyrefLoadOwned, - /// pops `i32`, loads anyref at that slot, dealloates anyref, pushes `anyref` - AnyrefLoadOptionOwned, /// pops `i32`, pushes string from that `char` StringFromChar, /// pops two `i32`, pushes a 64-bit number diff --git a/tests/wasm/imports.js b/tests/wasm/imports.js index 6d11ccb5de5..3254f817d65 100644 --- a/tests/wasm/imports.js +++ b/tests/wasm/imports.js @@ -115,3 +115,7 @@ class StaticMethodCheck { } exports.StaticMethodCheck = StaticMethodCheck; + +exports.receive_undefined = val => { + assert.strictEqual(val, undefined); +}; diff --git a/tests/wasm/imports.rs b/tests/wasm/imports.rs index 4dc2b4011bd..d6075a0e04b 100644 --- a/tests/wasm/imports.rs +++ b/tests/wasm/imports.rs @@ -57,6 +57,12 @@ extern "C" { fn static_method_of_right_this(); static STATIC_STRING: String; + + type PassOutOptionUndefined; + #[wasm_bindgen(js_name = "receive_undefined")] + fn receive_undefined_ref(arg: Option<&PassOutOptionUndefined>); + #[wasm_bindgen(js_name = "receive_undefined")] + fn receive_undefined_owned(arg: Option); } #[wasm_bindgen] @@ -248,3 +254,9 @@ fn static_string_ok() { fn static_method_of_has_right_this() { StaticMethodCheck::static_method_of_right_this(); } + +#[wasm_bindgen_test] +fn pass_out_options_as_undefined() { + receive_undefined_ref(None); + receive_undefined_owned(None); +} From 3298373e3d9cb884305daef296694154eba3d6f4 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 27 Nov 2019 10:02:50 -0800 Subject: [PATCH 12/35] Depend on git wit-walrus --- crates/cli-support/Cargo.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/cli-support/Cargo.toml b/crates/cli-support/Cargo.toml index 7761c748347..4985f1fefa1 100644 --- a/crates/cli-support/Cargo.toml +++ b/crates/cli-support/Cargo.toml @@ -25,5 +25,4 @@ wasm-bindgen-multi-value-xform = { path = '../multi-value-xform', version = '=0. wasm-bindgen-threads-xform = { path = '../threads-xform', version = '=0.2.55' } wasm-bindgen-wasm-conventions = { path = '../wasm-conventions', version = '=0.2.55' } wasm-bindgen-wasm-interpreter = { path = "../wasm-interpreter", version = '=0.2.55' } -#wit-walrus = { git = 'https://github.com/alexcrichton/wasm-interface-types', branch = 'walrus' } -wit-walrus = { path = "../../../wasm-interface-types/crates/walrus" } +wit-walrus = { git = 'https://github.com/bytecodealliance/wasm-interface-types' } From 4664199bbcfc8cc22451d9dbf1928554181333f7 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 27 Nov 2019 19:53:40 -0800 Subject: [PATCH 13/35] Fix botched merge --- crates/cli-support/src/wit/nonstandard.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/cli-support/src/wit/nonstandard.rs b/crates/cli-support/src/wit/nonstandard.rs index b83f0f16635..bb590800884 100644 --- a/crates/cli-support/src/wit/nonstandard.rs +++ b/crates/cli-support/src/wit/nonstandard.rs @@ -129,6 +129,8 @@ pub struct AuxStruct { pub name: String, /// The copied Rust comments to forward to JS pub comments: String, + /// Whether to generate helper methods for inspecting the class + pub is_inspectable: bool, } /// All possible types of imports that can be imported by a wasm module. From 93c31b5f49451f4ec81c9d1453dd2034cd72f82c Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 27 Nov 2019 19:59:27 -0800 Subject: [PATCH 14/35] Remove unnecessary commented out code --- crates/backend/src/codegen.rs | 2 -- tests/wasm/imports.rs | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs index 82ac71effee..16ceda1ad34 100644 --- a/crates/backend/src/codegen.rs +++ b/crates/backend/src/codegen.rs @@ -620,7 +620,6 @@ impl ToTokens for ast::ImportType { #[inline] fn none() -> Self::Abi { 0 - // JsValue::undefined().into_abi() } } @@ -628,7 +627,6 @@ impl ToTokens for ast::ImportType { #[inline] fn none() -> Self::Abi { 0 - // JsValue::undefined().into_abi() } } diff --git a/tests/wasm/imports.rs b/tests/wasm/imports.rs index d6075a0e04b..9e4ce8fdce7 100644 --- a/tests/wasm/imports.rs +++ b/tests/wasm/imports.rs @@ -258,5 +258,7 @@ fn static_method_of_has_right_this() { #[wasm_bindgen_test] fn pass_out_options_as_undefined() { receive_undefined_ref(None); + receive_undefined_ref(None); + receive_undefined_owned(None); receive_undefined_owned(None); } From 20ac2dc9b4cb2fea1851890e1e15a6c218fabdd3 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 27 Nov 2019 20:02:43 -0800 Subject: [PATCH 15/35] Beef up a test about optional anyref types --- tests/wasm/imports.js | 8 ++++++++ tests/wasm/imports.rs | 12 ++++++++++++ 2 files changed, 20 insertions(+) diff --git a/tests/wasm/imports.js b/tests/wasm/imports.js index 3254f817d65..44182a38645 100644 --- a/tests/wasm/imports.js +++ b/tests/wasm/imports.js @@ -119,3 +119,11 @@ exports.StaticMethodCheck = StaticMethodCheck; exports.receive_undefined = val => { assert.strictEqual(val, undefined); }; + +const VAL = {}; + +exports.receive_some = val => { + assert.strictEqual(val, VAL); +}; + +exports.get_some_val = () => VAL; diff --git a/tests/wasm/imports.rs b/tests/wasm/imports.rs index 9e4ce8fdce7..116c7aff8d4 100644 --- a/tests/wasm/imports.rs +++ b/tests/wasm/imports.rs @@ -58,11 +58,17 @@ extern "C" { static STATIC_STRING: String; + #[derive(Clone)] type PassOutOptionUndefined; + fn get_some_val() -> PassOutOptionUndefined; #[wasm_bindgen(js_name = "receive_undefined")] fn receive_undefined_ref(arg: Option<&PassOutOptionUndefined>); #[wasm_bindgen(js_name = "receive_undefined")] fn receive_undefined_owned(arg: Option); + #[wasm_bindgen(js_name = "receive_some")] + fn receive_some_ref(arg: Option<&PassOutOptionUndefined>); + #[wasm_bindgen(js_name = "receive_some")] + fn receive_some_owned(arg: Option); } #[wasm_bindgen] @@ -261,4 +267,10 @@ fn pass_out_options_as_undefined() { receive_undefined_ref(None); receive_undefined_owned(None); receive_undefined_owned(None); + + let v = get_some_val(); + receive_some_ref(Some(&v)); + receive_some_ref(Some(&v)); + receive_some_owned(Some(v.clone())); + receive_some_owned(Some(v)); } From 7f2729eea968ded6e4cbde5ffd68000bf69b248f Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 27 Nov 2019 20:09:45 -0800 Subject: [PATCH 16/35] Uncomment and fix init function generation --- crates/cli-support/src/js/mod.rs | 326 +++++++++++++++---------------- 1 file changed, 163 insertions(+), 163 deletions(-) diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 65a17171f1f..e2db31907fd 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -444,158 +444,155 @@ impl<'a> Context<'a> { needs_manual_start: bool, mut imports: Option<&mut String>, ) -> Result<(String, String), Error> { - panic!() - // let module_name = "wbg"; - // let mem = self.module.memories.get(self.memory); - // let (init_memory1, init_memory2) = if let Some(id) = mem.import { - // self.module.imports.get_mut(id).module = module_name.to_string(); - // let mut memory = String::from("new WebAssembly.Memory({"); - // memory.push_str(&format!("initial:{}", mem.initial)); - // if let Some(max) = mem.maximum { - // memory.push_str(&format!(",maximum:{}", max)); - // } - // if mem.shared { - // memory.push_str(",shared:true"); - // } - // memory.push_str("})"); - // self.imports_post.push_str("let memory;\n"); - // ( - // format!("memory = imports.{}.memory = maybe_memory;", module_name), - // format!("memory = imports.{}.memory = {};", module_name, memory), - // ) - // } else { - // (String::new(), String::new()) - // }; - // let init_memory_arg = if mem.import.is_some() { - // ", maybe_memory" - // } else { - // "" - // }; - // - // let default_module_path = match self.config.mode { - // OutputMode::Web => { - // "\ - // if (typeof module === 'undefined') { - // module = import.meta.url.replace(/\\.js$/, '_bg.wasm'); - // }" - // } - // _ => "", - // }; - // - // let ts = Self::ts_for_init_fn(mem.import.is_some(), !default_module_path.is_empty()); - // - // // Initialize the `imports` object for all import definitions that we're - // // directed to wire up. - // let mut imports_init = String::new(); - // if self.wasm_import_definitions.len() > 0 { - // imports_init.push_str("imports."); - // imports_init.push_str(module_name); - // imports_init.push_str(" = {};\n"); - // } - // for (id, js) in sorted_iter(&self.wasm_import_definitions) { - // let import = self.module.imports.get_mut(*id); - // import.module = module_name.to_string(); - // imports_init.push_str("imports."); - // imports_init.push_str(module_name); - // imports_init.push_str("."); - // imports_init.push_str(&import.name); - // imports_init.push_str(" = "); - // imports_init.push_str(js.trim()); - // imports_init.push_str(";\n"); - // } - // - // let extra_modules = self - // .module - // .imports - // .iter() - // .filter(|i| !self.wasm_import_definitions.contains_key(&i.id())) - // .filter(|i| { - // // Importing memory is handled specially in this area, so don't - // // consider this a candidate for importing from extra modules. - // match i.kind { - // walrus::ImportKind::Memory(_) => false, - // _ => true, - // } - // }) - // .map(|i| &i.module) - // .collect::>(); - // for (i, extra) in extra_modules.iter().enumerate() { - // let imports = match &mut imports { - // Some(list) => list, - // None => bail!( - // "cannot import from modules (`{}`) with `--no-modules`", - // extra - // ), - // }; - // imports.push_str(&format!("import * as __wbg_star{} from '{}';\n", i, extra)); - // imports_init.push_str(&format!("imports['{}'] = __wbg_star{};\n", extra, i)); - // } - // - // let js = format!( - // "\ - // function init(module{init_memory_arg}) {{ - // {default_module_path} - // let result; - // const imports = {{}}; - // {imports_init} - // if ((typeof URL === 'function' && module instanceof URL) || typeof module === 'string' || (typeof Request === 'function' && module instanceof Request)) {{ - // {init_memory2} - // const response = fetch(module); - // if (typeof WebAssembly.instantiateStreaming === 'function') {{ - // result = WebAssembly.instantiateStreaming(response, imports) - // .catch(e => {{ - // return response - // .then(r => {{ - // if (r.headers.get('Content-Type') != 'application/wasm') {{ - // console.warn(\"`WebAssembly.instantiateStreaming` failed \ - // because your server does not serve wasm with \ - // `application/wasm` MIME type. Falling back to \ - // `WebAssembly.instantiate` which is slower. Original \ - // error:\\n\", e); - // return r.arrayBuffer(); - // }} else {{ - // throw e; - // }} - // }}) - // .then(bytes => WebAssembly.instantiate(bytes, imports)); - // }}); - // }} else {{ - // result = response - // .then(r => r.arrayBuffer()) - // .then(bytes => WebAssembly.instantiate(bytes, imports)); - // }} - // }} else {{ - // {init_memory1} - // result = WebAssembly.instantiate(module, imports) - // .then(result => {{ - // if (result instanceof WebAssembly.Instance) {{ - // return {{ instance: result, module }}; - // }} else {{ - // return result; - // }} - // }}); - // }} - // return result.then(({{instance, module}}) => {{ - // wasm = instance.exports; - // init.__wbindgen_wasm_module = module; - // {start} - // return wasm; - // }}); - // }} - // ", - // init_memory_arg = init_memory_arg, - // default_module_path = default_module_path, - // init_memory1 = init_memory1, - // init_memory2 = init_memory2, - // start = if needs_manual_start { - // "wasm.__wbindgen_start();" - // } else { - // "" - // }, - // imports_init = imports_init, - // ); - // - // Ok((js, ts)) + let module_name = "wbg"; + let mut init_memory_arg = ""; + let mut init_memory1 = String::new(); + let mut init_memory2 = String::new(); + let mut has_memory = false; + if let Some(mem) = self.module.memories.iter().next() { + if let Some(id) = mem.import { + self.module.imports.get_mut(id).module = module_name.to_string(); + let mut memory = String::from("new WebAssembly.Memory({"); + memory.push_str(&format!("initial:{}", mem.initial)); + if let Some(max) = mem.maximum { + memory.push_str(&format!(",maximum:{}", max)); + } + if mem.shared { + memory.push_str(",shared:true"); + } + memory.push_str("})"); + self.imports_post.push_str("let memory;\n"); + init_memory1 = format!("memory = imports.{}.memory = maybe_memory;", module_name); + init_memory2 = format!("memory = imports.{}.memory = {};", module_name, memory); + init_memory_arg = ", maybe_memory"; + has_memory = true; + } + } + + let default_module_path = match self.config.mode { + OutputMode::Web => { + "\ + if (typeof module === 'undefined') { + module = import.meta.url.replace(/\\.js$/, '_bg.wasm'); + }" + } + _ => "", + }; + + let ts = Self::ts_for_init_fn(has_memory, !default_module_path.is_empty()); + + // Initialize the `imports` object for all import definitions that we're + // directed to wire up. + let mut imports_init = String::new(); + if self.wasm_import_definitions.len() > 0 { + imports_init.push_str("imports."); + imports_init.push_str(module_name); + imports_init.push_str(" = {};\n"); + } + for (id, js) in sorted_iter(&self.wasm_import_definitions) { + let import = self.module.imports.get_mut(*id); + import.module = module_name.to_string(); + imports_init.push_str("imports."); + imports_init.push_str(module_name); + imports_init.push_str("."); + imports_init.push_str(&import.name); + imports_init.push_str(" = "); + imports_init.push_str(js.trim()); + imports_init.push_str(";\n"); + } + + let extra_modules = self + .module + .imports + .iter() + .filter(|i| !self.wasm_import_definitions.contains_key(&i.id())) + .filter(|i| { + // Importing memory is handled specially in this area, so don't + // consider this a candidate for importing from extra modules. + match i.kind { + walrus::ImportKind::Memory(_) => false, + _ => true, + } + }) + .map(|i| &i.module) + .collect::>(); + for (i, extra) in extra_modules.iter().enumerate() { + let imports = match &mut imports { + Some(list) => list, + None => bail!( + "cannot import from modules (`{}`) with `--no-modules`", + extra + ), + }; + imports.push_str(&format!("import * as __wbg_star{} from '{}';\n", i, extra)); + imports_init.push_str(&format!("imports['{}'] = __wbg_star{};\n", extra, i)); + } + + let js = format!( + "\ + function init(module{init_memory_arg}) {{ + {default_module_path} + let result; + const imports = {{}}; + {imports_init} + if ((typeof URL === 'function' && module instanceof URL) || typeof module === 'string' || (typeof Request === 'function' && module instanceof Request)) {{ + {init_memory2} + const response = fetch(module); + if (typeof WebAssembly.instantiateStreaming === 'function') {{ + result = WebAssembly.instantiateStreaming(response, imports) + .catch(e => {{ + return response + .then(r => {{ + if (r.headers.get('Content-Type') != 'application/wasm') {{ + console.warn(\"`WebAssembly.instantiateStreaming` failed \ + because your server does not serve wasm with \ + `application/wasm` MIME type. Falling back to \ + `WebAssembly.instantiate` which is slower. Original \ + error:\\n\", e); + return r.arrayBuffer(); + }} else {{ + throw e; + }} + }}) + .then(bytes => WebAssembly.instantiate(bytes, imports)); + }}); + }} else {{ + result = response + .then(r => r.arrayBuffer()) + .then(bytes => WebAssembly.instantiate(bytes, imports)); + }} + }} else {{ + {init_memory1} + result = WebAssembly.instantiate(module, imports) + .then(result => {{ + if (result instanceof WebAssembly.Instance) {{ + return {{ instance: result, module }}; + }} else {{ + return result; + }} + }}); + }} + return result.then(({{instance, module}}) => {{ + wasm = instance.exports; + init.__wbindgen_wasm_module = module; + {start} + return wasm; + }}); + }} + ", + init_memory_arg = init_memory_arg, + default_module_path = default_module_path, + init_memory1 = init_memory1, + init_memory2 = init_memory2, + start = if needs_manual_start { + "wasm.__wbindgen_start();" + } else { + "" + }, + imports_init = imports_init, + ); + + Ok((js, ts)) } fn write_classes(&mut self) -> Result<(), Error> { @@ -976,20 +973,23 @@ impl<'a> Context<'a> { // This might be not very intuitive, but such calls are usually more // expensive in mainstream engines than staying in the JS, and // charCodeAt on ASCII strings is usually optimised to raw bytes. - let encode_as_ascii = "\ - let len = arg.length; - let ptr = malloc(len); + let encode_as_ascii = format!( + "\ + let len = arg.length; + let ptr = malloc(len); - const mem = getUint8Memory(); + const mem = {}(); - let offset = 0; + let offset = 0; - for (; offset < len; offset++) { - const code = arg.charCodeAt(offset); - if (code > 0x7F) break; - mem[ptr + offset] = code; - } - "; + for (; offset < len; offset++) {{ + const code = arg.charCodeAt(offset); + if (code > 0x7F) break; + mem[ptr + offset] = code; + }} + ", + mem + ); // TODO: // When converting a JS string to UTF-8, the maximum size is `arg.length * 3`, From 46ba8b60a595cbc9fd0e86359ce0d69e8ebba2e3 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 27 Nov 2019 20:10:33 -0800 Subject: [PATCH 17/35] Try to fix compilation of serde feature --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 05de9cb9fe4..3b54fb47b28 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -226,7 +226,7 @@ impl JsValue { T: for<'a> serde::de::Deserialize<'a>, { unsafe { - let ret = __wbindgen_json_serialize(&mut ret, self.idx); + let ret = __wbindgen_json_serialize(self.idx); let s = String::from_abi(ret); serde_json::from_str(&s) } From 7495d4d99be86262d430292e4627230c5adc5603 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Sat, 30 Nov 2019 11:04:46 -0800 Subject: [PATCH 18/35] Add some unit tests for the anyref transform Get some examples of what the transform looks like in `*.wat` files --- azure-pipelines.yml | 2 + crates/anyref-xform/Cargo.toml | 10 + crates/anyref-xform/tests/all.rs | 256 ++++++++++++++++++ .../anyref-xform/tests/anyref-param-owned.wat | 31 +++ crates/anyref-xform/tests/anyref-param.wat | 43 +++ .../tests/clone-ref-intrinsic.wat | 50 ++++ .../anyref-xform/tests/drop-ref-intrinsic.wat | 37 +++ crates/anyref-xform/tests/ret-anyref.wat | 33 +++ .../tests/table-grow-intrinsic.wat | 40 +++ .../tests/table-set-null-intrinsic.wat | 38 +++ 10 files changed, 540 insertions(+) create mode 100644 crates/anyref-xform/tests/all.rs create mode 100644 crates/anyref-xform/tests/anyref-param-owned.wat create mode 100644 crates/anyref-xform/tests/anyref-param.wat create mode 100644 crates/anyref-xform/tests/clone-ref-intrinsic.wat create mode 100644 crates/anyref-xform/tests/drop-ref-intrinsic.wat create mode 100644 crates/anyref-xform/tests/ret-anyref.wat create mode 100644 crates/anyref-xform/tests/table-grow-intrinsic.wat create mode 100644 crates/anyref-xform/tests/table-set-null-intrinsic.wat diff --git a/azure-pipelines.yml b/azure-pipelines.yml index c0f30506b74..99c13808bd8 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -96,6 +96,8 @@ jobs: displayName: "wasm-bindgen-cli-support tests" - script: cargo test -p wasm-bindgen-cli displayName: "wasm-bindgen-cli tests" + - script: cargo test -p wasm-bindgen-anyref-xform + displayName: "wasm-bindgen-anyref-xform tests" - job: test_web_sys displayName: "Run web-sys crate tests" diff --git a/crates/anyref-xform/Cargo.toml b/crates/anyref-xform/Cargo.toml index b2c7db7a320..536595905a4 100644 --- a/crates/anyref-xform/Cargo.toml +++ b/crates/anyref-xform/Cargo.toml @@ -14,3 +14,13 @@ edition = '2018' [dependencies] anyhow = "1.0" walrus = "0.13.0" + +[dev-dependencies] +rayon = "1.0" +wasmprinter = "0.2" +wast = "3.0" +wat = "1.0" + +[[test]] +name = "all" +harness = false diff --git a/crates/anyref-xform/tests/all.rs b/crates/anyref-xform/tests/all.rs new file mode 100644 index 00000000000..7634191585f --- /dev/null +++ b/crates/anyref-xform/tests/all.rs @@ -0,0 +1,256 @@ +//! A small test framework to execute a test function over all files in a +//! directory. +//! +//! Each file in the directory has its own `CHECK-ALL` annotation indicating the +//! expected output of the test. That can be automatically updated with +//! `BLESS=1` in the environment. Otherwise the test are checked against the +//! listed expectation. + +use anyhow::{anyhow, bail, Context, Result}; +use rayon::prelude::*; +use std::env; +use std::fs; +use std::path::{Path, PathBuf}; +use wast::parser::{Parse, Parser}; + +fn main() { + run("tests".as_ref(), runtest); +} + +fn runtest(test: &Test) -> Result { + let wasm = wat::parse_file(&test.file)?; + let mut walrus = walrus::Module::from_buffer(&wasm)?; + let mut cx = wasm_bindgen_anyref_xform::Context::default(); + cx.prepare(&mut walrus)?; + for directive in test.directives.iter() { + match &directive.kind { + DirectiveKind::Export(name) => { + let export = walrus + .exports + .iter() + .find(|e| e.name == *name) + .ok_or_else(|| anyhow!("failed to find export"))?; + cx.export_xform(export.id(), &directive.args, directive.ret_anyref); + } + DirectiveKind::Import(module, field) => { + let import = walrus + .imports + .iter() + .find(|e| e.module == *module && e.name == *field) + .ok_or_else(|| anyhow!("failed to find export"))?; + cx.import_xform(import.id(), &directive.args, directive.ret_anyref); + } + DirectiveKind::Table(idx) => { + cx.table_element_xform(*idx, &directive.args, directive.ret_anyref); + } + } + } + cx.run(&mut walrus)?; + walrus::passes::gc::run(&mut walrus); + let printed = wasmprinter::print_bytes(&walrus.emit_wasm())?; + Ok(printed) +} + +fn run(dir: &Path, run: fn(&Test) -> Result) { + let mut tests = Vec::new(); + find_tests(dir, &mut tests); + let filter = std::env::args().nth(1); + + let bless = env::var("BLESS").is_ok(); + let tests = tests + .iter() + .filter(|test| { + if let Some(filter) = &filter { + if let Some(s) = test.file_name().and_then(|s| s.to_str()) { + if !s.contains(filter) { + return false; + } + } + } + true + }) + .collect::>(); + + println!("\nrunning {} tests\n", tests.len()); + + let errors = tests + .par_iter() + .filter_map(|test| run_test(test, bless, run).err()) + .collect::>(); + + if !errors.is_empty() { + for msg in errors.iter() { + eprintln!("error: {:?}", msg); + } + + panic!("{} tests failed", errors.len()) + } + + println!("test result: ok. {} passed\n", tests.len()); +} + +fn run_test(test: &Path, bless: bool, run: fn(&Test) -> anyhow::Result) -> Result<()> { + (|| -> Result<_> { + let expected = Test::from_file(test)?; + let actual = run(&expected)?; + expected.check(&actual, bless)?; + Ok(()) + })() + .context(format!("test failed - {}", test.display()))?; + Ok(()) +} + +fn find_tests(path: &Path, tests: &mut Vec) { + for f in path.read_dir().unwrap() { + let f = f.unwrap(); + if f.file_type().unwrap().is_dir() { + find_tests(&f.path(), tests); + continue; + } + match f.path().extension().and_then(|s| s.to_str()) { + Some("wat") => {} + _ => continue, + } + tests.push(f.path()); + } +} + +struct Test { + file: PathBuf, + directives: Vec, + assertion: Option, +} + +struct Directive { + args: Vec<(usize, bool)>, + ret_anyref: bool, + kind: DirectiveKind, +} + +enum DirectiveKind { + Import(String, String), + Export(String), + Table(u32), +} + +impl Test { + fn from_file(path: &Path) -> Result { + let contents = fs::read_to_string(path)?; + let mut iter = contents.lines(); + let mut assertion = None; + let mut directives = Vec::new(); + while let Some(line) = iter.next() { + if line.starts_with("(; CHECK-ALL:") { + let mut pattern = String::new(); + while let Some(line) = iter.next() { + if line == ";)" { + break; + } + pattern.push_str(line); + pattern.push_str("\n"); + } + while pattern.ends_with("\n") { + pattern.pop(); + } + if iter.next().is_some() { + bail!("CHECK-ALL must be at the end of the file"); + } + assertion = Some(pattern); + continue; + } + + if !line.starts_with(";; @xform") { + continue; + } + let directive = &line[9..]; + let buf = wast::parser::ParseBuffer::new(directive)?; + directives.push(wast::parser::parse::(&buf)?); + } + Ok(Test { + file: path.to_path_buf(), + directives, + assertion, + }) + } + + fn check(&self, output: &str, bless: bool) -> Result<()> { + if bless { + update_output(&self.file, output) + } else if let Some(pattern) = &self.assertion { + if output == pattern { + return Ok(()); + } + bail!( + "expected\n {}\n\nactual\n {}", + pattern.replace("\n", "\n "), + output.replace("\n", "\n ") + ); + } else { + bail!( + "no test assertions were found in this file, but you can \ + rerun tests with `BLESS=1` to automatically add assertions \ + to this file" + ); + } + } +} + +fn update_output(path: &Path, output: &str) -> Result<()> { + let contents = fs::read_to_string(path)?; + let start = contents.find("(; CHECK-ALL:").unwrap_or(contents.len()); + + let mut new_output = String::new(); + for line in output.lines() { + new_output.push_str(line); + new_output.push_str("\n"); + } + let new = format!( + "{}\n\n(; CHECK-ALL:\n{}\n;)\n", + contents[..start].trim(), + new_output.trim_end() + ); + fs::write(path, new)?; + Ok(()) +} + +impl<'a> Parse<'a> for Directive { + fn parse(parser: Parser<'a>) -> wast::parser::Result { + use wast::kw; + wast::custom_keyword!(anyref_owned); + wast::custom_keyword!(anyref_borrowed); + wast::custom_keyword!(other); + + let kind = if parser.peek::() { + parser.parse::()?; + DirectiveKind::Import(parser.parse()?, parser.parse()?) + } else if parser.peek::() { + parser.parse::()?; + DirectiveKind::Export(parser.parse()?) + } else { + parser.parse::()?; + DirectiveKind::Table(parser.parse()?) + }; + let mut args = Vec::new(); + parser.parens(|p| { + while !p.is_empty() { + if parser.peek::() { + parser.parse::()?; + args.push((args.len(), true)); + } else if parser.peek::() { + parser.parse::()?; + args.push((args.len(), false)); + } else { + parser.parse::()?; + }; + } + Ok(()) + })?; + + let ret_anyref = parser.parse::>()?.is_some(); + Ok(Directive { + args, + ret_anyref, + kind, + }) + } +} diff --git a/crates/anyref-xform/tests/anyref-param-owned.wat b/crates/anyref-xform/tests/anyref-param-owned.wat new file mode 100644 index 00000000000..fa3148b9b1a --- /dev/null +++ b/crates/anyref-xform/tests/anyref-param-owned.wat @@ -0,0 +1,31 @@ +;; @xform export "foo" (anyref_owned) + +(module + (func $foo (export "foo") (param i32)) + (func $alloc (export "__wbindgen_anyref_table_alloc") (result i32) + i32.const 0) + (func $dealloc (export "__wbindgen_anyref_table_dealloc") (param i32)) +) + +(; CHECK-ALL: +(module + (type (;0;) (func (result i32))) + (type (;1;) (func (param i32))) + (type (;2;) (func (param anyref))) + (func $foo anyref shim (type 2) (param anyref) + (local i32) + call $alloc + local.tee 1 + local.get 0 + table.set 0 + local.get 1 + call $foo) + (func $alloc (type 0) (result i32) + i32.const 0) + (func $foo (type 1) (param i32)) + (func $dealloc (type 1) (param i32)) + (table (;0;) 32 anyref) + (export "foo" (func $foo anyref shim)) + (export "__wbindgen_anyref_table_alloc" (func $alloc)) + (export "__wbindgen_anyref_table_dealloc" (func $dealloc))) +;) diff --git a/crates/anyref-xform/tests/anyref-param.wat b/crates/anyref-xform/tests/anyref-param.wat new file mode 100644 index 00000000000..a9058858188 --- /dev/null +++ b/crates/anyref-xform/tests/anyref-param.wat @@ -0,0 +1,43 @@ +;; @xform export "foo" (anyref_borrowed) + +(module + (func $foo (export "foo") (param i32)) + (func $alloc (export "__wbindgen_anyref_table_alloc") (result i32) + i32.const 0) + (func $dealloc (export "__wbindgen_anyref_table_dealloc") (param i32)) +) + +(; CHECK-ALL: +(module + (type (;0;) (func (result i32))) + (type (;1;) (func (param i32))) + (type (;2;) (func (param anyref))) + (func $foo anyref shim (type 2) (param anyref) + (local i32) + global.get 0 + i32.const 1 + i32.sub + local.tee 1 + global.set 0 + local.get 1 + local.get 0 + table.set 0 + local.get 1 + call $foo + local.get 1 + ref.null + table.set 0 + local.get 1 + i32.const 1 + i32.add + global.set 0) + (func $alloc (type 0) (result i32) + i32.const 0) + (func $foo (type 1) (param i32)) + (func $dealloc (type 1) (param i32)) + (table (;0;) 32 anyref) + (global (;0;) (mut i32) (i32.const 32)) + (export "foo" (func $foo anyref shim)) + (export "__wbindgen_anyref_table_alloc" (func $alloc)) + (export "__wbindgen_anyref_table_dealloc" (func $dealloc))) +;) diff --git a/crates/anyref-xform/tests/clone-ref-intrinsic.wat b/crates/anyref-xform/tests/clone-ref-intrinsic.wat new file mode 100644 index 00000000000..47d384df834 --- /dev/null +++ b/crates/anyref-xform/tests/clone-ref-intrinsic.wat @@ -0,0 +1,50 @@ +;; @xform export "foo" (anyref_owned) anyref_owned + +(module + (import "__wbindgen_placeholder__" "__wbindgen_object_clone_ref" + (func $clone (param i32) (result i32))) + (func $foo (export "foo") (param i32) (result i32) + local.get 0 + call $clone) + (func $alloc (export "__wbindgen_anyref_table_alloc") (result i32) + i32.const 0) + (func $dealloc (export "__wbindgen_anyref_table_dealloc") (param i32)) +) + +(; CHECK-ALL: +(module + (type (;0;) (func (result i32))) + (type (;1;) (func (param i32))) + (type (;2;) (func (param i32) (result i32))) + (type (;3;) (func (param anyref) (result anyref))) + (func $foo anyref shim (type 3) (param anyref) (result anyref) + (local i32) + call $alloc + local.tee 1 + local.get 0 + table.set 0 + local.get 1 + call $foo + local.tee 1 + table.get 0 + local.get 1 + call $dealloc) + (func $__wbindgen_object_clone_ref (type 2) (param i32) (result i32) + (local i32) + call $alloc + local.tee 1 + local.get 0 + table.get 0 + table.set 0 + local.get 1) + (func $foo (type 2) (param i32) (result i32) + local.get 0 + call $__wbindgen_object_clone_ref) + (func $alloc (type 0) (result i32) + i32.const 0) + (func $dealloc (type 1) (param i32)) + (table (;0;) 32 anyref) + (export "foo" (func $foo anyref shim)) + (export "__wbindgen_anyref_table_alloc" (func $alloc)) + (export "__wbindgen_anyref_table_dealloc" (func $dealloc))) +;) diff --git a/crates/anyref-xform/tests/drop-ref-intrinsic.wat b/crates/anyref-xform/tests/drop-ref-intrinsic.wat new file mode 100644 index 00000000000..f1d2a2e67e2 --- /dev/null +++ b/crates/anyref-xform/tests/drop-ref-intrinsic.wat @@ -0,0 +1,37 @@ +;; @xform export "foo" (anyref_owned) + +(module + (import "__wbindgen_placeholder__" "__wbindgen_object_drop_ref" + (func $drop (param i32))) + (func $foo (export "foo") (param i32) + local.get 0 + call $drop) + (func $alloc (export "__wbindgen_anyref_table_alloc") (result i32) + i32.const 0) + (func $dealloc (export "__wbindgen_anyref_table_dealloc") (param i32)) +) + +(; CHECK-ALL: +(module + (type (;0;) (func (result i32))) + (type (;1;) (func (param i32))) + (type (;2;) (func (param anyref))) + (func $foo anyref shim (type 2) (param anyref) + (local i32) + call $alloc + local.tee 1 + local.get 0 + table.set 0 + local.get 1 + call $foo) + (func $foo (type 1) (param i32) + local.get 0 + call $dealloc) + (func $alloc (type 0) (result i32) + i32.const 0) + (func $dealloc (type 1) (param i32)) + (table (;0;) 32 anyref) + (export "foo" (func $foo anyref shim)) + (export "__wbindgen_anyref_table_alloc" (func $alloc)) + (export "__wbindgen_anyref_table_dealloc" (func $dealloc))) +;) diff --git a/crates/anyref-xform/tests/ret-anyref.wat b/crates/anyref-xform/tests/ret-anyref.wat new file mode 100644 index 00000000000..77df8ab6caa --- /dev/null +++ b/crates/anyref-xform/tests/ret-anyref.wat @@ -0,0 +1,33 @@ +;; @xform export "foo" () anyref_owned + +(module + (func $foo (export "foo") (result i32) + i32.const 0) + + (func $alloc (export "__wbindgen_anyref_table_alloc") (result i32) + i32.const 0) + (func $dealloc (export "__wbindgen_anyref_table_dealloc") (param i32)) +) + +(; CHECK-ALL: +(module + (type (;0;) (func (result i32))) + (type (;1;) (func (result anyref))) + (type (;2;) (func (param i32))) + (func $foo anyref shim (type 1) (result anyref) + (local i32) + call $foo + local.tee 0 + table.get 0 + local.get 0 + call $dealloc) + (func $foo (type 0) (result i32) + i32.const 0) + (func $alloc (type 0) (result i32) + i32.const 0) + (func $dealloc (type 2) (param i32)) + (table (;0;) 32 anyref) + (export "foo" (func $foo anyref shim)) + (export "__wbindgen_anyref_table_alloc" (func $alloc)) + (export "__wbindgen_anyref_table_dealloc" (func $dealloc))) +;) diff --git a/crates/anyref-xform/tests/table-grow-intrinsic.wat b/crates/anyref-xform/tests/table-grow-intrinsic.wat new file mode 100644 index 00000000000..363165b11cd --- /dev/null +++ b/crates/anyref-xform/tests/table-grow-intrinsic.wat @@ -0,0 +1,40 @@ +;; @xform export "foo" (anyref_owned) + +(module + (import "__wbindgen_anyref_xform__" "__wbindgen_anyref_table_grow" + (func $grow (param i32) (result i32))) + (func $foo (export "foo") (param i32) + i32.const 0 + call $grow + drop) + (func $alloc (export "__wbindgen_anyref_table_alloc") (result i32) + i32.const 0) + (func $dealloc (export "__wbindgen_anyref_table_dealloc") (param i32)) +) + +(; CHECK-ALL: +(module + (type (;0;) (func (result i32))) + (type (;1;) (func (param i32))) + (type (;2;) (func (param anyref))) + (func $foo anyref shim (type 2) (param anyref) + (local i32) + call $alloc + local.tee 1 + local.get 0 + table.set 0 + local.get 1 + call $foo) + (func $foo (type 1) (param i32) + ref.null + i32.const 0 + table.grow 0 + drop) + (func $alloc (type 0) (result i32) + i32.const 0) + (func $dealloc (type 1) (param i32)) + (table (;0;) 32 anyref) + (export "foo" (func $foo anyref shim)) + (export "__wbindgen_anyref_table_alloc" (func $alloc)) + (export "__wbindgen_anyref_table_dealloc" (func $dealloc))) +;) diff --git a/crates/anyref-xform/tests/table-set-null-intrinsic.wat b/crates/anyref-xform/tests/table-set-null-intrinsic.wat new file mode 100644 index 00000000000..17988bffa01 --- /dev/null +++ b/crates/anyref-xform/tests/table-set-null-intrinsic.wat @@ -0,0 +1,38 @@ +;; @xform export "foo" (anyref_owned) + +(module + (import "__wbindgen_anyref_xform__" "__wbindgen_anyref_table_set_null" + (func $set-null (param i32))) + (func $foo (export "foo") (param i32) + local.get 0 + call $set-null) + (func $alloc (export "__wbindgen_anyref_table_alloc") (result i32) + i32.const 0) + (func $dealloc (export "__wbindgen_anyref_table_dealloc") (param i32)) +) + +(; CHECK-ALL: +(module + (type (;0;) (func (result i32))) + (type (;1;) (func (param i32))) + (type (;2;) (func (param anyref))) + (func $foo anyref shim (type 2) (param anyref) + (local i32) + call $alloc + local.tee 1 + local.get 0 + table.set 0 + local.get 1 + call $foo) + (func $foo (type 1) (param i32) + local.get 0 + ref.null + table.set 0) + (func $alloc (type 0) (result i32) + i32.const 0) + (func $dealloc (type 1) (param i32)) + (table (;0;) 32 anyref) + (export "foo" (func $foo anyref shim)) + (export "__wbindgen_anyref_table_alloc" (func $alloc)) + (export "__wbindgen_anyref_table_dealloc" (func $dealloc))) +;) From 821094021d504f8235e62d0dac952b7cf23a133b Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Sat, 30 Nov 2019 11:13:44 -0800 Subject: [PATCH 19/35] Add some more anyref transform tests --- crates/anyref-xform/tests/all.rs | 8 +-- .../tests/import-anyref-owned.wat | 36 +++++++++++++ .../anyref-xform/tests/import-anyref-ret.wat | 36 +++++++++++++ crates/anyref-xform/tests/import-anyref.wat | 34 ++++++++++++ crates/anyref-xform/tests/mixed-export.wat | 52 +++++++++++++++++++ crates/anyref-xform/tests/mixed.wat | 50 ++++++++++++++++++ 6 files changed, 213 insertions(+), 3 deletions(-) create mode 100644 crates/anyref-xform/tests/import-anyref-owned.wat create mode 100644 crates/anyref-xform/tests/import-anyref-ret.wat create mode 100644 crates/anyref-xform/tests/import-anyref.wat create mode 100644 crates/anyref-xform/tests/mixed-export.wat create mode 100644 crates/anyref-xform/tests/mixed.wat diff --git a/crates/anyref-xform/tests/all.rs b/crates/anyref-xform/tests/all.rs index 7634191585f..7d07cf1b931 100644 --- a/crates/anyref-xform/tests/all.rs +++ b/crates/anyref-xform/tests/all.rs @@ -232,16 +232,18 @@ impl<'a> Parse<'a> for Directive { }; let mut args = Vec::new(); parser.parens(|p| { + let mut i = 0; while !p.is_empty() { if parser.peek::() { parser.parse::()?; - args.push((args.len(), true)); + args.push((i, true)); } else if parser.peek::() { parser.parse::()?; - args.push((args.len(), false)); + args.push((i, false)); } else { parser.parse::()?; - }; + } + i += 1; } Ok(()) })?; diff --git a/crates/anyref-xform/tests/import-anyref-owned.wat b/crates/anyref-xform/tests/import-anyref-owned.wat new file mode 100644 index 00000000000..36060be8900 --- /dev/null +++ b/crates/anyref-xform/tests/import-anyref-owned.wat @@ -0,0 +1,36 @@ +;; @xform import "" "a" (anyref_owned) + +(module + (import "" "a" (func $a (param i32))) + (func (export "foo") + i32.const 0 + call $a) + (func $alloc (export "__wbindgen_anyref_table_alloc") (result i32) + i32.const 0) + (func $dealloc (export "__wbindgen_anyref_table_dealloc") (param i32)) +) + +(; CHECK-ALL: +(module + (type (;0;) (func)) + (type (;1;) (func (result i32))) + (type (;2;) (func (param i32))) + (type (;3;) (func (param anyref))) + (import "" "a" (func $a (type 3))) + (func $a anyref shim (type 2) (param i32) + local.get 0 + table.get 0 + local.get 0 + call $dealloc + call $a) + (func (;2;) (type 0) + i32.const 0 + call $a anyref shim) + (func $alloc (type 1) (result i32) + i32.const 0) + (func $dealloc (type 2) (param i32)) + (table (;0;) 32 anyref) + (export "foo" (func 2)) + (export "__wbindgen_anyref_table_alloc" (func $alloc)) + (export "__wbindgen_anyref_table_dealloc" (func $dealloc))) +;) diff --git a/crates/anyref-xform/tests/import-anyref-ret.wat b/crates/anyref-xform/tests/import-anyref-ret.wat new file mode 100644 index 00000000000..4f692b88759 --- /dev/null +++ b/crates/anyref-xform/tests/import-anyref-ret.wat @@ -0,0 +1,36 @@ +;; @xform import "" "a" () anyref_owned + +(module + (import "" "a" (func $a (result i32))) + (func (export "foo") (result i32) + call $a) + (func $alloc (export "__wbindgen_anyref_table_alloc") (result i32) + i32.const 0) + (func $dealloc (export "__wbindgen_anyref_table_dealloc") (param i32)) +) + +(; CHECK-ALL: +(module + (type (;0;) (func (result i32))) + (type (;1;) (func (result anyref))) + (type (;2;) (func (param i32))) + (import "" "a" (func $a (type 1))) + (func $a anyref shim (type 0) (result i32) + (local i32 anyref) + call $a + local.set 1 + call $alloc + local.tee 0 + local.get 1 + table.set 0 + local.get 0) + (func (;2;) (type 0) (result i32) + call $a anyref shim) + (func $alloc (type 0) (result i32) + i32.const 0) + (func $dealloc (type 2) (param i32)) + (table (;0;) 32 anyref) + (export "foo" (func 2)) + (export "__wbindgen_anyref_table_alloc" (func $alloc)) + (export "__wbindgen_anyref_table_dealloc" (func $dealloc))) +;) diff --git a/crates/anyref-xform/tests/import-anyref.wat b/crates/anyref-xform/tests/import-anyref.wat new file mode 100644 index 00000000000..0a70f01dcc0 --- /dev/null +++ b/crates/anyref-xform/tests/import-anyref.wat @@ -0,0 +1,34 @@ +;; @xform import "" "a" (anyref_borrowed) + +(module + (import "" "a" (func $a (param i32))) + (func (export "foo") + i32.const 0 + call $a) + (func $alloc (export "__wbindgen_anyref_table_alloc") (result i32) + i32.const 0) + (func $dealloc (export "__wbindgen_anyref_table_dealloc") (param i32)) +) + +(; CHECK-ALL: +(module + (type (;0;) (func)) + (type (;1;) (func (result i32))) + (type (;2;) (func (param i32))) + (type (;3;) (func (param anyref))) + (import "" "a" (func $a (type 3))) + (func $a anyref shim (type 2) (param i32) + local.get 0 + table.get 0 + call $a) + (func (;2;) (type 0) + i32.const 0 + call $a anyref shim) + (func $alloc (type 1) (result i32) + i32.const 0) + (func $dealloc (type 2) (param i32)) + (table (;0;) 32 anyref) + (export "foo" (func 2)) + (export "__wbindgen_anyref_table_alloc" (func $alloc)) + (export "__wbindgen_anyref_table_dealloc" (func $dealloc))) +;) diff --git a/crates/anyref-xform/tests/mixed-export.wat b/crates/anyref-xform/tests/mixed-export.wat new file mode 100644 index 00000000000..85439757cf9 --- /dev/null +++ b/crates/anyref-xform/tests/mixed-export.wat @@ -0,0 +1,52 @@ +;; @xform export "a" (other anyref_borrowed other anyref_owned other) + +(module + (func $a (export "a") (param f32 i32 i64 i32 i32)) + (func $alloc (export "__wbindgen_anyref_table_alloc") (result i32) + i32.const 0) + (func $dealloc (export "__wbindgen_anyref_table_dealloc") (param i32)) +) + +(; CHECK-ALL: +(module + (type (;0;) (func (result i32))) + (type (;1;) (func (param i32))) + (type (;2;) (func (param f32 i32 i64 i32 i32))) + (type (;3;) (func (param f32 anyref i64 anyref i32))) + (func $a anyref shim (type 3) (param f32 anyref i64 anyref i32) + (local i32 i32) + global.get 0 + i32.const 1 + i32.sub + local.tee 5 + global.set 0 + local.get 0 + local.get 5 + local.get 1 + table.set 0 + local.get 5 + local.get 2 + call $alloc + local.tee 6 + local.get 3 + table.set 0 + local.get 6 + local.get 4 + call $a + local.get 5 + ref.null + table.set 0 + local.get 5 + i32.const 1 + i32.add + global.set 0) + (func $alloc (type 0) (result i32) + i32.const 0) + (func $a (type 2) (param f32 i32 i64 i32 i32)) + (func $dealloc (type 1) (param i32)) + (table (;0;) 32 anyref) + (global (;0;) (mut i32) (i32.const 32)) + (export "a" (func $a anyref shim)) + (export "__wbindgen_anyref_table_alloc" (func $alloc)) + (export "__wbindgen_anyref_table_dealloc" (func $dealloc))) +;) diff --git a/crates/anyref-xform/tests/mixed.wat b/crates/anyref-xform/tests/mixed.wat new file mode 100644 index 00000000000..d220a861966 --- /dev/null +++ b/crates/anyref-xform/tests/mixed.wat @@ -0,0 +1,50 @@ +;; @xform import "" "a" (other anyref_borrowed other anyref_owned other) + +(module + (import "" "a" (func $a (param f32 i32 i64 i32 i32))) + (func (export "foo") + f32.const 1 + i32.const 2 + i64.const 3 + i32.const 4 + i32.const 5 + call $a) + (func $alloc (export "__wbindgen_anyref_table_alloc") (result i32) + i32.const 0) + (func $dealloc (export "__wbindgen_anyref_table_dealloc") (param i32)) +) + +(; CHECK-ALL: +(module + (type (;0;) (func)) + (type (;1;) (func (result i32))) + (type (;2;) (func (param i32))) + (type (;3;) (func (param f32 i32 i64 i32 i32))) + (type (;4;) (func (param f32 anyref i64 anyref i32))) + (import "" "a" (func $a (type 4))) + (func $a anyref shim (type 3) (param f32 i32 i64 i32 i32) + local.get 0 + local.get 1 + table.get 0 + local.get 2 + local.get 3 + table.get 0 + local.get 3 + call $dealloc + local.get 4 + call $a) + (func (;2;) (type 0) + f32.const 0x1p+0 (;=1;) + i32.const 2 + i64.const 3 + i32.const 4 + i32.const 5 + call $a anyref shim) + (func $alloc (type 1) (result i32) + i32.const 0) + (func $dealloc (type 2) (param i32)) + (table (;0;) 32 anyref) + (export "foo" (func 2)) + (export "__wbindgen_anyref_table_alloc" (func $alloc)) + (export "__wbindgen_anyref_table_dealloc" (func $dealloc))) +;) From cbdbeb267db492bd0bba1e32070eef0245c30fe3 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Sat, 30 Nov 2019 11:15:36 -0800 Subject: [PATCH 20/35] Add an anyref function table test --- crates/anyref-xform/tests/table.wat | 35 +++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 crates/anyref-xform/tests/table.wat diff --git a/crates/anyref-xform/tests/table.wat b/crates/anyref-xform/tests/table.wat new file mode 100644 index 00000000000..a19b7cca376 --- /dev/null +++ b/crates/anyref-xform/tests/table.wat @@ -0,0 +1,35 @@ +;; @xform table 0 (anyref_owned) + +(module + (func $foo (param i32)) + (table (export "func") 0 funcref) + (elem (i32.const 0) 0) + (func $alloc (export "__wbindgen_anyref_table_alloc") (result i32) + i32.const 0) + (func $dealloc (export "__wbindgen_anyref_table_dealloc") (param i32)) +) + +(; CHECK-ALL: +(module + (type (;0;) (func (result i32))) + (type (;1;) (func (param i32))) + (type (;2;) (func (param anyref))) + (func $closure0 anyref shim (type 2) (param anyref) + (local i32) + call $alloc + local.tee 1 + local.get 0 + table.set 1 + local.get 1 + call $foo) + (func $alloc (type 0) (result i32) + i32.const 0) + (func $foo (type 1) (param i32)) + (func $dealloc (type 1) (param i32)) + (table (;0;) 2 funcref) + (table (;1;) 32 anyref) + (export "func" (table 0)) + (export "__wbindgen_anyref_table_alloc" (func $alloc)) + (export "__wbindgen_anyref_table_dealloc" (func $dealloc)) + (elem (;0;) (i32.const 0) $foo $closure0 anyref shim)) +;) From 24818fa3a019442a48743e5043857e91335e9f28 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Sat, 30 Nov 2019 11:56:36 -0800 Subject: [PATCH 21/35] Add some tests for the multivalue transform Basically the same as the anyref tests, but tailored for the multivalue transform instead. --- crates/multi-value-xform/Cargo.toml | 10 + crates/multi-value-xform/tests/align.wat | 37 ++++ crates/multi-value-xform/tests/all.rs | 231 ++++++++++++++++++++++ crates/multi-value-xform/tests/many.wat | 39 ++++ crates/multi-value-xform/tests/simple.wat | 93 +++++++++ 5 files changed, 410 insertions(+) create mode 100644 crates/multi-value-xform/tests/align.wat create mode 100644 crates/multi-value-xform/tests/all.rs create mode 100644 crates/multi-value-xform/tests/many.wat create mode 100644 crates/multi-value-xform/tests/simple.wat diff --git a/crates/multi-value-xform/Cargo.toml b/crates/multi-value-xform/Cargo.toml index 18399d18950..539be383086 100644 --- a/crates/multi-value-xform/Cargo.toml +++ b/crates/multi-value-xform/Cargo.toml @@ -14,3 +14,13 @@ edition = "2018" [dependencies] anyhow = "1.0" walrus = "0.13.0" + +[dev-dependencies] +rayon = "1.0" +wasmprinter = "0.2" +wast = "3.0" +wat = "1.0" + +[[test]] +name = "all" +harness = false diff --git a/crates/multi-value-xform/tests/align.wat b/crates/multi-value-xform/tests/align.wat new file mode 100644 index 00000000000..384d31da82f --- /dev/null +++ b/crates/multi-value-xform/tests/align.wat @@ -0,0 +1,37 @@ +;; @xform export "foo" (f64 i32 i64) + +(module + (global (mut i32) (i32.const 0)) + (memory 1) + + (func $foo (export "foo") (param i32)) +) + +(; CHECK-ALL: +(module + (type (;0;) (func (result f64 i32 i64))) + (type (;1;) (func (param i32))) + (func $foo multivalue shim (type 0) (result f64 i32 i64) + (local i32) + global.get 0 + i32.const 32 + i32.sub + local.tee 0 + global.set 0 + local.get 0 + call $foo + local.get 0 + f64.load + local.get 0 + i32.load offset=8 + local.get 0 + i64.load offset=16 + local.get 0 + i32.const 32 + i32.add + global.set 0) + (func $foo (type 1) (param i32)) + (memory (;0;) 1) + (global (;0;) (mut i32) (i32.const 0)) + (export "foo" (func $foo multivalue shim))) +;) diff --git a/crates/multi-value-xform/tests/all.rs b/crates/multi-value-xform/tests/all.rs new file mode 100644 index 00000000000..6c45f207e3b --- /dev/null +++ b/crates/multi-value-xform/tests/all.rs @@ -0,0 +1,231 @@ +//! A small test framework to execute a test function over all files in a +//! directory. +//! +//! Each file in the directory has its own `CHECK-ALL` annotation indicating the +//! expected output of the test. That can be automatically updated with +//! `BLESS=1` in the environment. Otherwise the test are checked against the +//! listed expectation. + +use anyhow::{bail, Context, Result}; +use rayon::prelude::*; +use std::env; +use std::fs; +use std::path::{Path, PathBuf}; +use wast::parser::{Parse, Parser}; + +fn main() { + run("tests".as_ref(), runtest); +} + +fn runtest(test: &Test) -> Result { + let wasm = wat::parse_file(&test.file)?; + let mut walrus = walrus::Module::from_buffer(&wasm)?; + let mut exports = Vec::new(); + let mut xforms = Vec::new(); + for directive in test.directives.iter() { + let export = walrus.exports.iter() + .find(|e| e.name == directive.name) + .unwrap(); + let id = match export.item { + walrus::ExportItem::Function(id) => id, + _ => panic!("must be function export"), + }; + exports.push(export.id()); + xforms.push((id, 0, directive.tys.clone())); + } + let memory = walrus.memories.iter().next().unwrap().id(); + let stack_pointer = walrus.globals.iter().next().unwrap().id(); + let ret = wasm_bindgen_multi_value_xform::run( + &mut walrus, + memory, + stack_pointer, + &xforms, + )?; + for (export, id) in exports.into_iter().zip(ret) { + walrus.exports.get_mut(export).item = walrus::ExportItem::Function(id); + } + walrus::passes::gc::run(&mut walrus); + let printed = wasmprinter::print_bytes(&walrus.emit_wasm())?; + Ok(printed) +} + +fn run(dir: &Path, run: fn(&Test) -> Result) { + let mut tests = Vec::new(); + find_tests(dir, &mut tests); + let filter = std::env::args().nth(1); + + let bless = env::var("BLESS").is_ok(); + let tests = tests + .iter() + .filter(|test| { + if let Some(filter) = &filter { + if let Some(s) = test.file_name().and_then(|s| s.to_str()) { + if !s.contains(filter) { + return false; + } + } + } + true + }) + .collect::>(); + + println!("\nrunning {} tests\n", tests.len()); + + let errors = tests + .par_iter() + .filter_map(|test| run_test(test, bless, run).err()) + .collect::>(); + + if !errors.is_empty() { + for msg in errors.iter() { + eprintln!("error: {:?}", msg); + } + + panic!("{} tests failed", errors.len()) + } + + println!("test result: ok. {} passed\n", tests.len()); +} + +fn run_test(test: &Path, bless: bool, run: fn(&Test) -> anyhow::Result) -> Result<()> { + (|| -> Result<_> { + let expected = Test::from_file(test)?; + let actual = run(&expected)?; + expected.check(&actual, bless)?; + Ok(()) + })() + .context(format!("test failed - {}", test.display()))?; + Ok(()) +} + +fn find_tests(path: &Path, tests: &mut Vec) { + for f in path.read_dir().unwrap() { + let f = f.unwrap(); + if f.file_type().unwrap().is_dir() { + find_tests(&f.path(), tests); + continue; + } + match f.path().extension().and_then(|s| s.to_str()) { + Some("wat") => {} + _ => continue, + } + tests.push(f.path()); + } +} + +struct Test { + file: PathBuf, + directives: Vec, + assertion: Option, +} + +struct Directive { + name: String, + tys: Vec, +} + +impl Test { + fn from_file(path: &Path) -> Result { + let contents = fs::read_to_string(path)?; + let mut iter = contents.lines(); + let mut assertion = None; + let mut directives = Vec::new(); + while let Some(line) = iter.next() { + if line.starts_with("(; CHECK-ALL:") { + let mut pattern = String::new(); + while let Some(line) = iter.next() { + if line == ";)" { + break; + } + pattern.push_str(line); + pattern.push_str("\n"); + } + while pattern.ends_with("\n") { + pattern.pop(); + } + if iter.next().is_some() { + bail!("CHECK-ALL must be at the end of the file"); + } + assertion = Some(pattern); + continue; + } + + if !line.starts_with(";; @xform") { + continue; + } + let directive = &line[9..]; + let buf = wast::parser::ParseBuffer::new(directive)?; + directives.push(wast::parser::parse::(&buf)?); + } + Ok(Test { + file: path.to_path_buf(), + directives, + assertion, + }) + } + + fn check(&self, output: &str, bless: bool) -> Result<()> { + if bless { + update_output(&self.file, output) + } else if let Some(pattern) = &self.assertion { + if output == pattern { + return Ok(()); + } + bail!( + "expected\n {}\n\nactual\n {}", + pattern.replace("\n", "\n "), + output.replace("\n", "\n ") + ); + } else { + bail!( + "no test assertions were found in this file, but you can \ + rerun tests with `BLESS=1` to automatically add assertions \ + to this file" + ); + } + } +} + +fn update_output(path: &Path, output: &str) -> Result<()> { + let contents = fs::read_to_string(path)?; + let start = contents.find("(; CHECK-ALL:").unwrap_or(contents.len()); + + let mut new_output = String::new(); + for line in output.lines() { + new_output.push_str(line); + new_output.push_str("\n"); + } + let new = format!( + "{}\n\n(; CHECK-ALL:\n{}\n;)\n", + contents[..start].trim(), + new_output.trim_end() + ); + fs::write(path, new)?; + Ok(()) +} + +impl<'a> Parse<'a> for Directive { + fn parse(parser: Parser<'a>) -> wast::parser::Result { + use wast::{kw, ValType}; + + parser.parse::()?; + let name = parser.parse()?; + let mut tys = Vec::new(); + parser.parens(|p| { + while !p.is_empty() { + tys.push(match p.parse()? { + ValType::I32 => walrus::ValType::I32, + ValType::I64 => walrus::ValType::I64, + ValType::F32 => walrus::ValType::F32, + ValType::F64 => walrus::ValType::F64, + _ => panic!(), + }); + } + Ok(()) + })?; + Ok(Directive { + name, + tys, + }) + } +} diff --git a/crates/multi-value-xform/tests/many.wat b/crates/multi-value-xform/tests/many.wat new file mode 100644 index 00000000000..ea48d8aebab --- /dev/null +++ b/crates/multi-value-xform/tests/many.wat @@ -0,0 +1,39 @@ +;; @xform export "foo" (i32 f32 f64 i64) + +(module + (global (mut i32) (i32.const 0)) + (memory 1) + + (func $foo (export "foo") (param i32)) +) + +(; CHECK-ALL: +(module + (type (;0;) (func (result i32 f32 f64 i64))) + (type (;1;) (func (param i32))) + (func $foo multivalue shim (type 0) (result i32 f32 f64 i64) + (local i32) + global.get 0 + i32.const 32 + i32.sub + local.tee 0 + global.set 0 + local.get 0 + call $foo + local.get 0 + i32.load + local.get 0 + f32.load offset=4 + local.get 0 + f64.load offset=8 + local.get 0 + i64.load offset=16 + local.get 0 + i32.const 32 + i32.add + global.set 0) + (func $foo (type 1) (param i32)) + (memory (;0;) 1) + (global (;0;) (mut i32) (i32.const 0)) + (export "foo" (func $foo multivalue shim))) +;) diff --git a/crates/multi-value-xform/tests/simple.wat b/crates/multi-value-xform/tests/simple.wat new file mode 100644 index 00000000000..c1ebc23ddbf --- /dev/null +++ b/crates/multi-value-xform/tests/simple.wat @@ -0,0 +1,93 @@ +;; @xform export "i32" (i32) +;; @xform export "i64" (i64) +;; @xform export "f32" (f32) +;; @xform export "f64" (f64) + +(module + (global (mut i32) (i32.const 0)) + (memory 1) + + (func $i32 (export "i32") (param i32)) + (func $i64 (export "i64") (param i32)) + (func $f32 (export "f32") (param i32)) + (func $f64 (export "f64") (param i32)) +) + +(; CHECK-ALL: +(module + (type (;0;) (func (result i32))) + (type (;1;) (func (result i64))) + (type (;2;) (func (result f32))) + (type (;3;) (func (result f64))) + (type (;4;) (func (param i32))) + (func $i32 multivalue shim (type 0) (result i32) + (local i32) + global.get 0 + i32.const 16 + i32.sub + local.tee 0 + global.set 0 + local.get 0 + call $i32 + local.get 0 + i32.load + local.get 0 + i32.const 16 + i32.add + global.set 0) + (func $i64 multivalue shim (type 1) (result i64) + (local i32) + global.get 0 + i32.const 16 + i32.sub + local.tee 0 + global.set 0 + local.get 0 + call $i64 + local.get 0 + i64.load + local.get 0 + i32.const 16 + i32.add + global.set 0) + (func $f32 multivalue shim (type 2) (result f32) + (local i32) + global.get 0 + i32.const 16 + i32.sub + local.tee 0 + global.set 0 + local.get 0 + call $f32 + local.get 0 + f32.load + local.get 0 + i32.const 16 + i32.add + global.set 0) + (func $f64 multivalue shim (type 3) (result f64) + (local i32) + global.get 0 + i32.const 16 + i32.sub + local.tee 0 + global.set 0 + local.get 0 + call $f64 + local.get 0 + f64.load + local.get 0 + i32.const 16 + i32.add + global.set 0) + (func $i32 (type 4) (param i32)) + (func $i64 (type 4) (param i32)) + (func $f32 (type 4) (param i32)) + (func $f64 (type 4) (param i32)) + (memory (;0;) 1) + (global (;0;) (mut i32) (i32.const 0)) + (export "i32" (func $i32 multivalue shim)) + (export "i64" (func $i64 multivalue shim)) + (export "f32" (func $f32 multivalue shim)) + (export "f64" (func $f64 multivalue shim))) +;) From 69261eadb06b47c2ea361b0f6b91e917e4598592 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Sat, 30 Nov 2019 11:57:00 -0800 Subject: [PATCH 22/35] Run rustfmt --- crates/multi-value-xform/tests/all.rs | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/crates/multi-value-xform/tests/all.rs b/crates/multi-value-xform/tests/all.rs index 6c45f207e3b..588abd26f20 100644 --- a/crates/multi-value-xform/tests/all.rs +++ b/crates/multi-value-xform/tests/all.rs @@ -23,7 +23,9 @@ fn runtest(test: &Test) -> Result { let mut exports = Vec::new(); let mut xforms = Vec::new(); for directive in test.directives.iter() { - let export = walrus.exports.iter() + let export = walrus + .exports + .iter() .find(|e| e.name == directive.name) .unwrap(); let id = match export.item { @@ -35,12 +37,7 @@ fn runtest(test: &Test) -> Result { } let memory = walrus.memories.iter().next().unwrap().id(); let stack_pointer = walrus.globals.iter().next().unwrap().id(); - let ret = wasm_bindgen_multi_value_xform::run( - &mut walrus, - memory, - stack_pointer, - &xforms, - )?; + let ret = wasm_bindgen_multi_value_xform::run(&mut walrus, memory, stack_pointer, &xforms)?; for (export, id) in exports.into_iter().zip(ret) { walrus.exports.get_mut(export).item = walrus::ExportItem::Function(id); } @@ -223,9 +220,6 @@ impl<'a> Parse<'a> for Directive { } Ok(()) })?; - Ok(Directive { - name, - tys, - }) + Ok(Directive { name, tys }) } } From a1e220370bd58c1bf883b6f8e4177dfed06aad2d Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Sat, 30 Nov 2019 11:57:27 -0800 Subject: [PATCH 23/35] Run multi-value tests on CI --- azure-pipelines.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 99c13808bd8..fd4321a262a 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -98,6 +98,8 @@ jobs: displayName: "wasm-bindgen-cli tests" - script: cargo test -p wasm-bindgen-anyref-xform displayName: "wasm-bindgen-anyref-xform tests" + - script: cargo test -p wasm-bindgen-multi-value-xform + displayName: "wasm-bindgen-multi-value-xform tests" - job: test_web_sys displayName: "Run web-sys crate tests" From a4bce8f5443d589859e73c8b321be7fc7fe970b8 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Sat, 30 Nov 2019 12:02:19 -0800 Subject: [PATCH 24/35] Fix the typescript output of wasm-bindgen --- crates/cli-support/src/js/binding.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/cli-support/src/js/binding.rs b/crates/cli-support/src/js/binding.rs index 523e8cf5da9..ba02f517394 100644 --- a/crates/cli-support/src/js/binding.rs +++ b/crates/cli-support/src/js/binding.rs @@ -145,6 +145,12 @@ impl<'a, 'b> Builder<'a, 'b> { assert!(js.stack.is_empty()); self.ts_args = js.typescript; + // Remove extraneous typescript args which were synthesized and aren't + // part of our function shim. + while self.ts_args.len() > function_args.len() { + self.ts_args.remove(0); + } + let mut ret = String::new(); ret.push_str("("); ret.push_str(&function_args.join(", ")); From 1487c9848b6a6aee81ae58bb9430543dcda1fe6a Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 2 Dec 2019 15:08:24 -0800 Subject: [PATCH 25/35] Update to published versions of all crates --- Cargo.toml | 1 - crates/anyref-xform/Cargo.toml | 2 +- crates/cli-support/Cargo.toml | 4 ++-- crates/cli/Cargo.toml | 2 +- crates/multi-value-xform/Cargo.toml | 2 +- crates/threads-xform/Cargo.toml | 2 +- crates/wasm-conventions/Cargo.toml | 2 +- crates/wasm-interpreter/Cargo.toml | 2 +- 8 files changed, 8 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6958fb6335f..b19cde12686 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -93,4 +93,3 @@ wasm-bindgen = { path = '.' } wasm-bindgen-futures = { path = 'crates/futures' } js-sys = { path = 'crates/js-sys' } web-sys = { path = 'crates/web-sys' } -walrus = { git = 'https://github.com/rustwasm/walrus' } diff --git a/crates/anyref-xform/Cargo.toml b/crates/anyref-xform/Cargo.toml index 536595905a4..e06899a0379 100644 --- a/crates/anyref-xform/Cargo.toml +++ b/crates/anyref-xform/Cargo.toml @@ -13,7 +13,7 @@ edition = '2018' [dependencies] anyhow = "1.0" -walrus = "0.13.0" +walrus = "0.14.0" [dev-dependencies] rayon = "1.0" diff --git a/crates/cli-support/Cargo.toml b/crates/cli-support/Cargo.toml index 4985f1fefa1..82e3f9e8f1d 100644 --- a/crates/cli-support/Cargo.toml +++ b/crates/cli-support/Cargo.toml @@ -18,11 +18,11 @@ log = "0.4" rustc-demangle = "0.1.13" serde_json = "1.0" tempfile = "3.0" -walrus = "0.13.0" +walrus = "0.14.0" wasm-bindgen-anyref-xform = { path = '../anyref-xform', version = '=0.2.55' } wasm-bindgen-shared = { path = "../shared", version = '=0.2.55' } wasm-bindgen-multi-value-xform = { path = '../multi-value-xform', version = '=0.2.55' } wasm-bindgen-threads-xform = { path = '../threads-xform', version = '=0.2.55' } wasm-bindgen-wasm-conventions = { path = '../wasm-conventions', version = '=0.2.55' } wasm-bindgen-wasm-interpreter = { path = "../wasm-interpreter", version = '=0.2.55' } -wit-walrus = { git = 'https://github.com/bytecodealliance/wasm-interface-types' } +wit-walrus = "0.1.0" diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 0fb5c867cd8..489e31b6ed3 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -24,7 +24,7 @@ rouille = { version = "3.0.0", default-features = false } serde = { version = "1.0", features = ['derive'] } serde_derive = "1.0" serde_json = "1.0" -walrus = { version = "0.13.0", features = ['parallel'] } +walrus = { version = "0.14.0", features = ['parallel'] } wasm-bindgen-cli-support = { path = "../cli-support", version = "=0.2.55" } wasm-bindgen-shared = { path = "../shared", version = "=0.2.55" } diff --git a/crates/multi-value-xform/Cargo.toml b/crates/multi-value-xform/Cargo.toml index 539be383086..16a35edd391 100644 --- a/crates/multi-value-xform/Cargo.toml +++ b/crates/multi-value-xform/Cargo.toml @@ -13,7 +13,7 @@ edition = "2018" [dependencies] anyhow = "1.0" -walrus = "0.13.0" +walrus = "0.14.0" [dev-dependencies] rayon = "1.0" diff --git a/crates/threads-xform/Cargo.toml b/crates/threads-xform/Cargo.toml index a104af458c2..e3132153d6c 100644 --- a/crates/threads-xform/Cargo.toml +++ b/crates/threads-xform/Cargo.toml @@ -13,5 +13,5 @@ edition = "2018" [dependencies] anyhow = "1.0" -walrus = "0.13.0" +walrus = "0.14.0" wasm-bindgen-wasm-conventions = { path = "../wasm-conventions", version = "=0.2.55" } diff --git a/crates/wasm-conventions/Cargo.toml b/crates/wasm-conventions/Cargo.toml index f6d41564042..3ece2d96c75 100644 --- a/crates/wasm-conventions/Cargo.toml +++ b/crates/wasm-conventions/Cargo.toml @@ -10,5 +10,5 @@ description = "Utilities for working with Wasm codegen conventions (usually esta edition = "2018" [dependencies] -walrus = "0.13.0" +walrus = "0.14.0" anyhow = "1.0" diff --git a/crates/wasm-interpreter/Cargo.toml b/crates/wasm-interpreter/Cargo.toml index 4fe0c6f1c34..f2ac7b8f5eb 100644 --- a/crates/wasm-interpreter/Cargo.toml +++ b/crates/wasm-interpreter/Cargo.toml @@ -14,7 +14,7 @@ edition = '2018' [dependencies] anyhow = "1.0" log = "0.4" -walrus = "0.13.0" +walrus = "0.14.0" [dev-dependencies] tempfile = "3" From 07bd358352c5522e6a5643eea98878f92a0affbf Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 3 Dec 2019 07:38:46 -0800 Subject: [PATCH 26/35] Remove magical names for anyref items This commit removes all the magical names flying around in the JS generation for managing anyref items and anyref tables. Instead IDs are set during the anyref pass and these are propagated to the end. This also deletes references to GC'ing code in the anyref module now that they've been verified to no longer be needed. --- crates/anyref-xform/src/lib.rs | 20 +- crates/cli-support/src/anyref.rs | 57 +----- crates/cli-support/src/js/binding.rs | 18 +- crates/cli-support/src/js/mod.rs | 234 +++++++++++++--------- crates/cli-support/src/lib.rs | 2 +- crates/cli-support/src/wit/nonstandard.rs | 6 + crates/cli-support/src/wit/section.rs | 3 + 7 files changed, 180 insertions(+), 160 deletions(-) diff --git a/crates/anyref-xform/src/lib.rs b/crates/anyref-xform/src/lib.rs index 0c49d7b085d..90d63872953 100644 --- a/crates/anyref-xform/src/lib.rs +++ b/crates/anyref-xform/src/lib.rs @@ -44,6 +44,12 @@ pub struct Context { table: Option, } +pub struct Meta { + pub table: TableId, + pub alloc: Option, + pub drop_slice: Option, +} + struct Transform<'a> { cx: &'a mut Context, @@ -161,7 +167,7 @@ impl Context { }) } - pub fn run(&mut self, module: &mut Module) -> Result<(), Error> { + pub fn run(&mut self, module: &mut Module) -> Result { let table = self.table.unwrap(); // Inject a stack pointer global which will be used for managing the @@ -171,6 +177,7 @@ impl Context { let mut heap_alloc = None; let mut heap_dealloc = None; + let mut drop_slice = None; // Find exports of some intrinsics which we only need for a runtime // implementation. @@ -182,7 +189,8 @@ impl Context { match export.name.as_str() { "__wbindgen_anyref_table_alloc" => heap_alloc = Some(f), "__wbindgen_anyref_table_dealloc" => heap_dealloc = Some(f), - _ => {} + "__wbindgen_drop_anyref_slice" => drop_slice = Some(f), + _ => continue, } } let mut clone_ref = None; @@ -226,7 +234,13 @@ impl Context { heap_dealloc, stack_pointer, } - .run(module) + .run(module)?; + + Ok(Meta { + table, + alloc: heap_alloc, + drop_slice, + }) } } diff --git a/crates/cli-support/src/anyref.rs b/crates/cli-support/src/anyref.rs index 41caa469457..c895bdd9624 100644 --- a/crates/cli-support/src/anyref.rs +++ b/crates/cli-support/src/anyref.rs @@ -1,11 +1,11 @@ use crate::wit::{AdapterKind, Instruction, NonstandardWitSection}; -use crate::wit::{AdapterType, InstructionData, StackChange}; +use crate::wit::{AdapterType, InstructionData, StackChange, WasmBindgenAux}; use anyhow::Error; use std::collections::HashMap; use walrus::Module; use wasm_bindgen_anyref_xform::Context; -pub fn process(module: &mut Module, wasm_interface_types: bool) -> Result<(), Error> { +pub fn process(module: &mut Module) -> Result<(), Error> { let mut cfg = Context::default(); cfg.prepare(module)?; let section = module @@ -43,52 +43,15 @@ pub fn process(module: &mut Module, wasm_interface_types: bool) -> Result<(), Er } } - cfg.run(module)?; + let meta = cfg.run(module)?; - // 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); - } - - // TODO: still needed? - // // Clean up now-unused intrinsics and shims and such - // walrus::passes::gc::run(module); - // - // // The GC pass above may end up removing some imported intrinsics. For - // // example `__wbindgen_object_clone_ref` is no longer needed after the - // // anyref pass. Make sure to delete the associated metadata for those - // // intrinsics so we don't try to access stale intrinsics later on. - // let remaining_imports = module - // .imports - // .iter() - // .map(|i| i.id()) - // .collect::>(); - // module - // .customs - // .get_typed_mut::() - // .expect("wit custom section should exist") - // .implements - // .retain(|(id, _)| remaining_imports.contains(id)); - // module - // .customs - // .get_typed_mut::() - // .expect("wasm-bindgen aux section should exist") - // .import_map - // .retain(|id, _| remaining_imports.contains(id)); + let section = module + .customs + .get_typed_mut::() + .expect("wit custom section should exist"); + section.anyref_table = Some(meta.table); + section.anyref_alloc = meta.alloc; + section.anyref_drop_slice = meta.drop_slice; Ok(()) } diff --git a/crates/cli-support/src/js/binding.rs b/crates/cli-support/src/js/binding.rs index ba02f517394..dc6e356ad60 100644 --- a/crates/cli-support/src/js/binding.rs +++ b/crates/cli-support/src/js/binding.rs @@ -647,15 +647,15 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction) -> Result<(), Error> { js.typescript_optional("any"); let val = js.pop(); js.cx.expose_is_like_none(); - // TODO: would be great to handle this in the anyref pass so we - // don't have to worry about it here, shouldn't have an extra - // switch. - if js.cx.config.anyref { - js.cx.expose_add_to_anyref_table()?; - js.push(format!("isLikeNone({0}) ? 0 : addToAnyrefTable({0})", val)); - } else { - js.cx.expose_add_heap_object(); - js.push(format!("isLikeNone({0}) ? 0 : addHeapObject({0})", val)); + match (js.cx.aux.anyref_table, js.cx.aux.anyref_alloc) { + (Some(table), Some(alloc)) => { + let alloc = js.cx.expose_add_to_anyref_table(table, alloc)?; + js.push(format!("isLikeNone({0}) ? 0 : {1}({0})", val, alloc)); + } + _ => { + js.cx.expose_add_heap_object(); + js.push(format!("isLikeNone({0}) ? 0 : addHeapObject({0})", val)); + } } } diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index e2db31907fd..36cff9a90a9 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -11,7 +11,7 @@ use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::fmt; use std::fs; use std::path::{Path, PathBuf}; -use walrus::{ExportId, ImportId, MemoryId, Module}; +use walrus::{ExportId, FunctionId, ImportId, MemoryId, Module, TableId}; mod binding; // mod incoming; @@ -55,6 +55,7 @@ pub struct Context<'a> { /// A mapping of a index for memories as we see them. Used in function /// names. memory_indices: HashMap, + table_indices: HashMap, } #[derive(Default)] @@ -101,6 +102,7 @@ impl<'a> Context<'a> { wit, aux, memory_indices: Default::default(), + table_indices: Default::default(), }) } @@ -1068,40 +1070,43 @@ impl<'a> Context<'a> { return Ok(ret); } self.expose_wasm_vector_len(); - if self.config.anyref { - // TODO: using `addToAnyrefTable` goes back and forth between wasm - // and JS a lot, we should have a bulk operation for this. - self.expose_add_to_anyref_table()?; - self.global(&format!( - " - function {}(array, malloc) {{ - const ptr = malloc(array.length * 4); - const mem = {}(); - for (let i = 0; i < array.length; i++) {{ - mem[ptr / 4 + i] = addToAnyrefTable(array[i]); - }} - WASM_VECTOR_LEN = array.length; - return ptr; - }} - ", - ret, mem, - )); - } else { - self.expose_add_heap_object(); - self.global(&format!( - " - function {}(array, malloc) {{ - const ptr = malloc(array.length * 4); - const mem = {}(); - for (let i = 0; i < array.length; i++) {{ - mem[ptr / 4 + i] = addHeapObject(array[i]); - }} - WASM_VECTOR_LEN = array.length; - return ptr; - }} - ", - ret, mem, - )); + match (self.aux.anyref_table, self.aux.anyref_alloc) { + (Some(table), Some(alloc)) => { + // TODO: using `addToAnyrefTable` goes back and forth between wasm + // and JS a lot, we should have a bulk operation for this. + let add = self.expose_add_to_anyref_table(table, alloc)?; + self.global(&format!( + " + function {}(array, malloc) {{ + const ptr = malloc(array.length * 4); + const mem = {}(); + for (let i = 0; i < array.length; i++) {{ + mem[ptr / 4 + i] = {}(array[i]); + }} + WASM_VECTOR_LEN = array.length; + return ptr; + }} + ", + ret, mem, add, + )); + } + _ => { + self.expose_add_heap_object(); + self.global(&format!( + " + function {}(array, malloc) {{ + const ptr = malloc(array.length * 4); + const mem = {}(); + for (let i = 0; i < array.length; i++) {{ + mem[ptr / 4 + i] = addHeapObject(array[i]); + }} + WASM_VECTOR_LEN = array.length; + return ptr; + }} + ", + ret, mem, + )); + } } Ok(ret) } @@ -1263,39 +1268,43 @@ impl<'a> Context<'a> { if !self.should_write_global(ret.to_string()) { return Ok(ret); } - if self.config.anyref { - self.global(&format!( - " - function {}(ptr, len) {{ - const mem = {}(); - const slice = mem.subarray(ptr / 4, ptr / 4 + len); - const result = []; - for (let i = 0; i < slice.length; i++) {{ - result.push(wasm.__wbg_anyref_table.get(slice[i])); + match (self.aux.anyref_table, self.aux.anyref_drop_slice) { + (Some(table), Some(drop)) => { + let table = self.export_name_of(table); + let drop = self.export_name_of(drop); + self.global(&format!( + " + function {}(ptr, len) {{ + const mem = {}(); + const slice = mem.subarray(ptr / 4, ptr / 4 + len); + const result = []; + for (let i = 0; i < slice.length; i++) {{ + result.push(wasm.{}.get(slice[i])); + }} + wasm.{}(ptr, len); + return result; }} - wasm.__wbindgen_drop_anyref_slice(ptr, len); - return result; - }} - ", - ret, mem, - )); - self.require_internal_export("__wbindgen_drop_anyref_slice")?; - } else { - self.expose_take_object(); - self.global(&format!( - " - function {}(ptr, len) {{ - const mem = {}(); - const slice = mem.subarray(ptr / 4, ptr / 4 + len); - const result = []; - for (let i = 0; i < slice.length; i++) {{ - result.push(takeObject(slice[i])); + ", + ret, mem, table, drop, + )); + } + _ => { + self.expose_take_object(); + self.global(&format!( + " + function {}(ptr, len) {{ + const mem = {}(); + const slice = mem.subarray(ptr / 4, ptr / 4 + len); + const result = []; + for (let i = 0; i < slice.length; i++) {{ + result.push(takeObject(slice[i])); + }} + return result; }} - return result; - }} - ", - ret, mem, - )); + ", + ret, mem, + )); + } } Ok(ret) } @@ -1443,9 +1452,7 @@ impl<'a> Context<'a> { } fn memview(&mut self, name: &'static str, js: &str, memory: walrus::MemoryId) -> MemView { - let next = self.memory_indices.len(); - let num = *self.memory_indices.entry(memory).or_insert(next); - let view = MemView { name, num }; + let view = self.memview_memory(name, memory); if !self.should_write_global(name.to_string()) { return view; } @@ -1467,6 +1474,18 @@ impl<'a> Context<'a> { return view; } + fn memview_memory(&mut self, name: &'static str, memory: walrus::MemoryId) -> MemView { + let next = self.memory_indices.len(); + let num = *self.memory_indices.entry(memory).or_insert(next); + MemView { name, num } + } + + fn memview_table(&mut self, name: &'static str, table: walrus::TableId) -> MemView { + let next = self.table_indices.len(); + let num = *self.table_indices.entry(table).or_insert(next); + MemView { name, num } + } + fn expose_assert_class(&mut self) { if !self.should_write_global("assert_class") { return; @@ -1569,25 +1588,29 @@ impl<'a> Context<'a> { return Ok(()); } self.require_internal_export("__wbindgen_exn_store")?; - if self.config.anyref { - self.expose_add_to_anyref_table()?; - self.global( - " - function handleError(e) { - const idx = addToAnyrefTable(e); - wasm.__wbindgen_exn_store(idx); - } - ", - ); - } else { - self.expose_add_heap_object(); - self.global( - " - function handleError(e) { - wasm.__wbindgen_exn_store(addHeapObject(e)); - } - ", - ); + match (self.aux.anyref_table, self.aux.anyref_alloc) { + (Some(table), Some(alloc)) => { + let add = self.expose_add_to_anyref_table(table, alloc)?; + self.global(&format!( + " + function handleError(e) {{ + const idx = {}(e); + wasm.__wbindgen_exn_store(idx); + }} + ", + add, + )); + } + _ => { + self.expose_add_heap_object(); + self.global( + " + function handleError(e) { + wasm.__wbindgen_exn_store(addHeapObject(e)); + } + ", + ); + } } Ok(()) } @@ -1851,23 +1874,30 @@ impl<'a> Context<'a> { true } - fn expose_add_to_anyref_table(&mut self) -> Result<(), Error> { + fn expose_add_to_anyref_table( + &mut self, + table: TableId, + alloc: FunctionId, + ) -> Result { + let view = self.memview_table("addToAnyrefTable", table); assert!(self.config.anyref); - if !self.should_write_global("add_to_anyref_table") { - return Ok(()); + if !self.should_write_global(view.to_string()) { + return Ok(view); } - self.require_internal_export("__wbindgen_anyref_table_alloc")?; - self.global( + let alloc = self.export_name_of(alloc); + let table = self.export_name_of(table); + self.global(&format!( " - function addToAnyrefTable(obj) { - const idx = wasm.__wbindgen_anyref_table_alloc(); - wasm.__wbg_anyref_table.set(idx, obj); + function {}(obj) {{ + const idx = wasm.{}(); + wasm.{}.set(idx, obj); return idx; - } + }} ", - ); + view, alloc, table, + )); - Ok(()) + Ok(view) } pub fn generate(&mut self) -> Result<(), Error> { @@ -2710,16 +2740,20 @@ impl<'a> Context<'a> { } Intrinsic::InitAnyrefTable => { + let table = self.aux.anyref_table + .ok_or_else(|| anyhow!("must enable anyref to use anyref intrinsic"))?; + let name = self.export_name_of(table); // Grow the table to insert our initial values, and then also // set the 0th slot to `undefined` since that's what we've // historically used for our ABI which is that the index of 0 // returns `undefined` for types like `None` going out. let mut base = format!( " - const table = wasm.__wbg_anyref_table; + const table = wasm.{}; const offset = table.grow({}); table.set(0, undefined); ", + name, INITIAL_HEAP_VALUES.len(), ); for (i, value) in INITIAL_HEAP_VALUES.iter().enumerate() { diff --git a/crates/cli-support/src/lib.rs b/crates/cli-support/src/lib.rs index 665f54cf6cc..3c9044cdc05 100755 --- a/crates/cli-support/src/lib.rs +++ b/crates/cli-support/src/lib.rs @@ -348,7 +348,7 @@ impl Bindgen { // currently off-by-default since `anyref` is still in development in // engines. if self.anyref { - anyref::process(&mut module, self.wasm_interface_types)?; + anyref::process(&mut module)?; } let aux = module diff --git a/crates/cli-support/src/wit/nonstandard.rs b/crates/cli-support/src/wit/nonstandard.rs index bb590800884..20f03cf5658 100644 --- a/crates/cli-support/src/wit/nonstandard.rs +++ b/crates/cli-support/src/wit/nonstandard.rs @@ -46,6 +46,12 @@ pub struct WasmBindgenAux { /// Auxiliary information to go into JS/TypeScript bindings describing the /// exported structs from Rust and their fields they've got exported. pub structs: Vec, + + /// Information about various internal functions used to manage the `anyref` + /// table, later used to process JS bindings. + pub anyref_table: Option, + pub anyref_alloc: Option, + pub anyref_drop_slice: Option, } pub type WasmBindgenAuxId = TypedCustomSectionId; diff --git a/crates/cli-support/src/wit/section.rs b/crates/cli-support/src/wit/section.rs index c813d466f88..f221b3c9b89 100644 --- a/crates/cli-support/src/wit/section.rs +++ b/crates/cli-support/src/wit/section.rs @@ -42,6 +42,9 @@ pub fn add( imports_with_assert_no_shim: _, // not relevant for this purpose enums, structs, + anyref_table: _, // not relevant + anyref_alloc: _, // not relevant + anyref_drop_slice: _, // not relevant } = aux; let adapter_context = |id: AdapterId| { From 0690f2ead3816435323bebf054c63f4461e4fa77 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 3 Dec 2019 07:40:31 -0800 Subject: [PATCH 27/35] Fix a typo --- crates/cli-support/src/anyref.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/cli-support/src/anyref.rs b/crates/cli-support/src/anyref.rs index c895bdd9624..19d85c2c3e4 100644 --- a/crates/cli-support/src/anyref.rs +++ b/crates/cli-support/src/anyref.rs @@ -181,7 +181,7 @@ fn import_xform( /// Adapts the `instrs` of an adapter function that calls an export. /// -/// The `instrus` must be generated by wasm-bindgen itself and follow the +/// The `instrs` must be generated by wasm-bindgen itself and follow the /// pattern matched below to pass off to the anyref transformation pass. The /// signature of the adapter doesn't change (it remains as anyref-aware) but the /// signature of the export we're calling will change during the transformation. From 20091ce2f3002c25d3bc277ac359df760a5dc2b9 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 3 Dec 2019 07:46:59 -0800 Subject: [PATCH 28/35] Add some comments around JS generation --- crates/cli-support/src/js/binding.rs | 37 +++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/crates/cli-support/src/js/binding.rs b/crates/cli-support/src/js/binding.rs index dc6e356ad60..44871e17002 100644 --- a/crates/cli-support/src/js/binding.rs +++ b/crates/cli-support/src/js/binding.rs @@ -33,14 +33,37 @@ pub struct Builder<'a, 'b> { log_error: bool, } -/// Helper struct used in incoming/outgoing to generate JS. +/// Helper struct used to create JS to process all instructions in an adapter +/// function. pub struct JsBuilder<'a, 'b> { + /// General context for building JS, used to manage intrinsic names, exposed + /// JS functions, etc. cx: &'a mut Context<'b>, + + /// The list of typescript arguments that we're going to have to this + /// function. typescript: Vec, + + /// The "prelude" of the function, or largely just the JS function we've + /// built so far. prelude: String, + + /// JS code to execute in a `finally` block in case any exceptions happen. finally: String, + + /// An index used to manage allocation of temporary indices, used to name + /// variables to ensure nothing clashes with anything else. tmp: usize, + + /// Names or expressions representing the arguments to the adapter. This is + /// use to translate the `arg.get` instruction. args: Vec, + + /// The wasm interface types "stack". The expressions pushed onto this stack + /// are intended to be *pure*, and if they're not, they should be pushed + /// into the `prelude`, assigned to a variable, and the variable should be + /// pushed to the stack. We're not super principled about this though, so + /// improvements will likely happen here over time. stack: Vec, } @@ -121,6 +144,18 @@ impl<'a, 'b> Builder<'a, 'b> { function_args.push(arg); } + // Translate all instructions, the fun loop! + // + // This loop will process all instructions for this adapter function. + // Each instruction will push/pop from the `js.stack` variable, and will + // eventually build up the entire `js.prelude` variable with all the JS + // code that we're going to be adding. Note that the stack at the end + // represents all returned values. + // + // We don't actually manage a literal stack at runtime, but instead we + // act as more of a compiler to generate straight-line code to make it + // more JIT-friendly. The generated code should be equivalent to the + // wasm interface types stack machine, however. for instr in instructions { instruction(&mut js, &instr.instr)?; } From 959e8a82fbcd45e96f631a19cbf449cc83e6871e Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 3 Dec 2019 07:47:08 -0800 Subject: [PATCH 29/35] Comment out dead code for now Leave a comment though indicating how it should be reenabled if anyone hits the error. --- crates/cli-support/src/js/binding.rs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/crates/cli-support/src/js/binding.rs b/crates/cli-support/src/js/binding.rs index 44871e17002..864b32d062f 100644 --- a/crates/cli-support/src/js/binding.rs +++ b/crates/cli-support/src/js/binding.rs @@ -168,14 +168,18 @@ impl<'a, 'b> Builder<'a, 'b> { let val = js.pop(); js.prelude(&format!("return {};", val)); } - _ => { - if true { - panic!() - } - let expr = js.stack.join(", "); - js.stack.truncate(0); - js.prelude(&format!("return [{}];", expr)); - } + + // TODO: this should be pretty trivial to support (commented out + // code below), but we should be sure to have a test for this + // somewhere. Currently I don't think it's possible to actually + // exercise this with just Rust code, so let's wait until we get + // some tests to enable this path. + _ => bail!("multi-value returns from adapters not supported yet"), + // _ => { + // let expr = js.stack.join(", "); + // js.stack.truncate(0); + // js.prelude(&format!("return [{}];", expr)); + // } } assert!(js.stack.is_empty()); self.ts_args = js.typescript; From f83f377a8ee421664e979683faaec53259146515 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 3 Dec 2019 07:48:09 -0800 Subject: [PATCH 30/35] Remove some typos --- crates/cli-support/src/js/binding.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/cli-support/src/js/binding.rs b/crates/cli-support/src/js/binding.rs index 864b32d062f..37bfac58184 100644 --- a/crates/cli-support/src/js/binding.rs +++ b/crates/cli-support/src/js/binding.rs @@ -565,7 +565,7 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction) -> Result<(), Error> { // If we're loading from the return pointer then we must have pushed // it earlier, and we always push the same value, so load that value // here - let expr = format!("{}()[{} / {} + {}]", mem, retptr_val, size, offset,); + let expr = format!("{}()[{} / {} + {}]", mem, retptr_val, size, offset); js.prelude(&format!("var r{} = {};", offset, expr)); js.push(format!("r{}", offset)); } @@ -930,7 +930,7 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction) -> Result<(), Error> { let i = js.tmp(); let b = js.pop(); let a = js.pop(); - js.prelude(&format!("var state{} = {{a: {}, b: {}}};", i, a, b,)); + js.prelude(&format!("var state{} = {{a: {}, b: {}}};", i, a, b)); let args = (0..*nargs) .map(|i| format!("arg{}", i)) .collect::>() From 18ba7eee4aa0eddc83280eb780984daaf18ff4a4 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 3 Dec 2019 07:48:31 -0800 Subject: [PATCH 31/35] Remove commented out modules --- crates/cli-support/src/js/mod.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 36cff9a90a9..2b71b789d8d 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -14,8 +14,6 @@ use std::path::{Path, PathBuf}; use walrus::{ExportId, FunctionId, ImportId, MemoryId, Module, TableId}; mod binding; -// mod incoming; -// mod outgoing; pub struct Context<'a> { globals: String, From 30af7572675bf0324eac5b55d7f2c1c572545759 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 3 Dec 2019 07:56:54 -0800 Subject: [PATCH 32/35] Improve tests for optional slices Also remove dead code for translating incoming slices since they're not supported yet in the trait implementations anyway. --- crates/cli-support/src/wit/incoming.rs | 36 ------------- tests/wasm/slice.js | 75 ++++++++------------------ tests/wasm/slice.rs | 24 ++++++--- 3 files changed, 39 insertions(+), 96 deletions(-) diff --git a/crates/cli-support/src/wit/incoming.rs b/crates/cli-support/src/wit/incoming.rs index f1da6eda79c..dc00b90c684 100644 --- a/crates/cli-support/src/wit/incoming.rs +++ b/crates/cli-support/src/wit/incoming.rs @@ -272,42 +272,6 @@ impl InstructionBuilder<'_, '_> { ); } - Descriptor::Ref(_) | Descriptor::RefMut(_) => { - let mutable = match arg { - Descriptor::Ref(_) => false, - _ => true, - }; - let kind = arg.vector_kind().ok_or_else(|| { - format_err!( - "unsupported optional slice type for calling Rust function from JS {:?}", - arg - ) - })?; - drop((kind, mutable)); - bail!("unsupported slice"); - // let malloc = self.cx.malloc()?; - // let mem = self.cx.memory()?; - // if mutable { - // let free = self.cx.free()?; - // self.instruction( - // &[AdapterType::Anyref], - // Instruction::OptionMutableSlice { - // kind, - // malloc, - // mem, - // free, - // }, - // &[AdapterType::I32; 2], - // ); - // } else { - // self.instruction( - // &[AdapterType::Anyref], - // Instruction::OptionVector { kind, malloc, mem }, - // &[AdapterType::I32; 2], - // ); - // } - } - Descriptor::String | Descriptor::CachedString | Descriptor::Vector(_) => { let kind = arg.vector_kind().ok_or_else(|| { format_err!( diff --git a/tests/wasm/slice.js b/tests/wasm/slice.js index 373e3859dfe..c23535e9b1f 100644 --- a/tests/wasm/slice.js +++ b/tests/wasm/slice.js @@ -41,63 +41,27 @@ exports.js_export = () => { assert.deepStrictEqual(wasm.export_f64(f64), f64); }; -exports.import_js_i8 = a => { +const test_import = (a, b, c) => { assert.strictEqual(a.length, 2); assert.strictEqual(a[0], 1); assert.strictEqual(a[1], 2); + assert.strictEqual(b.length, 2); + assert.strictEqual(b[0], 1); + assert.strictEqual(b[1], 2); + assert.strictEqual(c, undefined); return a; }; -exports.import_js_u8 = a => { - assert.strictEqual(a.length, 2); - assert.strictEqual(a[0], 1); - assert.strictEqual(a[1], 2); - return a; -}; - -exports.import_js_i16 = a => { - assert.strictEqual(a.length, 2); - assert.strictEqual(a[0], 1); - assert.strictEqual(a[1], 2); - return a; -}; - -exports.import_js_u16 = a => { - assert.strictEqual(a.length, 2); - assert.strictEqual(a[0], 1); - assert.strictEqual(a[1], 2); - return a; -}; - -exports.import_js_i32 = a => { - assert.strictEqual(a.length, 2); - assert.strictEqual(a[0], 1); - assert.strictEqual(a[1], 2); - return a; -}; -exports.import_js_isize = exports.import_js_i32; - -exports.import_js_u32 = a => { - assert.strictEqual(a.length, 2); - assert.strictEqual(a[0], 1); - assert.strictEqual(a[1], 2); - return a; -}; -exports.import_js_usize = exports.import_js_u32; - -exports.import_js_f32 = a => { - assert.strictEqual(a.length, 2); - assert.strictEqual(a[0], 1); - assert.strictEqual(a[1], 2); - return a; -}; - -exports.import_js_f64 = a => { - assert.strictEqual(a.length, 2); - assert.strictEqual(a[0], 1); - assert.strictEqual(a[1], 2); - return a; -}; +exports.import_js_i8 = test_import; +exports.import_js_u8 = test_import; +exports.import_js_i16 = test_import; +exports.import_js_u16 = test_import; +exports.import_js_i32 = test_import; +exports.import_js_isize = test_import; +exports.import_js_u32 = test_import; +exports.import_js_usize = test_import; +exports.import_js_f32 = test_import; +exports.import_js_f64 = test_import; exports.js_import = () => { const i8 = new Int8Array(2); @@ -152,12 +116,19 @@ exports.js_pass_array = () => { wasm.pass_array_rust_f64([1, 2]); }; -const import_mut_foo = a => { +const import_mut_foo = (a, b, c) => { assert.strictEqual(a.length, 3); assert.strictEqual(a[0], 1); assert.strictEqual(a[1], 2); a[0] = 4; a[1] = 5; + assert.strictEqual(b.length, 3); + assert.strictEqual(b[0], 4); + assert.strictEqual(b[1], 5); + assert.strictEqual(b[2], 6); + b[0] = 8; + b[1] = 7; + assert.strictEqual(c, undefined); }; exports.import_mut_js_i8 = import_mut_foo; diff --git a/tests/wasm/slice.rs b/tests/wasm/slice.rs index 8e299c0fec9..2f2457bc114 100644 --- a/tests/wasm/slice.rs +++ b/tests/wasm/slice.rs @@ -55,7 +55,7 @@ macro_rules! import_macro { ($(($rust:ident, $js:ident, $i:ident))*) => ($( #[wasm_bindgen(module = "tests/wasm/slice.js")] extern "C" { - fn $js(a: &[$i]) -> Vec<$i>; + fn $js(a: &[$i], b: Option<&[$i]>, c: Option<&[$i]>) -> Vec<$i>; } #[wasm_bindgen] @@ -63,7 +63,7 @@ macro_rules! import_macro { assert_eq!(a.len(), 2); assert_eq!(a[0], 1 as $i); assert_eq!(a[1], 2 as $i); - $js(a) + $js(a, Some(a), None) } )*) } @@ -120,19 +120,27 @@ macro_rules! import_mut_macro { $( #[wasm_bindgen(module = "tests/wasm/slice.js")] extern "C" { - fn $js(a: &mut [$i]); + fn $js(a: &mut [$i], b: Option<&mut [$i]>, c: Option<&mut [$i]>); } fn $rust() { - let mut buf = [ + let mut buf1 = [ 1 as $i, 2 as $i, 3 as $i, ]; - $js(&mut buf); - assert_eq!(buf[0], 4 as $i); - assert_eq!(buf[1], 5 as $i); - assert_eq!(buf[2], 3 as $i); + let mut buf2 = [ + 4 as $i, + 5 as $i, + 6 as $i, + ]; + $js(&mut buf1, Some(&mut buf2), None); + assert_eq!(buf1[0], 4 as $i); + assert_eq!(buf1[1], 5 as $i); + assert_eq!(buf1[2], 3 as $i); + assert_eq!(buf2[0], 8 as $i); + assert_eq!(buf2[1], 7 as $i); + assert_eq!(buf2[2], 6 as $i); } )* From a04272973703ce8fb8298b288ecb17d43e1738df Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 3 Dec 2019 08:01:23 -0800 Subject: [PATCH 33/35] Don't allow dead code in the CLI any more --- crates/cli-support/src/js/binding.rs | 5 --- crates/cli-support/src/js/mod.rs | 53 ++-------------------------- crates/cli-support/src/lib.rs | 1 - 3 files changed, 3 insertions(+), 56 deletions(-) diff --git a/crates/cli-support/src/js/binding.rs b/crates/cli-support/src/js/binding.rs index 37bfac58184..1e75af571a2 100644 --- a/crates/cli-support/src/js/binding.rs +++ b/crates/cli-support/src/js/binding.rs @@ -8,7 +8,6 @@ use crate::js::Context; use crate::wit::InstructionData; use crate::wit::{Adapter, AdapterId, AdapterKind, AdapterType, Instruction}; use anyhow::{anyhow, bail, Error}; -use std::collections::HashSet; use walrus::Module; /// A one-size-fits-all builder for processing WebIDL bindings and generating @@ -298,10 +297,6 @@ impl<'a, 'b> JsBuilder<'a, 'b> { } } - pub fn typescript_len(&self) -> usize { - self.typescript.len() - } - pub fn arg(&self, idx: u32) -> &str { &self.args[idx as usize] } diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 2b71b789d8d..f512e24731a 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -1906,23 +1906,10 @@ impl<'a> Context<'a> { }; self.generate_adapter(*id, adapter, instrs)?; } - // for (i, (idx, binding)) in bindings.elems.iter().enumerate() { - // self.generate_elem_binding(i, *idx, binding, bindings)?; - // } - // + // let mut pairs = aux.export_map.iter().collect::>(); // pairs.sort_by_key(|(k, _)| *k); // check_duplicated_getter_and_setter_names(&pairs)?; - // for (id, export) in pairs { - // self.generate_export(*id, export, bindings) - // .with_context(|| { - // format!( - // "failed to generate bindings for Rust export `{}`", - // export.debug_name, - // ) - // })?; - // } - // // for (id, import) in sorted_iter(&aux.import_map) { // let variadic = aux.imports_with_variadic.contains(&id); // let catch = aux.imports_with_catch.contains(&id); @@ -1949,40 +1936,6 @@ impl<'a> Context<'a> { Ok(()) } - // /// Generates a wrapper function for each bound element of the function - // /// table. These wrapper functions have the expected WebIDL signature we'd - // /// like them to have. This currently isn't part of the WebIDL bindings - // /// proposal, but the thinking is that it'd look something like this if - // /// added. - // /// - // /// Note that this is just an internal function shim used by closures and - // /// such, so we're not actually exporting anything here. - // fn generate_elem_binding( - // &mut self, - // idx: usize, - // elem_idx: u32, - // binding: &Binding, - // bindings: &NonstandardWitSection, - // ) -> Result<(), Error> { - // let webidl = bindings - // .types - // .get::(binding.webidl_ty) - // .unwrap(); - // self.export_function_table()?; - // let mut builder = binding::Builder::new(self); - // builder.disable_log_error(true); - // let js = builder.process(&binding, &webidl, true, &None, &mut |_, _, args| { - // Ok(format!( - // "wasm.__wbg_function_table.get({})({})", - // elem_idx, - // args.join(", ") - // )) - // })?; - // self.globals - // .push_str(&format!("function __wbg_elem_binding{}{}\n", idx, js)); - // Ok(()) - // } - fn generate_adapter( &mut self, id: AdapterId, @@ -2029,7 +1982,7 @@ impl<'a> Context<'a> { AuxExportKind::Method { consumed, .. } => builder.method(*consumed), } } - Kind::Import(import) => {} + Kind::Import(_) => {} Kind::Other => {} } @@ -2958,7 +2911,7 @@ impl<'a> Context<'a> { .insert(export.name.clone().into()); return export.name.clone(); } - let mut name = format!("__wbindgen_export_{}", self.next_export_idx); + let name = format!("__wbindgen_export_{}", self.next_export_idx); self.next_export_idx += 1; self.module.exports.add(&name, id); self.required_internal_exports.insert(name.clone().into()); diff --git a/crates/cli-support/src/lib.rs b/crates/cli-support/src/lib.rs index 3c9044cdc05..e3313998597 100755 --- a/crates/cli-support/src/lib.rs +++ b/crates/cli-support/src/lib.rs @@ -15,7 +15,6 @@ mod decode; mod descriptor; mod descriptors; mod intrinsic; -#[allow(dead_code, unused)] mod js; mod multivalue; pub mod wasm2es6js; From 9867d21830bf1c91b5b4df81b31c48ca4ef6cdda Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 3 Dec 2019 08:21:30 -0800 Subject: [PATCH 34/35] Fix logic for logging errors on imports Re-enable it for all imported functions, and then selectively disable it once an intrinsic is called. --- crates/cli-support/src/js/binding.rs | 18 +++---- crates/cli-support/src/js/mod.rs | 65 +++++++++++++------------- crates/cli-support/src/wit/section.rs | 4 +- crates/cli-support/src/wit/standard.rs | 3 -- 4 files changed, 45 insertions(+), 45 deletions(-) diff --git a/crates/cli-support/src/js/binding.rs b/crates/cli-support/src/js/binding.rs index 1e75af571a2..a034b10f9ca 100644 --- a/crates/cli-support/src/js/binding.rs +++ b/crates/cli-support/src/js/binding.rs @@ -75,7 +75,7 @@ pub struct TypescriptArg { impl<'a, 'b> Builder<'a, 'b> { pub fn new(cx: &'a mut Context<'b>) -> Builder<'a, 'b> { Builder { - log_error: cx.config.debug, + log_error: false, cx, ts_args: Vec::new(), ts_ret: None, @@ -97,10 +97,8 @@ impl<'a, 'b> Builder<'a, 'b> { self.catch = catch; } - pub fn disable_log_error(&mut self, disable: bool) { - if disable { - self.log_error = false; - } + pub fn log_error(&mut self, log: bool) { + self.log_error = log; } pub fn process( @@ -156,7 +154,7 @@ impl<'a, 'b> Builder<'a, 'b> { // more JIT-friendly. The generated code should be equivalent to the // wasm interface types stack machine, however. for instr in instructions { - instruction(&mut js, &instr.instr)?; + instruction(&mut js, &instr.instr, &mut self.log_error)?; } assert_eq!(js.stack.len(), adapter.results.len()); @@ -414,7 +412,7 @@ impl<'a, 'b> JsBuilder<'a, 'b> { } } -fn instruction(js: &mut JsBuilder, instr: &Instruction) -> Result<(), Error> { +fn instruction(js: &mut JsBuilder, instr: &Instruction, log_error: &mut bool) -> Result<(), Error> { // Here first properly aligned nonzero address is chosen to be the // out-pointer. We use the address for a BigInt64Array sometimes which // means it needs to be 8-byte aligned. Otherwise valid code is @@ -448,7 +446,7 @@ fn instruction(js: &mut JsBuilder, instr: &Instruction) -> Result<(), Error> { args.reverse(); // Call the function through an export of the underlying module. - let call = invoc.invoke(js.cx, &args, &mut js.prelude)?; + let call = invoc.invoke(js.cx, &args, &mut js.prelude, log_error)?; // And then figure out how to actually handle where the call // happens. This is pretty conditional depending on the number of @@ -1163,6 +1161,7 @@ impl Invocation { cx: &mut Context, args: &[String], prelude: &mut String, + log_error: &mut bool, ) -> Result { match self { Invocation::Core { id, .. } => { @@ -1179,6 +1178,9 @@ impl Invocation { }; let import = &cx.aux.import_map[id]; let variadic = cx.aux.imports_with_variadic.contains(id); + if cx.import_never_log_error(import) { + *log_error = false; + } cx.invoke_import(import, kind, args, variadic, prelude) } } diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index f512e24731a..5f472c5c3f8 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -1943,30 +1943,29 @@ impl<'a> Context<'a> { instrs: &[InstructionData], ) -> Result<(), Error> { enum Kind<'a> { - Import(&'a AuxImport), Export(&'a AuxExport), - Other, + Import(walrus::ImportId), + Adapter, } - let mut disable_log_error = true; let kind = match self.aux.export_map.get(&id) { Some(export) => Kind::Export(export), - None => match self.aux.import_map.get(&id) { - Some(import) => { - if true { - panic!() - } - disable_log_error = self.import_never_log_error(import); - Kind::Import(import) + None => { + let core = self.wit.implements.iter().find(|pair| pair.1 == id); + match core { + Some((core, _)) => Kind::Import(*core), + None => Kind::Adapter, } - None => Kind::Other, - }, + } }; // Construct a JS shim builder, and configure it based on the kind of // export that we're generating. let mut builder = binding::Builder::new(self); - builder.disable_log_error(disable_log_error); + builder.log_error(match kind { + Kind::Export(_) | Kind::Adapter => false, + Kind::Import(_) => builder.cx.config.debug, + }); builder.catch(builder.cx.aux.imports_with_catch.contains(&id)); let mut arg_names = &None; match kind { @@ -1983,7 +1982,7 @@ impl<'a> Context<'a> { } } Kind::Import(_) => {} - Kind::Other => {} + Kind::Adapter => {} } // Process the `binding` and generate a bunch of JS/TypeScript/etc. @@ -1991,8 +1990,14 @@ impl<'a> Context<'a> { .process(&adapter, instrs, arg_names) .with_context(|| match kind { Kind::Export(e) => format!("failed to generate bindings for `{}`", e.debug_name), - Kind::Import(i) => format!("failed to generate bindings for import {:?}", i), - Kind::Other => format!("failed to generates bindings for adapter"), + Kind::Import(i) => { + let i = builder.cx.module.imports.get(i); + format!( + "failed to generate bindings for import of `{}::{}`", + i.module, i.name + ) + } + Kind::Adapter => format!("failed to generates bindings for adapter"), })?; let ts = builder.typescript_signature(); let js_doc = builder.js_doc_comments(); @@ -2039,21 +2044,15 @@ impl<'a> Context<'a> { } } } - Kind::Import(_) => {} - Kind::Other => { - let core = self.wit.implements.iter().find(|pair| pair.1 == id); - match core { - Some((core, _)) => { - self.wasm_import_definitions - .insert(*core, format!("function{}", js)); - } - None => { - self.globals.push_str("function "); - self.globals.push_str(&self.adapter_name(id)); - self.globals.push_str(&js); - self.globals.push_str("\n\n"); - } - } + Kind::Import(core) => { + self.wasm_import_definitions + .insert(core, format!("function{}", js)); + } + Kind::Adapter => { + self.globals.push_str("function "); + self.globals.push_str(&self.adapter_name(id)); + self.globals.push_str(&js); + self.globals.push_str("\n\n"); } } Ok(()) @@ -2691,7 +2690,9 @@ impl<'a> Context<'a> { } Intrinsic::InitAnyrefTable => { - let table = self.aux.anyref_table + let table = self + .aux + .anyref_table .ok_or_else(|| anyhow!("must enable anyref to use anyref intrinsic"))?; let name = self.export_name_of(table); // Grow the table to insert our initial values, and then also diff --git a/crates/cli-support/src/wit/section.rs b/crates/cli-support/src/wit/section.rs index f221b3c9b89..54c89e1a474 100644 --- a/crates/cli-support/src/wit/section.rs +++ b/crates/cli-support/src/wit/section.rs @@ -42,8 +42,8 @@ pub fn add( imports_with_assert_no_shim: _, // not relevant for this purpose enums, structs, - anyref_table: _, // not relevant - anyref_alloc: _, // not relevant + anyref_table: _, // not relevant + anyref_alloc: _, // not relevant anyref_drop_slice: _, // not relevant } = aux; diff --git a/crates/cli-support/src/wit/standard.rs b/crates/cli-support/src/wit/standard.rs index 476b25fd4d5..0388d9d1db4 100644 --- a/crates/cli-support/src/wit/standard.rs +++ b/crates/cli-support/src/wit/standard.rs @@ -13,9 +13,6 @@ pub struct NonstandardWitSection { /// A list of adapter functions and the names they're exported under. pub exports: Vec<(String, AdapterId)>, - // /// A list of table elements that are wrapped by the given adapter - // /// function. - // pub elems: Vec<(u32, AdapterId)>, } pub type NonstandardWitSectionId = TypedCustomSectionId; From 79a911c2640379bb98f2bcbf6131a2842fcce5e1 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 3 Dec 2019 08:56:26 -0800 Subject: [PATCH 35/35] Re-enable the direct import optimization --- crates/cli-support/src/js/binding.rs | 4 + crates/cli-support/src/js/mod.rs | 220 ++++++++++++++++----------- crates/cli-support/src/wit/mod.rs | 87 +---------- 3 files changed, 146 insertions(+), 165 deletions(-) diff --git a/crates/cli-support/src/js/binding.rs b/crates/cli-support/src/js/binding.rs index a034b10f9ca..45b5c7c0bb9 100644 --- a/crates/cli-support/src/js/binding.rs +++ b/crates/cli-support/src/js/binding.rs @@ -107,6 +107,10 @@ impl<'a, 'b> Builder<'a, 'b> { instructions: &[InstructionData], explicit_arg_names: &Option>, ) -> Result { + if self.cx.aux.imports_with_assert_no_shim.contains(&adapter.id) { + bail!("generating a shim for something asserted to have no shim"); + } + let mut params = adapter.params.iter(); let mut function_args = Vec::new(); diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 5f472c5c3f8..8869f4e1b22 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -1,7 +1,7 @@ use crate::descriptor::VectorKind; use crate::intrinsic::Intrinsic; use crate::wit::{Adapter, AdapterId, AdapterJsImportKind, AuxValue}; -use crate::wit::{AdapterKind, InstructionData}; +use crate::wit::{AdapterKind, Instruction, InstructionData}; use crate::wit::{AuxEnum, AuxExport, AuxExportKind, AuxImport, AuxStruct}; use crate::wit::{JsImport, JsImportName, NonstandardWitSection, WasmBindgenAux}; use crate::{Bindgen, EncodeInto, OutputMode}; @@ -1910,15 +1910,7 @@ impl<'a> Context<'a> { // let mut pairs = aux.export_map.iter().collect::>(); // pairs.sort_by_key(|(k, _)| *k); // check_duplicated_getter_and_setter_names(&pairs)?; - // for (id, import) in sorted_iter(&aux.import_map) { - // let variadic = aux.imports_with_variadic.contains(&id); - // let catch = aux.imports_with_catch.contains(&id); - // let assert_no_shim = aux.imports_with_assert_no_shim.contains(&id); - // self.generate_import(*id, import, bindings, variadic, catch, assert_no_shim) - // .with_context(|| { - // format!("failed to generate bindings for import `{:?}`", import,) - // })?; - // } + for e in self.aux.enums.iter() { self.generate_enum(e)?; } @@ -1959,6 +1951,13 @@ impl<'a> Context<'a> { } }; + let catch = self.aux.imports_with_catch.contains(&id); + if let Kind::Import(core) = kind { + if !catch && self.attempt_direct_import(core, instrs)? { + return Ok(()); + } + } + // Construct a JS shim builder, and configure it based on the kind of // export that we're generating. let mut builder = binding::Builder::new(self); @@ -1966,7 +1965,7 @@ impl<'a> Context<'a> { Kind::Export(_) | Kind::Adapter => false, Kind::Import(_) => builder.cx.config.debug, }); - builder.catch(builder.cx.aux.imports_with_catch.contains(&id)); + builder.catch(catch); let mut arg_names = &None; match kind { Kind::Export(export) => { @@ -2055,54 +2054,9 @@ impl<'a> Context<'a> { self.globals.push_str("\n\n"); } } - Ok(()) + return Ok(()); } - // fn generate_import( - // &mut self, - // id: ImportId, - // import: &AuxImport, - // bindings: &NonstandardWitSection, - // variadic: bool, - // catch: bool, - // assert_no_shim: bool, - // ) -> Result<(), Error> { - // match import { - // AuxImport::Value(AuxValue::Bare(js)) - // if !variadic && !catch && self.import_does_not_require_glue(binding, webidl) => - // { - // self.direct_import(id, js) - // } - // _ => { - // if assert_no_shim { - // panic!( - // "imported function was annotated with `#[wasm_bindgen(assert_no_shim)]` \ - // but we need to generate a JS shim for it:\n\n\ - // \timport = {:?}\n\n\ - // \tbinding = {:?}\n\n\ - // \twebidl = {:?}", - // import, binding, webidl, - // ); - // } - // - // let disable_log_error = self.import_never_log_error(import); - // let mut builder = binding::Builder::new(self); - // builder.catch(catch)?; - // builder.disable_log_error(disable_log_error); - // let js = builder.process( - // &binding, - // &webidl, - // false, - // &None, - // &mut |cx, prelude, args| { - // cx.invoke_import(&binding, import, bindings, args, variadic, prelude) - // }, - // )?; - // Ok(()) - // } - // } - // } - /// Returns whether we should disable the logic, in debug mode, to catch an /// error, log it, and rethrow it. This is only intended for user-defined /// imports, not all imports of everything. @@ -2119,30 +2073,72 @@ impl<'a> Context<'a> { } } - // fn import_does_not_require_glue(&self, binding: &Binding, webidl: &ast::WitFunction) -> bool { - // if !self.config.anyref && binding.contains_anyref(self.module) { - // return false; - // } - // - // let wasm_ty = self.module.types.get(binding.wasm_ty); - // webidl.kind == AdapterJsImportKind::Normal - // && webidl::outgoing_do_not_require_glue( - // &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::>(), - // wasm_ty.results(), - // self.config.wasm_interface_types, - // ) - // } - - /// Emit a direct import directive that hooks up the `js` value specified to - /// the wasm import `id`. - fn direct_import(&mut self, id: ImportId, js: &JsImport) -> Result<(), Error> { + /// Attempts to directly hook up the `id` import in the wasm module with + /// the `instrs` specified. + /// + /// If this succeeds it returns `Ok(true)`, otherwise if it cannot be + /// directly imported then `Ok(false)` is returned. + fn attempt_direct_import( + &mut self, + id: ImportId, + instrs: &[InstructionData], + ) -> Result { + // First up extract the ID of the single called adapter, if any. + let mut call = None; + for instr in instrs { + match instr.instr { + Instruction::CallAdapter(id) => { + if call.is_some() { + return Ok(false); + } else { + call = Some(id); + } + } + Instruction::CallExport(_) + | Instruction::CallTableElement(_) + | Instruction::Standard(wit_walrus::Instruction::CallCore(_)) + | Instruction::Standard(wit_walrus::Instruction::CallAdapter(_)) => { + return Ok(false) + } + _ => {} + } + } + let adapter = match call { + Some(id) => id, + None => return Ok(false), + }; + match &self.wit.adapters[&adapter].kind { + AdapterKind::Import { kind, .. } => match kind { + AdapterJsImportKind::Normal => {} + // method/constructors need glue because we either need to + // invoke them as `new` or we need to invoke them with + // method-call syntax to get the `this` parameter right. + AdapterJsImportKind::Method | AdapterJsImportKind::Constructor => return Ok(false), + }, + // This is an adapter-to-adapter call, so it needs a shim. + AdapterKind::Local { .. } => return Ok(false), + } + + // Next up check to make sure that this import is to a bare JS value + // itself, no extra fluff intended. + let js = match &self.aux.import_map[&adapter] { + AuxImport::Value(AuxValue::Bare(js)) => js, + _ => return Ok(false), + }; + + // Make sure this isn't variadic in any way which means we need some + // sort of adapter glue. + if self.aux.imports_with_variadic.contains(&adapter) { + return Ok(false); + } + + // Ensure that every single instruction can be represented without JS + // glue being generated, aka it's covered by the JS ECMAScript bindings + // for wasm. + if !self.representable_without_js_glue(instrs) { + return Ok(false); + } + // If there's no field projection happening here and this is a direct // import from an ES-looking module, then we can actually just hook this // up directly in the wasm file itself. Note that this is covered in the @@ -2162,14 +2158,14 @@ impl<'a> Context<'a> { let import = self.module.imports.get_mut(id); import.module = module.clone(); import.name = name.clone(); - return Ok(()); + return Ok(true); } JsImportName::LocalModule { module, name } => { let module = self.config.local_module_name(module); let import = self.module.imports.get_mut(id); import.module = module; import.name = name.clone(); - return Ok(()); + return Ok(true); } JsImportName::InlineJs { unique_crate_identifier, @@ -2182,7 +2178,7 @@ impl<'a> Context<'a> { let import = self.module.imports.get_mut(id); import.module = module; import.name = name.clone(); - return Ok(()); + return Ok(true); } // Fall through below to requiring a JS shim to create an item @@ -2200,7 +2196,61 @@ impl<'a> Context<'a> { name = name, ); self.wasm_import_definitions.insert(id, js); - Ok(()) + Ok(true) + } + + fn representable_without_js_glue(&self, instrs: &[InstructionData]) -> bool { + use Instruction::*; + let standard_enabled = self.config.wasm_interface_types; + + let mut last_arg = None; + let mut saw_call = false; + for instr in instrs { + match instr.instr { + // Is an adapter section getting emitted? If so, then every + // standard operation is natively supported! + Standard(_) if standard_enabled => {} + + // Fetching arguments is just that, a fetch, so no need for + // glue. Note though that the arguments must be fetched in order + // for this to actually work, + Standard(wit_walrus::Instruction::ArgGet(i)) => { + if saw_call { + return false; + } + match (i, last_arg) { + (0, None) => last_arg = Some(0), + (n, Some(i)) if n == i + 1 => last_arg = Some(n), + _ => return false, + } + } + + // Similarly calling a function is the same as in JS, no glue + // needed. + CallAdapter(_) => saw_call = true, + + // Conversions to wasm integers are always supported since + // they're coerced into i32/f32/f64 appropriately. + Standard(wit_walrus::Instruction::IntToWasm { .. }) => {} + + // Converts from wasm to JS, however, only supports most + // integers. Converting into a u32 isn't supported because we + // need to generate glue to change the sign. + Standard(wit_walrus::Instruction::WasmToInt { + output: wit_walrus::ValType::U32, + .. + }) => return false, + Standard(wit_walrus::Instruction::WasmToInt { .. }) => {} + + // JS spec automatically coerces boolean values to i32 of 0 or 1 + // depending on true/false + I32FromBool => {} + + _ => return false, + } + } + + return true; } /// Generates a JS snippet appropriate for invoking `import`. diff --git a/crates/cli-support/src/wit/mod.rs b/crates/cli-support/src/wit/mod.rs index f4085a7f86e..2368d9e8ddb 100644 --- a/crates/cli-support/src/wit/mod.rs +++ b/crates/cli-support/src/wit/mod.rs @@ -497,16 +497,17 @@ impl<'a> Context<'a> { if *variadic { self.aux.imports_with_variadic.insert(id); } + + // Note that `catch`/`assert_no_shim` is applied not to the import + // itself but to the adapter shim we generated, so fetch that shim id + // and flag it as catch here. This basically just needs to be kept in + // sync with `js/mod.rs`. + let adapter = self.adapters.implements.last().unwrap().1; if *catch { - // Note that `catch` is applied not to the import itself but to the - // adapter shim we generated, so fetch that shim id and flag it as - // catch here. This basically just needs to be kept in sync with - // `js/mod.rs`. - let adapter = self.adapters.implements.last().unwrap().1; self.aux.imports_with_catch.insert(adapter); } if *assert_no_shim { - self.aux.imports_with_assert_no_shim.insert(id); + self.aux.imports_with_assert_no_shim.insert(adapter); } self.aux.import_map.insert(id, import); @@ -1374,77 +1375,3 @@ fn concatenate_comments(comments: &[&str]) -> String { .collect::>() .join("\n") } - -// /// Do we need to generate JS glue shims for these incoming bindings? -// 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 -// .iter() -// .zip(from_webidl_tys) -// .zip(to_wasm_tys) -// .enumerate() -// .all(|(i, ((expr, from_webidl_ty), to_wasm_ty))| match expr { -// NonstandardIncoming::Standard(e) => e.is_expressible_in_js_without_webidl_bindings( -// *from_webidl_ty, -// *to_wasm_ty, -// i as u32, -// ), -// _ => false, -// }) -// } -// -// /// Do we need to generate JS glue shims for these outgoing bindings? -// 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 -// .iter() -// .zip(from_wasm_tys) -// .zip(to_webidl_tys) -// .enumerate() -// .all(|(i, ((expr, from_wasm_ty), to_webidl_ty))| match expr { -// NonstandardOutgoing::Standard(e) => e.is_expressible_in_js_without_webidl_bindings( -// *from_wasm_ty, -// *to_webidl_ty, -// i as u32, -// ), -// _ => false, -// }) -// }