mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-03-16 02:00:51 +00:00
Merge pull request #1594 from alexcrichton/webidl-for-realz
Second large refactor for WebIDL bindings
This commit is contained in:
commit
e0ef329e17
@ -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' }
|
||||
|
@ -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])
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
507
crates/cli-support/src/js/binding.rs
Normal file
507
crates/cli-support/src/js/binding.rs
Normal 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;
|
||||
}
|
||||
}
|
556
crates/cli-support/src/js/incoming.rs
Normal file
556
crates/cli-support/src/js/incoming.rs
Normal 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")
|
||||
}
|
||||
}
|
@ -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,
|
||||
));
|
||||
}
|
||||
}
|
@ -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
|
||||
|
411
crates/cli-support/src/js/outgoing.rs
Normal file
411
crates/cli-support/src/js/outgoing.rs
Normal 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
@ -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.
|
||||
|
250
crates/cli-support/src/webidl/bindings.rs
Normal file
250
crates/cli-support/src/webidl/bindings.rs
Normal 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)
|
||||
}
|
489
crates/cli-support/src/webidl/incoming.rs
Normal file
489
crates/cli-support/src/webidl/incoming.rs
Normal 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 });
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
521
crates/cli-support/src/webidl/outgoing.rs
Normal file
521
crates/cli-support/src/webidl/outgoing.rs
Normal 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 });
|
||||
}
|
||||
}
|
@ -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>,
|
||||
}
|
||||
|
@ -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() {};
|
||||
|
Loading…
x
Reference in New Issue
Block a user