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() {};