mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-04-25 14:12:13 +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-shared = { path = "../shared", version = '=0.2.47' }
|
||||||
wasm-bindgen-threads-xform = { path = '../threads-xform', 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-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::{NonstandardIncoming, NonstandardOutgoing};
|
||||||
use crate::webidl::{AuxImport, ImportBinding, WasmBindgenAux, WebidlCustomSection};
|
use crate::webidl::{NonstandardWebidlSection, WasmBindgenAux};
|
||||||
use failure::Error;
|
use failure::Error;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use walrus::Module;
|
use walrus::Module;
|
||||||
use wasm_bindgen_anyref_xform::Context;
|
use wasm_bindgen_anyref_xform::Context;
|
||||||
|
use wasm_webidl_bindings::ast;
|
||||||
|
|
||||||
pub fn process(module: &mut Module) -> Result<(), Error> {
|
pub fn process(module: &mut Module) -> Result<(), Error> {
|
||||||
let mut cfg = Context::default();
|
let mut cfg = Context::default();
|
||||||
cfg.prepare(module)?;
|
cfg.prepare(module)?;
|
||||||
let bindings = module
|
let bindings = module
|
||||||
.customs
|
.customs
|
||||||
.get_typed_mut::<WebidlCustomSection>()
|
.get_typed_mut::<NonstandardWebidlSection>()
|
||||||
.expect("webidl custom section should exist");
|
.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() {
|
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);
|
cfg.export_xform(*export, &args, ret);
|
||||||
process_closure_arguments(&mut cfg, binding);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (import, kind) in bindings.imports.iter_mut() {
|
// Transform all imported functions in the module, using the bindings listed
|
||||||
let binding = match kind {
|
// for each imported function.
|
||||||
ImportBinding::Function(f) => f,
|
for (import, binding) in bindings.imports.iter_mut() {
|
||||||
ImportBinding::Constructor(f) => f,
|
let ty = module.types.get(binding.wasm_ty);
|
||||||
ImportBinding::Method(f) => f,
|
let args = Arguments::Outgoing(&mut binding.outgoing);
|
||||||
};
|
let (args, ret) = extract_anyrefs(ty, args);
|
||||||
let (args, ret) = extract_anyrefs(binding, 0);
|
|
||||||
cfg.import_xform(*import, &args, ret);
|
cfg.import_xform(*import, &args, ret);
|
||||||
process_closure_arguments(&mut cfg, binding);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let aux = module
|
// And finally transform all table elements that are used as function
|
||||||
.customs
|
// pointers for closures and such.
|
||||||
.get_typed_mut::<WasmBindgenAux>()
|
for (idx, binding) in bindings.elems.iter_mut() {
|
||||||
.expect("webidl custom section should exist");
|
let ty = module.types.get(binding.wasm_ty);
|
||||||
for import in aux.import_map.values_mut() {
|
let args = Arguments::Incoming(&mut binding.incoming);
|
||||||
match import {
|
let (args, ret) = extract_anyrefs(ty, args);
|
||||||
AuxImport::Closure(f) => process_closure(&mut cfg, f),
|
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<_>>();
|
.collect::<HashSet<_>>();
|
||||||
module
|
module
|
||||||
.customs
|
.customs
|
||||||
.get_typed_mut::<WebidlCustomSection>()
|
.get_typed_mut::<NonstandardWebidlSection>()
|
||||||
.expect("webidl custom section should exist")
|
.expect("webidl custom section should exist")
|
||||||
.imports
|
.imports
|
||||||
.retain(|id, _| remaining_imports.contains(id));
|
.retain(|id, _| remaining_imports.contains(id));
|
||||||
@ -68,44 +70,9 @@ pub fn process(module: &mut Module) -> Result<(), Error> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Process the `function` provided to ensure that all references to `Closure`
|
enum Arguments<'a> {
|
||||||
/// descriptors are processed below.
|
Incoming(&'a mut [NonstandardIncoming]),
|
||||||
fn process_closure_arguments(cfg: &mut Context, function: &mut Function) {
|
Outgoing(&'a mut [NonstandardOutgoing]),
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extract a description of the anyref arguments from the function signature
|
/// 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
|
/// 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
|
/// because two synthetic arguments are injected into the wasm signature which
|
||||||
/// aren't present in the `Function` signature.
|
/// aren't present in the `Function` signature.
|
||||||
fn extract_anyrefs(f: &Function, offset: usize) -> (Vec<(usize, bool)>, bool) {
|
fn extract_anyrefs(ty: &walrus::Type, args: Arguments<'_>) -> (Vec<(usize, bool)>, bool) {
|
||||||
let mut args = Vec::new();
|
let mut ret = Vec::new();
|
||||||
let mut cur = offset;
|
|
||||||
if f.ret.abi_returned_through_pointer() {
|
// First find all the `anyref` arguments in the input type, and we'll
|
||||||
cur += 1;
|
// assume that they're owned anyref arguments for now (the `true`)
|
||||||
}
|
for (i, arg) in ty.params().iter().enumerate() {
|
||||||
for arg in f.arguments.iter() {
|
if *arg == walrus::ValType::Anyref {
|
||||||
if arg.is_anyref() {
|
ret.push((i, true));
|
||||||
args.push((cur, true));
|
|
||||||
} else if arg.is_ref_anyref() {
|
|
||||||
args.push((cur, false));
|
|
||||||
}
|
}
|
||||||
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,
|
pub mutable: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub enum VectorKind {
|
pub enum VectorKind {
|
||||||
I8,
|
I8,
|
||||||
U8,
|
U8,
|
||||||
@ -99,10 +99,6 @@ pub enum VectorKind {
|
|||||||
Anyref,
|
Anyref,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Number {
|
|
||||||
u32: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Descriptor {
|
impl Descriptor {
|
||||||
pub fn decode(mut data: &[u32]) -> Descriptor {
|
pub fn decode(mut data: &[u32]) -> Descriptor {
|
||||||
let descriptor = Descriptor::_decode(&mut data, false);
|
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 {
|
pub fn unwrap_closure(self) -> Closure {
|
||||||
match self {
|
match self {
|
||||||
Descriptor::Closure(s) => *s,
|
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> {
|
pub fn vector_kind(&self) -> Option<VectorKind> {
|
||||||
let inner = match *self {
|
let inner = match *self {
|
||||||
Descriptor::String => return Some(VectorKind::String),
|
Descriptor::String => return Some(VectorKind::String),
|
||||||
@ -246,121 +189,6 @@ impl Descriptor {
|
|||||||
_ => None,
|
_ => 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 {
|
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::descriptor::VectorKind;
|
||||||
use crate::js::js2rust::Js2Rust;
|
use crate::intrinsic::Intrinsic;
|
||||||
use crate::js::rust2js::Rust2Js;
|
|
||||||
use crate::webidl::{AuxEnum, AuxExport, AuxExportKind, AuxImport, AuxStruct};
|
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 crate::{Bindgen, EncodeInto, OutputMode};
|
||||||
use failure::{bail, Error, ResultExt};
|
use failure::{bail, Error, ResultExt};
|
||||||
use std::collections::{BTreeMap, HashMap, HashSet};
|
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use walrus::{ExportId, ImportId, MemoryId, Module};
|
use walrus::{ExportId, ImportId, MemoryId, Module};
|
||||||
|
use wasm_webidl_bindings::ast;
|
||||||
|
|
||||||
|
mod binding;
|
||||||
|
mod incoming;
|
||||||
|
mod outgoing;
|
||||||
|
|
||||||
pub struct Context<'a> {
|
pub struct Context<'a> {
|
||||||
globals: String,
|
globals: String,
|
||||||
@ -21,7 +23,6 @@ pub struct Context<'a> {
|
|||||||
required_internal_exports: HashSet<&'static str>,
|
required_internal_exports: HashSet<&'static str>,
|
||||||
config: &'a Bindgen,
|
config: &'a Bindgen,
|
||||||
pub module: &'a mut Module,
|
pub module: &'a mut Module,
|
||||||
bindings: WebidlCustomSection,
|
|
||||||
|
|
||||||
/// A map representing the `import` statements we'll be generating in the JS
|
/// 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
|
/// 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(),
|
wasm_import_definitions: Default::default(),
|
||||||
exported_classes: Some(Default::default()),
|
exported_classes: Some(Default::default()),
|
||||||
config,
|
config,
|
||||||
bindings: *module
|
|
||||||
.customs
|
|
||||||
.delete_typed::<WebidlCustomSection>()
|
|
||||||
.unwrap(),
|
|
||||||
module,
|
module,
|
||||||
memory,
|
memory,
|
||||||
npm_dependencies: Default::default(),
|
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) {
|
fn expose_drop_ref(&mut self) {
|
||||||
if !self.should_write_global("drop_ref") {
|
if !self.should_write_global("drop_ref") {
|
||||||
return;
|
return;
|
||||||
@ -1454,6 +1438,31 @@ impl<'a> Context<'a> {
|
|||||||
Ok(())
|
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> {
|
fn pass_to_wasm_function(&mut self, t: VectorKind) -> Result<&'static str, Error> {
|
||||||
let s = match t {
|
let s = match t {
|
||||||
VectorKind::String => {
|
VectorKind::String => {
|
||||||
@ -1589,7 +1598,7 @@ impl<'a> Context<'a> {
|
|||||||
if (desc) return desc;
|
if (desc) return desc;
|
||||||
obj = Object.getPrototypeOf(obj);
|
obj = Object.getPrototypeOf(obj);
|
||||||
}
|
}
|
||||||
return {}
|
return {};
|
||||||
}
|
}
|
||||||
",
|
",
|
||||||
);
|
);
|
||||||
@ -1811,40 +1820,32 @@ impl<'a> Context<'a> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn take_object(&mut self, expr: &str) -> String {
|
pub fn generate(
|
||||||
if self.config.anyref {
|
&mut self,
|
||||||
expr.to_string()
|
aux: &WasmBindgenAux,
|
||||||
} else {
|
bindings: &NonstandardWebidlSection,
|
||||||
self.expose_take_object();
|
) -> Result<(), Error> {
|
||||||
format!("takeObject({})", expr)
|
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<_>>();
|
let mut pairs = aux.export_map.iter().collect::<Vec<_>>();
|
||||||
pairs.sort_by_key(|(k, _)| *k);
|
pairs.sort_by_key(|(k, _)| *k);
|
||||||
check_duplicated_getter_and_setter_names(&pairs)?;
|
check_duplicated_getter_and_setter_names(&pairs)?;
|
||||||
for (id, export) in pairs {
|
for (id, export) in pairs {
|
||||||
self.generate_export(*id, export).with_context(|_| {
|
self.generate_export(*id, export, bindings)
|
||||||
format!(
|
.with_context(|_| {
|
||||||
"failed to generate bindings for Rust export `{}`",
|
format!(
|
||||||
export.debug_name,
|
"failed to generate bindings for Rust export `{}`",
|
||||||
)
|
export.debug_name,
|
||||||
})?;
|
)
|
||||||
|
})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (id, import) in sorted_iter(&aux.import_map) {
|
for (id, import) in sorted_iter(&aux.import_map) {
|
||||||
let variadic = aux.imports_with_variadic.contains(&id);
|
let variadic = aux.imports_with_variadic.contains(&id);
|
||||||
let catch = aux.imports_with_catch.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(|_| {
|
.with_context(|_| {
|
||||||
format!("failed to generate bindings for import `{:?}`", import,)
|
format!("failed to generate bindings for import `{:?}`", import,)
|
||||||
})?;
|
})?;
|
||||||
@ -1866,75 +1867,111 @@ impl<'a> Context<'a> {
|
|||||||
Ok(())
|
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 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 {
|
match &export.kind {
|
||||||
AuxExportKind::Function(name) => {
|
AuxExportKind::Function(name) => {
|
||||||
let (js, ts, js_doc) = Js2Rust::new(&name, self)
|
self.export(&name, &format!("function{}", js), Some(docs))?;
|
||||||
.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.globals.push_str("\n");
|
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(&ts);
|
||||||
self.typescript.push_str("\n");
|
self.typescript.push_str(";\n");
|
||||||
}
|
}
|
||||||
AuxExportKind::Constructor(class) => {
|
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);
|
let exported = require_class(&mut self.exported_classes, class);
|
||||||
if exported.has_constructor {
|
if exported.has_constructor {
|
||||||
bail!("found duplicate constructor for class `{}`", class);
|
bail!("found duplicate constructor for class `{}`", class);
|
||||||
}
|
}
|
||||||
exported.has_constructor = true;
|
exported.has_constructor = true;
|
||||||
let docs = format_doc_comments(&export.comments, Some(raw_docs));
|
|
||||||
exported.push(&docs, "constructor", "", &js, &ts);
|
exported.push(&docs, "constructor", "", &js, &ts);
|
||||||
}
|
}
|
||||||
AuxExportKind::Getter { class, field: name }
|
AuxExportKind::Getter { class, field } => {
|
||||||
| AuxExportKind::Setter { class, field: name }
|
let ret_ty = builder.ts_ret.as_ref().unwrap().ty.clone();
|
||||||
| AuxExportKind::StaticFunction { class, name }
|
let exported = require_class(&mut self.exported_classes, class);
|
||||||
| AuxExportKind::Method { class, name, .. } => {
|
exported.push_getter(&docs, field, &js, &ret_ty);
|
||||||
let mut j2r = Js2Rust::new(name, self);
|
}
|
||||||
match export.kind {
|
AuxExportKind::Setter { class, field } => {
|
||||||
AuxExportKind::StaticFunction { .. } => {}
|
let arg_ty = builder.ts_args[0].ty.clone();
|
||||||
AuxExportKind::Method { consumed: true, .. } => {
|
let exported = require_class(&mut self.exported_classes, class);
|
||||||
j2r.method(true);
|
exported.push_setter(&docs, field, &js, &arg_ty);
|
||||||
}
|
}
|
||||||
_ => {
|
AuxExportKind::StaticFunction { class, name } => {
|
||||||
j2r.method(false);
|
let exported = require_class(&mut self.exported_classes, class);
|
||||||
}
|
exported.push(&docs, name, "static ", &js, &ts);
|
||||||
}
|
}
|
||||||
let (js, ts, raw_docs) = j2r
|
AuxExportKind::Method { class, name, .. } => {
|
||||||
.process(&descriptor, &export.arg_names)?
|
let exported = require_class(&mut self.exported_classes, class);
|
||||||
.finish("", &format!("wasm.{}", wasm_name));
|
exported.push(&docs, name, "", &js, &ts);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -1944,21 +1981,515 @@ impl<'a> Context<'a> {
|
|||||||
&mut self,
|
&mut self,
|
||||||
id: ImportId,
|
id: ImportId,
|
||||||
import: &AuxImport,
|
import: &AuxImport,
|
||||||
|
bindings: &NonstandardWebidlSection,
|
||||||
variadic: bool,
|
variadic: bool,
|
||||||
catch: bool,
|
catch: bool,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let signature = self.bindings.imports[&id].clone();
|
let binding = &bindings.imports[&id];
|
||||||
let catch_and_rethrow = self.config.debug;
|
let webidl = bindings
|
||||||
let js = Rust2Js::new(self)
|
.types
|
||||||
.catch_and_rethrow(catch_and_rethrow)
|
.get::<ast::WebidlFunction>(binding.webidl_ty)
|
||||||
.catch(catch)
|
.unwrap();
|
||||||
.variadic(variadic)
|
let mut builder = binding::Builder::new(self);
|
||||||
.process(&signature)?
|
builder.catch(catch)?;
|
||||||
.finish(import)?;
|
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);
|
self.wasm_import_definitions.insert(id, js);
|
||||||
Ok(())
|
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> {
|
fn generate_enum(&mut self, enum_: &AuxEnum) -> Result<(), Error> {
|
||||||
let mut variants = String::new();
|
let mut variants = String::new();
|
||||||
|
|
||||||
@ -2224,8 +2755,9 @@ impl ExportedClass {
|
|||||||
self.typescript.push_str(docs);
|
self.typescript.push_str(docs);
|
||||||
self.typescript.push_str(" ");
|
self.typescript.push_str(" ");
|
||||||
self.typescript.push_str(function_prefix);
|
self.typescript.push_str(function_prefix);
|
||||||
|
self.typescript.push_str(function_name);
|
||||||
self.typescript.push_str(ts);
|
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
|
/// 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
|
.customs
|
||||||
.delete_typed::<webidl::WasmBindgenAux>()
|
.delete_typed::<webidl::WasmBindgenAux>()
|
||||||
.expect("aux section should be present");
|
.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
|
// Write out all local JS snippets to the final destination now that
|
||||||
// we've collected them from all the programs.
|
// 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!
|
//! aggressively as possible!
|
||||||
|
|
||||||
use crate::decode;
|
use crate::decode;
|
||||||
use crate::descriptor::{Closure, Descriptor, Function};
|
use crate::descriptor::{Descriptor, Function};
|
||||||
use crate::descriptors::WasmBindgenDescriptorsSection;
|
use crate::descriptors::WasmBindgenDescriptorsSection;
|
||||||
use crate::intrinsic::Intrinsic;
|
use crate::intrinsic::Intrinsic;
|
||||||
use failure::{bail, Error};
|
use failure::{bail, Error};
|
||||||
@ -34,48 +34,90 @@ use std::path::PathBuf;
|
|||||||
use std::str;
|
use std::str;
|
||||||
use walrus::{ExportId, FunctionId, ImportId, Module, TypedCustomSectionId};
|
use walrus::{ExportId, FunctionId, ImportId, Module, TypedCustomSectionId};
|
||||||
use wasm_bindgen_shared::struct_function_export_name;
|
use wasm_bindgen_shared::struct_function_export_name;
|
||||||
|
use wasm_webidl_bindings::ast;
|
||||||
|
|
||||||
const PLACEHOLDER_MODULE: &str = "__wbindgen_placeholder__";
|
const PLACEHOLDER_MODULE: &str = "__wbindgen_placeholder__";
|
||||||
|
|
||||||
/// A "dummy" WebIDL custom section. This should be replaced with a true
|
mod bindings;
|
||||||
/// polyfill for the WebIDL bindings proposal.
|
mod incoming;
|
||||||
#[derive(Default, Debug)]
|
mod outgoing;
|
||||||
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>,
|
|
||||||
|
|
||||||
/// A map from imported function id to the expected binding of the
|
pub use self::incoming::NonstandardIncoming;
|
||||||
/// interface.
|
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
|
/// This is a list of pairs where the first element in the list is the
|
||||||
/// that each import is invoked. Note that this also affects the polyfill
|
/// element index in the function table being described and the `Binding`
|
||||||
/// glue generated.
|
/// describes the signature that it's supposed to have.
|
||||||
pub imports: HashMap<ImportId, ImportBinding>,
|
///
|
||||||
|
/// 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
|
/// A non-standard wasm-bindgen-specifi WebIDL binding. This is meant to vaguely
|
||||||
/// in a wasm module.
|
/// 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)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum ImportBinding {
|
pub struct Binding {
|
||||||
/// The imported function is considered to be a constructor, and will be
|
/// The WebAssembly type that the function is expected to have. Note that
|
||||||
/// invoked as if it has `new` in JS. The returned value is expected
|
/// this may not match the actual bound function's type! That's because this
|
||||||
/// to be `anyref`.
|
/// type includes `anyref` but the Rust compiler never emits anyref. This
|
||||||
Constructor(Function),
|
/// is, however, used for the `anyref` pass to know what to transform to
|
||||||
/// The imported function is considered to be a function that's called like
|
/// `anyref`.
|
||||||
/// a method in JS where the first argument should be `anyref` and it is
|
pub wasm_ty: walrus::TypeId,
|
||||||
/// passed as the `this` of the call.
|
|
||||||
Method(Function),
|
/// The WebIDL type of this binding, which is an index into the webidl
|
||||||
/// Just a bland normal import which represents some sort of function to
|
/// binding section's `types` field.
|
||||||
/// call, not much fancy going on here.
|
pub webidl_ty: ast::WebidlFunctionId,
|
||||||
Function(Function),
|
|
||||||
|
/// 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
|
/// 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
|
/// This import is intended to manufacture a JS closure with the given
|
||||||
/// signature and then return that back to Rust.
|
/// 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
|
/// 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
|
/// on the first object, passing along all other parameters and returning
|
||||||
@ -407,7 +454,7 @@ pub enum JsImportName {
|
|||||||
struct Context<'a> {
|
struct Context<'a> {
|
||||||
start_found: bool,
|
start_found: bool,
|
||||||
module: &'a mut Module,
|
module: &'a mut Module,
|
||||||
bindings: WebidlCustomSection,
|
bindings: NonstandardWebidlSection,
|
||||||
aux: WasmBindgenAux,
|
aux: WasmBindgenAux,
|
||||||
function_exports: HashMap<String, (ExportId, FunctionId)>,
|
function_exports: HashMap<String, (ExportId, FunctionId)>,
|
||||||
function_imports: HashMap<String, (ImportId, FunctionId)>,
|
function_imports: HashMap<String, (ImportId, FunctionId)>,
|
||||||
@ -416,7 +463,9 @@ struct Context<'a> {
|
|||||||
descriptors: HashMap<String, Descriptor>,
|
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 mut storage = Vec::new();
|
||||||
let programs = extract_programs(module, &mut storage)?;
|
let programs = extract_programs(module, &mut storage)?;
|
||||||
|
|
||||||
@ -431,7 +480,7 @@ pub fn process(module: &mut Module) -> Result<(WebidlCustomSectionId, WasmBindge
|
|||||||
module,
|
module,
|
||||||
start_found: false,
|
start_found: false,
|
||||||
};
|
};
|
||||||
cx.init();
|
cx.init()?;
|
||||||
|
|
||||||
for program in programs {
|
for program in programs {
|
||||||
cx.program(program)?;
|
cx.program(program)?;
|
||||||
@ -445,7 +494,7 @@ pub fn process(module: &mut Module) -> Result<(WebidlCustomSectionId, WasmBindge
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Context<'a> {
|
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
|
// Make a map from string name to ids of all exports
|
||||||
for export in self.module.exports.iter() {
|
for export in self.module.exports.iter() {
|
||||||
if let walrus::ExportItem::Function(f) = export.item {
|
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
|
// 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
|
// 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.
|
// location listed of what to import there for each item.
|
||||||
|
let mut intrinsics = Vec::new();
|
||||||
for import in self.module.imports.iter() {
|
for import in self.module.imports.iter() {
|
||||||
if import.module != PLACEHOLDER_MODULE {
|
if import.module != PLACEHOLDER_MODULE {
|
||||||
continue;
|
continue;
|
||||||
@ -465,15 +515,22 @@ impl<'a> Context<'a> {
|
|||||||
self.function_imports
|
self.function_imports
|
||||||
.insert(import.name.clone(), (import.id(), f));
|
.insert(import.name.clone(), (import.id(), f));
|
||||||
if let Some(intrinsic) = Intrinsic::from_symbol(&import.name) {
|
if let Some(intrinsic) = Intrinsic::from_symbol(&import.name) {
|
||||||
self.bindings
|
intrinsics.push((import.id(), intrinsic));
|
||||||
.imports
|
|
||||||
.insert(import.id(), ImportBinding::Function(intrinsic.binding()));
|
|
||||||
self.aux
|
|
||||||
.import_map
|
|
||||||
.insert(import.id(), AuxImport::Intrinsic(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
|
if let Some(custom) = self
|
||||||
.module
|
.module
|
||||||
@ -490,20 +547,57 @@ impl<'a> Context<'a> {
|
|||||||
|
|
||||||
// Register all the injected closure imports as that they're expected
|
// Register all the injected closure imports as that they're expected
|
||||||
// to manufacture a particular type of closure.
|
// 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 {
|
for (id, descriptor) in closure_imports {
|
||||||
self.aux
|
|
||||||
.import_map
|
|
||||||
.insert(id, AuxImport::Closure(descriptor));
|
|
||||||
let binding = Function {
|
let binding = Function {
|
||||||
shim_idx: 0,
|
shim_idx: 0,
|
||||||
arguments: vec![Descriptor::I32; 3],
|
arguments: vec![Descriptor::I32; 3],
|
||||||
ret: Descriptor::Anyref,
|
ret: Descriptor::Anyref,
|
||||||
};
|
};
|
||||||
self.bindings
|
bindings::register_import(
|
||||||
.imports
|
self.module,
|
||||||
.insert(id, ImportBinding::Function(binding));
|
&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> {
|
fn program(&mut self, program: decode::Program<'a>) -> Result<(), Error> {
|
||||||
@ -636,7 +730,7 @@ impl<'a> Context<'a> {
|
|||||||
kind,
|
kind,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
self.bindings.exports.insert(export_id, descriptor);
|
bindings::register_export(self.module, &mut self.bindings, export_id, descriptor)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -720,20 +814,34 @@ impl<'a> Context<'a> {
|
|||||||
// NB: `structural` is ignored for constructors since the
|
// NB: `structural` is ignored for constructors since the
|
||||||
// js type isn't expected to change anyway.
|
// js type isn't expected to change anyway.
|
||||||
decode::MethodKind::Constructor => {
|
decode::MethodKind::Constructor => {
|
||||||
self.bindings
|
bindings::register_import(
|
||||||
.imports
|
self.module,
|
||||||
.insert(import_id, ImportBinding::Constructor(descriptor));
|
&mut self.bindings,
|
||||||
|
import_id,
|
||||||
|
descriptor,
|
||||||
|
ast::WebidlFunctionKind::Constructor,
|
||||||
|
)?;
|
||||||
AuxImport::Value(AuxValue::Bare(class))
|
AuxImport::Value(AuxValue::Bare(class))
|
||||||
}
|
}
|
||||||
decode::MethodKind::Operation(op) => {
|
decode::MethodKind::Operation(op) => {
|
||||||
let (import, method) =
|
let (import, method) =
|
||||||
self.determine_import_op(class, function, *structural, op)?;
|
self.determine_import_op(class, function, *structural, op)?;
|
||||||
let binding = if method {
|
let kind = if method {
|
||||||
ImportBinding::Method(descriptor)
|
let kind = ast::WebidlFunctionKindMethod {
|
||||||
|
// TODO: what should this actually be?
|
||||||
|
ty: ast::WebidlScalarType::Any.into(),
|
||||||
|
};
|
||||||
|
ast::WebidlFunctionKind::Method(kind)
|
||||||
} else {
|
} 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
|
import
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -742,9 +850,13 @@ impl<'a> Context<'a> {
|
|||||||
// NB: `structural` is ignored for free functions since it's
|
// NB: `structural` is ignored for free functions since it's
|
||||||
// expected that the binding isn't changing anyway.
|
// expected that the binding isn't changing anyway.
|
||||||
None => {
|
None => {
|
||||||
self.bindings
|
bindings::register_import(
|
||||||
.imports
|
self.module,
|
||||||
.insert(import_id, ImportBinding::Function(descriptor));
|
&mut self.bindings,
|
||||||
|
import_id,
|
||||||
|
descriptor,
|
||||||
|
ast::WebidlFunctionKind::Static,
|
||||||
|
)?;
|
||||||
let name = self.determine_import(import, function.name)?;
|
let name = self.determine_import(import, function.name)?;
|
||||||
AuxImport::Value(AuxValue::Bare(name))
|
AuxImport::Value(AuxValue::Bare(name))
|
||||||
}
|
}
|
||||||
@ -867,14 +979,17 @@ impl<'a> Context<'a> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Register the signature of this imported shim
|
// Register the signature of this imported shim
|
||||||
self.bindings.imports.insert(
|
bindings::register_import(
|
||||||
|
self.module,
|
||||||
|
&mut self.bindings,
|
||||||
import_id,
|
import_id,
|
||||||
ImportBinding::Function(Function {
|
Function {
|
||||||
arguments: Vec::new(),
|
arguments: Vec::new(),
|
||||||
shim_idx: 0,
|
shim_idx: 0,
|
||||||
ret: Descriptor::Anyref,
|
ret: Descriptor::Anyref,
|
||||||
}),
|
},
|
||||||
);
|
ast::WebidlFunctionKind::Static,
|
||||||
|
)?;
|
||||||
|
|
||||||
// And then save off that this function is is an instanceof shim for an
|
// And then save off that this function is is an instanceof shim for an
|
||||||
// imported item.
|
// imported item.
|
||||||
@ -896,14 +1011,17 @@ impl<'a> Context<'a> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Register the signature of this imported shim
|
// Register the signature of this imported shim
|
||||||
self.bindings.imports.insert(
|
bindings::register_import(
|
||||||
|
self.module,
|
||||||
|
&mut self.bindings,
|
||||||
import_id,
|
import_id,
|
||||||
ImportBinding::Function(Function {
|
Function {
|
||||||
arguments: vec![Descriptor::Ref(Box::new(Descriptor::Anyref))],
|
arguments: vec![Descriptor::Ref(Box::new(Descriptor::Anyref))],
|
||||||
shim_idx: 0,
|
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
|
// And then save off that this function is is an instanceof shim for an
|
||||||
// imported item.
|
// imported item.
|
||||||
@ -944,7 +1062,12 @@ impl<'a> Context<'a> {
|
|||||||
shim_idx: 0,
|
shim_idx: 0,
|
||||||
ret: descriptor.clone(),
|
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(
|
self.aux.export_map.insert(
|
||||||
getter_id,
|
getter_id,
|
||||||
AuxExport {
|
AuxExport {
|
||||||
@ -969,7 +1092,12 @@ impl<'a> Context<'a> {
|
|||||||
shim_idx: 0,
|
shim_idx: 0,
|
||||||
ret: Descriptor::Unit,
|
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(
|
self.aux.export_map.insert(
|
||||||
setter_id,
|
setter_id,
|
||||||
AuxExport {
|
AuxExport {
|
||||||
@ -1000,9 +1128,13 @@ impl<'a> Context<'a> {
|
|||||||
arguments: vec![Descriptor::I32],
|
arguments: vec![Descriptor::I32],
|
||||||
ret: Descriptor::Anyref,
|
ret: Descriptor::Anyref,
|
||||||
};
|
};
|
||||||
self.bindings
|
bindings::register_import(
|
||||||
.imports
|
self.module,
|
||||||
.insert(*import_id, ImportBinding::Function(binding));
|
&mut self.bindings,
|
||||||
|
*import_id,
|
||||||
|
binding,
|
||||||
|
ast::WebidlFunctionKind::Static,
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -1148,7 +1280,7 @@ impl<'a> Context<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl walrus::CustomSection for WebidlCustomSection {
|
impl walrus::CustomSection for NonstandardWebidlSection {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"webidl custom section"
|
"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]
|
#[wasm_bindgen]
|
||||||
pub fn add(a: u32, b: u32) -> u32 {
|
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() {
|
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() {};
|
exports.baz$ = function() {};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user