mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-06-19 07:51:26 +00:00
Second large refactor for WebIDL bindings
This commit is the second, and hopefully last massive, refactor for using WebIDL bindings internally in `wasm-bindgen`. This commit actually fully executes on the task at hand, moving `wasm-bindgen` to internally using WebIDL bindings throughout its code generation, anyref passes, etc. This actually fixes a number of issues that have existed in the anyref pass for some time now! The main changes here are to basically remove the usage of `Descriptor` from generating JS bindings. Instead two new types are introduced: `NonstandardIncoming` and `NonstandardOutgoing` which are bindings lists used for incoming/outgoing bindings. These mirror the standard terminology and literally have variants which are the standard values. All `Descriptor` types are now mapped into lists of incoming/outgoing bindings and used for process in wasm-bindgen. All JS generation has been refactored and updated to now process these lists of bindings instead of the previous `Descriptor`. In other words this commit takes `js2rust.rs` and `rust2js.rs` and first splits them in two. Interpretation of `Descriptor` and what to do for conversions is in the binding selection modules. The actual generation of JS from the binding selection is now performed by `incoming.rs` and `outgoing.rs`. To boot this also deduplicates all the code between the argument handling of `js2rust.rs` and return value handling of `rust2js.rs`. This means that to implement a new binding you only need to implement it one place and it's implemented for free in the other! This commit is not the end of the story though. I would like to add a mdoe to `wasm-bindgen` that literally emits a WebIDL bindings section. That's left for a third (and hopefully final) refactoring which is also intended to optimize generated JS for bindings. This commit currently loses the optimization where an imported is hooked up by value directly whenever a shim isn't needed. It's planned that the next refactoring to emit a webidl binding section that can be added back in. It shouldn't be too too hard hopefully since all the scaffolding is in place now. cc #1524
This commit is contained in:
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 });
|
||||
}
|
||||
}
|
1430
crates/cli-support/src/webidl/mod.rs
Normal file
1430
crates/cli-support/src/webidl/mod.rs
Normal file
File diff suppressed because it is too large
Load Diff
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 });
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user