mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-06-15 22:11:23 +00:00
Rewrite wasm-bindgen with updated interface types proposal (#1882)
This commit is a pretty large scale rewrite of the internals of wasm-bindgen. No user-facing changes are expected as a result of this PR, but due to the scale of changes here it's likely inevitable that at least something will break. I'm hoping to get more testing in though before landing! The purpose of this PR is to update wasm-bindgen to the current state of the interface types proposal. The wasm-bindgen tool was last updated when it was still called "WebIDL bindings" so it's been awhile! All support is now based on https://github.com/bytecodealliance/wasm-interface-types which defines parsers/binary format/writers/etc for wasm-interface types. This is a pretty massive PR and unfortunately can't really be split up any more afaik. I don't really expect realistic review of all the code here (or commits), but some high-level changes are: * Interface types now consists of a set of "adapter functions". The IR in wasm-bindgen is modeled the same way not. * Each adapter function has a list of instructions, and these instructions work at a higher level than wasm itself, for example with strings. * The wasm-bindgen tool has a suite of instructions which are specific to it and not present in the standard. (like before with webidl bindings) * The anyref/multi-value transformations are now greatly simplified. They're simply "optimization passes" over adapter functions, removing instructions that are otherwise present. This way we don't have to juggle so much all over the place, and instructions always have the same meaning.
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@ -1,556 +0,0 @@
|
||||
//! 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 anyhow::{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::MutableSlice { 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)?;
|
||||
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, true)?;
|
||||
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")
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,451 +0,0 @@
|
||||
//! 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 anyhow::{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::CachedString {
|
||||
offset,
|
||||
length,
|
||||
owned,
|
||||
optional,
|
||||
} => {
|
||||
let ptr = self.arg(*offset);
|
||||
let len = self.arg(*length);
|
||||
let tmp = self.js.tmp();
|
||||
|
||||
if *optional {
|
||||
self.js.typescript_optional("string");
|
||||
} else {
|
||||
self.js.typescript_required("string");
|
||||
}
|
||||
|
||||
self.cx.expose_get_cached_string_from_wasm()?;
|
||||
|
||||
self.js.prelude(&format!(
|
||||
"const v{} = getCachedStringFromWasm({}, {});",
|
||||
tmp, ptr, len
|
||||
));
|
||||
|
||||
if *owned {
|
||||
self.prelude_free_cached_string(&ptr, &len)?;
|
||||
}
|
||||
|
||||
Ok(format!("v{}", tmp))
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
fn prelude_free_cached_string(&mut self, ptr: &str, len: &str) -> Result<(), Error> {
|
||||
self.js.prelude(&format!(
|
||||
"if ({ptr} !== 0) {{ wasm.__wbindgen_free({ptr}, {len}); }}",
|
||||
ptr = ptr,
|
||||
len = len,
|
||||
));
|
||||
|
||||
self.cx.require_internal_export("__wbindgen_free")
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user