mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-06-16 22:41:24 +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:
507
crates/cli-support/src/js/binding.rs
Normal file
507
crates/cli-support/src/js/binding.rs
Normal file
@ -0,0 +1,507 @@
|
||||
//! Support for actually generating a JS function shim.
|
||||
//!
|
||||
//! This `Builder` type is used to generate JS function shims which sit between
|
||||
//! exported functions, table elements, imports, etc. All function shims
|
||||
//! generated by `wasm-bindgen` run through this type.
|
||||
|
||||
use crate::js::incoming;
|
||||
use crate::js::outgoing;
|
||||
use crate::js::Context;
|
||||
use crate::webidl::Binding;
|
||||
use failure::{bail, Error};
|
||||
use std::collections::HashSet;
|
||||
use wasm_webidl_bindings::ast;
|
||||
|
||||
/// A one-size-fits-all builder for processing WebIDL bindings and generating
|
||||
/// JS.
|
||||
pub struct Builder<'a, 'b> {
|
||||
/// Parent context used to expose helper functions and such.
|
||||
cx: &'a mut Context<'b>,
|
||||
/// Prelude JS which is present before the main invocation to prepare
|
||||
/// arguments.
|
||||
args_prelude: String,
|
||||
/// Finally block to be executed regardless of the call's status, mostly
|
||||
/// used for cleanups like free'ing.
|
||||
finally: String,
|
||||
/// Code to execute after return value is materialized.
|
||||
ret_finally: String,
|
||||
/// Argument names to the JS function shim that we're generating.
|
||||
function_args: Vec<String>,
|
||||
/// JS expressions that are arguments to the function that we're calling.
|
||||
invoc_args: Vec<String>,
|
||||
/// JS to execute just before the return value is materialized.
|
||||
ret_prelude: String,
|
||||
/// The JS expression of the actual return value.
|
||||
ret_js: String,
|
||||
/// The TypeScript definition for each argument to this function.
|
||||
pub ts_args: Vec<TypescriptArg>,
|
||||
/// The TypeScript return value for this function.
|
||||
pub ts_ret: Option<TypescriptArg>,
|
||||
/// Whether or not this is building a constructor for a Rust class, and if
|
||||
/// so what class it's constructing.
|
||||
constructor: Option<String>,
|
||||
/// Whether or not this is building a method of a Rust class instance, and
|
||||
/// whether or not the method consumes `self` or not.
|
||||
method: Option<bool>,
|
||||
/// Whether or not we're catching exceptions from the main function
|
||||
/// invocation. Currently only used for imports.
|
||||
catch: bool,
|
||||
}
|
||||
|
||||
/// Helper struct used in incoming/outgoing to generate JS.
|
||||
pub struct JsBuilder {
|
||||
typescript: Vec<TypescriptArg>,
|
||||
prelude: String,
|
||||
finally: String,
|
||||
tmp: usize,
|
||||
args: Vec<String>,
|
||||
}
|
||||
|
||||
pub struct TypescriptArg {
|
||||
pub ty: String,
|
||||
pub name: String,
|
||||
pub optional: bool,
|
||||
}
|
||||
|
||||
impl<'a, 'b> Builder<'a, 'b> {
|
||||
pub fn new(cx: &'a mut Context<'b>) -> Builder<'a, 'b> {
|
||||
Builder {
|
||||
cx,
|
||||
args_prelude: String::new(),
|
||||
finally: String::new(),
|
||||
ret_finally: String::new(),
|
||||
function_args: Vec::new(),
|
||||
invoc_args: Vec::new(),
|
||||
ret_prelude: String::new(),
|
||||
ret_js: String::new(),
|
||||
ts_args: Vec::new(),
|
||||
ts_ret: None,
|
||||
constructor: None,
|
||||
method: None,
|
||||
catch: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn method(&mut self, consumed: bool) {
|
||||
self.method = Some(consumed);
|
||||
}
|
||||
|
||||
pub fn constructor(&mut self, class: &str) {
|
||||
self.constructor = Some(class.to_string());
|
||||
}
|
||||
|
||||
pub fn catch(&mut self, catch: bool) -> Result<(), Error> {
|
||||
if catch {
|
||||
self.cx.expose_handle_error()?;
|
||||
}
|
||||
self.catch = catch;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn process(
|
||||
&mut self,
|
||||
binding: &Binding,
|
||||
webidl: &ast::WebidlFunction,
|
||||
incoming_args: bool,
|
||||
explicit_arg_names: &Option<Vec<String>>,
|
||||
invoke: &mut dyn FnMut(&mut Context, &mut String, &[String]) -> Result<String, Error>,
|
||||
) -> Result<String, Error> {
|
||||
// used in `finalize` below
|
||||
if self.cx.config.debug {
|
||||
self.cx.expose_log_error();
|
||||
}
|
||||
|
||||
// First up we handle all the arguments. Depending on whether incoming
|
||||
// or outgoing ar the arguments this is pretty different.
|
||||
let mut arg_names = Vec::new();
|
||||
let mut js;
|
||||
if incoming_args {
|
||||
let mut webidl_params = webidl.params.iter();
|
||||
|
||||
// If we're returning via an out pointer then it's guaranteed to be the
|
||||
// first argument. This isn't an argument of the function shim we're
|
||||
// generating so synthesize the parameter and its value.
|
||||
if binding.return_via_outptr.is_some() {
|
||||
drop(webidl_params.next());
|
||||
self.cx.expose_global_argument_ptr()?;
|
||||
self.args_prelude
|
||||
.push_str("const retptr = globalArgumentPtr();\n");
|
||||
arg_names.push("retptr".to_string());
|
||||
}
|
||||
|
||||
// If this is a method then we're generating this as part of a class
|
||||
// method, so the leading parameter is the this pointer stored on
|
||||
// the JS object, so synthesize that here.
|
||||
match self.method {
|
||||
Some(true) => {
|
||||
drop(webidl_params.next());
|
||||
self.args_prelude.push_str("const ptr = this.ptr;\n");
|
||||
self.args_prelude.push_str("this.ptr = 0;\n");
|
||||
arg_names.push("ptr".to_string());
|
||||
}
|
||||
Some(false) => {
|
||||
drop(webidl_params.next());
|
||||
arg_names.push("this.ptr".to_string());
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
|
||||
// And now take the rest of the parameters and generate a name for them.
|
||||
for (i, _) in webidl_params.enumerate() {
|
||||
let arg = match explicit_arg_names {
|
||||
Some(list) => list[i].clone(),
|
||||
None => format!("arg{}", i),
|
||||
};
|
||||
self.function_args.push(arg.clone());
|
||||
arg_names.push(arg);
|
||||
}
|
||||
js = JsBuilder::new(arg_names);
|
||||
let mut args = incoming::Incoming::new(self.cx, &webidl.params, &mut js);
|
||||
for argument in binding.incoming.iter() {
|
||||
self.invoc_args.extend(args.process(argument)?);
|
||||
}
|
||||
} else {
|
||||
// If we're getting arguments from outgoing values then the ret ptr
|
||||
// is actually an argument of the function itself. That means that
|
||||
// `arg0` we generate below is the ret ptr, and we shouldn't
|
||||
// generate a JS binding for it and instead skip the first binding
|
||||
// listed.
|
||||
let mut skip = 0;
|
||||
if binding.return_via_outptr.is_some() {
|
||||
skip = 1;
|
||||
}
|
||||
|
||||
// And now take the rest of the parameters and generate a name for them.
|
||||
for i in 0..self.cx.module.types.get(binding.wasm_ty).params().len() {
|
||||
let arg = format!("arg{}", i);
|
||||
self.function_args.push(arg.clone());
|
||||
arg_names.push(arg);
|
||||
}
|
||||
js = JsBuilder::new(arg_names);
|
||||
let mut args = outgoing::Outgoing::new(self.cx, &mut js);
|
||||
for argument in binding.outgoing.iter().skip(skip) {
|
||||
self.invoc_args.push(args.process(argument)?);
|
||||
}
|
||||
}
|
||||
|
||||
// Save off the results of JS generation for the arguments.
|
||||
self.args_prelude.push_str(&js.prelude);
|
||||
self.finally.push_str(&js.finally);
|
||||
self.ts_args.extend(js.typescript);
|
||||
|
||||
// Remove extraneous typescript args which were synthesized and aren't
|
||||
// part of our function shim.
|
||||
while self.ts_args.len() > self.function_args.len() {
|
||||
self.ts_args.remove(0);
|
||||
}
|
||||
|
||||
// Handle the special case where there is no return value. In this case
|
||||
// we can skip all the logic below and go straight to the end.
|
||||
if incoming_args {
|
||||
if binding.outgoing.len() == 0 {
|
||||
assert!(binding.return_via_outptr.is_none());
|
||||
assert!(self.constructor.is_none());
|
||||
let invoc = invoke(self.cx, &mut self.args_prelude, &self.invoc_args)?;
|
||||
return Ok(self.finalize(&invoc));
|
||||
}
|
||||
assert_eq!(binding.outgoing.len(), 1);
|
||||
} else {
|
||||
if binding.incoming.len() == 0 {
|
||||
assert!(binding.return_via_outptr.is_none());
|
||||
assert!(self.constructor.is_none());
|
||||
let invoc = invoke(self.cx, &mut self.args_prelude, &self.invoc_args)?;
|
||||
return Ok(self.finalize(&invoc));
|
||||
}
|
||||
assert_eq!(binding.incoming.len(), 1);
|
||||
}
|
||||
|
||||
// Like above handling the return value is quite different based on
|
||||
// whether it's an outgoing argument or an incoming argument.
|
||||
let mut ret_args = Vec::new();
|
||||
let mut js;
|
||||
if incoming_args {
|
||||
match &binding.return_via_outptr {
|
||||
// If we have an outgoing value that requires multiple
|
||||
// aggregates then we're passing a return pointer (a global one)
|
||||
// to a wasm function, and then afterwards we're going to read
|
||||
// the results of that return pointer. Here we generate an
|
||||
// expression effectively which represents reading each value of
|
||||
// the return pointer that was filled in. These values are then
|
||||
// used by the outgoing builder as inputs to generate the final
|
||||
// actual return value.
|
||||
Some(list) => {
|
||||
let mut exposed = HashSet::new();
|
||||
for (i, ty) in list.iter().enumerate() {
|
||||
let (mem, size) = match ty {
|
||||
walrus::ValType::I32 => {
|
||||
if exposed.insert(*ty) {
|
||||
self.cx.expose_int32_memory();
|
||||
self.ret_prelude
|
||||
.push_str("const memi32 = getInt32Memory();\n");
|
||||
}
|
||||
("memi32", 4)
|
||||
}
|
||||
walrus::ValType::F32 => {
|
||||
if exposed.insert(*ty) {
|
||||
self.cx.expose_f32_memory();
|
||||
self.ret_prelude
|
||||
.push_str("const memf32 = getFloat32Memory();\n");
|
||||
}
|
||||
("memf32", 4)
|
||||
}
|
||||
walrus::ValType::F64 => {
|
||||
if exposed.insert(*ty) {
|
||||
self.cx.expose_f64_memory();
|
||||
self.ret_prelude
|
||||
.push_str("const memf64 = getFloat64Memory();\n");
|
||||
}
|
||||
("memf64", 8)
|
||||
}
|
||||
_ => bail!("invalid aggregate return type"),
|
||||
};
|
||||
ret_args.push(format!("{}[retptr / {} + {}]", mem, size, i));
|
||||
}
|
||||
}
|
||||
|
||||
// No return pointer? That's much easier! We just have one input
|
||||
// of `ret` which is created in the JS shim below.
|
||||
None => ret_args.push("ret".to_string()),
|
||||
}
|
||||
js = JsBuilder::new(ret_args);
|
||||
let mut ret = outgoing::Outgoing::new(self.cx, &mut js);
|
||||
let ret_js = ret.process(&binding.outgoing[0])?;
|
||||
self.ret_js.push_str(&ret_js);
|
||||
} else {
|
||||
// If there's an out ptr for an incoming argument then it means that
|
||||
// the first argument to our function is the return pointer, and we
|
||||
// need to fill that in. After we process the value we then write
|
||||
// each result of the processed value into the corresponding typed
|
||||
// array.
|
||||
js = JsBuilder::new(vec!["ret".to_string()]);
|
||||
let results = match &webidl.result {
|
||||
Some(ptr) => std::slice::from_ref(ptr),
|
||||
None => &[],
|
||||
};
|
||||
let mut ret = incoming::Incoming::new(self.cx, results, &mut js);
|
||||
let ret_js = ret.process(&binding.incoming[0])?;
|
||||
match &binding.return_via_outptr {
|
||||
Some(list) => {
|
||||
assert_eq!(list.len(), ret_js.len());
|
||||
for (i, js) in ret_js.iter().enumerate() {
|
||||
self.ret_finally
|
||||
.push_str(&format!("const ret{} = {};\n", i, js));
|
||||
}
|
||||
for (i, ty) in list.iter().enumerate() {
|
||||
let (mem, size) = match ty {
|
||||
walrus::ValType::I32 => {
|
||||
self.cx.expose_int32_memory();
|
||||
("getInt32Memory()", 4)
|
||||
}
|
||||
walrus::ValType::F32 => {
|
||||
self.cx.expose_f32_memory();
|
||||
("getFloat32Memory()", 4)
|
||||
}
|
||||
walrus::ValType::F64 => {
|
||||
self.cx.expose_f64_memory();
|
||||
("getFloat64Memory()", 8)
|
||||
}
|
||||
_ => bail!("invalid aggregate return type"),
|
||||
};
|
||||
self.ret_finally
|
||||
.push_str(&format!("{}[arg0 / {} + {}] = ret{};\n", mem, size, i, i));
|
||||
}
|
||||
}
|
||||
None => {
|
||||
assert_eq!(ret_js.len(), 1);
|
||||
self.ret_js.push_str(&ret_js[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.ret_finally.push_str(&js.finally);
|
||||
self.ret_prelude.push_str(&js.prelude);
|
||||
self.ts_ret = Some(js.typescript.remove(0));
|
||||
let invoc = invoke(self.cx, &mut self.args_prelude, &self.invoc_args)?;
|
||||
Ok(self.finalize(&invoc))
|
||||
}
|
||||
|
||||
// This method... is a mess. Refactorings and improvements are more than
|
||||
// welcome :)
|
||||
fn finalize(&self, invoc: &str) -> String {
|
||||
let mut js = String::new();
|
||||
js.push_str("(");
|
||||
js.push_str(&self.function_args.join(", "));
|
||||
js.push_str(") {\n");
|
||||
if self.args_prelude.len() > 0 {
|
||||
js.push_str(self.args_prelude.trim());
|
||||
js.push_str("\n");
|
||||
}
|
||||
|
||||
let mut call = String::new();
|
||||
if self.ts_ret.is_some() {
|
||||
call.push_str("const ret = ");
|
||||
}
|
||||
call.push_str(invoc);
|
||||
call.push_str(";\n");
|
||||
|
||||
if self.ret_prelude.len() > 0 {
|
||||
call.push_str(self.ret_prelude.trim());
|
||||
call.push_str("\n");
|
||||
}
|
||||
|
||||
if self.ret_js.len() > 0 {
|
||||
assert!(self.ts_ret.is_some());
|
||||
// Having a this field isn't supported yet, but shouldn't come up
|
||||
assert!(self.ret_finally.len() == 0);
|
||||
call.push_str("return ");
|
||||
call.push_str(&self.ret_js);
|
||||
call.push_str(";\n");
|
||||
} else if self.ret_finally.len() > 0 {
|
||||
call.push_str(self.ret_finally.trim());
|
||||
call.push_str("\n");
|
||||
}
|
||||
|
||||
if self.catch {
|
||||
call = format!("try {{\n{}}} catch (e) {{\n handleError(e)\n}}\n", call);
|
||||
}
|
||||
|
||||
// Generate a try/catch block in debug mode which handles unexpected and
|
||||
// unhandled exceptions, typically used on imports. This currently just
|
||||
// logs what happened, but keeps the exception being thrown to propagate
|
||||
// elsewhere.
|
||||
if self.cx.config.debug {
|
||||
call = format!("try {{\n{}}} catch (e) {{\n logError(e)\n}}\n", call);
|
||||
}
|
||||
|
||||
let finally = self.finally.trim();
|
||||
if finally.len() != 0 {
|
||||
call = format!("try {{\n{}}} finally {{\n{}\n}}\n", call, finally);
|
||||
}
|
||||
|
||||
js.push_str(&call);
|
||||
js.push_str("}");
|
||||
|
||||
return js;
|
||||
}
|
||||
|
||||
/// Returns the typescript signature of the binding that this has described.
|
||||
/// This is used to generate all the TypeScript definitions later on.
|
||||
///
|
||||
/// Note that the TypeScript returned here is just the argument list and the
|
||||
/// return value, it doesn't include the function name in any way.
|
||||
pub fn typescript_signature(&self) -> String {
|
||||
// Build up the typescript signature as well
|
||||
let mut omittable = true;
|
||||
let mut ts_args = Vec::new();
|
||||
for arg in self.ts_args.iter().rev() {
|
||||
// In TypeScript, we can mark optional parameters as omittable
|
||||
// using the `?` suffix, but only if they're not followed by
|
||||
// non-omittable parameters. Therefore iterate the parameter list
|
||||
// in reverse and stop using the `?` suffix for optional params as
|
||||
// soon as a non-optional parameter is encountered.
|
||||
if arg.optional {
|
||||
if omittable {
|
||||
ts_args.push(format!("{}?: {}", arg.name, arg.ty));
|
||||
} else {
|
||||
ts_args.push(format!("{}: {} | undefined", arg.name, arg.ty));
|
||||
}
|
||||
} else {
|
||||
omittable = false;
|
||||
ts_args.push(format!("{}: {}", arg.name, arg.ty));
|
||||
}
|
||||
}
|
||||
ts_args.reverse();
|
||||
let mut ts = format!("({})", ts_args.join(", "));
|
||||
|
||||
// Constructors have no listed return type in typescript
|
||||
if self.constructor.is_none() {
|
||||
ts.push_str(": ");
|
||||
if let Some(ty) = &self.ts_ret {
|
||||
ts.push_str(&ty.ty);
|
||||
if ty.optional {
|
||||
ts.push_str(" | undefined");
|
||||
}
|
||||
} else {
|
||||
ts.push_str("void");
|
||||
}
|
||||
}
|
||||
return ts;
|
||||
}
|
||||
|
||||
/// Returns a helpful JS doc comment which lists types for all parameters
|
||||
/// and the return value.
|
||||
pub fn js_doc_comments(&self) -> String {
|
||||
let mut ret: String = self
|
||||
.ts_args
|
||||
.iter()
|
||||
.map(|a| {
|
||||
if a.optional {
|
||||
format!("@param {{{} | undefined}} {}\n", a.ty, a.name)
|
||||
} else {
|
||||
format!("@param {{{}}} {}\n", a.ty, a.name)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
if let Some(ts) = &self.ts_ret {
|
||||
ret.push_str(&format!("@returns {{{}}}", ts.ty));
|
||||
}
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
impl JsBuilder {
|
||||
pub fn new(args: Vec<String>) -> JsBuilder {
|
||||
JsBuilder {
|
||||
args,
|
||||
tmp: 0,
|
||||
finally: String::new(),
|
||||
prelude: String::new(),
|
||||
typescript: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn typescript_len(&self) -> usize {
|
||||
self.typescript.len()
|
||||
}
|
||||
|
||||
pub fn arg(&self, idx: u32) -> &str {
|
||||
&self.args[idx as usize]
|
||||
}
|
||||
|
||||
pub fn typescript_required(&mut self, ty: &str) {
|
||||
let name = self.args[self.typescript.len()].clone();
|
||||
self.typescript.push(TypescriptArg {
|
||||
ty: ty.to_string(),
|
||||
optional: false,
|
||||
name,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn typescript_optional(&mut self, ty: &str) {
|
||||
let name = self.args[self.typescript.len()].clone();
|
||||
self.typescript.push(TypescriptArg {
|
||||
ty: ty.to_string(),
|
||||
optional: true,
|
||||
name,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn prelude(&mut self, prelude: &str) {
|
||||
for line in prelude.trim().lines() {
|
||||
self.prelude.push_str(line);
|
||||
self.prelude.push_str("\n");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn finally(&mut self, finally: &str) {
|
||||
for line in finally.trim().lines() {
|
||||
self.finally.push_str(line);
|
||||
self.finally.push_str("\n");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tmp(&mut self) -> usize {
|
||||
let ret = self.tmp;
|
||||
self.tmp += 1;
|
||||
return ret;
|
||||
}
|
||||
}
|
556
crates/cli-support/src/js/incoming.rs
Normal file
556
crates/cli-support/src/js/incoming.rs
Normal file
@ -0,0 +1,556 @@
|
||||
//! Implementation of taking a `NonstandardIncoming` binding and generating JS
|
||||
//! which represents it and executes it for what we need.
|
||||
//!
|
||||
//! This module is used to generate JS for all our incoming bindings which
|
||||
//! includes arguments going into exports or return values from imports.
|
||||
|
||||
use crate::descriptor::VectorKind;
|
||||
use crate::js::binding::JsBuilder;
|
||||
use crate::js::Context;
|
||||
use crate::webidl::NonstandardIncoming;
|
||||
use failure::{bail, Error};
|
||||
use wasm_webidl_bindings::ast;
|
||||
|
||||
pub struct Incoming<'a, 'b> {
|
||||
cx: &'a mut Context<'b>,
|
||||
types: &'a [ast::WebidlTypeRef],
|
||||
js: &'a mut JsBuilder,
|
||||
}
|
||||
|
||||
impl<'a, 'b> Incoming<'a, 'b> {
|
||||
pub fn new(
|
||||
cx: &'a mut Context<'b>,
|
||||
types: &'a [ast::WebidlTypeRef],
|
||||
js: &'a mut JsBuilder,
|
||||
) -> Incoming<'a, 'b> {
|
||||
Incoming { cx, types, js }
|
||||
}
|
||||
|
||||
pub fn process(&mut self, incoming: &NonstandardIncoming) -> Result<Vec<String>, Error> {
|
||||
let before = self.js.typescript_len();
|
||||
let ret = self.nonstandard(incoming)?;
|
||||
assert_eq!(before + 1, self.js.typescript_len());
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
fn nonstandard(&mut self, incoming: &NonstandardIncoming) -> Result<Vec<String>, Error> {
|
||||
let single = match incoming {
|
||||
NonstandardIncoming::Standard(val) => return self.standard(val),
|
||||
|
||||
// Evaluate the `val` binding, store it into a one-element `BigInt`
|
||||
// array (appropriately typed) and then use a 32-bit view into the
|
||||
// `BigInt` array to extract the high/low bits and pass them through
|
||||
// in the ABI.
|
||||
NonstandardIncoming::Int64 { val, signed } => {
|
||||
self.js.typescript_required("BigInt");
|
||||
let (expr, ty) = self.standard_typed(val)?;
|
||||
assert_eq!(ty, ast::WebidlScalarType::Any.into());
|
||||
let f = if *signed {
|
||||
self.cx.expose_int64_cvt_shim()
|
||||
} else {
|
||||
self.cx.expose_uint64_cvt_shim()
|
||||
};
|
||||
self.cx.expose_uint32_memory();
|
||||
let i = self.js.tmp();
|
||||
self.js.prelude(&format!(
|
||||
"
|
||||
{f}[0] = {expr};
|
||||
const low{i} = u32CvtShim[0];
|
||||
const high{i} = u32CvtShim[1];
|
||||
",
|
||||
i = i,
|
||||
f = f,
|
||||
expr = expr,
|
||||
));
|
||||
return Ok(vec![format!("low{}", i), format!("high{}", i)]);
|
||||
}
|
||||
|
||||
// Same as `IncomingBindingExpressionAllocCopy`, except we use a
|
||||
// different `VectorKind`
|
||||
NonstandardIncoming::AllocCopyInt64 {
|
||||
alloc_func_name: _,
|
||||
expr,
|
||||
signed,
|
||||
} => {
|
||||
let (expr, ty) = self.standard_typed(expr)?;
|
||||
assert_eq!(ty, ast::WebidlScalarType::Any.into());
|
||||
let kind = if *signed {
|
||||
VectorKind::I64
|
||||
} else {
|
||||
VectorKind::U64
|
||||
};
|
||||
let func = self.cx.pass_to_wasm_function(kind)?;
|
||||
self.js.typescript_required(kind.js_ty());
|
||||
return Ok(vec![
|
||||
format!("{}({})", func, expr),
|
||||
"WASM_VECTOR_LEN".to_string(),
|
||||
]);
|
||||
}
|
||||
|
||||
// Same as `IncomingBindingExpressionAllocCopy`, except we use a
|
||||
// different `VectorKind`
|
||||
NonstandardIncoming::AllocCopyAnyrefArray {
|
||||
alloc_func_name: _,
|
||||
expr,
|
||||
} => {
|
||||
let (expr, ty) = self.standard_typed(expr)?;
|
||||
assert_eq!(ty, ast::WebidlScalarType::Any.into());
|
||||
let func = self.cx.pass_to_wasm_function(VectorKind::Anyref)?;
|
||||
self.js.typescript_required(VectorKind::Anyref.js_ty());
|
||||
return Ok(vec![
|
||||
format!("{}({})", func, expr),
|
||||
"WASM_VECTOR_LEN".to_string(),
|
||||
]);
|
||||
}
|
||||
|
||||
// There's no `char` in JS, so we take a string instead and just
|
||||
// forward along the first code point to Rust.
|
||||
NonstandardIncoming::Char { val } => {
|
||||
let (expr, ty) = self.standard_typed(val)?;
|
||||
assert_eq!(ty, ast::WebidlScalarType::DomString.into());
|
||||
self.js.typescript_required("string");
|
||||
format!("{}.codePointAt(0)", expr)
|
||||
}
|
||||
|
||||
// When moving a type back into Rust we need to clear out the
|
||||
// internal pointer in JS to prevent it from being reused again in
|
||||
// the future.
|
||||
NonstandardIncoming::RustType { class, val } => {
|
||||
let (expr, ty) = self.standard_typed(val)?;
|
||||
assert_eq!(ty, ast::WebidlScalarType::Any.into());
|
||||
self.assert_class(&expr, &class);
|
||||
self.assert_not_moved(&expr);
|
||||
let i = self.js.tmp();
|
||||
self.js.prelude(&format!("const ptr{} = {}.ptr;", i, expr));
|
||||
self.js.prelude(&format!("{}.ptr = 0;", expr));
|
||||
self.js.typescript_required(class);
|
||||
format!("ptr{}", i)
|
||||
}
|
||||
|
||||
// Here we can simply pass along the pointer with no extra fluff
|
||||
// needed.
|
||||
NonstandardIncoming::RustTypeRef { class, val } => {
|
||||
let (expr, ty) = self.standard_typed(val)?;
|
||||
assert_eq!(ty, ast::WebidlScalarType::Any.into());
|
||||
self.assert_class(&expr, &class);
|
||||
self.assert_not_moved(&expr);
|
||||
self.js.typescript_required(class);
|
||||
format!("{}.ptr", expr)
|
||||
}
|
||||
|
||||
// the "stack-ful" nature means that we're always popping from the
|
||||
// stack, and make sure that we actually clear our reference to
|
||||
// allow stale values to get GC'd
|
||||
NonstandardIncoming::BorrowedAnyref { val } => {
|
||||
let (expr, ty) = self.standard_typed(val)?;
|
||||
assert_eq!(ty, ast::WebidlScalarType::Any.into());
|
||||
self.cx.expose_borrowed_objects();
|
||||
self.cx.expose_global_stack_pointer();
|
||||
self.js.finally("heap[stack_pointer++] = undefined;");
|
||||
self.js.typescript_required("any");
|
||||
format!("addBorrowedObject({})", expr)
|
||||
}
|
||||
|
||||
// Similar to `AllocCopy`, except that we deallocate in a finally
|
||||
// block.
|
||||
NonstandardIncoming::Slice { kind, val, mutable } => {
|
||||
let (expr, ty) = self.standard_typed(val)?;
|
||||
assert_eq!(ty, ast::WebidlScalarType::Any.into());
|
||||
let func = self.cx.pass_to_wasm_function(*kind)?;
|
||||
let i = self.js.tmp();
|
||||
self.js
|
||||
.prelude(&format!("const ptr{} = {}({});", i, func, expr));
|
||||
self.js
|
||||
.prelude(&format!("const len{} = WASM_VECTOR_LEN;", i));
|
||||
self.finally_free_slice(&expr, i, *kind, *mutable)?;
|
||||
self.js.typescript_required(kind.js_ty());
|
||||
return Ok(vec![format!("ptr{}", i), format!("len{}", i)]);
|
||||
}
|
||||
|
||||
// Pass `None` as a sentinel value that `val` will never take on.
|
||||
// This is only manufactured for specific underlying types.
|
||||
NonstandardIncoming::OptionU32Sentinel { val } => {
|
||||
let (expr, ty) = self.standard_typed(val)?;
|
||||
assert_eq!(ty, ast::WebidlScalarType::Any.into());
|
||||
self.cx.expose_is_like_none();
|
||||
self.js.typescript_optional("number");
|
||||
self.assert_optional_number(&expr);
|
||||
format!("isLikeNone({0}) ? 0xFFFFFF : {0}", expr)
|
||||
}
|
||||
|
||||
// Pass `true` as 1, `false` as 0, and `None` as a sentinel value.
|
||||
NonstandardIncoming::OptionBool { val } => {
|
||||
let (expr, ty) = self.standard_typed(val)?;
|
||||
assert_eq!(ty, ast::WebidlScalarType::Any.into());
|
||||
self.cx.expose_is_like_none();
|
||||
self.js.typescript_optional("boolean");
|
||||
self.assert_optional_bool(&expr);
|
||||
format!("isLikeNone({0}) ? 0xFFFFFF : {0} ? 1 : 0", expr)
|
||||
}
|
||||
|
||||
// Pass `None` as a sentinel value a character can never have
|
||||
NonstandardIncoming::OptionChar { val } => {
|
||||
let (expr, ty) = self.standard_typed(val)?;
|
||||
assert_eq!(ty, ast::WebidlScalarType::Any.into());
|
||||
self.cx.expose_is_like_none();
|
||||
self.js.typescript_optional("string");
|
||||
format!("isLikeNone({0}) ? 0xFFFFFF : {0}.codePointAt(0)", expr)
|
||||
}
|
||||
|
||||
// Pass `None` as the hole in the enum which no valid value can ever
|
||||
// take
|
||||
NonstandardIncoming::OptionIntegerEnum { val, hole } => {
|
||||
let (expr, ty) = self.standard_typed(val)?;
|
||||
assert_eq!(ty, ast::WebidlScalarType::Any.into());
|
||||
self.cx.expose_is_like_none();
|
||||
self.js.typescript_optional("number");
|
||||
self.assert_optional_number(&expr);
|
||||
format!("isLikeNone({0}) ? {1} : {0}", expr, hole)
|
||||
}
|
||||
|
||||
// `None` here is zero, but if `Some` then we need to clear out the
|
||||
// internal pointer because the value is being moved.
|
||||
NonstandardIncoming::OptionRustType { class, val } => {
|
||||
let (expr, ty) = self.standard_typed(val)?;
|
||||
assert_eq!(ty, ast::WebidlScalarType::Any.into());
|
||||
self.cx.expose_is_like_none();
|
||||
let i = self.js.tmp();
|
||||
self.js.prelude(&format!("let ptr{} = 0;", i));
|
||||
self.js.prelude(&format!("if (!isLikeNone({0})) {{", expr));
|
||||
self.assert_class(&expr, class);
|
||||
self.assert_not_moved(&expr);
|
||||
self.js.prelude(&format!("ptr{} = {}.ptr;", i, expr));
|
||||
self.js.prelude(&format!("{}.ptr = 0;", expr));
|
||||
self.js.prelude("}");
|
||||
self.js.typescript_optional(class);
|
||||
format!("ptr{}", i)
|
||||
}
|
||||
|
||||
// The ABI produces four values here, all zero for `None` and 1 in
|
||||
// the first for the last two being the low/high bits
|
||||
NonstandardIncoming::OptionInt64 { val, signed } => {
|
||||
let (expr, ty) = self.standard_typed(val)?;
|
||||
assert_eq!(ty, ast::WebidlScalarType::Any.into());
|
||||
self.cx.expose_is_like_none();
|
||||
let f = if *signed {
|
||||
self.cx.expose_int64_cvt_shim()
|
||||
} else {
|
||||
self.cx.expose_uint64_cvt_shim()
|
||||
};
|
||||
self.cx.expose_uint32_memory();
|
||||
let i = self.js.tmp();
|
||||
self.js.prelude(&format!(
|
||||
"\
|
||||
{f}[0] = isLikeNone({expr}) ? BigInt(0) : {expr};
|
||||
const low{i} = isLikeNone({expr}) ? 0 : u32CvtShim[0];
|
||||
const high{i} = isLikeNone({expr}) ? 0 : u32CvtShim[1];
|
||||
",
|
||||
i = i,
|
||||
f = f,
|
||||
expr = expr,
|
||||
));
|
||||
self.js.typescript_optional("BigInt");
|
||||
return Ok(vec![
|
||||
format!("!isLikeNone({0})", expr),
|
||||
"0".to_string(),
|
||||
format!("low{}", i),
|
||||
format!("high{}", i),
|
||||
]);
|
||||
}
|
||||
|
||||
// The ABI here is always an integral index into the anyref table,
|
||||
// and the anyref table just differs based on whether we ran the
|
||||
// anyref pass or not.
|
||||
NonstandardIncoming::OptionAnyref { val } => {
|
||||
let (expr, ty) = self.standard_typed(val)?;
|
||||
assert_eq!(ty, ast::WebidlScalarType::Any.into());
|
||||
self.cx.expose_is_like_none();
|
||||
self.js.typescript_optional("any");
|
||||
if self.cx.config.anyref {
|
||||
self.cx.expose_add_to_anyref_table()?;
|
||||
format!("isLikeNone({0}) ? 0 : addToAnyrefTable({0})", expr)
|
||||
} else {
|
||||
self.cx.expose_add_heap_object();
|
||||
format!("isLikeNone({0}) ? 0 : addHeapObject({0})", expr)
|
||||
}
|
||||
}
|
||||
|
||||
// Native types of wasm take a leading discriminant to indicate
|
||||
// whether the next value is valid or not.
|
||||
NonstandardIncoming::OptionNative { val } => {
|
||||
let (expr, ty) = self.standard_typed(val)?;
|
||||
assert_eq!(ty, ast::WebidlScalarType::Any.into());
|
||||
self.cx.expose_is_like_none();
|
||||
self.js.typescript_optional("number");
|
||||
self.assert_optional_number(&expr);
|
||||
return Ok(vec![
|
||||
format!("!isLikeNone({0})", expr),
|
||||
format!("isLikeNone({0}) ? 0 : {0}", expr),
|
||||
]);
|
||||
}
|
||||
|
||||
// Similar to `AllocCopy`, except we're handling the undefined case
|
||||
// and passing null for the pointer value.
|
||||
NonstandardIncoming::OptionVector { kind, val } => {
|
||||
let (expr, ty) = self.standard_typed(val)?;
|
||||
assert_eq!(ty, ast::WebidlScalarType::Any.into());
|
||||
let func = self.cx.pass_to_wasm_function(*kind)?;
|
||||
self.cx.expose_is_like_none();
|
||||
let i = self.js.tmp();
|
||||
self.js.prelude(&format!(
|
||||
"const ptr{i} = isLikeNone({0}) ? 0 : {f}({0});",
|
||||
expr,
|
||||
i = i,
|
||||
f = func,
|
||||
));
|
||||
self.js
|
||||
.prelude(&format!("const len{} = WASM_VECTOR_LEN;", i));
|
||||
self.js.typescript_optional(kind.js_ty());
|
||||
return Ok(vec![format!("ptr{}", i), format!("len{}", i)]);
|
||||
}
|
||||
|
||||
// An unfortunate smorgasboard of handling slices, transfers if
|
||||
// mutable, etc. Not the prettiest binding option here, and of
|
||||
// course never going to be standardized.
|
||||
NonstandardIncoming::OptionSlice { kind, val, mutable } => {
|
||||
let (expr, ty) = self.standard_typed(val)?;
|
||||
assert_eq!(ty, ast::WebidlScalarType::Any.into());
|
||||
let func = self.cx.pass_to_wasm_function(*kind)?;
|
||||
self.cx.expose_is_like_none();
|
||||
let i = self.js.tmp();
|
||||
self.js.prelude(&format!(
|
||||
"const ptr{i} = isLikeNone({0}) ? 0 : {f}({0});",
|
||||
expr,
|
||||
i = i,
|
||||
f = func,
|
||||
));
|
||||
self.js
|
||||
.prelude(&format!("const len{} = WASM_VECTOR_LEN;", i));
|
||||
self.js.finally(&format!("if (ptr{} !== 0) {{", i));
|
||||
self.finally_free_slice(&expr, i, *kind, *mutable)?;
|
||||
self.js.finally("}");
|
||||
self.js.typescript_optional(kind.js_ty());
|
||||
return Ok(vec![format!("ptr{}", i), format!("len{}", i)]);
|
||||
}
|
||||
};
|
||||
Ok(vec![single])
|
||||
}
|
||||
|
||||
/// Evaluates the `standard` binding expression, returning the JS expression
|
||||
/// needed to evaluate the binding.
|
||||
fn standard(
|
||||
&mut self,
|
||||
standard: &ast::IncomingBindingExpression,
|
||||
) -> Result<Vec<String>, Error> {
|
||||
let single = match standard {
|
||||
ast::IncomingBindingExpression::As(as_) => {
|
||||
let (expr, ty) = self.standard_typed(&as_.expr)?;
|
||||
match ty {
|
||||
ast::WebidlTypeRef::Scalar(ast::WebidlScalarType::Any) => {
|
||||
self.js.typescript_required("any");
|
||||
|
||||
// If the type here is anyref but we didn't run the
|
||||
// anyref pass that means we have to instead actually
|
||||
// pass in an index
|
||||
//
|
||||
// TODO: we should ideally move this `addHeapObject`
|
||||
// into a nonstanard binding whenever the anyref pass
|
||||
// doesn't already run rather than implicitly picking
|
||||
// it up here
|
||||
if self.cx.config.anyref {
|
||||
expr
|
||||
} else {
|
||||
self.cx.expose_add_heap_object();
|
||||
format!("addHeapObject({})", expr)
|
||||
}
|
||||
}
|
||||
ast::WebidlTypeRef::Scalar(ast::WebidlScalarType::Boolean) => {
|
||||
self.js.typescript_required("boolean");
|
||||
self.assert_bool(&expr);
|
||||
// JS will already coerce booleans into numbers for us
|
||||
expr
|
||||
}
|
||||
_ => {
|
||||
self.js.typescript_required("number");
|
||||
self.assert_number(&expr);
|
||||
expr
|
||||
}
|
||||
}
|
||||
}
|
||||
ast::IncomingBindingExpression::Get(_) => {
|
||||
bail!("unsupported bare `get` in webidl bindings");
|
||||
}
|
||||
ast::IncomingBindingExpression::AllocUtf8Str(expr) => {
|
||||
let (expr, ty) = self.standard_typed(&expr.expr)?;
|
||||
assert_eq!(ty, ast::WebidlScalarType::DomString.into());
|
||||
self.js.typescript_required("string");
|
||||
self.cx.expose_pass_string_to_wasm()?;
|
||||
return Ok(vec![
|
||||
format!("passStringToWasm({})", expr),
|
||||
"WASM_VECTOR_LEN".to_string(),
|
||||
]);
|
||||
}
|
||||
ast::IncomingBindingExpression::AllocCopy(expr) => {
|
||||
let (expr, ty) = self.standard_typed(&expr.expr)?;
|
||||
let scalar = match ty {
|
||||
ast::WebidlTypeRef::Scalar(s) => s,
|
||||
ast::WebidlTypeRef::Id(_) => {
|
||||
bail!("unsupported type passed to `alloc-copy` in webidl binding")
|
||||
}
|
||||
};
|
||||
let kind = match scalar {
|
||||
ast::WebidlScalarType::Int8Array => VectorKind::I8,
|
||||
ast::WebidlScalarType::Uint8Array => VectorKind::U8,
|
||||
ast::WebidlScalarType::Uint8ClampedArray => VectorKind::ClampedU8,
|
||||
ast::WebidlScalarType::Int16Array => VectorKind::I16,
|
||||
ast::WebidlScalarType::Uint16Array => VectorKind::U16,
|
||||
ast::WebidlScalarType::Int32Array => VectorKind::I32,
|
||||
ast::WebidlScalarType::Uint32Array => VectorKind::U32,
|
||||
ast::WebidlScalarType::Float32Array => VectorKind::F32,
|
||||
ast::WebidlScalarType::Float64Array => VectorKind::F64,
|
||||
_ => bail!("unsupported type passed to alloc-copy: {:?}", scalar),
|
||||
};
|
||||
self.js.typescript_required(kind.js_ty());
|
||||
let func = self.cx.pass_to_wasm_function(kind)?;
|
||||
return Ok(vec![
|
||||
format!("{}({})", func, expr),
|
||||
"WASM_VECTOR_LEN".to_string(),
|
||||
]);
|
||||
}
|
||||
ast::IncomingBindingExpression::EnumToI32(_) => {
|
||||
bail!("unsupported enum-to-i32 conversion in webidl binding");
|
||||
}
|
||||
ast::IncomingBindingExpression::Field(_) => {
|
||||
bail!("unsupported field accessor in webidl binding");
|
||||
}
|
||||
ast::IncomingBindingExpression::BindImport(_) => {
|
||||
bail!("unsupported import binding in webidl binding");
|
||||
}
|
||||
};
|
||||
Ok(vec![single])
|
||||
}
|
||||
|
||||
/// Evaluates the `standard` binding expression, returning both the
|
||||
/// JS expression to evaluate along with the WebIDL type of the expression.
|
||||
///
|
||||
/// Currently only supports `Get`.
|
||||
fn standard_typed(
|
||||
&mut self,
|
||||
standard: &ast::IncomingBindingExpression,
|
||||
) -> Result<(String, ast::WebidlTypeRef), Error> {
|
||||
match standard {
|
||||
ast::IncomingBindingExpression::As(_) => {
|
||||
bail!("unsupported as in webidl binding");
|
||||
}
|
||||
ast::IncomingBindingExpression::Get(expr) => {
|
||||
let arg = self.js.arg(expr.idx).to_string();
|
||||
let ty = self.types[expr.idx as usize];
|
||||
Ok((arg, ty))
|
||||
}
|
||||
ast::IncomingBindingExpression::AllocUtf8Str(_) => {
|
||||
bail!("unsupported alloc-utf8-str in webidl binding");
|
||||
}
|
||||
ast::IncomingBindingExpression::AllocCopy(_) => {
|
||||
bail!("unsupported alloc-copy in webidl binding");
|
||||
}
|
||||
ast::IncomingBindingExpression::EnumToI32(_) => {
|
||||
bail!("unsupported enum-to-i32 in webidl binding");
|
||||
}
|
||||
ast::IncomingBindingExpression::Field(_) => {
|
||||
bail!("unsupported field accessor in webidl binding");
|
||||
}
|
||||
ast::IncomingBindingExpression::BindImport(_) => {
|
||||
bail!("unsupported import binding in webidl binding");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_class(&mut self, arg: &str, class: &str) {
|
||||
self.cx.expose_assert_class();
|
||||
self.js
|
||||
.prelude(&format!("_assertClass({}, {});", arg, class));
|
||||
}
|
||||
|
||||
fn assert_number(&mut self, arg: &str) {
|
||||
if !self.cx.config.debug {
|
||||
return;
|
||||
}
|
||||
self.cx.expose_assert_num();
|
||||
self.js.prelude(&format!("_assertNum({});", arg));
|
||||
}
|
||||
|
||||
fn assert_bool(&mut self, arg: &str) {
|
||||
if !self.cx.config.debug {
|
||||
return;
|
||||
}
|
||||
self.cx.expose_assert_bool();
|
||||
self.js.prelude(&format!("_assertBoolean({});", arg));
|
||||
}
|
||||
|
||||
fn assert_optional_number(&mut self, arg: &str) {
|
||||
if !self.cx.config.debug {
|
||||
return;
|
||||
}
|
||||
self.cx.expose_is_like_none();
|
||||
self.js.prelude(&format!("if (!isLikeNone({})) {{", arg));
|
||||
self.assert_number(arg);
|
||||
self.js.prelude("}");
|
||||
}
|
||||
|
||||
fn assert_optional_bool(&mut self, arg: &str) {
|
||||
if !self.cx.config.debug {
|
||||
return;
|
||||
}
|
||||
self.cx.expose_is_like_none();
|
||||
self.js.prelude(&format!("if (!isLikeNone({})) {{", arg));
|
||||
self.assert_bool(arg);
|
||||
self.js.prelude("}");
|
||||
}
|
||||
|
||||
fn assert_not_moved(&mut self, arg: &str) {
|
||||
if !self.cx.config.debug {
|
||||
return;
|
||||
}
|
||||
self.js.prelude(&format!(
|
||||
"\
|
||||
if ({0}.ptr === 0) {{
|
||||
throw new Error('Attempt to use a moved value');
|
||||
}}
|
||||
",
|
||||
arg,
|
||||
));
|
||||
}
|
||||
|
||||
fn finally_free_slice(
|
||||
&mut self,
|
||||
expr: &str,
|
||||
i: usize,
|
||||
kind: VectorKind,
|
||||
mutable: bool,
|
||||
) -> Result<(), Error> {
|
||||
// If the slice was mutable it's currently a feature that we
|
||||
// mirror back updates to the original slice. This... is
|
||||
// arguably a misfeature of wasm-bindgen...
|
||||
if mutable {
|
||||
let get = self.cx.memview_function(kind);
|
||||
self.js.finally(&format!(
|
||||
"\
|
||||
{arg}.set({get}().subarray(\
|
||||
ptr{i} / {size}, \
|
||||
ptr{i} / {size} + len{i}\
|
||||
));\
|
||||
",
|
||||
i = i,
|
||||
arg = expr,
|
||||
get = get,
|
||||
size = kind.size()
|
||||
));
|
||||
}
|
||||
self.js.finally(&format!(
|
||||
"wasm.__wbindgen_free(ptr{i}, len{i} * {size});",
|
||||
i = i,
|
||||
size = kind.size(),
|
||||
));
|
||||
self.cx.require_internal_export("__wbindgen_free")
|
||||
}
|
||||
}
|
@ -1,880 +0,0 @@
|
||||
use crate::descriptor::{Descriptor, Function};
|
||||
use crate::js::Context;
|
||||
use failure::{bail, Error};
|
||||
|
||||
pub struct JsArgument {
|
||||
pub optional: bool,
|
||||
pub name: String,
|
||||
pub type_: String,
|
||||
}
|
||||
|
||||
impl JsArgument {
|
||||
fn required(name: String, type_: String) -> Self {
|
||||
Self {
|
||||
optional: false,
|
||||
name,
|
||||
type_,
|
||||
}
|
||||
}
|
||||
|
||||
fn optional(name: String, type_: String) -> Self {
|
||||
Self {
|
||||
optional: true,
|
||||
name,
|
||||
type_,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper struct for manufacturing a shim in JS used to translate JS types to
|
||||
/// Rust, aka pass from JS back into Rust
|
||||
pub struct Js2Rust<'a, 'b: 'a> {
|
||||
cx: &'a mut Context<'b>,
|
||||
|
||||
/// Arguments passed to the invocation of the wasm function, aka things that
|
||||
/// are only numbers.
|
||||
rust_arguments: Vec<String>,
|
||||
|
||||
/// Arguments and their types to the JS shim.
|
||||
pub js_arguments: Vec<JsArgument>,
|
||||
|
||||
/// Conversions that happen before we invoke the wasm function, such as
|
||||
/// converting a string to a ptr/length pair.
|
||||
prelude: String,
|
||||
|
||||
/// "Destructors" or cleanup that must happen after the wasm function
|
||||
/// finishes. This is scheduled in a `finally` block.
|
||||
finally: String,
|
||||
|
||||
/// Index of the next argument for unique name generation purposes.
|
||||
arg_idx: usize,
|
||||
|
||||
/// Typescript expression representing the type of the return value of this
|
||||
/// function.
|
||||
pub ret_ty: String,
|
||||
|
||||
/// Expression used to generate the return value. The string "RET" in this
|
||||
/// expression is replaced with the actual wasm invocation eventually.
|
||||
ret_expr: String,
|
||||
|
||||
/// Name of the JS shim/function that we're generating, primarily for
|
||||
/// TypeScript right now.
|
||||
js_name: String,
|
||||
|
||||
/// whether or not this generated function body will act like a constructor,
|
||||
/// meaning it doesn't actually return something but rather assigns to
|
||||
/// `this`
|
||||
///
|
||||
/// The string value here is the class that this should be a constructor
|
||||
/// for.
|
||||
constructor: Option<String>,
|
||||
|
||||
/// whether or not we're generating a method
|
||||
method: bool,
|
||||
}
|
||||
|
||||
impl<'a, 'b> Js2Rust<'a, 'b> {
|
||||
pub fn new(js_name: &str, cx: &'a mut Context<'b>) -> Js2Rust<'a, 'b> {
|
||||
Js2Rust {
|
||||
cx,
|
||||
js_name: js_name.to_string(),
|
||||
rust_arguments: Vec::new(),
|
||||
js_arguments: Vec::new(),
|
||||
prelude: String::new(),
|
||||
finally: String::new(),
|
||||
arg_idx: 0,
|
||||
ret_ty: String::new(),
|
||||
ret_expr: String::new(),
|
||||
constructor: None,
|
||||
method: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates all bindings necessary for the signature in `Function`,
|
||||
/// creating necessary argument conversions and return value processing.
|
||||
pub fn process(
|
||||
&mut self,
|
||||
function: &Function,
|
||||
opt_arg_names: &Option<Vec<String>>,
|
||||
) -> Result<&mut Self, Error> {
|
||||
// Chop off the implicit i32 first argument if we're a method since it
|
||||
// was already handled by `method` below.
|
||||
let arguments = if self.method {
|
||||
&function.arguments[1..]
|
||||
} else {
|
||||
&function.arguments[..]
|
||||
};
|
||||
let arg_names = match opt_arg_names {
|
||||
Some(arg_names) => arg_names.iter().map(|s| Some(s.as_str())).collect(),
|
||||
None => vec![None; arguments.len()],
|
||||
};
|
||||
assert_eq!(arg_names.len(), arguments.len());
|
||||
for (arg, arg_name) in arguments.iter().zip(arg_names) {
|
||||
// Process the function argument and assert that the metadata about
|
||||
// the number of arguments on the Rust side required is correct.
|
||||
let before = self.rust_arguments.len();
|
||||
self.argument(arg, arg_name)?;
|
||||
arg.assert_abi_arg_correct(before, self.rust_arguments.len());
|
||||
}
|
||||
|
||||
// Process the return argument, and assert that the metadata returned
|
||||
// about the descriptor is indeed correct.
|
||||
let before = self.rust_arguments.len();
|
||||
self.ret(&function.ret)?;
|
||||
function
|
||||
.ret
|
||||
.assert_abi_return_correct(before, self.rust_arguments.len());
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn constructor(&mut self, class: Option<&str>) -> &mut Self {
|
||||
self.constructor = class.map(|s| s.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/// Flag this shim as a method call into Rust, so the first Rust argument
|
||||
/// passed should be `this.ptr`.
|
||||
pub fn method(&mut self, consumed: bool) -> &mut Self {
|
||||
self.method = true;
|
||||
if self.cx.config.debug {
|
||||
self.prelude(
|
||||
"if (this.ptr === 0) {
|
||||
throw new Error('Attempt to use a moved value');
|
||||
}",
|
||||
);
|
||||
}
|
||||
if consumed {
|
||||
self.prelude(
|
||||
"\
|
||||
const ptr = this.ptr;\n\
|
||||
this.ptr = 0;\n\
|
||||
",
|
||||
);
|
||||
self.rust_arguments.insert(0, "ptr".to_string());
|
||||
} else {
|
||||
self.rust_arguments.insert(0, "this.ptr".to_string());
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Add extra processing to the prelude of this shim.
|
||||
pub fn prelude(&mut self, s: &str) -> &mut Self {
|
||||
for line in s.lines() {
|
||||
self.prelude.push_str(line);
|
||||
self.prelude.push_str("\n");
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Add extra processing to the finally block of this shim.
|
||||
pub fn finally(&mut self, s: &str) -> &mut Self {
|
||||
for line in s.lines() {
|
||||
self.finally.push_str(line);
|
||||
self.finally.push_str("\n");
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Add an Rust argument to be passed manually.
|
||||
pub fn rust_argument(&mut self, s: &str) -> &mut Self {
|
||||
self.rust_arguments.push(s.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
fn abi_arg(&mut self, opt_arg_name: Option<&str>) -> String {
|
||||
let ret = if let Some(x) = opt_arg_name {
|
||||
x.into()
|
||||
} else {
|
||||
format!("arg{}", self.arg_idx)
|
||||
};
|
||||
self.arg_idx += 1;
|
||||
ret
|
||||
}
|
||||
|
||||
fn argument(&mut self, arg: &Descriptor, arg_name: Option<&str>) -> Result<&mut Self, Error> {
|
||||
let i = self.arg_idx;
|
||||
let name = self.abi_arg(arg_name);
|
||||
|
||||
let (arg, optional) = match arg {
|
||||
Descriptor::Option(t) => (&**t, true),
|
||||
_ => (arg, false),
|
||||
};
|
||||
|
||||
if let Some(kind) = arg.vector_kind() {
|
||||
self.js_arguments
|
||||
.push(JsArgument::required(name.clone(), kind.js_ty().to_string()));
|
||||
|
||||
let func = self.cx.pass_to_wasm_function(kind)?;
|
||||
let val = if optional {
|
||||
self.cx.expose_is_like_none();
|
||||
format!("isLikeNone({}) ? [0, 0] : {}({})", name, func, name)
|
||||
} else {
|
||||
format!("{}({})", func, name)
|
||||
};
|
||||
self.prelude(&format!(
|
||||
"const ptr{i} = {val};\nconst len{i} = WASM_VECTOR_LEN;",
|
||||
i = i,
|
||||
val = val,
|
||||
));
|
||||
if arg.is_by_ref() {
|
||||
if optional {
|
||||
bail!("optional slices aren't currently supported");
|
||||
}
|
||||
if arg.is_mut_ref() {
|
||||
let get = self.cx.memview_function(kind);
|
||||
self.finally(&format!(
|
||||
"\
|
||||
{arg}.set({get}().subarray(\
|
||||
ptr{i} / {size}, \
|
||||
ptr{i} / {size} + len{i}\
|
||||
));\n\
|
||||
",
|
||||
i = i,
|
||||
arg = name,
|
||||
get = get,
|
||||
size = kind.size()
|
||||
));
|
||||
}
|
||||
self.finally(&format!(
|
||||
"\
|
||||
wasm.__wbindgen_free(ptr{i}, len{i} * {size});\n\
|
||||
",
|
||||
i = i,
|
||||
size = kind.size()
|
||||
));
|
||||
self.cx.require_internal_export("__wbindgen_free")?;
|
||||
}
|
||||
self.rust_arguments.push(format!("ptr{}", i));
|
||||
self.rust_arguments.push(format!("len{}", i));
|
||||
return Ok(self);
|
||||
}
|
||||
|
||||
if arg.is_anyref() {
|
||||
self.js_arguments
|
||||
.push(JsArgument::required(name.clone(), "any".to_string()));
|
||||
if self.cx.config.anyref {
|
||||
if optional {
|
||||
self.cx.expose_add_to_anyref_table()?;
|
||||
self.cx.expose_is_like_none();
|
||||
self.rust_arguments
|
||||
.push(format!("isLikeNone({0}) ? 0 : addToAnyrefTable({0})", name));
|
||||
} else {
|
||||
self.rust_arguments.push(name);
|
||||
}
|
||||
} else {
|
||||
self.cx.expose_add_heap_object();
|
||||
if optional {
|
||||
self.cx.expose_is_like_none();
|
||||
self.rust_arguments
|
||||
.push(format!("isLikeNone({0}) ? 0 : addHeapObject({0})", name));
|
||||
} else {
|
||||
self.rust_arguments.push(format!("addHeapObject({})", name));
|
||||
}
|
||||
}
|
||||
return Ok(self);
|
||||
}
|
||||
|
||||
if optional {
|
||||
self.cx.expose_is_like_none();
|
||||
|
||||
if arg.is_wasm_native() {
|
||||
self.js_arguments
|
||||
.push(JsArgument::optional(name.clone(), "number".to_string()));
|
||||
|
||||
if self.cx.config.debug {
|
||||
self.cx.expose_assert_num();
|
||||
self.prelude(&format!(
|
||||
"
|
||||
if (!isLikeNone({0})) {{
|
||||
_assertNum({0});
|
||||
}}
|
||||
",
|
||||
name
|
||||
));
|
||||
}
|
||||
|
||||
self.rust_arguments.push(format!("!isLikeNone({0})", name));
|
||||
self.rust_arguments
|
||||
.push(format!("isLikeNone({0}) ? 0 : {0}", name));
|
||||
return Ok(self);
|
||||
}
|
||||
|
||||
if arg.is_abi_as_u32() {
|
||||
self.js_arguments
|
||||
.push(JsArgument::optional(name.clone(), "number".to_string()));
|
||||
|
||||
if self.cx.config.debug {
|
||||
self.cx.expose_assert_num();
|
||||
self.prelude(&format!(
|
||||
"
|
||||
if (!isLikeNone({0})) {{
|
||||
_assertNum({0});
|
||||
}}
|
||||
",
|
||||
name
|
||||
));
|
||||
}
|
||||
|
||||
self.rust_arguments
|
||||
.push(format!("isLikeNone({0}) ? 0xFFFFFF : {0}", name));
|
||||
return Ok(self);
|
||||
}
|
||||
|
||||
if let Some(signed) = arg.get_64() {
|
||||
let f = if signed {
|
||||
self.cx.expose_int64_cvt_shim()
|
||||
} else {
|
||||
self.cx.expose_uint64_cvt_shim()
|
||||
};
|
||||
self.cx.expose_uint32_memory();
|
||||
self.js_arguments
|
||||
.push(JsArgument::optional(name.clone(), "BigInt".to_string()));
|
||||
self.prelude(&format!(
|
||||
"
|
||||
{f}[0] = isLikeNone({name}) ? BigInt(0) : {name};
|
||||
const low{i} = isLikeNone({name}) ? 0 : u32CvtShim[0];
|
||||
const high{i} = isLikeNone({name}) ? 0 : u32CvtShim[1];
|
||||
",
|
||||
i = i,
|
||||
f = f,
|
||||
name = name,
|
||||
));
|
||||
self.rust_arguments.push(format!("!isLikeNone({})", name));
|
||||
self.rust_arguments.push(format!("0"));
|
||||
self.rust_arguments.push(format!("low{}", i));
|
||||
self.rust_arguments.push(format!("high{}", i));
|
||||
return Ok(self);
|
||||
}
|
||||
|
||||
match *arg {
|
||||
Descriptor::Boolean => {
|
||||
self.js_arguments
|
||||
.push(JsArgument::optional(name.clone(), "boolean".to_string()));
|
||||
if self.cx.config.debug {
|
||||
self.cx.expose_assert_bool();
|
||||
self.prelude(&format!(
|
||||
"
|
||||
if (!isLikeNone({0})) {{
|
||||
_assertBoolean({0});
|
||||
}}
|
||||
",
|
||||
name,
|
||||
));
|
||||
}
|
||||
self.rust_arguments
|
||||
.push(format!("isLikeNone({0}) ? 0xFFFFFF : {0} ? 1 : 0", name));
|
||||
}
|
||||
Descriptor::Char => {
|
||||
self.js_arguments
|
||||
.push(JsArgument::optional(name.clone(), "string".to_string()));
|
||||
self.rust_arguments.push(format!(
|
||||
"isLikeNone({0}) ? 0xFFFFFF : {0}.codePointAt(0)",
|
||||
name
|
||||
));
|
||||
}
|
||||
Descriptor::Enum { hole } => {
|
||||
self.js_arguments
|
||||
.push(JsArgument::optional(name.clone(), "number".to_string()));
|
||||
self.rust_arguments
|
||||
.push(format!("isLikeNone({0}) ? {1} : {0}", name, hole));
|
||||
}
|
||||
Descriptor::RustStruct(ref s) => {
|
||||
self.js_arguments
|
||||
.push(JsArgument::optional(name.clone(), s.to_string()));
|
||||
self.prelude(&format!("let ptr{} = 0;", i));
|
||||
self.prelude(&format!("if (!isLikeNone({0})) {{", name));
|
||||
self.assert_class(&name, s);
|
||||
self.assert_not_moved(&name);
|
||||
self.prelude(&format!("ptr{} = {}.ptr;", i, name));
|
||||
self.prelude(&format!("{}.ptr = 0;", name));
|
||||
self.prelude("}");
|
||||
self.rust_arguments.push(format!("ptr{}", i));
|
||||
}
|
||||
_ => bail!(
|
||||
"unsupported optional argument type for calling Rust function from JS: {:?}",
|
||||
arg
|
||||
),
|
||||
}
|
||||
|
||||
return Ok(self);
|
||||
}
|
||||
|
||||
if let Some(s) = arg.rust_struct() {
|
||||
self.js_arguments
|
||||
.push(JsArgument::required(name.clone(), s.to_string()));
|
||||
self.assert_class(&name, s);
|
||||
self.assert_not_moved(&name);
|
||||
if arg.is_by_ref() {
|
||||
self.rust_arguments.push(format!("{}.ptr", name));
|
||||
} else {
|
||||
self.prelude(&format!("const ptr{} = {}.ptr;", i, name));
|
||||
self.prelude(&format!("{}.ptr = 0;", name));
|
||||
self.rust_arguments.push(format!("ptr{}", i));
|
||||
}
|
||||
return Ok(self);
|
||||
}
|
||||
|
||||
if arg.number().is_some() {
|
||||
self.js_arguments
|
||||
.push(JsArgument::required(name.clone(), "number".to_string()));
|
||||
|
||||
if self.cx.config.debug {
|
||||
self.cx.expose_assert_num();
|
||||
self.prelude(&format!("_assertNum({});", name));
|
||||
}
|
||||
|
||||
self.rust_arguments.push(name);
|
||||
return Ok(self);
|
||||
}
|
||||
|
||||
if let Some(signed) = arg.get_64() {
|
||||
let f = if signed {
|
||||
self.cx.expose_int64_cvt_shim()
|
||||
} else {
|
||||
self.cx.expose_uint64_cvt_shim()
|
||||
};
|
||||
self.cx.expose_uint32_memory();
|
||||
self.js_arguments
|
||||
.push(JsArgument::required(name.clone(), "BigInt".to_string()));
|
||||
self.prelude(&format!(
|
||||
"
|
||||
{f}[0] = {name};
|
||||
const low{i} = u32CvtShim[0];
|
||||
const high{i} = u32CvtShim[1];
|
||||
",
|
||||
i = i,
|
||||
f = f,
|
||||
name = name,
|
||||
));
|
||||
self.rust_arguments.push(format!("low{}", i));
|
||||
self.rust_arguments.push(format!("high{}", i));
|
||||
return Ok(self);
|
||||
}
|
||||
|
||||
if arg.is_ref_anyref() {
|
||||
self.js_arguments
|
||||
.push(JsArgument::required(name.clone(), "any".to_string()));
|
||||
if self.cx.config.anyref {
|
||||
self.rust_arguments.push(name);
|
||||
} else {
|
||||
// the "stack-ful" nature means that we're always popping from the
|
||||
// stack, and make sure that we actually clear our reference to
|
||||
// allow stale values to get GC'd
|
||||
self.cx.expose_borrowed_objects();
|
||||
self.cx.expose_global_stack_pointer();
|
||||
self.finally("heap[stack_pointer++] = undefined;");
|
||||
self.rust_arguments
|
||||
.push(format!("addBorrowedObject({})", name));
|
||||
}
|
||||
return Ok(self);
|
||||
}
|
||||
|
||||
match *arg {
|
||||
Descriptor::Boolean => {
|
||||
self.js_arguments
|
||||
.push(JsArgument::required(name.clone(), "boolean".to_string()));
|
||||
if self.cx.config.debug {
|
||||
self.cx.expose_assert_bool();
|
||||
self.prelude(&format!(
|
||||
"\
|
||||
_assertBoolean({name});\n\
|
||||
",
|
||||
name = name
|
||||
));
|
||||
}
|
||||
self.rust_arguments.push(format!("{}", name));
|
||||
}
|
||||
Descriptor::Char => {
|
||||
self.js_arguments
|
||||
.push(JsArgument::required(name.clone(), "string".to_string()));
|
||||
self.rust_arguments.push(format!("{}.codePointAt(0)", name))
|
||||
}
|
||||
_ => bail!(
|
||||
"unsupported argument type for calling Rust function from JS: {:?}",
|
||||
arg
|
||||
),
|
||||
}
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
fn ret(&mut self, ty: &Descriptor) -> Result<&mut Self, Error> {
|
||||
if let Some(name) = ty.rust_struct() {
|
||||
match &self.constructor {
|
||||
Some(class) if class == name => {
|
||||
self.ret_expr = format!("this.ptr = RET;");
|
||||
if self.cx.config.weak_refs {
|
||||
self.ret_expr.push_str(&format!(
|
||||
"\
|
||||
{}FinalizationGroup.register(this, this.ptr, this.ptr);
|
||||
",
|
||||
name
|
||||
));
|
||||
}
|
||||
}
|
||||
Some(class) => bail!("constructor for `{}` cannot return `{}`", class, name),
|
||||
None => {
|
||||
self.ret_ty = name.to_string();
|
||||
self.cx.require_class_wrap(name);
|
||||
self.ret_expr = format!("return {name}.__wrap(RET);", name = name);
|
||||
}
|
||||
}
|
||||
return Ok(self);
|
||||
}
|
||||
|
||||
if self.constructor.is_some() {
|
||||
bail!("constructor functions must return a Rust structure")
|
||||
}
|
||||
|
||||
if let Descriptor::Unit = ty {
|
||||
self.ret_ty = "void".to_string();
|
||||
self.ret_expr = format!("return RET;");
|
||||
return Ok(self);
|
||||
}
|
||||
|
||||
let (ty, optional) = match ty {
|
||||
Descriptor::Option(t) => (&**t, true),
|
||||
_ => (ty, false),
|
||||
};
|
||||
|
||||
if let Some(ty) = ty.vector_kind() {
|
||||
self.ret_ty = ty.js_ty().to_string();
|
||||
let f = self.cx.expose_get_vector_from_wasm(ty)?;
|
||||
self.cx.expose_global_argument_ptr()?;
|
||||
self.cx.expose_uint32_memory();
|
||||
self.cx.require_internal_export("__wbindgen_free")?;
|
||||
self.prelude("const retptr = globalArgumentPtr();");
|
||||
self.rust_arguments.insert(0, "retptr".to_string());
|
||||
self.ret_expr = format!(
|
||||
"\
|
||||
RET;\n\
|
||||
const mem = getUint32Memory();\n\
|
||||
const rustptr = mem[retptr / 4];\n\
|
||||
const rustlen = mem[retptr / 4 + 1];\n\
|
||||
{guard}
|
||||
const realRet = {}(rustptr, rustlen).slice();\n\
|
||||
wasm.__wbindgen_free(rustptr, rustlen * {});\n\
|
||||
return realRet;\n\
|
||||
",
|
||||
f,
|
||||
ty.size(),
|
||||
guard = if optional {
|
||||
"if (rustptr === 0) return;"
|
||||
} else {
|
||||
""
|
||||
},
|
||||
);
|
||||
return Ok(self);
|
||||
}
|
||||
|
||||
// No need to worry about `optional` here, the abi representation means
|
||||
// that `takeObject` will naturally pluck out `undefined`.
|
||||
if ty.is_anyref() {
|
||||
self.ret_ty = "any".to_string();
|
||||
self.ret_expr = format!("return {};", self.cx.take_object("RET"));
|
||||
return Ok(self);
|
||||
}
|
||||
|
||||
if optional {
|
||||
if ty.is_wasm_native() {
|
||||
self.ret_ty = "number | undefined".to_string();
|
||||
self.cx.expose_global_argument_ptr()?;
|
||||
self.cx.expose_uint32_memory();
|
||||
match ty {
|
||||
Descriptor::I32 => self.cx.expose_int32_memory(),
|
||||
Descriptor::U32 => (),
|
||||
Descriptor::F32 => self.cx.expose_f32_memory(),
|
||||
Descriptor::F64 => self.cx.expose_f64_memory(),
|
||||
_ => (),
|
||||
};
|
||||
self.prelude("const retptr = globalArgumentPtr();");
|
||||
self.rust_arguments.insert(0, "retptr".to_string());
|
||||
self.ret_expr = format!(
|
||||
"
|
||||
RET;
|
||||
const present = getUint32Memory()[retptr / 4];
|
||||
const value = {mem}[retptr / {size} + 1];
|
||||
return present === 0 ? undefined : value;
|
||||
",
|
||||
size = match ty {
|
||||
Descriptor::I32 => 4,
|
||||
Descriptor::U32 => 4,
|
||||
Descriptor::F32 => 4,
|
||||
Descriptor::F64 => 8,
|
||||
_ => unreachable!(),
|
||||
},
|
||||
mem = match ty {
|
||||
Descriptor::I32 => "getInt32Memory()",
|
||||
Descriptor::U32 => "getUint32Memory()",
|
||||
Descriptor::F32 => "getFloat32Memory()",
|
||||
Descriptor::F64 => "getFloat64Memory()",
|
||||
_ => unreachable!(),
|
||||
}
|
||||
);
|
||||
return Ok(self);
|
||||
}
|
||||
|
||||
if ty.is_abi_as_u32() {
|
||||
self.ret_ty = "number | undefined".to_string();
|
||||
self.ret_expr = "
|
||||
const ret = RET;
|
||||
return ret === 0xFFFFFF ? undefined : ret;
|
||||
"
|
||||
.to_string();
|
||||
return Ok(self);
|
||||
}
|
||||
|
||||
if let Some(signed) = ty.get_64() {
|
||||
self.ret_ty = "BigInt | undefined".to_string();
|
||||
self.cx.expose_global_argument_ptr()?;
|
||||
let f = if signed {
|
||||
self.cx.expose_int64_memory();
|
||||
"getInt64Memory"
|
||||
} else {
|
||||
self.cx.expose_uint64_memory();
|
||||
"getUint64Memory"
|
||||
};
|
||||
self.prelude("const retptr = globalArgumentPtr();");
|
||||
self.rust_arguments.insert(0, "retptr".to_string());
|
||||
self.ret_expr = format!(
|
||||
"
|
||||
RET;
|
||||
const present = getUint32Memory()[retptr / 4];
|
||||
const value = {}()[retptr / 8 + 1];
|
||||
return present === 0 ? undefined : value;
|
||||
",
|
||||
f
|
||||
);
|
||||
return Ok(self);
|
||||
}
|
||||
|
||||
match *ty {
|
||||
Descriptor::Boolean => {
|
||||
self.ret_ty = "boolean | undefined".to_string();
|
||||
self.ret_expr = "
|
||||
const ret = RET;
|
||||
return ret === 0xFFFFFF ? undefined : ret !== 0;
|
||||
"
|
||||
.to_string();
|
||||
return Ok(self);
|
||||
}
|
||||
Descriptor::Char => {
|
||||
self.ret_ty = "string | undefined".to_string();
|
||||
self.ret_expr = "
|
||||
const ret = RET;
|
||||
return ret === 0xFFFFFF ? undefined : String.fromCodePoint(ret);
|
||||
"
|
||||
.to_string();
|
||||
return Ok(self);
|
||||
}
|
||||
Descriptor::Enum { hole } => {
|
||||
self.ret_ty = "number | undefined".to_string();
|
||||
self.ret_expr = format!(
|
||||
"
|
||||
const ret = RET;
|
||||
return ret === {} ? undefined : ret;
|
||||
",
|
||||
hole
|
||||
);
|
||||
return Ok(self);
|
||||
}
|
||||
Descriptor::RustStruct(ref name) => {
|
||||
self.ret_ty = format!("{} | undefined", name);
|
||||
self.cx.require_class_wrap(name);
|
||||
self.ret_expr = format!(
|
||||
"
|
||||
const ptr = RET;
|
||||
return ptr === 0 ? undefined : {}.__wrap(ptr);
|
||||
",
|
||||
name,
|
||||
);
|
||||
return Ok(self);
|
||||
}
|
||||
_ => bail!(
|
||||
"unsupported optional return type for calling Rust function from JS: {:?}",
|
||||
ty
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
if ty.is_ref_anyref() {
|
||||
self.ret_ty = "any".to_string();
|
||||
self.cx.expose_get_object();
|
||||
self.ret_expr = format!("return getObject(RET);");
|
||||
return Ok(self);
|
||||
}
|
||||
|
||||
if ty.is_by_ref() {
|
||||
bail!("cannot return references from Rust to JS yet")
|
||||
}
|
||||
|
||||
if let Some(name) = ty.rust_struct() {
|
||||
self.ret_ty = name.to_string();
|
||||
self.cx.require_class_wrap(name);
|
||||
self.ret_expr = format!("return {name}.__wrap(RET);", name = name);
|
||||
return Ok(self);
|
||||
}
|
||||
|
||||
if let Some(num) = ty.number() {
|
||||
self.ret_ty = "number".to_string();
|
||||
if num.is_u32() {
|
||||
self.ret_expr = format!("return RET >>> 0;");
|
||||
} else {
|
||||
self.ret_expr = format!("return RET;");
|
||||
}
|
||||
return Ok(self);
|
||||
}
|
||||
|
||||
if let Some(signed) = ty.get_64() {
|
||||
self.ret_ty = "BigInt".to_string();
|
||||
self.cx.expose_global_argument_ptr()?;
|
||||
let f = if signed {
|
||||
self.cx.expose_int64_memory();
|
||||
"getInt64Memory"
|
||||
} else {
|
||||
self.cx.expose_uint64_memory();
|
||||
"getUint64Memory"
|
||||
};
|
||||
self.prelude("const retptr = globalArgumentPtr();");
|
||||
self.rust_arguments.insert(0, "retptr".to_string());
|
||||
self.ret_expr = format!(
|
||||
"\
|
||||
RET;\n\
|
||||
return {}()[retptr / 8];\n\
|
||||
",
|
||||
f
|
||||
);
|
||||
return Ok(self);
|
||||
}
|
||||
|
||||
match *ty {
|
||||
Descriptor::Boolean => {
|
||||
self.ret_ty = "boolean".to_string();
|
||||
self.ret_expr = format!("return (RET) !== 0;");
|
||||
}
|
||||
Descriptor::Char => {
|
||||
self.ret_ty = "string".to_string();
|
||||
self.ret_expr = format!("return String.fromCodePoint(RET);")
|
||||
}
|
||||
_ => bail!(
|
||||
"unsupported return type for calling Rust function from JS: {:?}",
|
||||
ty
|
||||
),
|
||||
}
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn js_doc_comments(&self) -> String {
|
||||
let mut ret: String = self
|
||||
.js_arguments
|
||||
.iter()
|
||||
.map(|a| {
|
||||
if a.optional {
|
||||
format!("@param {{{} | undefined}} {}\n", a.type_, a.name)
|
||||
} else {
|
||||
format!("@param {{{}}} {}\n", a.type_, a.name)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
ret.push_str(&format!("@returns {{{}}}", self.ret_ty));
|
||||
ret
|
||||
}
|
||||
|
||||
/// Generate the actual function.
|
||||
///
|
||||
/// The `prefix` specified is typically the string "function" but may be
|
||||
/// different for classes. The `invoc` is the function expression that we're
|
||||
/// invoking, like `wasm.bar` or `this.f`.
|
||||
///
|
||||
/// Returns two strings, the first of which is the JS expression for the
|
||||
/// generated function shim and the second is a TypeScript signature of the
|
||||
/// JS expression.
|
||||
pub fn finish(&mut self, prefix: &str, invoc: &str) -> (String, String, String) {
|
||||
let js_args = self
|
||||
.js_arguments
|
||||
.iter()
|
||||
.map(|s| &s.name[..])
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
let mut js = format!("{}({}) {{\n", prefix, js_args);
|
||||
js.push_str(&self.prelude);
|
||||
let rust_args = self.rust_arguments.join(", ");
|
||||
|
||||
let invoc = self
|
||||
.ret_expr
|
||||
.replace("RET", &format!("{}({})", invoc, rust_args));
|
||||
let invoc = if self.finally.len() == 0 {
|
||||
invoc
|
||||
} else {
|
||||
format!(
|
||||
"\
|
||||
try {{\n\
|
||||
{}
|
||||
\n}} finally {{\n\
|
||||
{}
|
||||
}}\n\
|
||||
",
|
||||
&invoc, &self.finally,
|
||||
)
|
||||
};
|
||||
js.push_str(&invoc);
|
||||
js.push_str("\n}");
|
||||
|
||||
// Determine TS parameter list
|
||||
let mut omittable = true;
|
||||
let mut ts_args = Vec::with_capacity(self.js_arguments.len());
|
||||
for arg in self.js_arguments.iter().rev() {
|
||||
// In TypeScript, we can mark optional parameters as omittable
|
||||
// using the `?` suffix, but only if they're not followed by
|
||||
// non-omittable parameters. Therefore iterate the parameter list
|
||||
// in reverse and stop using the `?` suffix for optional params as
|
||||
// soon as a non-optional parameter is encountered.
|
||||
if arg.optional {
|
||||
if omittable {
|
||||
ts_args.push(format!("{}?: {}", arg.name, arg.type_));
|
||||
} else {
|
||||
ts_args.push(format!("{}: {} | undefined", arg.name, arg.type_));
|
||||
}
|
||||
} else {
|
||||
omittable = false;
|
||||
ts_args.push(format!("{}: {}", arg.name, arg.type_));
|
||||
}
|
||||
}
|
||||
ts_args.reverse();
|
||||
let ts_args = ts_args.join(", ");
|
||||
|
||||
let mut ts = if prefix.is_empty() {
|
||||
format!("{}({})", self.js_name, ts_args)
|
||||
} else {
|
||||
format!("{} {}({})", prefix, self.js_name, ts_args)
|
||||
};
|
||||
if self.constructor.is_none() {
|
||||
ts.push_str(": ");
|
||||
ts.push_str(&self.ret_ty);
|
||||
}
|
||||
ts.push(';');
|
||||
|
||||
(js, ts, self.js_doc_comments())
|
||||
}
|
||||
|
||||
fn assert_class(&mut self, arg: &str, class: &str) {
|
||||
if !self.cx.config.debug {
|
||||
return;
|
||||
}
|
||||
self.cx.expose_assert_class();
|
||||
self.prelude(&format!("_assertClass({}, {});", arg, class));
|
||||
}
|
||||
|
||||
fn assert_not_moved(&mut self, arg: &str) {
|
||||
if !self.cx.config.debug {
|
||||
return;
|
||||
}
|
||||
self.prelude(&format!(
|
||||
"\
|
||||
if ({0}.ptr === 0) {{
|
||||
throw new Error('Attempt to use a moved value');
|
||||
}}
|
||||
",
|
||||
arg,
|
||||
));
|
||||
}
|
||||
}
|
@ -1,17 +1,19 @@
|
||||
mod js2rust;
|
||||
mod rust2js;
|
||||
|
||||
use crate::descriptor::VectorKind;
|
||||
use crate::js::js2rust::Js2Rust;
|
||||
use crate::js::rust2js::Rust2Js;
|
||||
use crate::intrinsic::Intrinsic;
|
||||
use crate::webidl::{AuxEnum, AuxExport, AuxExportKind, AuxImport, AuxStruct};
|
||||
use crate::webidl::{JsImport, JsImportName, WasmBindgenAux, WebidlCustomSection};
|
||||
use crate::webidl::{AuxValue, Binding};
|
||||
use crate::webidl::{JsImport, JsImportName, NonstandardWebidlSection, WasmBindgenAux};
|
||||
use crate::{Bindgen, EncodeInto, OutputMode};
|
||||
use failure::{bail, Error, ResultExt};
|
||||
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use walrus::{ExportId, ImportId, MemoryId, Module};
|
||||
use wasm_webidl_bindings::ast;
|
||||
|
||||
mod binding;
|
||||
mod incoming;
|
||||
mod outgoing;
|
||||
|
||||
pub struct Context<'a> {
|
||||
globals: String,
|
||||
@ -21,7 +23,6 @@ pub struct Context<'a> {
|
||||
required_internal_exports: HashSet<&'static str>,
|
||||
config: &'a Bindgen,
|
||||
pub module: &'a mut Module,
|
||||
bindings: WebidlCustomSection,
|
||||
|
||||
/// A map representing the `import` statements we'll be generating in the JS
|
||||
/// glue. The key is the module we're importing from and the value is the
|
||||
@ -89,10 +90,6 @@ impl<'a> Context<'a> {
|
||||
wasm_import_definitions: Default::default(),
|
||||
exported_classes: Some(Default::default()),
|
||||
config,
|
||||
bindings: *module
|
||||
.customs
|
||||
.delete_typed::<WebidlCustomSection>()
|
||||
.unwrap(),
|
||||
module,
|
||||
memory,
|
||||
npm_dependencies: Default::default(),
|
||||
@ -660,19 +657,6 @@ impl<'a> Context<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn expose_does_not_exist(&mut self) {
|
||||
if !self.should_write_global("does_not_exist") {
|
||||
return;
|
||||
}
|
||||
self.global(
|
||||
"
|
||||
function doesNotExist() {
|
||||
throw new Error('imported function or type does not exist');
|
||||
}
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
fn expose_drop_ref(&mut self) {
|
||||
if !self.should_write_global("drop_ref") {
|
||||
return;
|
||||
@ -1444,6 +1428,31 @@ impl<'a> Context<'a> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn expose_log_error(&mut self) {
|
||||
if !self.should_write_global("log_error") {
|
||||
return;
|
||||
}
|
||||
self.global(
|
||||
"\
|
||||
function logError(e) {
|
||||
let error = (function () {
|
||||
try {
|
||||
return e instanceof Error \
|
||||
? `${e.message}\\n\\nStack:\\n${e.stack}` \
|
||||
: e.toString();
|
||||
} catch(_) {
|
||||
return \"<failed to stringify thrown value>\";
|
||||
}
|
||||
}());
|
||||
console.error(\"wasm-bindgen: imported JS function that \
|
||||
was not marked as `catch` threw an error:\", \
|
||||
error);
|
||||
throw e;
|
||||
}
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
fn pass_to_wasm_function(&mut self, t: VectorKind) -> Result<&'static str, Error> {
|
||||
let s = match t {
|
||||
VectorKind::String => {
|
||||
@ -1579,7 +1588,7 @@ impl<'a> Context<'a> {
|
||||
if (desc) return desc;
|
||||
obj = Object.getPrototypeOf(obj);
|
||||
}
|
||||
return {}
|
||||
return {};
|
||||
}
|
||||
",
|
||||
);
|
||||
@ -1801,40 +1810,32 @@ impl<'a> Context<'a> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn take_object(&mut self, expr: &str) -> String {
|
||||
if self.config.anyref {
|
||||
expr.to_string()
|
||||
} else {
|
||||
self.expose_take_object();
|
||||
format!("takeObject({})", expr)
|
||||
pub fn generate(
|
||||
&mut self,
|
||||
aux: &WasmBindgenAux,
|
||||
bindings: &NonstandardWebidlSection,
|
||||
) -> Result<(), Error> {
|
||||
for (i, (idx, binding)) in bindings.elems.iter().enumerate() {
|
||||
self.generate_elem_binding(i, *idx, binding, bindings)?;
|
||||
}
|
||||
}
|
||||
|
||||
fn get_object(&mut self, expr: &str) -> String {
|
||||
if self.config.anyref {
|
||||
expr.to_string()
|
||||
} else {
|
||||
self.expose_get_object();
|
||||
format!("getObject({})", expr)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate(&mut self, aux: &WasmBindgenAux) -> Result<(), Error> {
|
||||
let mut pairs = aux.export_map.iter().collect::<Vec<_>>();
|
||||
pairs.sort_by_key(|(k, _)| *k);
|
||||
check_duplicated_getter_and_setter_names(&pairs)?;
|
||||
for (id, export) in pairs {
|
||||
self.generate_export(*id, export).with_context(|_| {
|
||||
format!(
|
||||
"failed to generate bindings for Rust export `{}`",
|
||||
export.debug_name,
|
||||
)
|
||||
})?;
|
||||
self.generate_export(*id, export, bindings)
|
||||
.with_context(|_| {
|
||||
format!(
|
||||
"failed to generate bindings for Rust export `{}`",
|
||||
export.debug_name,
|
||||
)
|
||||
})?;
|
||||
}
|
||||
|
||||
for (id, import) in sorted_iter(&aux.import_map) {
|
||||
let variadic = aux.imports_with_variadic.contains(&id);
|
||||
let catch = aux.imports_with_catch.contains(&id);
|
||||
self.generate_import(*id, import, variadic, catch)
|
||||
self.generate_import(*id, import, bindings, variadic, catch)
|
||||
.with_context(|_| {
|
||||
format!("failed to generate bindings for import `{:?}`", import,)
|
||||
})?;
|
||||
@ -1856,75 +1857,111 @@ impl<'a> Context<'a> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn generate_export(&mut self, id: ExportId, export: &AuxExport) -> Result<(), Error> {
|
||||
/// Generates a wrapper function for each bound element of the function
|
||||
/// table. These wrapper functions have the expected WebIDL signature we'd
|
||||
/// like them to have. This currently isn't part of the WebIDL bindings
|
||||
/// proposal, but the thinking is that it'd look something like this if
|
||||
/// added.
|
||||
///
|
||||
/// Note that this is just an internal function shim used by closures and
|
||||
/// such, so we're not actually exporting anything here.
|
||||
fn generate_elem_binding(
|
||||
&mut self,
|
||||
idx: usize,
|
||||
elem_idx: u32,
|
||||
binding: &Binding,
|
||||
bindings: &NonstandardWebidlSection,
|
||||
) -> Result<(), Error> {
|
||||
let webidl = bindings
|
||||
.types
|
||||
.get::<ast::WebidlFunction>(binding.webidl_ty)
|
||||
.unwrap();
|
||||
self.export_function_table()?;
|
||||
let mut builder = binding::Builder::new(self);
|
||||
let js = builder.process(&binding, &webidl, true, &None, &mut |_, _, args| {
|
||||
Ok(format!(
|
||||
"wasm.__wbg_function_table.get({})({})",
|
||||
elem_idx,
|
||||
args.join(", ")
|
||||
))
|
||||
})?;
|
||||
self.globals
|
||||
.push_str(&format!("function __wbg_elem_binding{}{}\n", idx, js));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn generate_export(
|
||||
&mut self,
|
||||
id: ExportId,
|
||||
export: &AuxExport,
|
||||
bindings: &NonstandardWebidlSection,
|
||||
) -> Result<(), Error> {
|
||||
let wasm_name = self.module.exports.get(id).name.clone();
|
||||
let descriptor = self.bindings.exports[&id].clone();
|
||||
let binding = &bindings.exports[&id];
|
||||
let webidl = bindings
|
||||
.types
|
||||
.get::<ast::WebidlFunction>(binding.webidl_ty)
|
||||
.unwrap();
|
||||
|
||||
// Construct a JS shim builder, and configure it based on the kind of
|
||||
// export that we're generating.
|
||||
let mut builder = binding::Builder::new(self);
|
||||
match &export.kind {
|
||||
AuxExportKind::Function(_) => {}
|
||||
AuxExportKind::StaticFunction { .. } => {}
|
||||
AuxExportKind::Constructor(class) => builder.constructor(class),
|
||||
AuxExportKind::Getter { .. } | AuxExportKind::Setter { .. } => builder.method(false),
|
||||
AuxExportKind::Method { consumed, .. } => builder.method(*consumed),
|
||||
}
|
||||
|
||||
// Process the `binding` and generate a bunch of JS/TypeScript/etc.
|
||||
let js = builder.process(
|
||||
&binding,
|
||||
&webidl,
|
||||
true,
|
||||
&export.arg_names,
|
||||
&mut |_, _, args| Ok(format!("wasm.{}({})", wasm_name, args.join(", "))),
|
||||
)?;
|
||||
let ts = builder.typescript_signature();
|
||||
let js_doc = builder.js_doc_comments();
|
||||
let docs = format_doc_comments(&export.comments, Some(js_doc));
|
||||
|
||||
// Once we've got all the JS then put it in the right location dependin
|
||||
// on what's being exported.
|
||||
match &export.kind {
|
||||
AuxExportKind::Function(name) => {
|
||||
let (js, ts, js_doc) = Js2Rust::new(&name, self)
|
||||
.process(&descriptor, &export.arg_names)?
|
||||
.finish("function", &format!("wasm.{}", wasm_name));
|
||||
self.export(
|
||||
&name,
|
||||
&js,
|
||||
Some(format_doc_comments(&export.comments, Some(js_doc))),
|
||||
)?;
|
||||
self.export(&name, &format!("function{}", js), Some(docs))?;
|
||||
self.globals.push_str("\n");
|
||||
self.typescript.push_str("export ");
|
||||
self.typescript.push_str("export function ");
|
||||
self.typescript.push_str(&name);
|
||||
self.typescript.push_str(&ts);
|
||||
self.typescript.push_str("\n");
|
||||
self.typescript.push_str(";\n");
|
||||
}
|
||||
AuxExportKind::Constructor(class) => {
|
||||
let (js, ts, raw_docs) = Js2Rust::new("constructor", self)
|
||||
.constructor(Some(&class))
|
||||
.process(&descriptor, &export.arg_names)?
|
||||
.finish("", &format!("wasm.{}", wasm_name));
|
||||
let exported = require_class(&mut self.exported_classes, class);
|
||||
if exported.has_constructor {
|
||||
bail!("found duplicate constructor for class `{}`", class);
|
||||
}
|
||||
exported.has_constructor = true;
|
||||
let docs = format_doc_comments(&export.comments, Some(raw_docs));
|
||||
exported.push(&docs, "constructor", "", &js, &ts);
|
||||
}
|
||||
AuxExportKind::Getter { class, field: name }
|
||||
| AuxExportKind::Setter { class, field: name }
|
||||
| AuxExportKind::StaticFunction { class, name }
|
||||
| AuxExportKind::Method { class, name, .. } => {
|
||||
let mut j2r = Js2Rust::new(name, self);
|
||||
match export.kind {
|
||||
AuxExportKind::StaticFunction { .. } => {}
|
||||
AuxExportKind::Method { consumed: true, .. } => {
|
||||
j2r.method(true);
|
||||
}
|
||||
_ => {
|
||||
j2r.method(false);
|
||||
}
|
||||
}
|
||||
let (js, ts, raw_docs) = j2r
|
||||
.process(&descriptor, &export.arg_names)?
|
||||
.finish("", &format!("wasm.{}", wasm_name));
|
||||
let docs = format_doc_comments(&export.comments, Some(raw_docs));
|
||||
match export.kind {
|
||||
AuxExportKind::Getter { .. } => {
|
||||
let ret_ty = j2r.ret_ty.clone();
|
||||
let exported = require_class(&mut self.exported_classes, class);
|
||||
exported.push_getter(&docs, name, &js, &ret_ty);
|
||||
}
|
||||
AuxExportKind::Setter { .. } => {
|
||||
let arg_ty = &j2r.js_arguments[0].type_.clone();
|
||||
let exported = require_class(&mut self.exported_classes, class);
|
||||
exported.push_setter(&docs, name, &js, &arg_ty);
|
||||
}
|
||||
AuxExportKind::StaticFunction { .. } => {
|
||||
let exported = require_class(&mut self.exported_classes, class);
|
||||
exported.push(&docs, name, "static ", &js, &ts);
|
||||
}
|
||||
_ => {
|
||||
let exported = require_class(&mut self.exported_classes, class);
|
||||
exported.push(&docs, name, "", &js, &ts);
|
||||
}
|
||||
}
|
||||
AuxExportKind::Getter { class, field } => {
|
||||
let ret_ty = builder.ts_ret.as_ref().unwrap().ty.clone();
|
||||
let exported = require_class(&mut self.exported_classes, class);
|
||||
exported.push_getter(&docs, field, &js, &ret_ty);
|
||||
}
|
||||
AuxExportKind::Setter { class, field } => {
|
||||
let arg_ty = builder.ts_args[0].ty.clone();
|
||||
let exported = require_class(&mut self.exported_classes, class);
|
||||
exported.push_setter(&docs, field, &js, &arg_ty);
|
||||
}
|
||||
AuxExportKind::StaticFunction { class, name } => {
|
||||
let exported = require_class(&mut self.exported_classes, class);
|
||||
exported.push(&docs, name, "static ", &js, &ts);
|
||||
}
|
||||
AuxExportKind::Method { class, name, .. } => {
|
||||
let exported = require_class(&mut self.exported_classes, class);
|
||||
exported.push(&docs, name, "", &js, &ts);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@ -1934,21 +1971,515 @@ impl<'a> Context<'a> {
|
||||
&mut self,
|
||||
id: ImportId,
|
||||
import: &AuxImport,
|
||||
bindings: &NonstandardWebidlSection,
|
||||
variadic: bool,
|
||||
catch: bool,
|
||||
) -> Result<(), Error> {
|
||||
let signature = self.bindings.imports[&id].clone();
|
||||
let catch_and_rethrow = self.config.debug;
|
||||
let js = Rust2Js::new(self)
|
||||
.catch_and_rethrow(catch_and_rethrow)
|
||||
.catch(catch)
|
||||
.variadic(variadic)
|
||||
.process(&signature)?
|
||||
.finish(import)?;
|
||||
let binding = &bindings.imports[&id];
|
||||
let webidl = bindings
|
||||
.types
|
||||
.get::<ast::WebidlFunction>(binding.webidl_ty)
|
||||
.unwrap();
|
||||
let mut builder = binding::Builder::new(self);
|
||||
builder.catch(catch)?;
|
||||
let js = builder.process(&binding, &webidl, false, &None, &mut |cx, prelude, args| {
|
||||
cx.invoke_import(&binding, import, bindings, args, variadic, prelude)
|
||||
})?;
|
||||
let js = format!("function{}", js);
|
||||
self.wasm_import_definitions.insert(id, js);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Generates a JS snippet appropriate for invoking `import`.
|
||||
///
|
||||
/// This is generating code for `binding` where `bindings` has more type
|
||||
/// infomation. The `args` array is the list of JS expressions representing
|
||||
/// the arguments to pass to JS. Finally `variadic` indicates whether the
|
||||
/// last argument is a list to be splatted in a variadic way, and `prelude`
|
||||
/// is a location to push some more initialization JS if necessary.
|
||||
///
|
||||
/// The returned value here is a JS expression which evaluates to the
|
||||
/// purpose of `AuxImport`, which depends on the kind of import.
|
||||
fn invoke_import(
|
||||
&mut self,
|
||||
binding: &Binding,
|
||||
import: &AuxImport,
|
||||
bindings: &NonstandardWebidlSection,
|
||||
args: &[String],
|
||||
variadic: bool,
|
||||
prelude: &mut String,
|
||||
) -> Result<String, Error> {
|
||||
let webidl_ty: &ast::WebidlFunction = bindings.types.get(binding.webidl_ty).unwrap();
|
||||
let variadic_args = |js_arguments: &[String]| {
|
||||
Ok(if !variadic {
|
||||
format!("{}", js_arguments.join(", "))
|
||||
} else {
|
||||
let (last_arg, args) = match js_arguments.split_last() {
|
||||
Some(pair) => pair,
|
||||
None => bail!("a function with no arguments cannot be variadic"),
|
||||
};
|
||||
if args.len() > 0 {
|
||||
format!("{}, ...{}", args.join(", "), last_arg)
|
||||
} else {
|
||||
format!("...{}", last_arg)
|
||||
}
|
||||
})
|
||||
};
|
||||
match import {
|
||||
AuxImport::Value(val) => match webidl_ty.kind {
|
||||
ast::WebidlFunctionKind::Constructor => {
|
||||
let js = match val {
|
||||
AuxValue::Bare(js) => self.import_name(js)?,
|
||||
_ => bail!("invalid import set for constructor"),
|
||||
};
|
||||
Ok(format!("new {}({})", js, variadic_args(&args)?))
|
||||
}
|
||||
ast::WebidlFunctionKind::Method(_) => {
|
||||
let descriptor = |anchor: &str, extra: &str, field: &str, which: &str| {
|
||||
format!(
|
||||
"GetOwnOrInheritedPropertyDescriptor({}{}, '{}').{}",
|
||||
anchor, extra, field, which
|
||||
)
|
||||
};
|
||||
let js = match val {
|
||||
AuxValue::Bare(js) => self.import_name(js)?,
|
||||
AuxValue::Getter(class, field) => {
|
||||
self.expose_get_inherited_descriptor();
|
||||
let class = self.import_name(class)?;
|
||||
descriptor(&class, ".prototype", field, "get")
|
||||
}
|
||||
AuxValue::ClassGetter(class, field) => {
|
||||
self.expose_get_inherited_descriptor();
|
||||
let class = self.import_name(class)?;
|
||||
descriptor(&class, "", field, "get")
|
||||
}
|
||||
AuxValue::Setter(class, field) => {
|
||||
self.expose_get_inherited_descriptor();
|
||||
let class = self.import_name(class)?;
|
||||
descriptor(&class, ".prototype", field, "set")
|
||||
}
|
||||
AuxValue::ClassSetter(class, field) => {
|
||||
self.expose_get_inherited_descriptor();
|
||||
let class = self.import_name(class)?;
|
||||
descriptor(&class, "", field, "set")
|
||||
}
|
||||
};
|
||||
Ok(format!("{}.call({})", js, variadic_args(&args)?))
|
||||
}
|
||||
ast::WebidlFunctionKind::Static => {
|
||||
let js = match val {
|
||||
AuxValue::Bare(js) => self.import_name(js)?,
|
||||
_ => bail!("invalid import set for constructor"),
|
||||
};
|
||||
Ok(format!("{}({})", js, variadic_args(&args)?))
|
||||
}
|
||||
},
|
||||
|
||||
AuxImport::Instanceof(js) => {
|
||||
assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static);
|
||||
assert!(!variadic);
|
||||
assert_eq!(args.len(), 1);
|
||||
let js = self.import_name(js)?;
|
||||
Ok(format!("{} instanceof {}", args[0], js))
|
||||
}
|
||||
|
||||
AuxImport::Static(js) => {
|
||||
assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static);
|
||||
assert!(!variadic);
|
||||
assert_eq!(args.len(), 0);
|
||||
self.import_name(js)
|
||||
}
|
||||
|
||||
AuxImport::Closure {
|
||||
dtor,
|
||||
mutable,
|
||||
binding_idx,
|
||||
nargs,
|
||||
} => {
|
||||
assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static);
|
||||
assert!(!variadic);
|
||||
assert_eq!(args.len(), 3);
|
||||
let arg_names = (0..*nargs)
|
||||
.map(|i| format!("arg{}", i))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
let mut js = format!("({}) => {{\n", arg_names);
|
||||
// First up with a closure we increment the internal reference
|
||||
// count. This ensures that the Rust closure environment won't
|
||||
// be deallocated while we're invoking it.
|
||||
js.push_str("state.cnt++;\n");
|
||||
|
||||
self.export_function_table()?;
|
||||
let dtor = format!("wasm.__wbg_function_table.get({})", dtor);
|
||||
let call = format!("__wbg_elem_binding{}", binding_idx);
|
||||
|
||||
if *mutable {
|
||||
// For mutable closures they can't be invoked recursively.
|
||||
// To handle that we swap out the `this.a` pointer with zero
|
||||
// while we invoke it. If we finish and the closure wasn't
|
||||
// destroyed, then we put back the pointer so a future
|
||||
// invocation can succeed.
|
||||
js.push_str("const a = state.a;\n");
|
||||
js.push_str("state.a = 0;\n");
|
||||
js.push_str("try {\n");
|
||||
js.push_str(&format!("return {}(a, state.b, {});\n", call, arg_names));
|
||||
js.push_str("} finally {\n");
|
||||
js.push_str("if (--state.cnt === 0) ");
|
||||
js.push_str(&dtor);
|
||||
js.push_str("(a, state.b);\n");
|
||||
js.push_str("else state.a = a;\n");
|
||||
js.push_str("}\n");
|
||||
} else {
|
||||
// For shared closures they can be invoked recursively so we
|
||||
// just immediately pass through `this.a`. If we end up
|
||||
// executing the destructor, however, we clear out the
|
||||
// `this.a` pointer to prevent it being used again the
|
||||
// future.
|
||||
js.push_str("try {\n");
|
||||
js.push_str(&format!(
|
||||
"return {}(state.a, state.b, {});\n",
|
||||
call, arg_names
|
||||
));
|
||||
js.push_str("} finally {\n");
|
||||
js.push_str("if (--state.cnt === 0) {\n");
|
||||
js.push_str(&dtor);
|
||||
js.push_str("(state.a, state.b);\n");
|
||||
js.push_str("state.a = 0;\n");
|
||||
js.push_str("}\n");
|
||||
js.push_str("}\n");
|
||||
}
|
||||
js.push_str("}\n");
|
||||
|
||||
prelude.push_str(&format!(
|
||||
"
|
||||
const state = {{ a: {arg0}, b: {arg1}, cnt: 1 }};
|
||||
const real = {body};
|
||||
real.original = state;
|
||||
",
|
||||
body = js,
|
||||
arg0 = &args[0],
|
||||
arg1 = &args[1],
|
||||
));
|
||||
Ok("real".to_string())
|
||||
}
|
||||
|
||||
AuxImport::StructuralMethod(name) => {
|
||||
assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static);
|
||||
let (receiver, args) = match args.split_first() {
|
||||
Some(pair) => pair,
|
||||
None => bail!("structural method calls must have at least one argument"),
|
||||
};
|
||||
Ok(format!("{}.{}({})", receiver, name, variadic_args(args)?))
|
||||
}
|
||||
|
||||
AuxImport::StructuralGetter(field) => {
|
||||
assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static);
|
||||
assert!(!variadic);
|
||||
assert_eq!(args.len(), 1);
|
||||
Ok(format!("{}.{}", args[0], field))
|
||||
}
|
||||
|
||||
AuxImport::StructuralClassGetter(class, field) => {
|
||||
assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static);
|
||||
assert!(!variadic);
|
||||
assert_eq!(args.len(), 0);
|
||||
let class = self.import_name(class)?;
|
||||
Ok(format!("{}.{}", class, field))
|
||||
}
|
||||
|
||||
AuxImport::StructuralSetter(field) => {
|
||||
assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static);
|
||||
assert!(!variadic);
|
||||
assert_eq!(args.len(), 2);
|
||||
Ok(format!("{}.{} = {}", args[0], field, args[1]))
|
||||
}
|
||||
|
||||
AuxImport::StructuralClassSetter(class, field) => {
|
||||
assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static);
|
||||
assert!(!variadic);
|
||||
assert_eq!(args.len(), 1);
|
||||
let class = self.import_name(class)?;
|
||||
Ok(format!("{}.{} = {}", class, field, args[0]))
|
||||
}
|
||||
|
||||
AuxImport::IndexingGetterOfClass(class) => {
|
||||
assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static);
|
||||
assert!(!variadic);
|
||||
assert_eq!(args.len(), 1);
|
||||
let class = self.import_name(class)?;
|
||||
Ok(format!("{}[{}]", class, args[0]))
|
||||
}
|
||||
|
||||
AuxImport::IndexingGetterOfObject => {
|
||||
assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static);
|
||||
assert!(!variadic);
|
||||
assert_eq!(args.len(), 2);
|
||||
Ok(format!("{}[{}]", args[0], args[1]))
|
||||
}
|
||||
|
||||
AuxImport::IndexingSetterOfClass(class) => {
|
||||
assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static);
|
||||
assert!(!variadic);
|
||||
assert_eq!(args.len(), 2);
|
||||
let class = self.import_name(class)?;
|
||||
Ok(format!("{}[{}] = {}", class, args[0], args[1]))
|
||||
}
|
||||
|
||||
AuxImport::IndexingSetterOfObject => {
|
||||
assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static);
|
||||
assert!(!variadic);
|
||||
assert_eq!(args.len(), 3);
|
||||
Ok(format!("{}[{}] = {}", args[0], args[1], args[2]))
|
||||
}
|
||||
|
||||
AuxImport::IndexingDeleterOfClass(class) => {
|
||||
assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static);
|
||||
assert!(!variadic);
|
||||
assert_eq!(args.len(), 1);
|
||||
let class = self.import_name(class)?;
|
||||
Ok(format!("delete {}[{}]", class, args[0]))
|
||||
}
|
||||
|
||||
AuxImport::IndexingDeleterOfObject => {
|
||||
assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static);
|
||||
assert!(!variadic);
|
||||
assert_eq!(args.len(), 2);
|
||||
Ok(format!("delete {}[{}]", args[0], args[1]))
|
||||
}
|
||||
|
||||
AuxImport::WrapInExportedClass(class) => {
|
||||
assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static);
|
||||
assert!(!variadic);
|
||||
assert_eq!(args.len(), 1);
|
||||
self.require_class_wrap(class);
|
||||
Ok(format!("{}.__wrap({})", class, args[0]))
|
||||
}
|
||||
|
||||
AuxImport::Intrinsic(intrinsic) => {
|
||||
assert!(webidl_ty.kind == ast::WebidlFunctionKind::Static);
|
||||
assert!(!variadic);
|
||||
self.invoke_intrinsic(intrinsic, args, prelude)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Same as `invoke_import` above, except more specialized and only used for
|
||||
/// generating the JS expression needed to implement a particular intrinsic.
|
||||
fn invoke_intrinsic(
|
||||
&mut self,
|
||||
intrinsic: &Intrinsic,
|
||||
args: &[String],
|
||||
prelude: &mut String,
|
||||
) -> Result<String, Error> {
|
||||
let expr = match intrinsic {
|
||||
Intrinsic::JsvalEq => {
|
||||
assert_eq!(args.len(), 2);
|
||||
format!("{} === {}", args[0], args[1])
|
||||
}
|
||||
|
||||
Intrinsic::IsFunction => {
|
||||
assert_eq!(args.len(), 1);
|
||||
format!("typeof({}) === 'function'", args[0])
|
||||
}
|
||||
|
||||
Intrinsic::IsUndefined => {
|
||||
assert_eq!(args.len(), 1);
|
||||
format!("{} === undefined", args[0])
|
||||
}
|
||||
|
||||
Intrinsic::IsNull => {
|
||||
assert_eq!(args.len(), 1);
|
||||
format!("{} === null", args[0])
|
||||
}
|
||||
|
||||
Intrinsic::IsObject => {
|
||||
assert_eq!(args.len(), 1);
|
||||
prelude.push_str(&format!("const val = {};\n", args[0]));
|
||||
format!("typeof(val) === 'object' && val !== null")
|
||||
}
|
||||
|
||||
Intrinsic::IsSymbol => {
|
||||
assert_eq!(args.len(), 1);
|
||||
format!("typeof({}) === 'symbol'", args[0])
|
||||
}
|
||||
|
||||
Intrinsic::IsString => {
|
||||
assert_eq!(args.len(), 1);
|
||||
format!("typeof({}) === 'string'", args[0])
|
||||
}
|
||||
|
||||
Intrinsic::ObjectCloneRef => {
|
||||
assert_eq!(args.len(), 1);
|
||||
args[0].clone()
|
||||
}
|
||||
|
||||
Intrinsic::ObjectDropRef => {
|
||||
assert_eq!(args.len(), 1);
|
||||
args[0].clone()
|
||||
}
|
||||
|
||||
Intrinsic::CallbackDrop => {
|
||||
assert_eq!(args.len(), 1);
|
||||
prelude.push_str(&format!("const obj = {}.original;\n", args[0]));
|
||||
prelude.push_str("if (obj.cnt-- == 1) {\n");
|
||||
prelude.push_str("obj.a = 0;\n");
|
||||
prelude.push_str("return true;\n");
|
||||
prelude.push_str("}\n");
|
||||
"false".to_string()
|
||||
}
|
||||
|
||||
Intrinsic::CallbackForget => {
|
||||
assert_eq!(args.len(), 1);
|
||||
args[0].clone()
|
||||
}
|
||||
|
||||
Intrinsic::NumberNew => {
|
||||
assert_eq!(args.len(), 1);
|
||||
args[0].clone()
|
||||
}
|
||||
|
||||
Intrinsic::StringNew => {
|
||||
assert_eq!(args.len(), 1);
|
||||
args[0].clone()
|
||||
}
|
||||
|
||||
Intrinsic::SymbolNamedNew => {
|
||||
assert_eq!(args.len(), 1);
|
||||
format!("Symbol({})", args[0])
|
||||
}
|
||||
|
||||
Intrinsic::SymbolAnonymousNew => {
|
||||
assert_eq!(args.len(), 0);
|
||||
"Symbol()".to_string()
|
||||
}
|
||||
|
||||
Intrinsic::NumberGet => {
|
||||
assert_eq!(args.len(), 2);
|
||||
self.expose_uint8_memory();
|
||||
prelude.push_str(&format!("const obj = {};\n", args[0]));
|
||||
prelude.push_str("if (typeof(obj) === 'number') return obj;\n");
|
||||
prelude.push_str(&format!("getUint8Memory()[{}] = 1;\n", args[1]));
|
||||
"0".to_string()
|
||||
}
|
||||
|
||||
Intrinsic::StringGet => {
|
||||
self.expose_pass_string_to_wasm()?;
|
||||
self.expose_uint32_memory();
|
||||
assert_eq!(args.len(), 2);
|
||||
prelude.push_str(&format!("const obj = {};\n", args[0]));
|
||||
prelude.push_str("if (typeof(obj) !== 'string') return 0;\n");
|
||||
prelude.push_str("const ptr = passStringToWasm(obj);\n");
|
||||
prelude.push_str(&format!(
|
||||
"getUint32Memory()[{} / 4] = WASM_VECTOR_LEN;\n",
|
||||
args[1],
|
||||
));
|
||||
"ptr".to_string()
|
||||
}
|
||||
|
||||
Intrinsic::BooleanGet => {
|
||||
assert_eq!(args.len(), 1);
|
||||
prelude.push_str(&format!("const v = {};\n", args[0]));
|
||||
format!("typeof(v) === 'boolean' ? (v ? 1 : 0) : 2")
|
||||
}
|
||||
|
||||
Intrinsic::Throw => {
|
||||
assert_eq!(args.len(), 1);
|
||||
format!("throw new Error({})", args[0])
|
||||
}
|
||||
|
||||
Intrinsic::Rethrow => {
|
||||
assert_eq!(args.len(), 1);
|
||||
format!("throw {}", args[0])
|
||||
}
|
||||
|
||||
Intrinsic::Module => {
|
||||
assert_eq!(args.len(), 0);
|
||||
if !self.config.mode.no_modules() && !self.config.mode.web() {
|
||||
bail!(
|
||||
"`wasm_bindgen::module` is currently only supported with \
|
||||
`--target no-modules` and `--target web`"
|
||||
);
|
||||
}
|
||||
format!("init.__wbindgen_wasm_module")
|
||||
}
|
||||
|
||||
Intrinsic::Memory => {
|
||||
assert_eq!(args.len(), 0);
|
||||
self.memory().to_string()
|
||||
}
|
||||
|
||||
Intrinsic::FunctionTable => {
|
||||
assert_eq!(args.len(), 0);
|
||||
self.export_function_table()?;
|
||||
format!("wasm.__wbg_function_table")
|
||||
}
|
||||
|
||||
Intrinsic::DebugString => {
|
||||
assert_eq!(args.len(), 1);
|
||||
self.expose_debug_string();
|
||||
format!("debugString({})", args[0])
|
||||
}
|
||||
|
||||
Intrinsic::JsonParse => {
|
||||
assert_eq!(args.len(), 1);
|
||||
format!("JSON.parse({})", args[0])
|
||||
}
|
||||
|
||||
Intrinsic::JsonSerialize => {
|
||||
assert_eq!(args.len(), 1);
|
||||
format!("JSON.stringify({})", args[0])
|
||||
}
|
||||
|
||||
Intrinsic::AnyrefHeapLiveCount => {
|
||||
assert_eq!(args.len(), 0);
|
||||
if self.config.anyref {
|
||||
// Eventually we should add support to the anyref-xform to
|
||||
// re-write calls to the imported
|
||||
// `__wbindgen_anyref_heap_live_count` function into calls to
|
||||
// the exported `__wbindgen_anyref_heap_live_count_impl`
|
||||
// function, and to un-export that function.
|
||||
//
|
||||
// But for now, we just bounce wasm -> js -> wasm because it is
|
||||
// easy.
|
||||
self.require_internal_export("__wbindgen_anyref_heap_live_count_impl")?;
|
||||
"wasm.__wbindgen_anyref_heap_live_count_impl()".into()
|
||||
} else {
|
||||
self.expose_global_heap();
|
||||
prelude.push_str(
|
||||
"
|
||||
let free_count = 0;
|
||||
let next = heap_next;
|
||||
while (next < heap.length) {
|
||||
free_count += 1;
|
||||
next = heap[next];
|
||||
}
|
||||
",
|
||||
);
|
||||
format!(
|
||||
"heap.length - free_count - {} - {}",
|
||||
INITIAL_HEAP_OFFSET,
|
||||
INITIAL_HEAP_VALUES.len(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Intrinsic::InitAnyrefTable => {
|
||||
self.expose_anyref_table();
|
||||
String::from(
|
||||
"
|
||||
const table = wasm.__wbg_anyref_table;
|
||||
const offset = table.grow(4);
|
||||
table.set(offset + 0, undefined);
|
||||
table.set(offset + 1, null);
|
||||
table.set(offset + 2, true);
|
||||
table.set(offset + 3, false);
|
||||
",
|
||||
)
|
||||
}
|
||||
};
|
||||
Ok(expr)
|
||||
}
|
||||
|
||||
fn generate_enum(&mut self, enum_: &AuxEnum) -> Result<(), Error> {
|
||||
let mut variants = String::new();
|
||||
|
||||
@ -2214,8 +2745,9 @@ impl ExportedClass {
|
||||
self.typescript.push_str(docs);
|
||||
self.typescript.push_str(" ");
|
||||
self.typescript.push_str(function_prefix);
|
||||
self.typescript.push_str(function_name);
|
||||
self.typescript.push_str(ts);
|
||||
self.typescript.push_str("\n");
|
||||
self.typescript.push_str(";\n");
|
||||
}
|
||||
|
||||
/// Used for adding a getter to a class, mainly to ensure that TypeScript
|
||||
|
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
Reference in New Issue
Block a user