From 3cc30843e3181142101666e981fd950897ef0312 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 10 Jun 2019 07:09:51 -0700 Subject: [PATCH] Second large refactor for WebIDL bindings This commit is the second, and hopefully last massive, refactor for using WebIDL bindings internally in `wasm-bindgen`. This commit actually fully executes on the task at hand, moving `wasm-bindgen` to internally using WebIDL bindings throughout its code generation, anyref passes, etc. This actually fixes a number of issues that have existed in the anyref pass for some time now! The main changes here are to basically remove the usage of `Descriptor` from generating JS bindings. Instead two new types are introduced: `NonstandardIncoming` and `NonstandardOutgoing` which are bindings lists used for incoming/outgoing bindings. These mirror the standard terminology and literally have variants which are the standard values. All `Descriptor` types are now mapped into lists of incoming/outgoing bindings and used for process in wasm-bindgen. All JS generation has been refactored and updated to now process these lists of bindings instead of the previous `Descriptor`. In other words this commit takes `js2rust.rs` and `rust2js.rs` and first splits them in two. Interpretation of `Descriptor` and what to do for conversions is in the binding selection modules. The actual generation of JS from the binding selection is now performed by `incoming.rs` and `outgoing.rs`. To boot this also deduplicates all the code between the argument handling of `js2rust.rs` and return value handling of `rust2js.rs`. This means that to implement a new binding you only need to implement it one place and it's implemented for free in the other! This commit is not the end of the story though. I would like to add a mdoe to `wasm-bindgen` that literally emits a WebIDL bindings section. That's left for a third (and hopefully final) refactoring which is also intended to optimize generated JS for bindings. This commit currently loses the optimization where an imported is hooked up by value directly whenever a shim isn't needed. It's planned that the next refactoring to emit a webidl binding section that can be added back in. It shouldn't be too too hard hopefully since all the scaffolding is in place now. cc #1524 --- crates/cli-support/Cargo.toml | 1 + crates/cli-support/src/anyref.rs | 145 +- crates/cli-support/src/descriptor.rs | 180 +-- crates/cli-support/src/js/binding.rs | 507 +++++++ crates/cli-support/src/js/incoming.rs | 556 ++++++++ crates/cli-support/src/js/js2rust.rs | 880 ------------ crates/cli-support/src/js/mod.rs | 758 ++++++++-- crates/cli-support/src/js/outgoing.rs | 411 ++++++ crates/cli-support/src/js/rust2js.rs | 1216 ----------------- crates/cli-support/src/lib.rs | 7 +- crates/cli-support/src/webidl/bindings.rs | 250 ++++ crates/cli-support/src/webidl/incoming.rs | 489 +++++++ .../src/{webidl.rs => webidl/mod.rs} | 284 ++-- crates/cli-support/src/webidl/outgoing.rs | 521 +++++++ examples/add/src/lib.rs | 316 ++++- tests/wasm/imports.js | 2 +- 16 files changed, 3984 insertions(+), 2539 deletions(-) create mode 100644 crates/cli-support/src/js/binding.rs create mode 100644 crates/cli-support/src/js/incoming.rs delete mode 100644 crates/cli-support/src/js/js2rust.rs create mode 100644 crates/cli-support/src/js/outgoing.rs delete mode 100644 crates/cli-support/src/js/rust2js.rs create mode 100644 crates/cli-support/src/webidl/bindings.rs create mode 100644 crates/cli-support/src/webidl/incoming.rs rename crates/cli-support/src/{webidl.rs => webidl/mod.rs} (83%) create mode 100644 crates/cli-support/src/webidl/outgoing.rs diff --git a/crates/cli-support/Cargo.toml b/crates/cli-support/Cargo.toml index 79f8633a25c..9dfb2870c47 100644 --- a/crates/cli-support/Cargo.toml +++ b/crates/cli-support/Cargo.toml @@ -23,3 +23,4 @@ wasm-bindgen-anyref-xform = { path = '../anyref-xform', version = '=0.2.47' } wasm-bindgen-shared = { path = "../shared", version = '=0.2.47' } wasm-bindgen-threads-xform = { path = '../threads-xform', version = '=0.2.47' } wasm-bindgen-wasm-interpreter = { path = "../wasm-interpreter", version = '=0.2.47' } +wasm-webidl-bindings = { git = 'https://github.com/alexcrichton/wasm-webidl-bindings', branch = 'optional-text' } diff --git a/crates/cli-support/src/anyref.rs b/crates/cli-support/src/anyref.rs index 5576a8913f1..d2556d40021 100644 --- a/crates/cli-support/src/anyref.rs +++ b/crates/cli-support/src/anyref.rs @@ -1,43 +1,45 @@ -use crate::descriptor::{Closure, Descriptor, Function}; -use crate::webidl::{AuxImport, ImportBinding, WasmBindgenAux, WebidlCustomSection}; +use crate::webidl::{NonstandardIncoming, NonstandardOutgoing}; +use crate::webidl::{NonstandardWebidlSection, WasmBindgenAux}; use failure::Error; use std::collections::HashSet; use walrus::Module; use wasm_bindgen_anyref_xform::Context; +use wasm_webidl_bindings::ast; pub fn process(module: &mut Module) -> Result<(), Error> { let mut cfg = Context::default(); cfg.prepare(module)?; let bindings = module .customs - .get_typed_mut::() + .get_typed_mut::() .expect("webidl custom section should exist"); + // Transform all exported functions in the module, using the bindings listed + // for each exported function. for (export, binding) in bindings.exports.iter_mut() { - let (args, ret) = extract_anyrefs(binding, 0); + 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); - process_closure_arguments(&mut cfg, binding); } - for (import, kind) in bindings.imports.iter_mut() { - let binding = match kind { - ImportBinding::Function(f) => f, - ImportBinding::Constructor(f) => f, - ImportBinding::Method(f) => f, - }; - let (args, ret) = extract_anyrefs(binding, 0); + // 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); - process_closure_arguments(&mut cfg, binding); } - let aux = module - .customs - .get_typed_mut::() - .expect("webidl custom section should exist"); - for import in aux.import_map.values_mut() { - match import { - AuxImport::Closure(f) => process_closure(&mut cfg, f), - _ => {} + // 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; } } @@ -55,7 +57,7 @@ pub fn process(module: &mut Module) -> Result<(), Error> { .collect::>(); module .customs - .get_typed_mut::() + .get_typed_mut::() .expect("webidl custom section should exist") .imports .retain(|id, _| remaining_imports.contains(id)); @@ -68,44 +70,9 @@ pub fn process(module: &mut Module) -> Result<(), Error> { Ok(()) } -/// Process the `function` provided to ensure that all references to `Closure` -/// descriptors are processed below. -fn process_closure_arguments(cfg: &mut Context, function: &mut Function) { - for arg in function.arguments.iter_mut() { - process_descriptor(cfg, arg); - } - process_descriptor(cfg, &mut function.ret); - - fn process_descriptor(cfg: &mut Context, descriptor: &mut Descriptor) { - match descriptor { - Descriptor::Ref(d) - | Descriptor::RefMut(d) - | Descriptor::Option(d) - | Descriptor::Slice(d) - | Descriptor::Vector(d) => process_descriptor(cfg, d), - Descriptor::Closure(c) => process_closure(cfg, c), - Descriptor::Function(c) => process_function(cfg, c), - _ => {} - } - } - - fn process_function(cfg: &mut Context, function: &mut Function) { - let (args, ret) = extract_anyrefs(&function, 2); - if let Some(new) = cfg.table_element_xform(function.shim_idx, &args, ret) { - function.shim_idx = new; - } - process_closure_arguments(cfg, function); - } -} - -/// Ensure that the `Closure` is processed in case any of its arguments -/// recursively contain `anyref` and such. -fn process_closure(cfg: &mut Context, closure: &mut Closure) { - let (args, ret) = extract_anyrefs(&closure.function, 2); - if let Some(new) = cfg.table_element_xform(closure.shim_idx, &args, ret) { - closure.shim_idx = new; - } - process_closure_arguments(cfg, &mut closure.function); +enum Arguments<'a> { + Incoming(&'a mut [NonstandardIncoming]), + Outgoing(&'a mut [NonstandardOutgoing]), } /// Extract a description of the anyref arguments from the function signature @@ -120,19 +87,53 @@ fn process_closure(cfg: &mut Context, closure: &mut Closure) { /// 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(f: &Function, offset: usize) -> (Vec<(usize, bool)>, bool) { - let mut args = Vec::new(); - let mut cur = offset; - if f.ret.abi_returned_through_pointer() { - cur += 1; +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)); + } } - for arg in f.arguments.iter() { - if arg.is_anyref() { - args.push((cur, true)); - } else if arg.is_ref_anyref() { - args.push((cur, false)); + + // 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()); + } + } + 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()); + } } - cur += arg.abi_arg_count(); } - (args, f.ret.is_anyref()) + (ret, ty.results() == &[walrus::ValType::Anyref]) } diff --git a/crates/cli-support/src/descriptor.rs b/crates/cli-support/src/descriptor.rs index 1d2632340fd..f2189e34a53 100644 --- a/crates/cli-support/src/descriptor.rs +++ b/crates/cli-support/src/descriptor.rs @@ -82,7 +82,7 @@ pub struct Closure { pub mutable: bool, } -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Debug)] pub enum VectorKind { I8, U8, @@ -99,10 +99,6 @@ pub enum VectorKind { Anyref, } -pub struct Number { - u32: bool, -} - impl Descriptor { pub fn decode(mut data: &[u32]) -> Descriptor { let descriptor = Descriptor::_decode(&mut data, false); @@ -154,52 +150,6 @@ impl Descriptor { } } - /// Returns `Some` if this type is a number, and the returned `Number` type - /// can be accessed to learn more about what kind of number this is. - pub fn number(&self) -> Option { - match *self { - Descriptor::I8 - | Descriptor::U8 - | Descriptor::I16 - | Descriptor::U16 - | Descriptor::I32 - | Descriptor::F32 - | Descriptor::F64 - | Descriptor::Enum { .. } => Some(Number { u32: false }), - Descriptor::U32 => Some(Number { u32: true }), - _ => None, - } - } - - pub fn is_wasm_native(&self) -> bool { - match *self { - Descriptor::I32 | Descriptor::U32 | Descriptor::F32 | Descriptor::F64 => true, - _ => return false, - } - } - - pub fn is_abi_as_u32(&self) -> bool { - match *self { - Descriptor::I8 | Descriptor::U8 | Descriptor::I16 | Descriptor::U16 => true, - _ => return false, - } - } - - pub fn get_64(&self) -> Option { - match *self { - Descriptor::I64 => Some(true), - Descriptor::U64 => Some(false), - _ => None, - } - } - - pub fn is_ref_anyref(&self) -> bool { - match *self { - Descriptor::Ref(ref s) => s.is_anyref(), - _ => return false, - } - } - pub fn unwrap_closure(self) -> Closure { match self { Descriptor::Closure(s) => *s, @@ -207,13 +157,6 @@ impl Descriptor { } } - pub fn is_anyref(&self) -> bool { - match *self { - Descriptor::Anyref => true, - _ => false, - } - } - pub fn vector_kind(&self) -> Option { let inner = match *self { Descriptor::String => return Some(VectorKind::String), @@ -246,121 +189,6 @@ impl Descriptor { _ => None, } } - - pub fn rust_struct(&self) -> Option<&str> { - let inner = match *self { - Descriptor::Ref(ref d) => &**d, - Descriptor::RefMut(ref d) => &**d, - ref d => d, - }; - match *inner { - Descriptor::RustStruct(ref s) => Some(s), - _ => None, - } - } - - pub fn stack_closure(&self) -> Option<(&Function, bool)> { - let (inner, mutable) = match *self { - Descriptor::Ref(ref d) => (&**d, false), - Descriptor::RefMut(ref d) => (&**d, true), - _ => return None, - }; - match *inner { - Descriptor::Function(ref f) => Some((f, mutable)), - _ => None, - } - } - - pub fn is_by_ref(&self) -> bool { - match *self { - Descriptor::Ref(_) | Descriptor::RefMut(_) => true, - _ => false, - } - } - - pub fn is_mut_ref(&self) -> bool { - match *self { - Descriptor::RefMut(_) => true, - _ => false, - } - } - - pub fn abi_returned_through_pointer(&self) -> bool { - if self.vector_kind().is_some() { - return true; - } - if self.get_64().is_some() { - return true; - } - match self { - Descriptor::Option(inner) => match &**inner { - Descriptor::Anyref - | Descriptor::RustStruct(_) - | Descriptor::Enum { .. } - | Descriptor::Char - | Descriptor::Boolean - | Descriptor::I8 - | Descriptor::U8 - | Descriptor::I16 - | Descriptor::U16 => false, - _ => true, - }, - _ => false, - } - } - - pub fn abi_arg_count(&self) -> usize { - if let Descriptor::Option(inner) = self { - if inner.get_64().is_some() { - return 4; - } - if let Descriptor::Ref(inner) = &**inner { - match &**inner { - Descriptor::Anyref => return 1, - _ => {} - } - } - } - if self.stack_closure().is_some() { - return 2; - } - if self.abi_returned_through_pointer() { - 2 - } else { - 1 - } - } - - pub fn assert_abi_return_correct(&self, before: usize, after: usize) { - if before != after { - assert_eq!( - before + 1, - after, - "abi_returned_through_pointer wrong for {:?}", - self, - ); - assert!( - self.abi_returned_through_pointer(), - "abi_returned_through_pointer wrong for {:?}", - self, - ); - } else { - assert!( - !self.abi_returned_through_pointer(), - "abi_returned_through_pointer wrong for {:?}", - self, - ); - } - } - - pub fn assert_abi_arg_correct(&self, before: usize, after: usize) { - assert_eq!( - before + self.abi_arg_count(), - after, - "abi_arg_count wrong for {:?}", - self, - ); - } } fn get(a: &mut &[u32]) -> u32 { @@ -435,9 +263,3 @@ impl VectorKind { } } } - -impl Number { - pub fn is_u32(&self) -> bool { - self.u32 - } -} diff --git a/crates/cli-support/src/js/binding.rs b/crates/cli-support/src/js/binding.rs new file mode 100644 index 00000000000..c011a9bbba7 --- /dev/null +++ b/crates/cli-support/src/js/binding.rs @@ -0,0 +1,507 @@ +//! Support for actually generating a JS function shim. +//! +//! This `Builder` type is used to generate JS function shims which sit between +//! 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 failure::{bail, Error}; +use std::collections::HashSet; +use wasm_webidl_bindings::ast; + +/// A one-size-fits-all builder for processing WebIDL bindings and generating +/// JS. +pub struct Builder<'a, 'b> { + /// Parent context used to expose helper functions and such. + 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. + pub ts_ret: Option, + /// Whether or not this is building a constructor for a Rust class, and if + /// so what class it's constructing. + constructor: Option, + /// Whether or not this is building a method of a Rust class instance, and + /// whether or not the method consumes `self` or not. + method: Option, + /// Whether or not we're catching exceptions from the main function + /// invocation. Currently only used for imports. + catch: bool, +} + +/// Helper struct used in incoming/outgoing to generate JS. +pub struct JsBuilder { + typescript: Vec, + prelude: String, + finally: String, + tmp: usize, + args: Vec, +} + +pub struct TypescriptArg { + pub ty: String, + pub name: String, + pub optional: bool, +} + +impl<'a, 'b> Builder<'a, 'b> { + pub fn new(cx: &'a mut Context<'b>) -> Builder<'a, 'b> { + Builder { + 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, + method: None, + catch: false, + } + } + + pub fn method(&mut self, consumed: bool) { + self.method = Some(consumed); + } + + pub fn constructor(&mut self, class: &str) { + self.constructor = Some(class.to_string()); + } + + pub fn catch(&mut self, catch: bool) -> Result<(), Error> { + if catch { + self.cx.expose_handle_error()?; + } + self.catch = catch; + Ok(()) + } + + pub fn process( + &mut self, + binding: &Binding, + webidl: &ast::WebidlFunction, + incoming_args: bool, + explicit_arg_names: &Option>, + invoke: &mut dyn FnMut(&mut Context, &mut String, &[String]) -> Result, + ) -> Result { + // used in `finalize` below + if self.cx.config.debug { + 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. + if binding.return_via_outptr.is_some() { + drop(webidl_params.next()); + self.cx.expose_global_argument_ptr()?; + self.args_prelude + .push_str("const retptr = globalArgumentPtr();\n"); + arg_names.push("retptr".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(true) => { + drop(webidl_params.next()); + 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()); + } + Some(false) => { + drop(webidl_params.next()); + arg_names.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)?); + } + } + + // 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! We just have one input + // of `ret` which is created in the JS shim below. + None => ret_args.push("ret".to_string()), + } + 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)) + } + + // 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 = "); + } + call.push_str(invoc); + call.push_str(";\n"); + + if self.ret_prelude.len() > 0 { + call.push_str(self.ret_prelude.trim()); + call.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"); + } + + if self.catch { + call = format!("try {{\n{}}} catch (e) {{\n handleError(e)\n}}\n", call); + } + + // Generate a try/catch block in debug mode which handles unexpected and + // unhandled exceptions, typically used on imports. This currently just + // logs what happened, but keeps the exception being thrown to propagate + // elsewhere. + if self.cx.config.debug { + 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("}"); + + return js; + } + + /// Returns the typescript signature of the binding that this has described. + /// This is used to generate all the TypeScript definitions later on. + /// + /// Note that the TypeScript returned here is just the argument list and the + /// return value, it doesn't include the function name in any way. + pub fn typescript_signature(&self) -> String { + // Build up the typescript signature as well + let mut omittable = true; + let mut ts_args = Vec::new(); + for arg in self.ts_args.iter().rev() { + // In TypeScript, we can mark optional parameters as omittable + // using the `?` suffix, but only if they're not followed by + // non-omittable parameters. Therefore iterate the parameter list + // in reverse and stop using the `?` suffix for optional params as + // soon as a non-optional parameter is encountered. + if arg.optional { + if omittable { + ts_args.push(format!("{}?: {}", arg.name, arg.ty)); + } else { + ts_args.push(format!("{}: {} | undefined", arg.name, arg.ty)); + } + } else { + omittable = false; + ts_args.push(format!("{}: {}", arg.name, arg.ty)); + } + } + ts_args.reverse(); + let mut ts = format!("({})", ts_args.join(", ")); + + // Constructors have no listed return type in typescript + if self.constructor.is_none() { + ts.push_str(": "); + if let Some(ty) = &self.ts_ret { + ts.push_str(&ty.ty); + if ty.optional { + ts.push_str(" | undefined"); + } + } else { + ts.push_str("void"); + } + } + return ts; + } + + /// Returns a helpful JS doc comment which lists types for all parameters + /// and the return value. + pub fn js_doc_comments(&self) -> String { + let mut ret: String = self + .ts_args + .iter() + .map(|a| { + if a.optional { + format!("@param {{{} | undefined}} {}\n", a.ty, a.name) + } else { + format!("@param {{{}}} {}\n", a.ty, a.name) + } + }) + .collect(); + if let Some(ts) = &self.ts_ret { + ret.push_str(&format!("@returns {{{}}}", ts.ty)); + } + ret + } +} + +impl JsBuilder { + pub fn new(args: Vec) -> JsBuilder { + JsBuilder { + args, + tmp: 0, + finally: String::new(), + prelude: String::new(), + typescript: Vec::new(), + } + } + + pub fn typescript_len(&self) -> usize { + self.typescript.len() + } + + pub fn arg(&self, idx: u32) -> &str { + &self.args[idx as usize] + } + + pub fn typescript_required(&mut self, ty: &str) { + let name = self.args[self.typescript.len()].clone(); + self.typescript.push(TypescriptArg { + ty: ty.to_string(), + optional: false, + name, + }); + } + + pub fn typescript_optional(&mut self, ty: &str) { + let name = self.args[self.typescript.len()].clone(); + self.typescript.push(TypescriptArg { + ty: ty.to_string(), + optional: true, + name, + }); + } + + pub fn prelude(&mut self, prelude: &str) { + for line in prelude.trim().lines() { + 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"); + } + } + + pub fn tmp(&mut self) -> usize { + let ret = self.tmp; + self.tmp += 1; + return ret; + } +} diff --git a/crates/cli-support/src/js/incoming.rs b/crates/cli-support/src/js/incoming.rs new file mode 100644 index 00000000000..2fe897bf47d --- /dev/null +++ b/crates/cli-support/src/js/incoming.rs @@ -0,0 +1,556 @@ +//! 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 failure::{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), + + // 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` + 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(), + ]); + } + + // 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::Slice { 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)?; + 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, *mutable)?; + self.js.typescript_required(kind.js_ty()); + 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 } => { + 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/js2rust.rs b/crates/cli-support/src/js/js2rust.rs deleted file mode 100644 index 6dbefd1fcf5..00000000000 --- a/crates/cli-support/src/js/js2rust.rs +++ /dev/null @@ -1,880 +0,0 @@ -use crate::descriptor::{Descriptor, Function}; -use crate::js::Context; -use failure::{bail, Error}; - -pub struct JsArgument { - pub optional: bool, - pub name: String, - pub type_: String, -} - -impl JsArgument { - fn required(name: String, type_: String) -> Self { - Self { - optional: false, - name, - type_, - } - } - - fn optional(name: String, type_: String) -> Self { - Self { - optional: true, - name, - type_, - } - } -} - -/// Helper struct for manufacturing a shim in JS used to translate JS types to -/// Rust, aka pass from JS back into Rust -pub struct Js2Rust<'a, 'b: 'a> { - cx: &'a mut Context<'b>, - - /// Arguments passed to the invocation of the wasm function, aka things that - /// are only numbers. - rust_arguments: Vec, - - /// Arguments and their types to the JS shim. - pub js_arguments: Vec, - - /// Conversions that happen before we invoke the wasm function, such as - /// converting a string to a ptr/length pair. - prelude: String, - - /// "Destructors" or cleanup that must happen after the wasm function - /// finishes. This is scheduled in a `finally` block. - finally: String, - - /// Index of the next argument for unique name generation purposes. - arg_idx: usize, - - /// Typescript expression representing the type of the return value of this - /// function. - pub ret_ty: String, - - /// Expression used to generate the return value. The string "RET" in this - /// expression is replaced with the actual wasm invocation eventually. - ret_expr: String, - - /// Name of the JS shim/function that we're generating, primarily for - /// TypeScript right now. - js_name: String, - - /// whether or not this generated function body will act like a constructor, - /// meaning it doesn't actually return something but rather assigns to - /// `this` - /// - /// The string value here is the class that this should be a constructor - /// for. - constructor: Option, - - /// whether or not we're generating a method - method: bool, -} - -impl<'a, 'b> Js2Rust<'a, 'b> { - pub fn new(js_name: &str, cx: &'a mut Context<'b>) -> Js2Rust<'a, 'b> { - Js2Rust { - cx, - js_name: js_name.to_string(), - rust_arguments: Vec::new(), - js_arguments: Vec::new(), - prelude: String::new(), - finally: String::new(), - arg_idx: 0, - ret_ty: String::new(), - ret_expr: String::new(), - constructor: None, - method: false, - } - } - - /// Generates all bindings necessary for the signature in `Function`, - /// creating necessary argument conversions and return value processing. - pub fn process( - &mut self, - function: &Function, - opt_arg_names: &Option>, - ) -> Result<&mut Self, Error> { - // Chop off the implicit i32 first argument if we're a method since it - // was already handled by `method` below. - let arguments = if self.method { - &function.arguments[1..] - } else { - &function.arguments[..] - }; - let arg_names = match opt_arg_names { - Some(arg_names) => arg_names.iter().map(|s| Some(s.as_str())).collect(), - None => vec![None; arguments.len()], - }; - assert_eq!(arg_names.len(), arguments.len()); - for (arg, arg_name) in arguments.iter().zip(arg_names) { - // Process the function argument and assert that the metadata about - // the number of arguments on the Rust side required is correct. - let before = self.rust_arguments.len(); - self.argument(arg, arg_name)?; - arg.assert_abi_arg_correct(before, self.rust_arguments.len()); - } - - // Process the return argument, and assert that the metadata returned - // about the descriptor is indeed correct. - let before = self.rust_arguments.len(); - self.ret(&function.ret)?; - function - .ret - .assert_abi_return_correct(before, self.rust_arguments.len()); - Ok(self) - } - - pub fn constructor(&mut self, class: Option<&str>) -> &mut Self { - self.constructor = class.map(|s| s.to_string()); - self - } - - /// Flag this shim as a method call into Rust, so the first Rust argument - /// passed should be `this.ptr`. - pub fn method(&mut self, consumed: bool) -> &mut Self { - self.method = true; - if self.cx.config.debug { - self.prelude( - "if (this.ptr === 0) { - throw new Error('Attempt to use a moved value'); - }", - ); - } - if consumed { - self.prelude( - "\ - const ptr = this.ptr;\n\ - this.ptr = 0;\n\ - ", - ); - self.rust_arguments.insert(0, "ptr".to_string()); - } else { - self.rust_arguments.insert(0, "this.ptr".to_string()); - } - self - } - - /// Add extra processing to the prelude of this shim. - pub fn prelude(&mut self, s: &str) -> &mut Self { - for line in s.lines() { - self.prelude.push_str(line); - self.prelude.push_str("\n"); - } - self - } - - /// Add extra processing to the finally block of this shim. - pub fn finally(&mut self, s: &str) -> &mut Self { - for line in s.lines() { - self.finally.push_str(line); - self.finally.push_str("\n"); - } - self - } - - /// Add an Rust argument to be passed manually. - pub fn rust_argument(&mut self, s: &str) -> &mut Self { - self.rust_arguments.push(s.to_string()); - self - } - - fn abi_arg(&mut self, opt_arg_name: Option<&str>) -> String { - let ret = if let Some(x) = opt_arg_name { - x.into() - } else { - format!("arg{}", self.arg_idx) - }; - self.arg_idx += 1; - ret - } - - fn argument(&mut self, arg: &Descriptor, arg_name: Option<&str>) -> Result<&mut Self, Error> { - let i = self.arg_idx; - let name = self.abi_arg(arg_name); - - let (arg, optional) = match arg { - Descriptor::Option(t) => (&**t, true), - _ => (arg, false), - }; - - if let Some(kind) = arg.vector_kind() { - self.js_arguments - .push(JsArgument::required(name.clone(), kind.js_ty().to_string())); - - let func = self.cx.pass_to_wasm_function(kind)?; - let val = if optional { - self.cx.expose_is_like_none(); - format!("isLikeNone({}) ? [0, 0] : {}({})", name, func, name) - } else { - format!("{}({})", func, name) - }; - self.prelude(&format!( - "const ptr{i} = {val};\nconst len{i} = WASM_VECTOR_LEN;", - i = i, - val = val, - )); - if arg.is_by_ref() { - if optional { - bail!("optional slices aren't currently supported"); - } - if arg.is_mut_ref() { - let get = self.cx.memview_function(kind); - self.finally(&format!( - "\ - {arg}.set({get}().subarray(\ - ptr{i} / {size}, \ - ptr{i} / {size} + len{i}\ - ));\n\ - ", - i = i, - arg = name, - get = get, - size = kind.size() - )); - } - self.finally(&format!( - "\ - wasm.__wbindgen_free(ptr{i}, len{i} * {size});\n\ - ", - i = i, - size = kind.size() - )); - self.cx.require_internal_export("__wbindgen_free")?; - } - self.rust_arguments.push(format!("ptr{}", i)); - self.rust_arguments.push(format!("len{}", i)); - return Ok(self); - } - - if arg.is_anyref() { - self.js_arguments - .push(JsArgument::required(name.clone(), "any".to_string())); - if self.cx.config.anyref { - if optional { - self.cx.expose_add_to_anyref_table()?; - self.cx.expose_is_like_none(); - self.rust_arguments - .push(format!("isLikeNone({0}) ? 0 : addToAnyrefTable({0})", name)); - } else { - self.rust_arguments.push(name); - } - } else { - self.cx.expose_add_heap_object(); - if optional { - self.cx.expose_is_like_none(); - self.rust_arguments - .push(format!("isLikeNone({0}) ? 0 : addHeapObject({0})", name)); - } else { - self.rust_arguments.push(format!("addHeapObject({})", name)); - } - } - return Ok(self); - } - - if optional { - self.cx.expose_is_like_none(); - - if arg.is_wasm_native() { - self.js_arguments - .push(JsArgument::optional(name.clone(), "number".to_string())); - - if self.cx.config.debug { - self.cx.expose_assert_num(); - self.prelude(&format!( - " - if (!isLikeNone({0})) {{ - _assertNum({0}); - }} - ", - name - )); - } - - self.rust_arguments.push(format!("!isLikeNone({0})", name)); - self.rust_arguments - .push(format!("isLikeNone({0}) ? 0 : {0}", name)); - return Ok(self); - } - - if arg.is_abi_as_u32() { - self.js_arguments - .push(JsArgument::optional(name.clone(), "number".to_string())); - - if self.cx.config.debug { - self.cx.expose_assert_num(); - self.prelude(&format!( - " - if (!isLikeNone({0})) {{ - _assertNum({0}); - }} - ", - name - )); - } - - self.rust_arguments - .push(format!("isLikeNone({0}) ? 0xFFFFFF : {0}", name)); - return Ok(self); - } - - if let Some(signed) = arg.get_64() { - let f = if signed { - self.cx.expose_int64_cvt_shim() - } else { - self.cx.expose_uint64_cvt_shim() - }; - self.cx.expose_uint32_memory(); - self.js_arguments - .push(JsArgument::optional(name.clone(), "BigInt".to_string())); - self.prelude(&format!( - " - {f}[0] = isLikeNone({name}) ? BigInt(0) : {name}; - const low{i} = isLikeNone({name}) ? 0 : u32CvtShim[0]; - const high{i} = isLikeNone({name}) ? 0 : u32CvtShim[1]; - ", - i = i, - f = f, - name = name, - )); - self.rust_arguments.push(format!("!isLikeNone({})", name)); - self.rust_arguments.push(format!("0")); - self.rust_arguments.push(format!("low{}", i)); - self.rust_arguments.push(format!("high{}", i)); - return Ok(self); - } - - match *arg { - Descriptor::Boolean => { - self.js_arguments - .push(JsArgument::optional(name.clone(), "boolean".to_string())); - if self.cx.config.debug { - self.cx.expose_assert_bool(); - self.prelude(&format!( - " - if (!isLikeNone({0})) {{ - _assertBoolean({0}); - }} - ", - name, - )); - } - self.rust_arguments - .push(format!("isLikeNone({0}) ? 0xFFFFFF : {0} ? 1 : 0", name)); - } - Descriptor::Char => { - self.js_arguments - .push(JsArgument::optional(name.clone(), "string".to_string())); - self.rust_arguments.push(format!( - "isLikeNone({0}) ? 0xFFFFFF : {0}.codePointAt(0)", - name - )); - } - Descriptor::Enum { hole } => { - self.js_arguments - .push(JsArgument::optional(name.clone(), "number".to_string())); - self.rust_arguments - .push(format!("isLikeNone({0}) ? {1} : {0}", name, hole)); - } - Descriptor::RustStruct(ref s) => { - self.js_arguments - .push(JsArgument::optional(name.clone(), s.to_string())); - self.prelude(&format!("let ptr{} = 0;", i)); - self.prelude(&format!("if (!isLikeNone({0})) {{", name)); - self.assert_class(&name, s); - self.assert_not_moved(&name); - self.prelude(&format!("ptr{} = {}.ptr;", i, name)); - self.prelude(&format!("{}.ptr = 0;", name)); - self.prelude("}"); - self.rust_arguments.push(format!("ptr{}", i)); - } - _ => bail!( - "unsupported optional argument type for calling Rust function from JS: {:?}", - arg - ), - } - - return Ok(self); - } - - if let Some(s) = arg.rust_struct() { - self.js_arguments - .push(JsArgument::required(name.clone(), s.to_string())); - self.assert_class(&name, s); - self.assert_not_moved(&name); - if arg.is_by_ref() { - self.rust_arguments.push(format!("{}.ptr", name)); - } else { - self.prelude(&format!("const ptr{} = {}.ptr;", i, name)); - self.prelude(&format!("{}.ptr = 0;", name)); - self.rust_arguments.push(format!("ptr{}", i)); - } - return Ok(self); - } - - if arg.number().is_some() { - self.js_arguments - .push(JsArgument::required(name.clone(), "number".to_string())); - - if self.cx.config.debug { - self.cx.expose_assert_num(); - self.prelude(&format!("_assertNum({});", name)); - } - - self.rust_arguments.push(name); - return Ok(self); - } - - if let Some(signed) = arg.get_64() { - let f = if signed { - self.cx.expose_int64_cvt_shim() - } else { - self.cx.expose_uint64_cvt_shim() - }; - self.cx.expose_uint32_memory(); - self.js_arguments - .push(JsArgument::required(name.clone(), "BigInt".to_string())); - self.prelude(&format!( - " - {f}[0] = {name}; - const low{i} = u32CvtShim[0]; - const high{i} = u32CvtShim[1]; - ", - i = i, - f = f, - name = name, - )); - self.rust_arguments.push(format!("low{}", i)); - self.rust_arguments.push(format!("high{}", i)); - return Ok(self); - } - - if arg.is_ref_anyref() { - self.js_arguments - .push(JsArgument::required(name.clone(), "any".to_string())); - if self.cx.config.anyref { - self.rust_arguments.push(name); - } else { - // 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 - self.cx.expose_borrowed_objects(); - self.cx.expose_global_stack_pointer(); - self.finally("heap[stack_pointer++] = undefined;"); - self.rust_arguments - .push(format!("addBorrowedObject({})", name)); - } - return Ok(self); - } - - match *arg { - Descriptor::Boolean => { - self.js_arguments - .push(JsArgument::required(name.clone(), "boolean".to_string())); - if self.cx.config.debug { - self.cx.expose_assert_bool(); - self.prelude(&format!( - "\ - _assertBoolean({name});\n\ - ", - name = name - )); - } - self.rust_arguments.push(format!("{}", name)); - } - Descriptor::Char => { - self.js_arguments - .push(JsArgument::required(name.clone(), "string".to_string())); - self.rust_arguments.push(format!("{}.codePointAt(0)", name)) - } - _ => bail!( - "unsupported argument type for calling Rust function from JS: {:?}", - arg - ), - } - Ok(self) - } - - fn ret(&mut self, ty: &Descriptor) -> Result<&mut Self, Error> { - if let Some(name) = ty.rust_struct() { - match &self.constructor { - Some(class) if class == name => { - self.ret_expr = format!("this.ptr = RET;"); - if self.cx.config.weak_refs { - self.ret_expr.push_str(&format!( - "\ - {}FinalizationGroup.register(this, this.ptr, this.ptr); - ", - name - )); - } - } - Some(class) => bail!("constructor for `{}` cannot return `{}`", class, name), - None => { - self.ret_ty = name.to_string(); - self.cx.require_class_wrap(name); - self.ret_expr = format!("return {name}.__wrap(RET);", name = name); - } - } - return Ok(self); - } - - if self.constructor.is_some() { - bail!("constructor functions must return a Rust structure") - } - - if let Descriptor::Unit = ty { - self.ret_ty = "void".to_string(); - self.ret_expr = format!("return RET;"); - return Ok(self); - } - - let (ty, optional) = match ty { - Descriptor::Option(t) => (&**t, true), - _ => (ty, false), - }; - - if let Some(ty) = ty.vector_kind() { - self.ret_ty = ty.js_ty().to_string(); - let f = self.cx.expose_get_vector_from_wasm(ty)?; - self.cx.expose_global_argument_ptr()?; - self.cx.expose_uint32_memory(); - self.cx.require_internal_export("__wbindgen_free")?; - self.prelude("const retptr = globalArgumentPtr();"); - self.rust_arguments.insert(0, "retptr".to_string()); - self.ret_expr = format!( - "\ - RET;\n\ - const mem = getUint32Memory();\n\ - const rustptr = mem[retptr / 4];\n\ - const rustlen = mem[retptr / 4 + 1];\n\ - {guard} - const realRet = {}(rustptr, rustlen).slice();\n\ - wasm.__wbindgen_free(rustptr, rustlen * {});\n\ - return realRet;\n\ - ", - f, - ty.size(), - guard = if optional { - "if (rustptr === 0) return;" - } else { - "" - }, - ); - return Ok(self); - } - - // No need to worry about `optional` here, the abi representation means - // that `takeObject` will naturally pluck out `undefined`. - if ty.is_anyref() { - self.ret_ty = "any".to_string(); - self.ret_expr = format!("return {};", self.cx.take_object("RET")); - return Ok(self); - } - - if optional { - if ty.is_wasm_native() { - self.ret_ty = "number | undefined".to_string(); - self.cx.expose_global_argument_ptr()?; - self.cx.expose_uint32_memory(); - match ty { - Descriptor::I32 => self.cx.expose_int32_memory(), - Descriptor::U32 => (), - Descriptor::F32 => self.cx.expose_f32_memory(), - Descriptor::F64 => self.cx.expose_f64_memory(), - _ => (), - }; - self.prelude("const retptr = globalArgumentPtr();"); - self.rust_arguments.insert(0, "retptr".to_string()); - self.ret_expr = format!( - " - RET; - const present = getUint32Memory()[retptr / 4]; - const value = {mem}[retptr / {size} + 1]; - return present === 0 ? undefined : value; - ", - size = match ty { - Descriptor::I32 => 4, - Descriptor::U32 => 4, - Descriptor::F32 => 4, - Descriptor::F64 => 8, - _ => unreachable!(), - }, - mem = match ty { - Descriptor::I32 => "getInt32Memory()", - Descriptor::U32 => "getUint32Memory()", - Descriptor::F32 => "getFloat32Memory()", - Descriptor::F64 => "getFloat64Memory()", - _ => unreachable!(), - } - ); - return Ok(self); - } - - if ty.is_abi_as_u32() { - self.ret_ty = "number | undefined".to_string(); - self.ret_expr = " - const ret = RET; - return ret === 0xFFFFFF ? undefined : ret; - " - .to_string(); - return Ok(self); - } - - if let Some(signed) = ty.get_64() { - self.ret_ty = "BigInt | undefined".to_string(); - self.cx.expose_global_argument_ptr()?; - let f = if signed { - self.cx.expose_int64_memory(); - "getInt64Memory" - } else { - self.cx.expose_uint64_memory(); - "getUint64Memory" - }; - self.prelude("const retptr = globalArgumentPtr();"); - self.rust_arguments.insert(0, "retptr".to_string()); - self.ret_expr = format!( - " - RET; - const present = getUint32Memory()[retptr / 4]; - const value = {}()[retptr / 8 + 1]; - return present === 0 ? undefined : value; - ", - f - ); - return Ok(self); - } - - match *ty { - Descriptor::Boolean => { - self.ret_ty = "boolean | undefined".to_string(); - self.ret_expr = " - const ret = RET; - return ret === 0xFFFFFF ? undefined : ret !== 0; - " - .to_string(); - return Ok(self); - } - Descriptor::Char => { - self.ret_ty = "string | undefined".to_string(); - self.ret_expr = " - const ret = RET; - return ret === 0xFFFFFF ? undefined : String.fromCodePoint(ret); - " - .to_string(); - return Ok(self); - } - Descriptor::Enum { hole } => { - self.ret_ty = "number | undefined".to_string(); - self.ret_expr = format!( - " - const ret = RET; - return ret === {} ? undefined : ret; - ", - hole - ); - return Ok(self); - } - Descriptor::RustStruct(ref name) => { - self.ret_ty = format!("{} | undefined", name); - self.cx.require_class_wrap(name); - self.ret_expr = format!( - " - const ptr = RET; - return ptr === 0 ? undefined : {}.__wrap(ptr); - ", - name, - ); - return Ok(self); - } - _ => bail!( - "unsupported optional return type for calling Rust function from JS: {:?}", - ty - ), - }; - } - - if ty.is_ref_anyref() { - self.ret_ty = "any".to_string(); - self.cx.expose_get_object(); - self.ret_expr = format!("return getObject(RET);"); - return Ok(self); - } - - if ty.is_by_ref() { - bail!("cannot return references from Rust to JS yet") - } - - if let Some(name) = ty.rust_struct() { - self.ret_ty = name.to_string(); - self.cx.require_class_wrap(name); - self.ret_expr = format!("return {name}.__wrap(RET);", name = name); - return Ok(self); - } - - if let Some(num) = ty.number() { - self.ret_ty = "number".to_string(); - if num.is_u32() { - self.ret_expr = format!("return RET >>> 0;"); - } else { - self.ret_expr = format!("return RET;"); - } - return Ok(self); - } - - if let Some(signed) = ty.get_64() { - self.ret_ty = "BigInt".to_string(); - self.cx.expose_global_argument_ptr()?; - let f = if signed { - self.cx.expose_int64_memory(); - "getInt64Memory" - } else { - self.cx.expose_uint64_memory(); - "getUint64Memory" - }; - self.prelude("const retptr = globalArgumentPtr();"); - self.rust_arguments.insert(0, "retptr".to_string()); - self.ret_expr = format!( - "\ - RET;\n\ - return {}()[retptr / 8];\n\ - ", - f - ); - return Ok(self); - } - - match *ty { - Descriptor::Boolean => { - self.ret_ty = "boolean".to_string(); - self.ret_expr = format!("return (RET) !== 0;"); - } - Descriptor::Char => { - self.ret_ty = "string".to_string(); - self.ret_expr = format!("return String.fromCodePoint(RET);") - } - _ => bail!( - "unsupported return type for calling Rust function from JS: {:?}", - ty - ), - } - Ok(self) - } - - pub fn js_doc_comments(&self) -> String { - let mut ret: String = self - .js_arguments - .iter() - .map(|a| { - if a.optional { - format!("@param {{{} | undefined}} {}\n", a.type_, a.name) - } else { - format!("@param {{{}}} {}\n", a.type_, a.name) - } - }) - .collect(); - ret.push_str(&format!("@returns {{{}}}", self.ret_ty)); - ret - } - - /// Generate the actual function. - /// - /// The `prefix` specified is typically the string "function" but may be - /// different for classes. The `invoc` is the function expression that we're - /// invoking, like `wasm.bar` or `this.f`. - /// - /// Returns two strings, the first of which is the JS expression for the - /// generated function shim and the second is a TypeScript signature of the - /// JS expression. - pub fn finish(&mut self, prefix: &str, invoc: &str) -> (String, String, String) { - let js_args = self - .js_arguments - .iter() - .map(|s| &s.name[..]) - .collect::>() - .join(", "); - let mut js = format!("{}({}) {{\n", prefix, js_args); - js.push_str(&self.prelude); - let rust_args = self.rust_arguments.join(", "); - - let invoc = self - .ret_expr - .replace("RET", &format!("{}({})", invoc, rust_args)); - let invoc = if self.finally.len() == 0 { - invoc - } else { - format!( - "\ - try {{\n\ - {} - \n}} finally {{\n\ - {} - }}\n\ - ", - &invoc, &self.finally, - ) - }; - js.push_str(&invoc); - js.push_str("\n}"); - - // Determine TS parameter list - let mut omittable = true; - let mut ts_args = Vec::with_capacity(self.js_arguments.len()); - for arg in self.js_arguments.iter().rev() { - // In TypeScript, we can mark optional parameters as omittable - // using the `?` suffix, but only if they're not followed by - // non-omittable parameters. Therefore iterate the parameter list - // in reverse and stop using the `?` suffix for optional params as - // soon as a non-optional parameter is encountered. - if arg.optional { - if omittable { - ts_args.push(format!("{}?: {}", arg.name, arg.type_)); - } else { - ts_args.push(format!("{}: {} | undefined", arg.name, arg.type_)); - } - } else { - omittable = false; - ts_args.push(format!("{}: {}", arg.name, arg.type_)); - } - } - ts_args.reverse(); - let ts_args = ts_args.join(", "); - - let mut ts = if prefix.is_empty() { - format!("{}({})", self.js_name, ts_args) - } else { - format!("{} {}({})", prefix, self.js_name, ts_args) - }; - if self.constructor.is_none() { - ts.push_str(": "); - ts.push_str(&self.ret_ty); - } - ts.push(';'); - - (js, ts, self.js_doc_comments()) - } - - fn assert_class(&mut self, arg: &str, class: &str) { - if !self.cx.config.debug { - return; - } - self.cx.expose_assert_class(); - self.prelude(&format!("_assertClass({}, {});", arg, class)); - } - - 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, - )); - } -} diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 80f7c63f701..03673ce8ca5 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -1,17 +1,19 @@ -mod js2rust; -mod rust2js; - use crate::descriptor::VectorKind; -use crate::js::js2rust::Js2Rust; -use crate::js::rust2js::Rust2Js; +use crate::intrinsic::Intrinsic; use crate::webidl::{AuxEnum, AuxExport, AuxExportKind, AuxImport, AuxStruct}; -use crate::webidl::{JsImport, JsImportName, WasmBindgenAux, WebidlCustomSection}; +use crate::webidl::{AuxValue, Binding}; +use crate::webidl::{JsImport, JsImportName, NonstandardWebidlSection, WasmBindgenAux}; use crate::{Bindgen, EncodeInto, OutputMode}; use failure::{bail, Error, ResultExt}; use std::collections::{BTreeMap, 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; pub struct Context<'a> { globals: String, @@ -21,7 +23,6 @@ pub struct Context<'a> { required_internal_exports: HashSet<&'static str>, config: &'a Bindgen, pub module: &'a mut Module, - bindings: WebidlCustomSection, /// 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 @@ -89,10 +90,6 @@ impl<'a> Context<'a> { wasm_import_definitions: Default::default(), exported_classes: Some(Default::default()), config, - bindings: *module - .customs - .delete_typed::() - .unwrap(), module, memory, npm_dependencies: Default::default(), @@ -660,19 +657,6 @@ impl<'a> Context<'a> { } } - fn expose_does_not_exist(&mut self) { - if !self.should_write_global("does_not_exist") { - return; - } - self.global( - " - function doesNotExist() { - throw new Error('imported function or type does not exist'); - } - ", - ); - } - fn expose_drop_ref(&mut self) { if !self.should_write_global("drop_ref") { return; @@ -1444,6 +1428,31 @@ impl<'a> Context<'a> { Ok(()) } + fn expose_log_error(&mut self) { + if !self.should_write_global("log_error") { + return; + } + self.global( + "\ + function logError(e) { + let error = (function () { + try { + return e instanceof Error \ + ? `${e.message}\\n\\nStack:\\n${e.stack}` \ + : e.toString(); + } catch(_) { + return \"\"; + } + }()); + console.error(\"wasm-bindgen: imported JS function that \ + was not marked as `catch` threw an error:\", \ + error); + throw e; + } + ", + ); + } + fn pass_to_wasm_function(&mut self, t: VectorKind) -> Result<&'static str, Error> { let s = match t { VectorKind::String => { @@ -1579,7 +1588,7 @@ impl<'a> Context<'a> { if (desc) return desc; obj = Object.getPrototypeOf(obj); } - return {} + return {}; } ", ); @@ -1801,40 +1810,32 @@ impl<'a> Context<'a> { Ok(()) } - fn take_object(&mut self, expr: &str) -> String { - if self.config.anyref { - expr.to_string() - } else { - self.expose_take_object(); - format!("takeObject({})", expr) - } - } - - fn get_object(&mut self, expr: &str) -> String { - if self.config.anyref { - expr.to_string() - } else { - self.expose_get_object(); - format!("getObject({})", expr) + 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)?; } - } - pub fn generate(&mut self, aux: &WasmBindgenAux) -> Result<(), Error> { 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).with_context(|_| { - format!( - "failed to generate bindings for Rust export `{}`", - export.debug_name, - ) - })?; + 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); - self.generate_import(*id, import, variadic, catch) + self.generate_import(*id, import, bindings, variadic, catch) .with_context(|_| { format!("failed to generate bindings for import `{:?}`", import,) })?; @@ -1856,75 +1857,111 @@ impl<'a> Context<'a> { Ok(()) } - fn generate_export(&mut self, id: ExportId, export: &AuxExport) -> Result<(), Error> { + /// 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); + 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 descriptor = self.bindings.exports[&id].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. + let mut builder = binding::Builder::new(self); + 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), + } + + // Process the `binding` and generate a bunch of JS/TypeScript/etc. + let js = builder.process( + &binding, + &webidl, + true, + &export.arg_names, + &mut |_, _, args| Ok(format!("wasm.{}({})", wasm_name, args.join(", "))), + )?; + 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 dependin + // on what's being exported. match &export.kind { AuxExportKind::Function(name) => { - let (js, ts, js_doc) = Js2Rust::new(&name, self) - .process(&descriptor, &export.arg_names)? - .finish("function", &format!("wasm.{}", wasm_name)); - self.export( - &name, - &js, - Some(format_doc_comments(&export.comments, Some(js_doc))), - )?; + self.export(&name, &format!("function{}", js), Some(docs))?; self.globals.push_str("\n"); - self.typescript.push_str("export "); + self.typescript.push_str("export function "); + self.typescript.push_str(&name); self.typescript.push_str(&ts); - self.typescript.push_str("\n"); + self.typescript.push_str(";\n"); } AuxExportKind::Constructor(class) => { - let (js, ts, raw_docs) = Js2Rust::new("constructor", self) - .constructor(Some(&class)) - .process(&descriptor, &export.arg_names)? - .finish("", &format!("wasm.{}", wasm_name)); let exported = require_class(&mut self.exported_classes, class); if exported.has_constructor { bail!("found duplicate constructor for class `{}`", class); } exported.has_constructor = true; - let docs = format_doc_comments(&export.comments, Some(raw_docs)); exported.push(&docs, "constructor", "", &js, &ts); } - AuxExportKind::Getter { class, field: name } - | AuxExportKind::Setter { class, field: name } - | AuxExportKind::StaticFunction { class, name } - | AuxExportKind::Method { class, name, .. } => { - let mut j2r = Js2Rust::new(name, self); - match export.kind { - AuxExportKind::StaticFunction { .. } => {} - AuxExportKind::Method { consumed: true, .. } => { - j2r.method(true); - } - _ => { - j2r.method(false); - } - } - let (js, ts, raw_docs) = j2r - .process(&descriptor, &export.arg_names)? - .finish("", &format!("wasm.{}", wasm_name)); - let docs = format_doc_comments(&export.comments, Some(raw_docs)); - match export.kind { - AuxExportKind::Getter { .. } => { - let ret_ty = j2r.ret_ty.clone(); - let exported = require_class(&mut self.exported_classes, class); - exported.push_getter(&docs, name, &js, &ret_ty); - } - AuxExportKind::Setter { .. } => { - let arg_ty = &j2r.js_arguments[0].type_.clone(); - let exported = require_class(&mut self.exported_classes, class); - exported.push_setter(&docs, name, &js, &arg_ty); - } - AuxExportKind::StaticFunction { .. } => { - let exported = require_class(&mut self.exported_classes, class); - exported.push(&docs, name, "static ", &js, &ts); - } - _ => { - let exported = require_class(&mut self.exported_classes, class); - exported.push(&docs, name, "", &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); } } Ok(()) @@ -1934,21 +1971,515 @@ impl<'a> Context<'a> { &mut self, id: ImportId, import: &AuxImport, + bindings: &NonstandardWebidlSection, variadic: bool, catch: bool, ) -> Result<(), Error> { - let signature = self.bindings.imports[&id].clone(); - let catch_and_rethrow = self.config.debug; - let js = Rust2Js::new(self) - .catch_and_rethrow(catch_and_rethrow) - .catch(catch) - .variadic(variadic) - .process(&signature)? - .finish(import)?; + let binding = &bindings.imports[&id]; + let webidl = bindings + .types + .get::(binding.webidl_ty) + .unwrap(); + let mut builder = binding::Builder::new(self); + builder.catch(catch)?; + let js = builder.process(&binding, &webidl, false, &None, &mut |cx, prelude, args| { + cx.invoke_import(&binding, import, bindings, args, variadic, prelude) + })?; + let js = format!("function{}", js); self.wasm_import_definitions.insert(id, js); Ok(()) } + /// Generates a JS snippet appropriate for invoking `import`. + /// + /// This is generating code for `binding` where `bindings` has more type + /// infomation. The `args` array is the list of JS expressions representing + /// the arguments to pass to JS. Finally `variadic` indicates whether the + /// last argument is a list to be splatted in a variadic way, and `prelude` + /// is a location to push some more initialization JS if necessary. + /// + /// The returned value here is a JS expression which evaluates to the + /// purpose of `AuxImport`, which depends on the kind of import. + fn invoke_import( + &mut self, + binding: &Binding, + import: &AuxImport, + bindings: &NonstandardWebidlSection, + 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(", ")) + } else { + let (last_arg, args) = match js_arguments.split_last() { + Some(pair) => pair, + None => bail!("a function with no arguments cannot be variadic"), + }; + if args.len() > 0 { + format!("{}, ...{}", args.join(", "), last_arg) + } else { + format!("...{}", last_arg) + } + }) + }; + match import { + AuxImport::Value(val) => match webidl_ty.kind { + ast::WebidlFunctionKind::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(_) => { + let descriptor = |anchor: &str, extra: &str, field: &str, which: &str| { + format!( + "GetOwnOrInheritedPropertyDescriptor({}{}, '{}').{}", + anchor, extra, field, which + ) + }; + let js = match val { + AuxValue::Bare(js) => self.import_name(js)?, + AuxValue::Getter(class, field) => { + self.expose_get_inherited_descriptor(); + let class = self.import_name(class)?; + descriptor(&class, ".prototype", field, "get") + } + AuxValue::ClassGetter(class, field) => { + self.expose_get_inherited_descriptor(); + let class = self.import_name(class)?; + descriptor(&class, "", field, "get") + } + AuxValue::Setter(class, field) => { + self.expose_get_inherited_descriptor(); + let class = self.import_name(class)?; + descriptor(&class, ".prototype", field, "set") + } + AuxValue::ClassSetter(class, field) => { + self.expose_get_inherited_descriptor(); + let class = self.import_name(class)?; + descriptor(&class, "", field, "set") + } + }; + Ok(format!("{}.call({})", js, variadic_args(&args)?)) + } + ast::WebidlFunctionKind::Static => { + let js = match val { + AuxValue::Bare(js) => self.import_name(js)?, + _ => bail!("invalid import set for constructor"), + }; + Ok(format!("{}({})", js, variadic_args(&args)?)) + } + }, + + AuxImport::Instanceof(js) => { + assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static); + assert!(!variadic); + assert_eq!(args.len(), 1); + let js = self.import_name(js)?; + Ok(format!("{} instanceof {}", args[0], js)) + } + + AuxImport::Static(js) => { + assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static); + assert!(!variadic); + assert_eq!(args.len(), 0); + self.import_name(js) + } + + AuxImport::Closure { + dtor, + mutable, + binding_idx, + nargs, + } => { + assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static); + assert!(!variadic); + assert_eq!(args.len(), 3); + let arg_names = (0..*nargs) + .map(|i| format!("arg{}", i)) + .collect::>() + .join(", "); + let mut js = format!("({}) => {{\n", arg_names); + // First up with a closure we increment the internal reference + // count. This ensures that the Rust closure environment won't + // be deallocated while we're invoking it. + js.push_str("state.cnt++;\n"); + + self.export_function_table()?; + let dtor = format!("wasm.__wbg_function_table.get({})", dtor); + let call = format!("__wbg_elem_binding{}", binding_idx); + + if *mutable { + // For mutable closures they can't be invoked recursively. + // To handle that we swap out the `this.a` pointer with zero + // while we invoke it. If we finish and the closure wasn't + // destroyed, then we put back the pointer so a future + // invocation can succeed. + js.push_str("const a = state.a;\n"); + js.push_str("state.a = 0;\n"); + js.push_str("try {\n"); + js.push_str(&format!("return {}(a, state.b, {});\n", call, arg_names)); + js.push_str("} finally {\n"); + js.push_str("if (--state.cnt === 0) "); + js.push_str(&dtor); + js.push_str("(a, state.b);\n"); + js.push_str("else state.a = a;\n"); + js.push_str("}\n"); + } else { + // For shared closures they can be invoked recursively so we + // just immediately pass through `this.a`. If we end up + // executing the destructor, however, we clear out the + // `this.a` pointer to prevent it being used again the + // future. + js.push_str("try {\n"); + js.push_str(&format!( + "return {}(state.a, state.b, {});\n", + call, arg_names + )); + js.push_str("} finally {\n"); + js.push_str("if (--state.cnt === 0) {\n"); + js.push_str(&dtor); + js.push_str("(state.a, state.b);\n"); + js.push_str("state.a = 0;\n"); + js.push_str("}\n"); + js.push_str("}\n"); + } + js.push_str("}\n"); + + prelude.push_str(&format!( + " + const state = {{ a: {arg0}, b: {arg1}, cnt: 1 }}; + const real = {body}; + real.original = state; + ", + body = js, + arg0 = &args[0], + arg1 = &args[1], + )); + Ok("real".to_string()) + } + + AuxImport::StructuralMethod(name) => { + assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static); + let (receiver, args) = match args.split_first() { + Some(pair) => pair, + None => bail!("structural method calls must have at least one argument"), + }; + Ok(format!("{}.{}({})", receiver, name, variadic_args(args)?)) + } + + AuxImport::StructuralGetter(field) => { + assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static); + assert!(!variadic); + assert_eq!(args.len(), 1); + Ok(format!("{}.{}", args[0], field)) + } + + AuxImport::StructuralClassGetter(class, field) => { + assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static); + assert!(!variadic); + assert_eq!(args.len(), 0); + let class = self.import_name(class)?; + Ok(format!("{}.{}", class, field)) + } + + AuxImport::StructuralSetter(field) => { + assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static); + 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!(!variadic); + assert_eq!(args.len(), 1); + let class = self.import_name(class)?; + Ok(format!("{}.{} = {}", class, field, args[0])) + } + + AuxImport::IndexingGetterOfClass(class) => { + assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static); + assert!(!variadic); + assert_eq!(args.len(), 1); + let class = self.import_name(class)?; + Ok(format!("{}[{}]", class, args[0])) + } + + AuxImport::IndexingGetterOfObject => { + assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static); + assert!(!variadic); + assert_eq!(args.len(), 2); + Ok(format!("{}[{}]", args[0], args[1])) + } + + AuxImport::IndexingSetterOfClass(class) => { + assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static); + assert!(!variadic); + assert_eq!(args.len(), 2); + let class = self.import_name(class)?; + Ok(format!("{}[{}] = {}", class, args[0], args[1])) + } + + AuxImport::IndexingSetterOfObject => { + assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static); + 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!(!variadic); + assert_eq!(args.len(), 1); + let class = self.import_name(class)?; + Ok(format!("delete {}[{}]", class, args[0])) + } + + AuxImport::IndexingDeleterOfObject => { + assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static); + 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!(!variadic); + assert_eq!(args.len(), 1); + self.require_class_wrap(class); + Ok(format!("{}.__wrap({})", class, args[0])) + } + + AuxImport::Intrinsic(intrinsic) => { + assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static); + assert!(!variadic); + self.invoke_intrinsic(intrinsic, args, prelude) + } + } + } + + /// Same as `invoke_import` above, except more specialized and only used for + /// generating the JS expression needed to implement a particular intrinsic. + fn invoke_intrinsic( + &mut self, + intrinsic: &Intrinsic, + args: &[String], + prelude: &mut String, + ) -> Result { + let expr = match intrinsic { + Intrinsic::JsvalEq => { + assert_eq!(args.len(), 2); + format!("{} === {}", args[0], args[1]) + } + + Intrinsic::IsFunction => { + assert_eq!(args.len(), 1); + format!("typeof({}) === 'function'", args[0]) + } + + Intrinsic::IsUndefined => { + assert_eq!(args.len(), 1); + format!("{} === undefined", args[0]) + } + + Intrinsic::IsNull => { + assert_eq!(args.len(), 1); + format!("{} === null", args[0]) + } + + Intrinsic::IsObject => { + assert_eq!(args.len(), 1); + prelude.push_str(&format!("const val = {};\n", args[0])); + format!("typeof(val) === 'object' && val !== null") + } + + Intrinsic::IsSymbol => { + assert_eq!(args.len(), 1); + format!("typeof({}) === 'symbol'", args[0]) + } + + Intrinsic::IsString => { + assert_eq!(args.len(), 1); + format!("typeof({}) === 'string'", args[0]) + } + + Intrinsic::ObjectCloneRef => { + assert_eq!(args.len(), 1); + args[0].clone() + } + + Intrinsic::ObjectDropRef => { + assert_eq!(args.len(), 1); + args[0].clone() + } + + Intrinsic::CallbackDrop => { + assert_eq!(args.len(), 1); + prelude.push_str(&format!("const obj = {}.original;\n", args[0])); + prelude.push_str("if (obj.cnt-- == 1) {\n"); + prelude.push_str("obj.a = 0;\n"); + prelude.push_str("return true;\n"); + prelude.push_str("}\n"); + "false".to_string() + } + + Intrinsic::CallbackForget => { + assert_eq!(args.len(), 1); + args[0].clone() + } + + Intrinsic::NumberNew => { + assert_eq!(args.len(), 1); + args[0].clone() + } + + Intrinsic::StringNew => { + assert_eq!(args.len(), 1); + args[0].clone() + } + + Intrinsic::SymbolNamedNew => { + assert_eq!(args.len(), 1); + format!("Symbol({})", args[0]) + } + + Intrinsic::SymbolAnonymousNew => { + assert_eq!(args.len(), 0); + "Symbol()".to_string() + } + + Intrinsic::NumberGet => { + assert_eq!(args.len(), 2); + self.expose_uint8_memory(); + 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() + } + + Intrinsic::StringGet => { + self.expose_pass_string_to_wasm()?; + self.expose_uint32_memory(); + assert_eq!(args.len(), 2); + 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() + } + + Intrinsic::BooleanGet => { + assert_eq!(args.len(), 1); + prelude.push_str(&format!("const v = {};\n", args[0])); + format!("typeof(v) === 'boolean' ? (v ? 1 : 0) : 2") + } + + Intrinsic::Throw => { + assert_eq!(args.len(), 1); + format!("throw new Error({})", args[0]) + } + + Intrinsic::Rethrow => { + assert_eq!(args.len(), 1); + format!("throw {}", args[0]) + } + + Intrinsic::Module => { + assert_eq!(args.len(), 0); + if !self.config.mode.no_modules() && !self.config.mode.web() { + bail!( + "`wasm_bindgen::module` is currently only supported with \ + `--target no-modules` and `--target web`" + ); + } + format!("init.__wbindgen_wasm_module") + } + + Intrinsic::Memory => { + assert_eq!(args.len(), 0); + self.memory().to_string() + } + + Intrinsic::FunctionTable => { + assert_eq!(args.len(), 0); + self.export_function_table()?; + format!("wasm.__wbg_function_table") + } + + Intrinsic::DebugString => { + assert_eq!(args.len(), 1); + self.expose_debug_string(); + format!("debugString({})", args[0]) + } + + Intrinsic::JsonParse => { + assert_eq!(args.len(), 1); + format!("JSON.parse({})", args[0]) + } + + Intrinsic::JsonSerialize => { + assert_eq!(args.len(), 1); + format!("JSON.stringify({})", args[0]) + } + + Intrinsic::AnyrefHeapLiveCount => { + assert_eq!(args.len(), 0); + if self.config.anyref { + // Eventually we should add support to the anyref-xform to + // re-write calls to the imported + // `__wbindgen_anyref_heap_live_count` function into calls to + // the exported `__wbindgen_anyref_heap_live_count_impl` + // function, and to un-export that function. + // + // But for now, we just bounce wasm -> js -> wasm because it is + // easy. + self.require_internal_export("__wbindgen_anyref_heap_live_count_impl")?; + "wasm.__wbindgen_anyref_heap_live_count_impl()".into() + } else { + self.expose_global_heap(); + prelude.push_str( + " + let free_count = 0; + let next = heap_next; + while (next < heap.length) { + free_count += 1; + next = heap[next]; + } + ", + ); + format!( + "heap.length - free_count - {} - {}", + INITIAL_HEAP_OFFSET, + INITIAL_HEAP_VALUES.len(), + ) + } + } + + Intrinsic::InitAnyrefTable => { + self.expose_anyref_table(); + String::from( + " + const table = wasm.__wbg_anyref_table; + const offset = table.grow(4); + table.set(offset + 0, undefined); + table.set(offset + 1, null); + table.set(offset + 2, true); + table.set(offset + 3, false); + ", + ) + } + }; + Ok(expr) + } + fn generate_enum(&mut self, enum_: &AuxEnum) -> Result<(), Error> { let mut variants = String::new(); @@ -2214,8 +2745,9 @@ impl ExportedClass { self.typescript.push_str(docs); self.typescript.push_str(" "); self.typescript.push_str(function_prefix); + self.typescript.push_str(function_name); self.typescript.push_str(ts); - self.typescript.push_str("\n"); + self.typescript.push_str(";\n"); } /// Used for adding a getter to a class, mainly to ensure that TypeScript diff --git a/crates/cli-support/src/js/outgoing.rs b/crates/cli-support/src/js/outgoing.rs new file mode 100644 index 00000000000..c0e2abe08bf --- /dev/null +++ b/crates/cli-support/src/js/outgoing.rs @@ -0,0 +1,411 @@ +//! 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 failure::{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::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") + } +} diff --git a/crates/cli-support/src/js/rust2js.rs b/crates/cli-support/src/js/rust2js.rs deleted file mode 100644 index e18fba190b4..00000000000 --- a/crates/cli-support/src/js/rust2js.rs +++ /dev/null @@ -1,1216 +0,0 @@ -use crate::descriptor::Descriptor; -use crate::intrinsic::Intrinsic; -use crate::js::{Context, Js2Rust}; -use crate::webidl::{AuxImport, AuxValue, ImportBinding}; -use failure::{bail, Error}; - -/// Helper struct for manufacturing a shim in JS used to translate Rust types to -/// JS, then invoking an imported JS function. -pub struct Rust2Js<'a, 'b: 'a> { - cx: &'a mut Context<'b>, - - /// Arguments of the JS shim that we're generating, aka the variables passed - /// from Rust which are only numbers. - shim_arguments: Vec, - - /// Arguments which are forwarded to the imported JS function - js_arguments: Vec, - - /// Conversions that happen before we invoke the wasm function, such as - /// converting a string to a ptr/length pair. - prelude: String, - - /// "Destructors" or cleanup that must happen after the wasm function - /// finishes. This is scheduled in a `finally` block. - finally: String, - - /// Next global index to write to when passing arguments via the single - /// global stack. - global_idx: usize, - - /// Index of the next argument for unique name generation purposes. - arg_idx: usize, - - /// Expression used to generate the return value. The string "JS" in this - /// expression is replaced with the actual JS invocation eventually. - ret_expr: String, - - /// Whether or not we're catching JS exceptions - catch: bool, - catch_and_rethrow: bool, - - /// Whether or not the last argument is a slice representing variadic arguments. - variadic: bool, - - /// What sort of style this invocation will be like, see the variants of - /// this enum for more information. - style: Style, - - /// list of arguments that are anyref, and whether they're an owned anyref - /// or not. - anyref_args: Vec<(usize, bool)>, - ret_anyref: bool, -} - -#[derive(PartialEq)] -enum Style { - /// The imported function is expected to be invoked with `new` to create a - /// JS object. - Constructor, - /// The imported function is expected to be invoked where the first - /// parameter is the `this` and the rest of the arguments are the - /// function's arguments. - Method, - /// Just a normal function call. - Function, -} - -impl<'a, 'b> Rust2Js<'a, 'b> { - pub fn new(cx: &'a mut Context<'b>) -> Rust2Js<'a, 'b> { - Rust2Js { - cx, - shim_arguments: Vec::new(), - js_arguments: Vec::new(), - prelude: String::new(), - finally: String::new(), - global_idx: 0, - arg_idx: 0, - ret_expr: String::new(), - catch: false, - catch_and_rethrow: false, - variadic: false, - anyref_args: Vec::new(), - ret_anyref: false, - style: Style::Function, - } - } - - pub fn catch(&mut self, catch: bool) -> &mut Self { - self.catch = catch; - self - } - - pub fn catch_and_rethrow(&mut self, catch_and_rethrow: bool) -> &mut Self { - self.catch_and_rethrow = catch_and_rethrow; - self - } - - pub fn variadic(&mut self, variadic: bool) -> &mut Self { - self.variadic = variadic; - self - } - - /// Generates all bindings necessary for the signature in `Function`, - /// creating necessary argument conversions and return value processing. - pub fn process(&mut self, binding: &ImportBinding) -> Result<&mut Self, Error> { - let function = match binding { - ImportBinding::Constructor(f) => { - self.style = Style::Constructor; - f - } - ImportBinding::Method(f) => { - self.style = Style::Method; - f - } - ImportBinding::Function(f) => { - self.style = Style::Function; - f - } - }; - for arg in function.arguments.iter() { - // Process the function argument and assert that the metadata about - // the number of arguments on the Rust side required is correct. - let before = self.shim_arguments.len(); - self.argument(arg)?; - arg.assert_abi_arg_correct(before, self.shim_arguments.len()); - } - // Process the return argument, and assert that the metadata returned - // about the descriptor is indeed correct. - let before = self.shim_arguments.len(); - self.ret(&function.ret)?; - function - .ret - .assert_abi_return_correct(before, self.shim_arguments.len()); - Ok(self) - } - - /// Get a generated name for an argument. - fn shim_argument(&mut self) -> String { - let s = format!("arg{}", self.arg_idx); - self.arg_idx += 1; - self.shim_arguments.push(s.clone()); - s - } - - fn argument(&mut self, arg: &Descriptor) -> Result<(), Error> { - let abi = self.shim_argument(); - - let (arg, optional) = match arg { - Descriptor::Option(t) => (&**t, true), - _ => (arg, false), - }; - - if let Some(ty) = arg.vector_kind() { - let abi2 = self.shim_argument(); - let f = self.cx.expose_get_vector_from_wasm(ty)?; - self.prelude(&format!( - "let v{0} = {prefix}{func}({0}, {1});", - abi, - abi2, - func = f, - prefix = if optional { - format!("{} == 0 ? undefined : ", abi) - } else { - String::new() - }, - )); - - if !arg.is_by_ref() { - self.prelude(&format!( - "\ - {start} - v{0} = v{0}.slice(); - wasm.__wbindgen_free({0}, {1} * {size}); - {end}\ - ", - abi, - abi2, - size = ty.size(), - start = if optional { - format!("if ({} !== 0) {{", abi) - } else { - String::new() - }, - end = if optional { "}" } else { "" }, - )); - self.cx.require_internal_export("__wbindgen_free")?; - } - self.js_arguments.push(format!("v{}", abi)); - return Ok(()); - } - - // No need to special case `optional` here because `takeObject` will - // naturally work. - if arg.is_anyref() { - let arg = self.cx.take_object(&abi); - self.js_arguments.push(arg); - self.anyref_args.push((self.arg_idx - 1, true)); - return Ok(()); - } else if arg.is_ref_anyref() { - let arg = self.cx.get_object(&abi); - self.js_arguments.push(arg); - self.anyref_args.push((self.arg_idx - 1, false)); - return Ok(()); - } - - if optional { - if arg.is_wasm_native() { - let value = self.shim_argument(); - self.js_arguments.push(format!( - "{present} === 0 ? undefined : {value}", - value = value, - present = abi, - )); - return Ok(()); - } - - if arg.is_abi_as_u32() { - self.js_arguments - .push(format!("{0} === 0xFFFFFF ? undefined : {0}", abi)); - return Ok(()); - } - - if let Some(signed) = arg.get_64() { - let f = if signed { - self.cx.expose_int64_cvt_shim() - } else { - self.cx.expose_uint64_cvt_shim() - }; - self.shim_argument(); - let low = self.shim_argument(); - let high = self.shim_argument(); - let name = format!("n{}", abi); - self.prelude(&format!( - " - u32CvtShim[0] = {present} === 0 ? 0 : {low}; - u32CvtShim[1] = {present} === 0 ? 0 : {high}; - const {name} = {present} === 0 ? undefined : {f}[0]; - ", - present = abi, - low = low, - high = high, - f = f, - name = name, - )); - self.js_arguments.push(name); - return Ok(()); - } - - match *arg { - Descriptor::Boolean => { - self.js_arguments - .push(format!("{0} === 0xFFFFFF ? undefined : {0} !== 0", abi)); - return Ok(()); - } - Descriptor::Enum { hole } => { - self.js_arguments - .push(format!("{0} === {1} ? undefined : {0}", abi, hole)); - return Ok(()); - } - Descriptor::Char => { - self.js_arguments.push(format!( - "{0} === 0xFFFFFF ? undefined : String.fromCodePoint({0})", - abi - )); - return Ok(()); - } - Descriptor::RustStruct(ref class) => { - self.cx.require_class_wrap(class); - let assign = format!( - "let c{0} = {0} === 0 ? undefined : {1}.__wrap({0});", - abi, class - ); - self.prelude(&assign); - self.js_arguments.push(format!("c{}", abi)); - return Ok(()); - } - _ => bail!( - "unsupported optional argument type for calling JS function from Rust: {:?}", - arg - ), - }; - } - - if let Some(signed) = arg.get_64() { - let f = if signed { - self.cx.expose_int64_cvt_shim() - } else { - self.cx.expose_uint64_cvt_shim() - }; - let high = self.shim_argument(); - let name = format!("n{}", abi); - self.prelude(&format!( - "\ - u32CvtShim[0] = {low}; - u32CvtShim[1] = {high}; - const {name} = {f}[0]; - ", - low = abi, - high = high, - f = f, - name = name, - )); - self.js_arguments.push(name); - return Ok(()); - } - - if let Some(class) = arg.rust_struct() { - if arg.is_by_ref() { - bail!("cannot invoke JS functions with custom ref types yet") - } - self.cx.require_class_wrap(class); - let assign = format!("let c{0} = {1}.__wrap({0});", abi, class); - self.prelude(&assign); - self.js_arguments.push(format!("c{}", abi)); - return Ok(()); - } - - if let Some((f, mutable)) = arg.stack_closure() { - let arg2 = self.shim_argument(); - let (js, _ts, _js_doc) = { - let mut builder = Js2Rust::new("", self.cx); - if mutable { - builder - .prelude("let a = this.a;\n") - .prelude("this.a = 0;\n") - .rust_argument("a") - .finally("this.a = a;\n"); - } else { - builder.rust_argument("this.a"); - } - builder - .rust_argument("this.b") - .process(f, &None)? - .finish("function", "this.f") - }; - self.cx.export_function_table()?; - self.global_idx(); - self.prelude(&format!( - "\ - let cb{0} = {js};\n\ - cb{0}.f = wasm.__wbg_function_table.get({idx});\n\ - cb{0}.a = {0};\n\ - cb{0}.b = {1};\n\ - ", - abi, - arg2, - js = js, - idx = f.shim_idx, - )); - self.finally(&format!("cb{0}.a = cb{0}.b = 0;", abi)); - self.js_arguments.push(format!("cb{0}.bind(cb{0})", abi)); - return Ok(()); - } - - if let Some(num) = arg.number() { - if num.is_u32() { - self.js_arguments.push(format!("{} >>> 0", abi)); - } else { - self.js_arguments.push(abi); - } - return Ok(()); - } - - let invoc_arg = match *arg { - Descriptor::Boolean => format!("{} !== 0", abi), - Descriptor::Char => format!("String.fromCodePoint({})", abi), - _ => bail!( - "unsupported argument type for calling JS function from Rust: {:?}", - arg - ), - }; - self.js_arguments.push(invoc_arg); - Ok(()) - } - - fn ret(&mut self, ty: &Descriptor) -> Result<(), Error> { - if let Descriptor::Unit = ty { - self.ret_expr = "JS;".to_string(); - return Ok(()); - } - let (ty, optional) = match ty { - Descriptor::Option(t) => (&**t, true), - _ => (ty, false), - }; - if ty.is_by_ref() { - bail!("cannot return a reference from JS to Rust") - } - if let Some(ty) = ty.vector_kind() { - let f = self.cx.pass_to_wasm_function(ty)?; - self.cx.expose_uint32_memory(); - self.shim_arguments.insert(0, "ret".to_string()); - let mut prelude = String::new(); - let expr = if optional { - prelude.push_str("const val = JS;"); - self.cx.expose_is_like_none(); - format!("isLikeNone(val) ? [0, 0] : {}(val)", f) - } else { - format!("{}(JS)", f) - }; - self.ret_expr = format!( - "\ - {} - const retptr = {}; - const retlen = WASM_VECTOR_LEN; - const mem = getUint32Memory(); - mem[ret / 4] = retptr; - mem[ret / 4 + 1] = retlen; - ", - prelude, expr - ); - return Ok(()); - } - if ty.is_anyref() { - if self.cx.config.anyref { - if optional { - self.cx.expose_add_to_anyref_table()?; - self.cx.expose_is_like_none(); - self.ret_expr = " - const val = JS; - return isLikeNone(val) ? 0 : addToAnyrefTable(val); - " - .to_string(); - } else { - self.ret_anyref = true; - self.ret_expr = "return JS;".to_string() - } - } else { - self.cx.expose_add_heap_object(); - if optional { - self.cx.expose_is_like_none(); - self.ret_expr = " - const val = JS; - return isLikeNone(val) ? 0 : addHeapObject(val); - " - .to_string(); - } else { - self.ret_expr = "return addHeapObject(JS);".to_string() - } - } - return Ok(()); - } - if optional { - self.cx.expose_is_like_none(); - - if ty.is_wasm_native() { - self.cx.expose_uint32_memory(); - match ty { - Descriptor::I32 => self.cx.expose_int32_memory(), - Descriptor::U32 => (), - Descriptor::F32 => self.cx.expose_f32_memory(), - Descriptor::F64 => self.cx.expose_f64_memory(), - _ => (), - }; - self.shim_arguments.insert(0, "ret".to_string()); - self.ret_expr = format!( - " - const val = JS; - getUint32Memory()[ret / 4] = !isLikeNone(val); - {mem}[ret / {size} + 1] = isLikeNone(val) ? 0 : val; - ", - size = match ty { - Descriptor::I32 => 4, - Descriptor::U32 => 4, - Descriptor::F32 => 4, - Descriptor::F64 => 8, - _ => unreachable!(), - }, - mem = match ty { - Descriptor::I32 => "getInt32Memory()", - Descriptor::U32 => "getUint32Memory()", - Descriptor::F32 => "getFloat32Memory()", - Descriptor::F64 => "getFloat64Memory()", - _ => unreachable!(), - } - ); - return Ok(()); - } - - if ty.is_abi_as_u32() { - self.ret_expr = " - const val = JS; - return isLikeNone(val) ? 0xFFFFFF : val; - " - .to_string(); - return Ok(()); - } - - if let Some(signed) = ty.get_64() { - self.cx.expose_uint32_memory(); - let f = if signed { - self.cx.expose_int64_memory(); - "getInt64Memory" - } else { - self.cx.expose_uint64_memory(); - "getUint64Memory" - }; - self.shim_arguments.insert(0, "ret".to_string()); - self.ret_expr = format!( - " - const val = JS; - getUint32Memory()[ret / 4] = !isLikeNone(val); - {}()[ret / 8 + 1] = isLikeNone(val) ? BigInt(0) : val; - ", - f - ); - return Ok(()); - } - - match *ty { - Descriptor::Boolean => { - self.ret_expr = " - const val = JS; - return isLikeNone(val) ? 0xFFFFFF : val ? 1 : 0; - " - .to_string(); - } - Descriptor::Char => { - self.ret_expr = " - const val = JS; - return isLikeNone(val) ? 0xFFFFFF : val.codePointAt(0); - " - .to_string(); - } - Descriptor::Enum { hole } => { - self.ret_expr = format!( - " - const val = JS; - return isLikeNone(val) ? {} : val; - ", - hole - ); - } - Descriptor::RustStruct(ref class) => { - // Like below, assert the type - self.ret_expr = format!( - "\ - const val = JS; - if (isLikeNone(val)) - return 0; - if (!(val instanceof {0})) {{ - throw new Error('expected value of type {0}'); - }} - const ret = val.ptr; - val.ptr = 0; - return ret;\ - ", - class - ); - } - _ => bail!( - "unsupported optional return type for calling JS function from Rust: {:?}", - ty - ), - }; - - return Ok(()); - } - if ty.number().is_some() { - self.ret_expr = "return JS;".to_string(); - return Ok(()); - } - if let Some(signed) = ty.get_64() { - let f = if signed { - self.cx.expose_int64_memory(); - "getInt64Memory" - } else { - self.cx.expose_uint64_memory(); - "getUint64Memory" - }; - self.shim_arguments.insert(0, "ret".to_string()); - self.ret_expr = format!( - "\ - const val = JS;\n\ - {}()[ret / 8] = val;\n\ - ", - f - ); - return Ok(()); - } - - if let Some(class) = ty.rust_struct() { - if ty.is_by_ref() { - bail!("cannot invoke JS functions returning custom ref types yet") - } - // Insert an assertion to the type of the returned value as - // otherwise this will cause memory unsafety on the Rust side of - // things. - self.ret_expr = format!( - "\ - const val = JS; - if (!(val instanceof {0})) {{ - throw new Error('expected value of type {0}'); - }} - const ret = val.ptr; - val.ptr = 0; - return ret;\ - ", - class - ); - return Ok(()); - } - - self.ret_expr = match *ty { - Descriptor::Boolean => "return JS;".to_string(), - Descriptor::Char => "return JS.codePointAt(0);".to_string(), - _ => bail!( - "unsupported return type for calling JS function from Rust: {:?}", - ty - ), - }; - Ok(()) - } - - /// Returns whether this shim won't actually do anything when called other - /// than forward the invocation somewhere else. - /// - /// This is used as an optimization to wire up imports directly where - /// possible and avoid a shim in some circumstances. - fn is_noop(&self) -> bool { - let Rust2Js { - // fields which may affect whether we do nontrivial work - catch, - catch_and_rethrow, - finally, - js_arguments, - prelude, - ret_expr, - variadic, - shim_arguments, - - // all other fields, listed explicitly here so if one is added we'll - // trigger a nonexhaustive error. - arg_idx: _, - cx: _, - global_idx: _, - anyref_args: _, - ret_anyref: _, - style, - } = self; - - !catch && - !catch_and_rethrow && - !variadic && - prelude.is_empty() && - finally.is_empty() && - // make sure our faux return expression is "simple" by not - // performing any sort of transformation on the return value - (ret_expr == "JS;" || ret_expr == "return JS;") && - // similarly we want to make sure that all the arguments are simply - // forwarded from the shim we would generate to the import, - // requiring no transformations - js_arguments == shim_arguments && - // method/constructor invocations require some JS shimming right - // now, so only normal function-style invocations may get wired up - *style == Style::Function - } - - pub fn finish(&mut self, target: &AuxImport) -> Result { - let variadic = self.variadic; - let variadic_args = |js_arguments: &[String]| { - Ok(if !variadic { - format!("{}", js_arguments.join(", ")) - } else { - let (last_arg, args) = match js_arguments.split_last() { - Some(pair) => pair, - None => bail!("a function with no arguments cannot be variadic"), - }; - if args.len() > 0 { - format!("{}, ...{}", args.join(", "), last_arg) - } else { - format!("...{}", last_arg) - } - }) - }; - - let invoc = match target { - AuxImport::Value(val) => match self.style { - Style::Constructor => { - let js = match val { - AuxValue::Bare(js) => self.cx.import_name(js)?, - _ => bail!("invalid import set for constructor"), - }; - format!("new {}({})", js, variadic_args(&self.js_arguments)?) - } - Style::Method => { - let descriptor = |anchor: &str, extra: &str, field: &str, which: &str| { - format!( - "GetOwnOrInheritedPropertyDescriptor({}{}, '{}').{}", - anchor, extra, field, which - ) - }; - let js = match val { - AuxValue::Bare(js) => self.cx.import_name(js)?, - AuxValue::Getter(class, field) => { - self.cx.expose_get_inherited_descriptor(); - let class = self.cx.import_name(class)?; - descriptor(&class, ".prototype", field, "get") - } - AuxValue::ClassGetter(class, field) => { - self.cx.expose_get_inherited_descriptor(); - let class = self.cx.import_name(class)?; - descriptor(&class, "", field, "get") - } - AuxValue::Setter(class, field) => { - self.cx.expose_get_inherited_descriptor(); - let class = self.cx.import_name(class)?; - descriptor(&class, ".prototype", field, "set") - } - AuxValue::ClassSetter(class, field) => { - self.cx.expose_get_inherited_descriptor(); - let class = self.cx.import_name(class)?; - descriptor(&class, "", field, "set") - } - }; - format!("{}.call({})", js, variadic_args(&self.js_arguments)?) - } - Style::Function => { - let js = match val { - AuxValue::Bare(js) => self.cx.import_name(js)?, - _ => bail!("invalid import set for constructor"), - }; - if self.is_noop() { - self.cx.expose_does_not_exist(); - // TODO: comment this - let js = format!("typeof {} === 'undefined' ? doesNotExist : {0}", js); - return Ok(js); - } - format!("{}({})", js, variadic_args(&self.js_arguments)?) - } - }, - - AuxImport::Instanceof(js) => { - let js = self.cx.import_name(js)?; - assert!(self.style == Style::Function); - assert!(!variadic); - assert_eq!(self.js_arguments.len(), 1); - format!("{} instanceof {}", self.js_arguments[0], js) - } - - AuxImport::Static(js) => { - assert!(self.style == Style::Function); - assert!(!variadic); - assert_eq!(self.js_arguments.len(), 0); - self.cx.import_name(js)? - } - - AuxImport::Closure(closure) => { - assert!(self.style == Style::Function); - assert!(!variadic); - assert_eq!(self.js_arguments.len(), 3); - let (js, _ts, _js_doc) = { - let mut builder = Js2Rust::new("", self.cx); - - // First up with a closure we increment the internal reference - // count. This ensures that the Rust closure environment won't - // be deallocated while we're invoking it. - builder.prelude("this.cnt++;"); - - if closure.mutable { - // For mutable closures they can't be invoked recursively. - // To handle that we swap out the `this.a` pointer with zero - // while we invoke it. If we finish and the closure wasn't - // destroyed, then we put back the pointer so a future - // invocation can succeed. - builder - .prelude("let a = this.a;") - .prelude("this.a = 0;") - .rust_argument("a") - .rust_argument("b") - .finally("if (--this.cnt === 0) d(a, b);") - .finally("else this.a = a;"); - } else { - // For shared closures they can be invoked recursively so we - // just immediately pass through `this.a`. If we end up - // executing the destructor, however, we clear out the - // `this.a` pointer to prevent it being used again the - // future. - builder - .rust_argument("this.a") - .rust_argument("b") - .finally("if (--this.cnt === 0) {") - .finally("d(this.a, b);") - .finally("this.a = 0;") - .finally("}"); - } - builder - .process(&closure.function, &None)? - .finish("function", "f") - }; - self.cx.export_function_table()?; - let body = format!( - " - const f = wasm.__wbg_function_table.get({}); - const d = wasm.__wbg_function_table.get({}); - const b = {}; - const cb = {}; - cb.a = {}; - cb.cnt = 1; - let real = cb.bind(cb); - real.original = cb; - ", - closure.shim_idx, - closure.dtor_idx, - &self.js_arguments[1], - js, - &self.js_arguments[0], - ); - self.prelude(&body); - "real".to_string() - } - - AuxImport::StructuralMethod(name) => { - assert!(self.style == Style::Function); - let (receiver, args) = match self.js_arguments.split_first() { - Some(pair) => pair, - None => bail!("structural method calls must have at least one argument"), - }; - format!("{}.{}({})", receiver, name, variadic_args(args)?) - } - - AuxImport::StructuralGetter(field) => { - assert!(self.style == Style::Function); - assert!(!variadic); - assert_eq!(self.js_arguments.len(), 1); - format!("{}.{}", self.js_arguments[0], field) - } - - AuxImport::StructuralClassGetter(class, field) => { - assert!(self.style == Style::Function); - assert!(!variadic); - assert_eq!(self.js_arguments.len(), 0); - let class = self.cx.import_name(class)?; - format!("{}.{}", class, field) - } - - AuxImport::StructuralSetter(field) => { - assert!(self.style == Style::Function); - assert!(!variadic); - assert_eq!(self.js_arguments.len(), 2); - format!( - "{}.{} = {}", - self.js_arguments[0], field, self.js_arguments[1] - ) - } - - AuxImport::StructuralClassSetter(class, field) => { - assert!(self.style == Style::Function); - assert!(!variadic); - assert_eq!(self.js_arguments.len(), 1); - let class = self.cx.import_name(class)?; - format!("{}.{} = {}", class, field, self.js_arguments[0]) - } - - AuxImport::IndexingGetterOfClass(class) => { - assert!(self.style == Style::Function); - assert!(!variadic); - assert_eq!(self.js_arguments.len(), 1); - let class = self.cx.import_name(class)?; - format!("{}[{}]", class, self.js_arguments[0]) - } - - AuxImport::IndexingGetterOfObject => { - assert!(self.style == Style::Function); - assert!(!variadic); - assert_eq!(self.js_arguments.len(), 2); - format!("{}[{}]", self.js_arguments[0], self.js_arguments[1]) - } - - AuxImport::IndexingSetterOfClass(class) => { - assert!(self.style == Style::Function); - assert!(!variadic); - assert_eq!(self.js_arguments.len(), 2); - let class = self.cx.import_name(class)?; - format!( - "{}[{}] = {}", - class, self.js_arguments[0], self.js_arguments[1] - ) - } - - AuxImport::IndexingSetterOfObject => { - assert!(self.style == Style::Function); - assert!(!variadic); - assert_eq!(self.js_arguments.len(), 3); - format!( - "{}[{}] = {}", - self.js_arguments[0], self.js_arguments[1], self.js_arguments[2] - ) - } - - AuxImport::IndexingDeleterOfClass(class) => { - assert!(self.style == Style::Function); - assert!(!variadic); - assert_eq!(self.js_arguments.len(), 1); - let class = self.cx.import_name(class)?; - format!("delete {}[{}]", class, self.js_arguments[0]) - } - - AuxImport::IndexingDeleterOfObject => { - assert!(self.style == Style::Function); - assert!(!variadic); - assert_eq!(self.js_arguments.len(), 2); - format!("delete {}[{}]", self.js_arguments[0], self.js_arguments[1]) - } - - AuxImport::WrapInExportedClass(class) => { - assert!(self.style == Style::Function); - assert!(!variadic); - assert_eq!(self.js_arguments.len(), 1); - self.cx.require_class_wrap(class); - if self.is_noop() { - return Ok(format!("{}.__wrap", class)); - } - format!("{}.__wrap({})", class, self.js_arguments[0]) - } - - AuxImport::Intrinsic(intrinsic) => { - assert!(self.style == Style::Function); - assert!(!variadic); - self.intrinsic_expr(intrinsic)? - } - }; - let mut invoc = self.ret_expr.replace("JS", &invoc); - - if self.catch { - self.cx.expose_handle_error()?; - invoc = format!( - "\ - try {{\n\ - {} - }} catch (e) {{\n\ - handleError(e);\n\ - }}\ - ", - &invoc - ); - } else if self.catch_and_rethrow { - invoc = format!( - "\ - try {{\n\ - {} - }} catch (e) {{\n\ - let error = (function () {{ - try {{ - return e instanceof Error \ - ? `${{e.message}}\\n\\nStack:\\n${{e.stack}}` \ - : e.toString(); - }} catch(_) {{ - return \"\"; - }} - }}()); - console.error(\"wasm-bindgen: imported JS function that \ - was not marked as `catch` threw an error:\", \ - error); - throw e; - }}\ - ", - &invoc, - ); - } - - if self.finally.len() > 0 { - invoc = format!( - "\ - try {{\n\ - {} - }} finally {{\n\ - {} - }}\ - ", - &invoc, &self.finally - ); - } - let mut ret = String::new(); - ret.push_str("function("); - ret.push_str(&self.shim_arguments.join(", ")); - ret.push_str(") {\n"); - ret.push_str(&self.prelude); - - ret.push_str(&invoc); - ret.push_str("\n}\n"); - - Ok(ret) - } - - fn global_idx(&mut self) -> usize { - let ret = self.global_idx; - self.global_idx += 1; - ret - } - - fn prelude(&mut self, s: &str) -> &mut Self { - for line in s.lines() { - self.prelude.push_str(line); - self.prelude.push_str("\n"); - } - self - } - - fn finally(&mut self, s: &str) -> &mut Self { - for line in s.lines() { - self.finally.push_str(line); - self.finally.push_str("\n"); - } - self - } - - fn intrinsic_expr(&mut self, intrinsic: &Intrinsic) -> Result { - let expr = match intrinsic { - Intrinsic::JsvalEq => { - assert_eq!(self.js_arguments.len(), 2); - format!("{} === {}", self.js_arguments[0], self.js_arguments[1]) - } - - Intrinsic::IsFunction => { - assert_eq!(self.js_arguments.len(), 1); - format!("typeof({}) === 'function'", self.js_arguments[0]) - } - - Intrinsic::IsUndefined => { - assert_eq!(self.js_arguments.len(), 1); - format!("{} === undefined", self.js_arguments[0]) - } - - Intrinsic::IsNull => { - assert_eq!(self.js_arguments.len(), 1); - format!("{} === null", self.js_arguments[0]) - } - - Intrinsic::IsObject => { - assert_eq!(self.js_arguments.len(), 1); - self.prelude(&format!("const val = {};", self.js_arguments[0])); - format!("typeof(val) === 'object' && val !== null ? 1 : 0") - } - - Intrinsic::IsSymbol => { - assert_eq!(self.js_arguments.len(), 1); - format!("typeof({}) === 'symbol'", self.js_arguments[0]) - } - - Intrinsic::IsString => { - assert_eq!(self.js_arguments.len(), 1); - format!("typeof({}) === 'string'", self.js_arguments[0]) - } - - Intrinsic::ObjectCloneRef => { - assert_eq!(self.js_arguments.len(), 1); - self.js_arguments[0].clone() - } - - Intrinsic::ObjectDropRef => { - assert_eq!(self.js_arguments.len(), 1); - self.js_arguments[0].clone() - } - - Intrinsic::CallbackDrop => { - assert_eq!(self.js_arguments.len(), 1); - self.prelude(&format!("const obj = {}.original;", self.js_arguments[0])); - self.prelude("if (obj.cnt-- == 1) {"); - self.prelude("obj.a = 0;"); - self.prelude("return true;"); - self.prelude("}"); - "false".to_string() - } - - Intrinsic::CallbackForget => { - assert_eq!(self.js_arguments.len(), 1); - self.js_arguments[0].clone() - } - - Intrinsic::NumberNew => { - assert_eq!(self.js_arguments.len(), 1); - self.js_arguments[0].clone() - } - - Intrinsic::StringNew => { - assert_eq!(self.js_arguments.len(), 1); - self.js_arguments[0].clone() - } - - Intrinsic::SymbolNamedNew => { - assert_eq!(self.js_arguments.len(), 1); - format!("Symbol({})", self.js_arguments[0]) - } - - Intrinsic::SymbolAnonymousNew => { - assert_eq!(self.js_arguments.len(), 0); - "Symbol()".to_string() - } - - Intrinsic::NumberGet => { - assert_eq!(self.js_arguments.len(), 2); - self.cx.expose_uint8_memory(); - self.prelude(&format!("const obj = {};", self.js_arguments[0])); - self.prelude("if (typeof(obj) === 'number') return obj;"); - self.prelude(&format!("getUint8Memory()[{}] = 1;", self.js_arguments[1])); - "0".to_string() - } - - Intrinsic::StringGet => { - self.cx.expose_pass_string_to_wasm()?; - self.cx.expose_uint32_memory(); - assert_eq!(self.js_arguments.len(), 2); - self.prelude(&format!("const obj = {};", self.js_arguments[0])); - self.prelude("if (typeof(obj) !== 'string') return 0;"); - self.prelude("const ptr = passStringToWasm(obj);"); - self.prelude(&format!( - "getUint32Memory()[{} / 4] = WASM_VECTOR_LEN;", - self.js_arguments[1], - )); - "ptr".to_string() - } - - Intrinsic::BooleanGet => { - assert_eq!(self.js_arguments.len(), 1); - self.prelude(&format!("const v = {};", self.js_arguments[0])); - format!("typeof(v) === 'boolean' ? (v ? 1 : 0) : 2") - } - Intrinsic::Throw => { - assert_eq!(self.js_arguments.len(), 1); - format!("throw new Error({})", self.js_arguments[0]) - } - - Intrinsic::Rethrow => { - assert_eq!(self.js_arguments.len(), 1); - format!("throw {}", self.js_arguments[0]) - } - - Intrinsic::Module => { - assert_eq!(self.js_arguments.len(), 0); - if !self.cx.config.mode.no_modules() && !self.cx.config.mode.web() { - bail!( - "`wasm_bindgen::module` is currently only supported with \ - `--target no-modules` and `--target web`" - ); - } - format!("init.__wbindgen_wasm_module") - } - - Intrinsic::Memory => { - assert_eq!(self.js_arguments.len(), 0); - self.cx.memory().to_string() - } - - Intrinsic::FunctionTable => { - assert_eq!(self.js_arguments.len(), 0); - self.cx.export_function_table()?; - format!("wasm.__wbg_function_table") - } - - Intrinsic::DebugString => { - assert_eq!(self.js_arguments.len(), 1); - self.cx.expose_debug_string(); - format!("debugString({})", self.js_arguments[0]) - } - - Intrinsic::JsonParse => { - assert_eq!(self.js_arguments.len(), 1); - format!("JSON.parse({})", self.js_arguments[0]) - } - - Intrinsic::JsonSerialize => { - assert_eq!(self.js_arguments.len(), 1); - format!("JSON.stringify({})", self.js_arguments[0]) - } - - Intrinsic::AnyrefHeapLiveCount => { - assert_eq!(self.js_arguments.len(), 0); - if self.cx.config.anyref { - // Eventually we should add support to the anyref-xform to - // re-write calls to the imported - // `__wbindgen_anyref_heap_live_count` function into calls to - // the exported `__wbindgen_anyref_heap_live_count_impl` - // function, and to un-export that function. - // - // But for now, we just bounce wasm -> js -> wasm because it is - // easy. - self.cx - .require_internal_export("__wbindgen_anyref_heap_live_count_impl")?; - "wasm.__wbindgen_anyref_heap_live_count_impl()".into() - } else { - self.cx.expose_global_heap(); - self.prelude( - " - let free_count = 0; - let next = heap_next; - while (next < heap.length) { - free_count += 1; - next = heap[next]; - } - ", - ); - format!( - "heap.length - free_count - {} - {}", - super::INITIAL_HEAP_OFFSET, - super::INITIAL_HEAP_VALUES.len(), - ) - } - } - - Intrinsic::InitAnyrefTable => { - self.cx.expose_anyref_table(); - String::from( - " - const table = wasm.__wbg_anyref_table; - const offset = table.grow(4); - table.set(offset + 0, undefined); - table.set(offset + 1, null); - table.set(offset + 2, true); - table.set(offset + 3, false); - ", - ) - } - }; - Ok(expr) - } -} diff --git a/crates/cli-support/src/lib.rs b/crates/cli-support/src/lib.rs index c2160d92340..66873c6de29 100755 --- a/crates/cli-support/src/lib.rs +++ b/crates/cli-support/src/lib.rs @@ -342,7 +342,12 @@ impl Bindgen { .customs .delete_typed::() .expect("aux section should be present"); - cx.generate(&aux)?; + let bindings = cx + .module + .customs + .delete_typed::() + .unwrap(); + cx.generate(&aux, &bindings)?; // Write out all local JS snippets to the final destination now that // we've collected them from all the programs. diff --git a/crates/cli-support/src/webidl/bindings.rs b/crates/cli-support/src/webidl/bindings.rs new file mode 100644 index 00000000000..3fea596ea39 --- /dev/null +++ b/crates/cli-support/src/webidl/bindings.rs @@ -0,0 +1,250 @@ +//! 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 failure::{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/webidl/incoming.rs b/crates/cli-support/src/webidl/incoming.rs new file mode 100644 index 00000000000..7d99f9df855 --- /dev/null +++ b/crates/cli-support/src/webidl/incoming.rs @@ -0,0 +1,489 @@ +//! Nonstandard and wasm-bindgen specific definition of incoming bindings to a +//! wasm module. +//! +//! 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 the mirror operation, going from WebAssembly to JS, is found in +//! the `outgoing.rs` module. + +use crate::descriptor::{Descriptor, VectorKind}; +use failure::{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, + }, + + /// JS is passing a typed slice of data into Rust. Currently this is + /// implemented with a deallocation in the JS shim, hence a custom binding. + /// + /// TODO: we should move deallocation into Rust so we can use a vanilla and + /// standard webidl binding here. + Slice { + kind: VectorKind, + val: ast::IncomingBindingExpression, + mutable: bool, + }, + + /// 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, +} + +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. + pub fn process(&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 + // 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(); + 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 => { + let binding = self.expr_as(ValType::I32); + self.wasm.push(ValType::I32); + self.webidl.push(ast::WebidlScalarType::Boolean); + self.bindings.push(NonstandardIncoming::Standard(binding)); + } + 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 }); + } + 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)); + } + 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(), + }); + } + 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::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::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::Vector(_) => { + use wasm_webidl_bindings::ast::WebidlScalarType::*; + + 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]); + 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); + } + } + } + + // Can't be passed from JS to Rust yet + Descriptor::Function(_) | + Descriptor::Closure(_) | + + // Always behind a `Ref` + Descriptor::Slice(_) => bail!( + "unsupported argument type for calling Rust function from JS: {:?}", + 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::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(), + }); + } + 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 }); + } + Descriptor::String | 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]); + self.bindings.push(NonstandardIncoming::Slice { + kind, + val: self.expr_get(), + mutable, + }); + self.webidl.push(ast::WebidlScalarType::Any); + } + _ => bail!( + "unsupported reference argument type for calling Rust function from JS: {:?}", + arg + ), + } + Ok(()) + } + + 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); + } + 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::I64 | Descriptor::U64 => { + let expr = self.expr_get(); + 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 }); + } + 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 }); + } + 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 }); + } + 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, + }); + } + 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, + class: name.to_string(), + }); + } + + 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::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 + ), + } + 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 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, 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)); + } + + 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 }); + } + + 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 }); + } + + 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 }); + } +} diff --git a/crates/cli-support/src/webidl.rs b/crates/cli-support/src/webidl/mod.rs similarity index 83% rename from crates/cli-support/src/webidl.rs rename to crates/cli-support/src/webidl/mod.rs index c8913cdacdf..1a2cb1429eb 100644 --- a/crates/cli-support/src/webidl.rs +++ b/crates/cli-support/src/webidl/mod.rs @@ -24,7 +24,7 @@ //! aggressively as possible! use crate::decode; -use crate::descriptor::{Closure, Descriptor, Function}; +use crate::descriptor::{Descriptor, Function}; use crate::descriptors::WasmBindgenDescriptorsSection; use crate::intrinsic::Intrinsic; use failure::{bail, Error}; @@ -34,48 +34,90 @@ 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__"; -/// A "dummy" WebIDL custom section. This should be replaced with a true -/// polyfill for the WebIDL bindings proposal. +mod bindings; +mod incoming; +mod outgoing; + +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 WebidlCustomSection { - /// A map from exported function id to the expected signature of the - /// interface. +pub struct NonstandardWebidlSection { + /// 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. /// - /// The expected signature will contain rich types like strings/js - /// values/etc. A WebIDL binding will be needed to ensure the JS export of - /// the wasm mdoule either has this expected signature or a shim will need - /// to get generated to ensure the right signature in JS is respected. - pub exports: HashMap, - - /// A map from imported function id to the expected binding of the - /// interface. + /// 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. /// - /// This will directly translate to WebIDL bindings and how it's expected - /// that each import is invoked. Note that this also affects the polyfill - /// glue generated. - pub imports: HashMap, + /// The index within this table itself is then used to call actually + /// transformed functions. + pub elems: Vec<(u32, Binding)>, } -pub type WebidlCustomSectionId = TypedCustomSectionId; +pub type NonstandardWebidlSectionId = TypedCustomSectionId; -/// The types of functionality that can be imported and listed for each import -/// in a wasm module. +/// 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 enum ImportBinding { - /// The imported function is considered to be a constructor, and will be - /// invoked as if it has `new` in JS. The returned value is expected - /// to be `anyref`. - Constructor(Function), - /// The imported function is considered to be a function that's called like - /// a method in JS where the first argument should be `anyref` and it is - /// passed as the `this` of the call. - Method(Function), - /// Just a bland normal import which represents some sort of function to - /// call, not much fancy going on here. - Function(Function), +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>, } /// A synthetic custom section which is not standardized, never will be, and @@ -237,7 +279,12 @@ pub enum AuxImport { /// This import is intended to manufacture a JS closure with the given /// signature and then return that back to Rust. - Closure(Closure), + 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 @@ -407,7 +454,7 @@ pub enum JsImportName { struct Context<'a> { start_found: bool, module: &'a mut Module, - bindings: WebidlCustomSection, + bindings: NonstandardWebidlSection, aux: WasmBindgenAux, function_exports: HashMap, function_imports: HashMap, @@ -416,7 +463,9 @@ struct Context<'a> { descriptors: HashMap, } -pub fn process(module: &mut Module) -> Result<(WebidlCustomSectionId, WasmBindgenAuxId), Error> { +pub fn process( + module: &mut Module, +) -> Result<(NonstandardWebidlSectionId, WasmBindgenAuxId), Error> { let mut storage = Vec::new(); let programs = extract_programs(module, &mut storage)?; @@ -431,7 +480,7 @@ pub fn process(module: &mut Module) -> Result<(WebidlCustomSectionId, WasmBindge module, start_found: false, }; - cx.init(); + cx.init()?; for program in programs { cx.program(program)?; @@ -445,7 +494,7 @@ pub fn process(module: &mut Module) -> Result<(WebidlCustomSectionId, WasmBindge } impl<'a> Context<'a> { - fn init(&mut self) { + fn init(&mut self) -> Result<(), Error> { // Make a map from string name to ids of all exports for export in self.module.exports.iter() { if let walrus::ExportItem::Function(f) = export.item { @@ -457,6 +506,7 @@ impl<'a> Context<'a> { // Make a map from string name to ids of all imports from our // placeholder module name which we'll want to be sure that we've got a // location listed of what to import there for each item. + let mut intrinsics = Vec::new(); for import in self.module.imports.iter() { if import.module != PLACEHOLDER_MODULE { continue; @@ -465,15 +515,22 @@ impl<'a> Context<'a> { self.function_imports .insert(import.name.clone(), (import.id(), f)); if let Some(intrinsic) = Intrinsic::from_symbol(&import.name) { - self.bindings - .imports - .insert(import.id(), ImportBinding::Function(intrinsic.binding())); - self.aux - .import_map - .insert(import.id(), AuxImport::Intrinsic(intrinsic)); + intrinsics.push((import.id(), intrinsic)); } } } + for (id, intrinsic) in intrinsics { + bindings::register_import( + self.module, + &mut self.bindings, + id, + intrinsic.binding(), + ast::WebidlFunctionKind::Static, + )?; + self.aux + .import_map + .insert(id, AuxImport::Intrinsic(intrinsic)); + } if let Some(custom) = self .module @@ -490,20 +547,57 @@ impl<'a> Context<'a> { // 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 { - self.aux - .import_map - .insert(id, AuxImport::Closure(descriptor)); let binding = Function { shim_idx: 0, arguments: vec![Descriptor::I32; 3], ret: Descriptor::Anyref, }; - self.bindings - .imports - .insert(id, ImportBinding::Function(binding)); + 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(()) } fn program(&mut self, program: decode::Program<'a>) -> Result<(), Error> { @@ -636,7 +730,7 @@ impl<'a> Context<'a> { kind, }, ); - self.bindings.exports.insert(export_id, descriptor); + bindings::register_export(self.module, &mut self.bindings, export_id, descriptor)?; Ok(()) } @@ -720,20 +814,34 @@ impl<'a> Context<'a> { // NB: `structural` is ignored for constructors since the // js type isn't expected to change anyway. decode::MethodKind::Constructor => { - self.bindings - .imports - .insert(import_id, ImportBinding::Constructor(descriptor)); + 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 binding = if method { - ImportBinding::Method(descriptor) + let kind = if method { + let kind = ast::WebidlFunctionKindMethod { + // TODO: what should this actually be? + ty: ast::WebidlScalarType::Any.into(), + }; + ast::WebidlFunctionKind::Method(kind) } else { - ImportBinding::Function(descriptor) + ast::WebidlFunctionKind::Static }; - self.bindings.imports.insert(import_id, binding); + bindings::register_import( + self.module, + &mut self.bindings, + import_id, + descriptor, + kind, + )?; import } } @@ -742,9 +850,13 @@ impl<'a> Context<'a> { // NB: `structural` is ignored for free functions since it's // expected that the binding isn't changing anyway. None => { - self.bindings - .imports - .insert(import_id, ImportBinding::Function(descriptor)); + 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)) } @@ -867,14 +979,17 @@ impl<'a> Context<'a> { }; // Register the signature of this imported shim - self.bindings.imports.insert( + bindings::register_import( + self.module, + &mut self.bindings, import_id, - ImportBinding::Function(Function { + Function { arguments: Vec::new(), shim_idx: 0, ret: Descriptor::Anyref, - }), - ); + }, + ast::WebidlFunctionKind::Static, + )?; // And then save off that this function is is an instanceof shim for an // imported item. @@ -896,14 +1011,17 @@ impl<'a> Context<'a> { }; // Register the signature of this imported shim - self.bindings.imports.insert( + bindings::register_import( + self.module, + &mut self.bindings, import_id, - ImportBinding::Function(Function { + Function { arguments: vec![Descriptor::Ref(Box::new(Descriptor::Anyref))], shim_idx: 0, - ret: Descriptor::I32, - }), - ); + ret: Descriptor::Boolean, + }, + ast::WebidlFunctionKind::Static, + )?; // And then save off that this function is is an instanceof shim for an // imported item. @@ -944,7 +1062,12 @@ impl<'a> Context<'a> { shim_idx: 0, ret: descriptor.clone(), }; - self.bindings.exports.insert(getter_id, getter_descriptor); + bindings::register_export( + self.module, + &mut self.bindings, + getter_id, + getter_descriptor, + )?; self.aux.export_map.insert( getter_id, AuxExport { @@ -969,7 +1092,12 @@ impl<'a> Context<'a> { shim_idx: 0, ret: Descriptor::Unit, }; - self.bindings.exports.insert(setter_id, setter_descriptor); + bindings::register_export( + self.module, + &mut self.bindings, + setter_id, + setter_descriptor, + )?; self.aux.export_map.insert( setter_id, AuxExport { @@ -1000,9 +1128,13 @@ impl<'a> Context<'a> { arguments: vec![Descriptor::I32], ret: Descriptor::Anyref, }; - self.bindings - .imports - .insert(*import_id, ImportBinding::Function(binding)); + bindings::register_import( + self.module, + &mut self.bindings, + *import_id, + binding, + ast::WebidlFunctionKind::Static, + )?; } Ok(()) @@ -1148,7 +1280,7 @@ impl<'a> Context<'a> { } } -impl walrus::CustomSection for WebidlCustomSection { +impl walrus::CustomSection for NonstandardWebidlSection { fn name(&self) -> &str { "webidl custom section" } diff --git a/crates/cli-support/src/webidl/outgoing.rs b/crates/cli-support/src/webidl/outgoing.rs new file mode 100644 index 00000000000..0917f752d8f --- /dev/null +++ b/crates/cli-support/src/webidl/outgoing.rs @@ -0,0 +1,521 @@ +//! 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 failure::{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 `&[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. +#[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, + + // 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>, +} + +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); + } + + /// 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> { + 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()); + 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::Float), + Descriptor::F64 => self.standard_as(ValType::F64, ast::WebidlScalarType::Double), + 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::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::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::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::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 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/examples/add/src/lib.rs b/examples/add/src/lib.rs index ad4ffde34f4..2b69e764040 100644 --- a/examples/add/src/lib.rs +++ b/examples/add/src/lib.rs @@ -2,5 +2,319 @@ use wasm_bindgen::prelude::*; #[wasm_bindgen] pub fn add(a: u32, b: u32) -> u32 { - a + b + lol as u32 +} + +#[wasm_bindgen] +pub enum Enum { + A, + B, +} + +#[wasm_bindgen] +pub struct Rust {} + +#[wasm_bindgen] +pub fn wut( + // anyref + _: &JsValue, + _: JsValue, + // rust + _: &Rust, + _: Rust, + _: Enum, + _: bool, + _: char, + // numbers + _: f32, + _: f64, + _: i8, + _: u8, + _: i16, + _: u16, + _: i32, + _: u32, + _: i64, + _: u64, + // slices + _: &[u8], + _: &[i8], + _: &[u16], + _: &[i16], + _: &[u32], + _: &[i32], + _: &[u64], + _: &[i64], + _: &[f32], + _: &[f64], + // vectors + _: Vec, + _: Vec, + _: Vec, + _: Vec, + _: Vec, + _: Vec, + _: Vec, + _: Vec, + _: Vec, + _: Vec, + // option float + _: Option, + _: Option, + // option integer + _: Option, + _: Option, + _: Option, + _: Option, + _: Option, + _: Option, + _: Option, + _: Option, + // option misc + _: Option, + _: Option, + _: Option, + _: Option, + // option vectors + _: Option>, + _: Option>, + _: Option>, + _: Option>, + _: Option>, + _: Option>, + _: Option>, + _: Option>, + _: Option>, + _: Option>, +) { +} + +#[wasm_bindgen] +pub fn goo(x: u32) { + unsafe { + std::mem::transmute::(x)(); + } +} + +#[wasm_bindgen] +pub fn r1() -> Rust { + loop {} +} +#[wasm_bindgen] +pub fn r2() -> Vec { + loop {} +} +#[wasm_bindgen] +pub fn r3() -> JsValue { + loop {} +} +#[wasm_bindgen] +pub fn r4() -> i8 { + loop {} +} +#[wasm_bindgen] +pub fn r5() -> u8 { + loop {} +} +#[wasm_bindgen] +pub fn r6() -> i16 { + loop {} +} +#[wasm_bindgen] +pub fn r7() -> u16 { + loop {} +} +#[wasm_bindgen] +pub fn r8() -> i32 { + loop {} +} +#[wasm_bindgen] +pub fn r9() -> u32 { + loop {} +} +#[wasm_bindgen] +pub fn r10() -> i64 { + loop {} +} +#[wasm_bindgen] +pub fn r11() -> u64 { + loop {} +} +#[wasm_bindgen] +pub fn r12() -> f32 { + loop {} +} +#[wasm_bindgen] +pub fn r13() -> f64 { + loop {} +} +#[wasm_bindgen] +pub fn r14() -> bool { + loop {} +} +#[wasm_bindgen] +pub fn r15() -> char { + loop {} +} +#[wasm_bindgen] +pub fn r16() -> Enum { + loop {} +} +#[wasm_bindgen] +pub fn r17() -> Option> { + loop {} +} +#[wasm_bindgen] +pub fn r18() -> Option { + loop {} +} +#[wasm_bindgen] +pub fn r19() -> Option { + loop {} +} +#[wasm_bindgen] +pub fn r20() -> Option { + loop {} +} +#[wasm_bindgen] +pub fn r21() -> Option { + loop {} +} +#[wasm_bindgen] +pub fn r22() -> Option { + loop {} +} + +#[wasm_bindgen] +extern "C" { + pub fn lol( + // anyref + _: &JsValue, + _: JsValue, + // rust + // _: &Rust, + _: Rust, + _: Enum, + _: bool, + _: char, + // numbers + _: f32, + _: f64, + _: i8, + _: u8, + _: i16, + _: u16, + _: i32, + _: u32, + _: i64, + _: u64, + // slices + _: &[u8], + _: &[i8], + _: &[u16], + _: &[i16], + _: &[u32], + _: &[i32], + _: &[u64], + _: &[i64], + _: &[f32], + _: &[f64], + // vectors + _: Vec, + _: Vec, + _: Vec, + _: Vec, + _: Vec, + _: Vec, + _: Vec, + _: Vec, + _: Vec, + _: Vec, + // option float + _: Option, + _: Option, + // option integer + _: Option, + _: Option, + _: Option, + _: Option, + _: Option, + _: Option, + _: Option, + _: Option, + // option misc + _: Option, + _: Option, + _: Option, + _: Option, + // option vectors + _: Option>, + _: Option>, + _: Option>, + _: Option>, + _: Option>, + _: Option>, + _: Option>, + _: Option>, + _: Option>, + _: Option>, + // option slices + _: Option<&[u8]>, + _: Option<&[i8]>, + _: Option<&[u16]>, + _: Option<&[i16]>, + _: Option<&[u32]>, + _: Option<&[i32]>, + _: Option<&[u64]>, + _: Option<&[i64]>, + _: Option<&[f32]>, + _: Option<&[f64]>, + // closures + _: &dyn Fn(), + _: &mut dyn FnMut(), + _: &Closure, + _: &Closure, + ); +} + +macro_rules! t { + ($($n:ident : $t:ty,)*) => ( + $( + #[wasm_bindgen] + pub fn $n() -> u32 { + #[wasm_bindgen] + extern "C" { + #[wasm_bindgen(js_namespace = nowhere)] + fn $n() -> $t; + } + return $n as u32; + } + + )* + ) +} + +t! { + x1: i8, + x2: u8, + x3: i16, + x4: u16, + x5: i32, + x6: u32, + x7: i64, + x8: u64, + x9: f32, + x10: f64, + x11: Rust, + x12: Vec, + x13: JsValue, + x14: bool, + x15: char, + x16: Enum, + x17: Option>, + x18: Option, + x19: Option, + x20: Option, + x21: Option, + x22: Option, } diff --git a/tests/wasm/imports.js b/tests/wasm/imports.js index a2aef2e1ba3..f464a65174f 100644 --- a/tests/wasm/imports.js +++ b/tests/wasm/imports.js @@ -90,7 +90,7 @@ exports.touch_custom_type = function() { }; exports.interpret_2_as_custom_type = function() { - assert.throws(wasm.interpret_2_as_custom_type, /expected value of type CustomType/); + assert.throws(wasm.interpret_2_as_custom_type, /expected instance of CustomType/); }; exports.baz$ = function() {};