Merge pull request #1594 from alexcrichton/webidl-for-realz

Second large refactor for WebIDL bindings
This commit is contained in:
Alex Crichton 2019-06-25 08:21:24 +02:00 committed by GitHub
commit e0ef329e17
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 3986 additions and 2541 deletions

View File

@ -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' }

View File

@ -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::<WebidlCustomSection>()
.get_typed_mut::<NonstandardWebidlSection>()
.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::<WasmBindgenAux>()
.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::<HashSet<_>>();
module
.customs
.get_typed_mut::<WebidlCustomSection>()
.get_typed_mut::<NonstandardWebidlSection>()
.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;
}
for arg in f.arguments.iter() {
if arg.is_anyref() {
args.push((cur, true));
} else if arg.is_ref_anyref() {
args.push((cur, false));
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));
}
cur += arg.abi_arg_count();
}
(args, f.ret.is_anyref())
// 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());
}
}
}
(ret, ty.results() == &[walrus::ValType::Anyref])
}

View File

@ -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<Number> {
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<bool> {
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<VectorKind> {
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
}
}

View File

@ -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<String>,
/// JS expressions that are arguments to the function that we're calling.
invoc_args: Vec<String>,
/// 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<TypescriptArg>,
/// The TypeScript return value for this function.
pub ts_ret: Option<TypescriptArg>,
/// Whether or not this is building a constructor for a Rust class, and if
/// so what class it's constructing.
constructor: Option<String>,
/// 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<bool>,
/// 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<TypescriptArg>,
prelude: String,
finally: String,
tmp: usize,
args: Vec<String>,
}
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<Vec<String>>,
invoke: &mut dyn FnMut(&mut Context, &mut String, &[String]) -> Result<String, Error>,
) -> Result<String, Error> {
// 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<String>) -> 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;
}
}

View File

@ -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<Vec<String>, 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<Vec<String>, 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<Vec<String>, 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")
}
}

View File

@ -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<String>,
/// Arguments and their types to the JS shim.
pub js_arguments: Vec<JsArgument>,
/// 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<String>,
/// 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<Vec<String>>,
) -> 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::<Vec<_>>()
.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,
));
}
}

View File

@ -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::<WebidlCustomSection>()
.unwrap(),
module,
memory,
npm_dependencies: Default::default(),
@ -670,19 +667,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;
@ -1454,6 +1438,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 \"<failed to stringify thrown value>\";
}
}());
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 => {
@ -1589,7 +1598,7 @@ impl<'a> Context<'a> {
if (desc) return desc;
obj = Object.getPrototypeOf(obj);
}
return {}
return {};
}
",
);
@ -1811,40 +1820,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)
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)?;
}
}
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) -> Result<(), Error> {
let mut pairs = aux.export_map.iter().collect::<Vec<_>>();
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,)
})?;
@ -1866,75 +1867,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::<ast::WebidlFunction>(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::<ast::WebidlFunction>(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(())
@ -1944,21 +1981,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::<ast::WebidlFunction>(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<String, Error> {
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::<Vec<_>>()
.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<String, Error> {
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();
@ -2224,8 +2755,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

View File

@ -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<String, Error> {
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<String, Error> {
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::<Vec<_>>()
.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<String, Error> {
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")
}
}

File diff suppressed because it is too large Load Diff

View File

@ -342,7 +342,12 @@ impl Bindgen {
.customs
.delete_typed::<webidl::WasmBindgenAux>()
.expect("aux section should be present");
cx.generate(&aux)?;
let bindings = cx
.module
.customs
.delete_typed::<webidl::NonstandardWebidlSection>()
.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.

View File

@ -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<u32, Error> {
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<Binding, Error> {
// 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<Vec<walrus::ValType>>) {
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)
}

View File

@ -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<ast::IncomingBindingExpression>,
/// 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<ast::IncomingBindingExpression>,
},
/// 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<ValType>,
/// The WebIDL scalar types which match what JS will be providing.
pub webidl: Vec<ast::WebidlScalarType>,
/// The list of bindings necessary to connect `wasm` to `webidl` above.
pub bindings: Vec<NonstandardIncoming>,
}
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 });
}
}

View File

@ -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.
#[derive(Default, Debug)]
pub struct WebidlCustomSection {
/// A map from exported function id to the expected signature of the
/// interface.
///
/// 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<ExportId, Function>,
mod bindings;
mod incoming;
mod outgoing;
/// A map from imported function id to the expected binding of the
/// interface.
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 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<ExportId, Binding>,
/// 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<ImportId, Binding>,
/// For closures and such we'll be calling entries in the function table
/// with rich arguments (just like we call exports) so to do that we
/// describe all the elem indices that we need to modify here as well.
///
/// This 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<ImportId, ImportBinding>,
/// This is a list of pairs where the first element in the list is the
/// element index in the function table being described and the `Binding`
/// describes the signature that it's supposed to have.
///
/// The index within this table itself is then used to call actually
/// transformed functions.
pub elems: Vec<(u32, Binding)>,
}
pub type WebidlCustomSectionId = TypedCustomSectionId<WebidlCustomSection>;
pub type NonstandardWebidlSectionId = TypedCustomSectionId<NonstandardWebidlSection>;
/// 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<NonstandardIncoming>,
/// A list of outgoing bindings. For exports this is the return value and
/// for imports this is the list of arguments.
pub outgoing: Vec<NonstandardOutgoing>,
/// 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<Vec<walrus::ValType>>,
}
/// 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<String, (ExportId, FunctionId)>,
function_imports: HashMap<String, (ImportId, FunctionId)>,
@ -416,7 +463,9 @@ struct Context<'a> {
descriptors: HashMap<String, Descriptor>,
}
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"
}

View File

@ -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<ValType>,
/// The WebIDL types that we're passing along out of wasm.
pub webidl: Vec<ast::WebidlScalarType>,
/// The list of bindings we've created, currently 1:1 with the webidl above.
pub bindings: Vec<NonstandardOutgoing>,
// 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 });
}
}

View File

@ -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<u8>,
_: Vec<i8>,
_: Vec<u16>,
_: Vec<i16>,
_: Vec<u32>,
_: Vec<i32>,
_: Vec<u64>,
_: Vec<i64>,
_: Vec<f32>,
_: Vec<f64>,
// option float
_: Option<f32>,
_: Option<f64>,
// option integer
_: Option<i8>,
_: Option<u8>,
_: Option<i16>,
_: Option<u16>,
_: Option<i32>,
_: Option<u32>,
_: Option<i64>,
_: Option<u64>,
// option misc
_: Option<bool>,
_: Option<char>,
_: Option<Enum>,
_: Option<Rust>,
// option vectors
_: Option<Vec<u8>>,
_: Option<Vec<i8>>,
_: Option<Vec<u16>>,
_: Option<Vec<i16>>,
_: Option<Vec<u32>>,
_: Option<Vec<i32>>,
_: Option<Vec<u64>>,
_: Option<Vec<i64>>,
_: Option<Vec<f32>>,
_: Option<Vec<f64>>,
) {
}
#[wasm_bindgen]
pub fn goo(x: u32) {
unsafe {
std::mem::transmute::<u32, fn()>(x)();
}
}
#[wasm_bindgen]
pub fn r1() -> Rust {
loop {}
}
#[wasm_bindgen]
pub fn r2() -> Vec<u32> {
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<Vec<u32>> {
loop {}
}
#[wasm_bindgen]
pub fn r18() -> Option<i32> {
loop {}
}
#[wasm_bindgen]
pub fn r19() -> Option<bool> {
loop {}
}
#[wasm_bindgen]
pub fn r20() -> Option<char> {
loop {}
}
#[wasm_bindgen]
pub fn r21() -> Option<Enum> {
loop {}
}
#[wasm_bindgen]
pub fn r22() -> Option<Rust> {
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<u8>,
_: Vec<i8>,
_: Vec<u16>,
_: Vec<i16>,
_: Vec<u32>,
_: Vec<i32>,
_: Vec<u64>,
_: Vec<i64>,
_: Vec<f32>,
_: Vec<f64>,
// option float
_: Option<f32>,
_: Option<f64>,
// option integer
_: Option<i8>,
_: Option<u8>,
_: Option<i16>,
_: Option<u16>,
_: Option<i32>,
_: Option<u32>,
_: Option<i64>,
_: Option<u64>,
// option misc
_: Option<bool>,
_: Option<char>,
_: Option<Enum>,
_: Option<Rust>,
// option vectors
_: Option<Vec<u8>>,
_: Option<Vec<i8>>,
_: Option<Vec<u16>>,
_: Option<Vec<i16>>,
_: Option<Vec<u32>>,
_: Option<Vec<i32>>,
_: Option<Vec<u64>>,
_: Option<Vec<i64>>,
_: Option<Vec<f32>>,
_: Option<Vec<f64>>,
// 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<dyn Fn()>,
_: &Closure<dyn FnMut()>,
);
}
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<u32>,
x13: JsValue,
x14: bool,
x15: char,
x16: Enum,
x17: Option<Vec<u32>>,
x18: Option<i32>,
x19: Option<char>,
x20: Option<bool>,
x21: Option<Rust>,
x22: Option<Enum>,
}

View File

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