mirror of
https://github.com/rustwasm/wasm-bindgen.git
synced 2024-11-30 12:33:54 +03:00
Make wasm-bindgen
compatible with the standard C ABI (#3595)
* Make `wasm-bindgen` compatible with the standard C ABI * Update the guide * Fix reference tests For some reason a couple of functions got switched around; no actual meaningful changes. * Add changelog entry * Add spacing * Mention `WasmAbi` in the changelog * fix a tiny typo
This commit is contained in:
parent
2c622715c9
commit
51e89ebee5
11
CHANGELOG.md
11
CHANGELOG.md
@ -79,6 +79,17 @@
|
||||
When exported constructors return `Self`.
|
||||
[#3562](https://github.com/rustwasm/wasm-bindgen/pull/3562)
|
||||
|
||||
* Made `wasm-bindgen` forwards-compatible with the standard C ABI.
|
||||
[#3595](https://github.com/rustwasm/wasm-bindgen/pull/3595)
|
||||
|
||||
* Changed the design of the internal `WasmAbi` trait. Rather than marking a type
|
||||
which can be passed directly as a parameter/result to/from JS, it now lets
|
||||
types specify how they can be split into / recreated from multiple primitive
|
||||
types which are then passed to/from JS.
|
||||
`WasmPrimitive` now serves the old function of `WasmAbi`, minus allowing
|
||||
`#[repr(C)]` types.
|
||||
[#3595](https://github.com/rustwasm/wasm-bindgen/pull/3595)
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed bindings and comments for `Atomics.wait`.
|
||||
|
@ -3,6 +3,7 @@ use crate::encode;
|
||||
use crate::Diagnostic;
|
||||
use once_cell::sync::Lazy;
|
||||
use proc_macro2::{Ident, Literal, Span, TokenStream};
|
||||
use quote::format_ident;
|
||||
use quote::quote_spanned;
|
||||
use quote::{quote, ToTokens};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
@ -142,7 +143,7 @@ impl TryToTokens for ast::LinkToModule {
|
||||
let link_function_name = self.0.link_function_name(0);
|
||||
let name = Ident::new(&link_function_name, Span::call_site());
|
||||
let wasm_bindgen = &self.0.wasm_bindgen;
|
||||
let abi_ret = quote! { <std::string::String as #wasm_bindgen::convert::FromWasmAbi>::Abi };
|
||||
let abi_ret = quote! { #wasm_bindgen::convert::WasmRet<<std::string::String as #wasm_bindgen::convert::FromWasmAbi>::Abi> };
|
||||
let extern_fn = extern_fn(&name, &[], &[], &[], abi_ret);
|
||||
(quote! {
|
||||
{
|
||||
@ -150,7 +151,7 @@ impl TryToTokens for ast::LinkToModule {
|
||||
#extern_fn
|
||||
|
||||
unsafe {
|
||||
<std::string::String as #wasm_bindgen::convert::FromWasmAbi>::from_abi(#name())
|
||||
<std::string::String as #wasm_bindgen::convert::FromWasmAbi>::from_abi(#name().join())
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -401,7 +402,7 @@ impl ToTokens for ast::StructField {
|
||||
#[cfg_attr(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))), no_mangle)]
|
||||
#[doc(hidden)]
|
||||
pub unsafe extern "C" fn #getter(js: u32)
|
||||
-> <#ty as #wasm_bindgen::convert::IntoWasmAbi>::Abi
|
||||
-> #wasm_bindgen::convert::WasmRet<<#ty as #wasm_bindgen::convert::IntoWasmAbi>::Abi>
|
||||
{
|
||||
use #wasm_bindgen::__rt::{WasmRefCell, assert_not_null};
|
||||
use #wasm_bindgen::convert::IntoWasmAbi;
|
||||
@ -412,7 +413,7 @@ impl ToTokens for ast::StructField {
|
||||
let js = js as *mut WasmRefCell<#struct_name>;
|
||||
assert_not_null(js);
|
||||
let val = #val;
|
||||
<#ty as IntoWasmAbi>::into_abi(val)
|
||||
<#ty as IntoWasmAbi>::into_abi(val).into()
|
||||
}
|
||||
};
|
||||
})
|
||||
@ -432,6 +433,9 @@ impl ToTokens for ast::StructField {
|
||||
return;
|
||||
}
|
||||
|
||||
let abi = quote! { <#ty as #wasm_bindgen::convert::FromWasmAbi>::Abi };
|
||||
let (args, names) = splat(wasm_bindgen, &Ident::new("val", rust_name.span()), &abi);
|
||||
|
||||
(quote! {
|
||||
#[cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))]
|
||||
#[automatically_derived]
|
||||
@ -440,13 +444,14 @@ impl ToTokens for ast::StructField {
|
||||
#[doc(hidden)]
|
||||
pub unsafe extern "C" fn #setter(
|
||||
js: u32,
|
||||
val: <#ty as #wasm_bindgen::convert::FromWasmAbi>::Abi,
|
||||
#(#args,)*
|
||||
) {
|
||||
use #wasm_bindgen::__rt::{WasmRefCell, assert_not_null};
|
||||
use #wasm_bindgen::convert::FromWasmAbi;
|
||||
|
||||
let js = js as *mut WasmRefCell<#struct_name>;
|
||||
assert_not_null(js);
|
||||
let val = <#abi as #wasm_bindgen::convert::WasmAbi>::join(#(#names),*);
|
||||
let val = <#ty as FromWasmAbi>::from_abi(val);
|
||||
(*js).borrow_mut().#rust_name = val;
|
||||
}
|
||||
@ -525,52 +530,61 @@ impl TryToTokens for ast::Export {
|
||||
elem,
|
||||
..
|
||||
}) => {
|
||||
args.push(quote! {
|
||||
#ident: <#elem as #wasm_bindgen::convert::RefMutFromWasmAbi>::Abi
|
||||
});
|
||||
let abi = quote! { <#elem as #wasm_bindgen::convert::RefMutFromWasmAbi>::Abi };
|
||||
let (prim_args, prim_names) = splat(wasm_bindgen, &ident, &abi);
|
||||
args.extend(prim_args);
|
||||
arg_conversions.push(quote! {
|
||||
let mut #ident = unsafe {
|
||||
<#elem as #wasm_bindgen::convert::RefMutFromWasmAbi>
|
||||
::ref_mut_from_abi(#ident)
|
||||
::ref_mut_from_abi(
|
||||
<#abi as #wasm_bindgen::convert::WasmAbi>::join(#(#prim_names),*)
|
||||
)
|
||||
};
|
||||
let #ident = &mut *#ident;
|
||||
});
|
||||
}
|
||||
syn::Type::Reference(syn::TypeReference { elem, .. }) => {
|
||||
if self.function.r#async {
|
||||
args.push(quote! {
|
||||
#ident: <#elem as #wasm_bindgen::convert::LongRefFromWasmAbi>::Abi
|
||||
});
|
||||
let abi =
|
||||
quote! { <#elem as #wasm_bindgen::convert::LongRefFromWasmAbi>::Abi };
|
||||
let (prim_args, prim_names) = splat(wasm_bindgen, &ident, &abi);
|
||||
args.extend(prim_args);
|
||||
arg_conversions.push(quote! {
|
||||
let #ident = unsafe {
|
||||
<#elem as #wasm_bindgen::convert::LongRefFromWasmAbi>
|
||||
::long_ref_from_abi(#ident)
|
||||
::long_ref_from_abi(
|
||||
<#abi as #wasm_bindgen::convert::WasmAbi>::join(#(#prim_names),*)
|
||||
)
|
||||
};
|
||||
let #ident = <<#elem as #wasm_bindgen::convert::LongRefFromWasmAbi>
|
||||
::Anchor as core::borrow::Borrow<#elem>>
|
||||
::borrow(&#ident);
|
||||
});
|
||||
} else {
|
||||
args.push(quote! {
|
||||
#ident: <#elem as #wasm_bindgen::convert::RefFromWasmAbi>::Abi
|
||||
});
|
||||
let abi = quote! { <#elem as #wasm_bindgen::convert::RefFromWasmAbi>::Abi };
|
||||
let (prim_args, prim_names) = splat(wasm_bindgen, &ident, &abi);
|
||||
args.extend(prim_args);
|
||||
arg_conversions.push(quote! {
|
||||
let #ident = unsafe {
|
||||
<#elem as #wasm_bindgen::convert::RefFromWasmAbi>
|
||||
::ref_from_abi(#ident)
|
||||
::ref_from_abi(
|
||||
<#abi as #wasm_bindgen::convert::WasmAbi>::join(#(#prim_names),*)
|
||||
)
|
||||
};
|
||||
let #ident = &*#ident;
|
||||
});
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
args.push(quote! {
|
||||
#ident: <#ty as #wasm_bindgen::convert::FromWasmAbi>::Abi
|
||||
});
|
||||
let abi = quote! { <#ty as #wasm_bindgen::convert::FromWasmAbi>::Abi };
|
||||
let (prim_args, prim_names) = splat(wasm_bindgen, &ident, &abi);
|
||||
args.extend(prim_args);
|
||||
arg_conversions.push(quote! {
|
||||
let #ident = unsafe {
|
||||
<#ty as #wasm_bindgen::convert::FromWasmAbi>
|
||||
::from_abi(#ident)
|
||||
::from_abi(
|
||||
<#abi as #wasm_bindgen::convert::WasmAbi>::join(#(#prim_names),*)
|
||||
)
|
||||
};
|
||||
});
|
||||
}
|
||||
@ -642,7 +656,7 @@ impl TryToTokens for ast::Export {
|
||||
}
|
||||
|
||||
let projection = quote! { <#ret_ty as #wasm_bindgen::convert::ReturnWasmAbi> };
|
||||
let convert_ret = quote! { #projection::return_abi(#ret) };
|
||||
let convert_ret = quote! { #projection::return_abi(#ret).into() };
|
||||
let describe_ret = quote! {
|
||||
<#ret_ty as WasmDescribe>::describe();
|
||||
<#inner_ret_ty as WasmDescribe>::describe();
|
||||
@ -664,7 +678,7 @@ impl TryToTokens for ast::Export {
|
||||
all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))),
|
||||
export_name = #export_name,
|
||||
)]
|
||||
pub unsafe extern "C" fn #generated_name(#(#args),*) -> #projection::Abi {
|
||||
pub unsafe extern "C" fn #generated_name(#(#args),*) -> #wasm_bindgen::convert::WasmRet<#projection::Abi> {
|
||||
#start_check
|
||||
|
||||
let #ret = #call;
|
||||
@ -1147,10 +1161,11 @@ impl TryToTokens for ast::ImportFunction {
|
||||
),
|
||||
};
|
||||
|
||||
abi_argument_names.push(name.clone());
|
||||
abi_arguments.push(quote! {
|
||||
#name: <#ty as #wasm_bindgen::convert::IntoWasmAbi>::Abi
|
||||
});
|
||||
let abi = quote! { <#ty as #wasm_bindgen::convert::IntoWasmAbi>::Abi };
|
||||
let (prim_args, prim_names) = splat(wasm_bindgen, &name, &abi);
|
||||
abi_arguments.extend(prim_args);
|
||||
abi_argument_names.extend(prim_names.iter().cloned());
|
||||
|
||||
let var = if i == 0 && is_method {
|
||||
quote! { self }
|
||||
} else {
|
||||
@ -1160,6 +1175,7 @@ impl TryToTokens for ast::ImportFunction {
|
||||
arg_conversions.push(quote! {
|
||||
let #name = <#ty as #wasm_bindgen::convert::IntoWasmAbi>
|
||||
::into_abi(#var);
|
||||
let (#(#prim_names),*) = <#abi as #wasm_bindgen::convert::WasmAbi>::split(#name);
|
||||
});
|
||||
}
|
||||
let abi_ret;
|
||||
@ -1173,12 +1189,13 @@ impl TryToTokens for ast::ImportFunction {
|
||||
}
|
||||
Some(ref ty) => {
|
||||
if self.function.r#async {
|
||||
abi_ret =
|
||||
quote! { <js_sys::Promise as #wasm_bindgen::convert::FromWasmAbi>::Abi };
|
||||
abi_ret = quote! {
|
||||
#wasm_bindgen::convert::WasmRet<<js_sys::Promise as #wasm_bindgen::convert::FromWasmAbi>::Abi>
|
||||
};
|
||||
let future = quote! {
|
||||
#wasm_bindgen_futures::JsFuture::from(
|
||||
<js_sys::Promise as #wasm_bindgen::convert::FromWasmAbi>
|
||||
::from_abi(#ret_ident)
|
||||
::from_abi(#ret_ident.join())
|
||||
).await
|
||||
};
|
||||
convert_ret = if self.catch {
|
||||
@ -1188,22 +1205,23 @@ impl TryToTokens for ast::ImportFunction {
|
||||
};
|
||||
} else {
|
||||
abi_ret = quote! {
|
||||
<#ty as #wasm_bindgen::convert::FromWasmAbi>::Abi
|
||||
#wasm_bindgen::convert::WasmRet<<#ty as #wasm_bindgen::convert::FromWasmAbi>::Abi>
|
||||
};
|
||||
convert_ret = quote! {
|
||||
<#ty as #wasm_bindgen::convert::FromWasmAbi>
|
||||
::from_abi(#ret_ident)
|
||||
::from_abi(#ret_ident.join())
|
||||
};
|
||||
}
|
||||
}
|
||||
None => {
|
||||
if self.function.r#async {
|
||||
abi_ret =
|
||||
quote! { <js_sys::Promise as #wasm_bindgen::convert::FromWasmAbi>::Abi };
|
||||
abi_ret = quote! {
|
||||
#wasm_bindgen::convert::WasmRet<<js_sys::Promise as #wasm_bindgen::convert::FromWasmAbi>::Abi>
|
||||
};
|
||||
let future = quote! {
|
||||
#wasm_bindgen_futures::JsFuture::from(
|
||||
<js_sys::Promise as #wasm_bindgen::convert::FromWasmAbi>
|
||||
::from_abi(#ret_ident)
|
||||
::from_abi(#ret_ident.join())
|
||||
).await
|
||||
};
|
||||
convert_ret = if self.catch {
|
||||
@ -1421,6 +1439,10 @@ impl ToTokens for ast::ImportStatic {
|
||||
let shim_name = &self.shim;
|
||||
let vis = &self.vis;
|
||||
let wasm_bindgen = &self.wasm_bindgen;
|
||||
|
||||
let abi_ret = quote! {
|
||||
#wasm_bindgen::convert::WasmRet<<#ty as #wasm_bindgen::convert::FromWasmAbi>::Abi>
|
||||
};
|
||||
(quote! {
|
||||
#[automatically_derived]
|
||||
#vis static #name: #wasm_bindgen::JsStatic<#ty> = {
|
||||
@ -1428,16 +1450,16 @@ impl ToTokens for ast::ImportStatic {
|
||||
#[link(wasm_import_module = "__wbindgen_placeholder__")]
|
||||
#[cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))]
|
||||
extern "C" {
|
||||
fn #shim_name() -> <#ty as #wasm_bindgen::convert::FromWasmAbi>::Abi;
|
||||
fn #shim_name() -> #abi_ret;
|
||||
}
|
||||
|
||||
#[cfg(not(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi")))))]
|
||||
unsafe fn #shim_name() -> <#ty as wasm_bindgen::convert::FromWasmAbi>::Abi {
|
||||
unsafe fn #shim_name() -> #abi_ret {
|
||||
panic!("cannot access imported statics on non-wasm targets")
|
||||
}
|
||||
|
||||
unsafe {
|
||||
<#ty as #wasm_bindgen::convert::FromWasmAbi>::from_abi(#shim_name())
|
||||
<#ty as #wasm_bindgen::convert::FromWasmAbi>::from_abi(#shim_name().join())
|
||||
}
|
||||
}
|
||||
thread_local!(static _VAL: #ty = init(););
|
||||
@ -1540,6 +1562,32 @@ fn extern_fn(
|
||||
}
|
||||
}
|
||||
|
||||
/// Splats an argument with the given name and ABI type into 4 arguments, one
|
||||
/// for each primitive that the ABI type splits into.
|
||||
///
|
||||
/// Returns an `(args, names)` pair, where `args` is the list of arguments to
|
||||
/// be inserted into the function signature, and `names` is a list of the names
|
||||
/// of those arguments.
|
||||
fn splat(
|
||||
wasm_bindgen: &syn::Path,
|
||||
name: &Ident,
|
||||
abi: &TokenStream,
|
||||
) -> (Vec<TokenStream>, Vec<Ident>) {
|
||||
let mut args = Vec::new();
|
||||
let mut names = Vec::new();
|
||||
|
||||
for n in 1..=4 {
|
||||
let arg_name = format_ident!("{name}_{n}");
|
||||
let prim_name = format_ident!("Prim{n}");
|
||||
args.push(quote! {
|
||||
#arg_name: <#abi as #wasm_bindgen::convert::WasmAbi>::#prim_name
|
||||
});
|
||||
names.push(arg_name);
|
||||
}
|
||||
|
||||
(args, names)
|
||||
}
|
||||
|
||||
/// Converts `span` into a stream of tokens, and attempts to ensure that `input`
|
||||
/// has all the appropriate span information so errors in it point to `span`.
|
||||
fn respan(input: TokenStream, span: &dyn ToTokens) -> TokenStream {
|
||||
|
@ -5,8 +5,8 @@
|
||||
(type (;3;) (func (param i32) (result i32)))
|
||||
(import "./reference_test_bg.js" "__wbindgen_init_externref_table" (func (;0;) (type 0)))
|
||||
(func $__wbindgen_exn_store (;1;) (type 2) (param i32))
|
||||
(func $__externref_table_dealloc (;2;) (type 2) (param i32))
|
||||
(func $exported (;3;) (type 2) (param i32))
|
||||
(func $exported (;2;) (type 2) (param i32))
|
||||
(func $__externref_table_dealloc (;3;) (type 2) (param i32))
|
||||
(func $__externref_table_alloc (;4;) (type 1) (result i32))
|
||||
(func $__wbindgen_add_to_stack_pointer (;5;) (type 3) (param i32) (result i32))
|
||||
(table (;0;) 128 externref)
|
||||
|
@ -1,8 +1,8 @@
|
||||
(module
|
||||
(type (;0;) (func (result i32)))
|
||||
(type (;1;) (func (param i32)))
|
||||
(func $__wbg_classbuilder_free (;0;) (type 1) (param i32))
|
||||
(func $classbuilder_builder (;1;) (type 0) (result i32))
|
||||
(func $classbuilder_builder (;0;) (type 0) (result i32))
|
||||
(func $__wbg_classbuilder_free (;1;) (type 1) (param i32))
|
||||
(memory (;0;) 17)
|
||||
(export "memory" (memory 0))
|
||||
(export "__wbg_classbuilder_free" (func $__wbg_classbuilder_free))
|
||||
|
@ -1,8 +1,8 @@
|
||||
(module
|
||||
(type (;0;) (func (result i32)))
|
||||
(type (;1;) (func (param i32)))
|
||||
(func $__wbg_classconstructor_free (;0;) (type 1) (param i32))
|
||||
(func $classconstructor_new (;1;) (type 0) (result i32))
|
||||
(func $classconstructor_new (;0;) (type 0) (result i32))
|
||||
(func $__wbg_classconstructor_free (;1;) (type 1) (param i32))
|
||||
(memory (;0;) 17)
|
||||
(export "memory" (memory 0))
|
||||
(export "__wbg_classconstructor_free" (func $__wbg_classconstructor_free))
|
||||
|
@ -21,14 +21,12 @@ a Rust value to a JS one. There's a few points here:
|
||||
|
||||
* We'll get to `WasmDescribe` later in this section.
|
||||
|
||||
* The associated type `Abi` is what will actually be generated as an argument /
|
||||
return type for the `extern "C"` functions used to declare wasm imports/exports.
|
||||
* The associated type `Abi` is the type of the raw data that we actually want to pass to JS.
|
||||
The bound `WasmAbi` is implemented for primitive types like `u32` and `f64`,
|
||||
which can be represented directly as WebAssembly values, as well of a couple
|
||||
of `#[repr(C)]` types like `WasmSlice`:
|
||||
of other types like `WasmSlice`:
|
||||
|
||||
```rust
|
||||
#[repr(C)]
|
||||
pub struct WasmSlice {
|
||||
pub ptr: u32,
|
||||
pub len: u32,
|
||||
@ -36,9 +34,28 @@ a Rust value to a JS one. There's a few points here:
|
||||
```
|
||||
|
||||
This struct, which is how things like strings are represented in FFI, isn't
|
||||
a WebAssembly primitive type and so isn't mapped directly to a WebAssembly
|
||||
parameter / return value; instead, the C ABI flattens it out into two arguments
|
||||
or stores it on the stack.
|
||||
a WebAssembly primitive type, and so it can't be mapped directly to a
|
||||
WebAssembly parameter / return value. This is why `WasmAbi` lets types specify
|
||||
how they can be split up into multiple WebAssembly parameters:
|
||||
|
||||
```rust
|
||||
impl WasmAbi for WasmSlice {
|
||||
fn split(self) -> (u32, u32, (), ()) {
|
||||
(self.ptr, self.len, (), ())
|
||||
}
|
||||
|
||||
// some other details to specify return type of `split`, go in the other direction
|
||||
}
|
||||
```
|
||||
|
||||
This means that a `WasmSlice` gets split up into two `u32` parameters.
|
||||
The extra unit types on the end are there because Rust doesn't let us make
|
||||
`WasmAbi` generic over variable-length tuples, so we just take tuples of 4
|
||||
elements. The unit types still end up getting passed to/from JS, but the C ABI
|
||||
just completely ignores them and doesn't generate any arguments.
|
||||
|
||||
Since we can't return multiple values, when returning a `WasmSlice` we instead
|
||||
put the two `u32`s into a `#[repr(C)]` struct and return that.
|
||||
|
||||
* And finally we have the `into_abi` function, returning the `Abi` associated
|
||||
type which will be actually passed to JS.
|
||||
@ -94,4 +111,3 @@ and remain anonymous.
|
||||
The `From*` family of traits are used for converting the Rust arguments in Rust
|
||||
exported functions to JS. They are also used for the return value in JS
|
||||
functions imported into Rust.
|
||||
|
||||
|
@ -559,7 +559,7 @@ pub trait IntoWasmClosure<T: ?Sized> {
|
||||
|
||||
macro_rules! doit {
|
||||
($(
|
||||
($($var:ident)*)
|
||||
($($var:ident $arg1:ident $arg2:ident $arg3:ident $arg4:ident)*)
|
||||
)*) => ($(
|
||||
unsafe impl<$($var,)* R> WasmClosure for dyn Fn($($var),*) -> R + 'static
|
||||
where $($var: FromWasmAbi + 'static,)*
|
||||
@ -570,8 +570,13 @@ macro_rules! doit {
|
||||
unsafe extern "C" fn invoke<$($var: FromWasmAbi,)* R: ReturnWasmAbi>(
|
||||
a: usize,
|
||||
b: usize,
|
||||
$($var: <$var as FromWasmAbi>::Abi),*
|
||||
) -> <R as ReturnWasmAbi>::Abi {
|
||||
$(
|
||||
$arg1: <$var::Abi as WasmAbi>::Prim1,
|
||||
$arg2: <$var::Abi as WasmAbi>::Prim2,
|
||||
$arg3: <$var::Abi as WasmAbi>::Prim3,
|
||||
$arg4: <$var::Abi as WasmAbi>::Prim4,
|
||||
)*
|
||||
) -> WasmRet<R::Abi> {
|
||||
if a == 0 {
|
||||
throw_str("closure invoked after being dropped");
|
||||
}
|
||||
@ -582,11 +587,11 @@ macro_rules! doit {
|
||||
let f: *const dyn Fn($($var),*) -> R =
|
||||
FatPtr { fields: (a, b) }.ptr;
|
||||
$(
|
||||
let $var = <$var as FromWasmAbi>::from_abi($var);
|
||||
let $var = <$var as FromWasmAbi>::from_abi($var::Abi::join($arg1, $arg2, $arg3, $arg4));
|
||||
)*
|
||||
(*f)($($var),*)
|
||||
};
|
||||
ret.return_abi()
|
||||
ret.return_abi().into()
|
||||
}
|
||||
|
||||
inform(invoke::<$($var,)* R> as u32);
|
||||
@ -622,8 +627,13 @@ macro_rules! doit {
|
||||
unsafe extern "C" fn invoke<$($var: FromWasmAbi,)* R: ReturnWasmAbi>(
|
||||
a: usize,
|
||||
b: usize,
|
||||
$($var: <$var as FromWasmAbi>::Abi),*
|
||||
) -> <R as ReturnWasmAbi>::Abi {
|
||||
$(
|
||||
$arg1: <$var::Abi as WasmAbi>::Prim1,
|
||||
$arg2: <$var::Abi as WasmAbi>::Prim2,
|
||||
$arg3: <$var::Abi as WasmAbi>::Prim3,
|
||||
$arg4: <$var::Abi as WasmAbi>::Prim4,
|
||||
)*
|
||||
) -> WasmRet<R::Abi> {
|
||||
if a == 0 {
|
||||
throw_str("closure invoked recursively or after being dropped");
|
||||
}
|
||||
@ -635,11 +645,11 @@ macro_rules! doit {
|
||||
FatPtr { fields: (a, b) }.ptr;
|
||||
let f = f as *mut dyn FnMut($($var),*) -> R;
|
||||
$(
|
||||
let $var = <$var as FromWasmAbi>::from_abi($var);
|
||||
let $var = <$var as FromWasmAbi>::from_abi($var::Abi::join($arg1, $arg2, $arg3, $arg4));
|
||||
)*
|
||||
(*f)($($var),*)
|
||||
};
|
||||
ret.return_abi()
|
||||
ret.return_abi().into()
|
||||
}
|
||||
|
||||
inform(invoke::<$($var,)* R> as u32);
|
||||
@ -732,14 +742,14 @@ macro_rules! doit {
|
||||
|
||||
doit! {
|
||||
()
|
||||
(A)
|
||||
(A B)
|
||||
(A B C)
|
||||
(A B C D)
|
||||
(A B C D E)
|
||||
(A B C D E F)
|
||||
(A B C D E F G)
|
||||
(A B C D E F G H)
|
||||
(A a1 a2 a3 a4)
|
||||
(A a1 a2 a3 a4 B b1 b2 b3 b4)
|
||||
(A a1 a2 a3 a4 B b1 b2 b3 b4 C c1 c2 c3 c4)
|
||||
(A a1 a2 a3 a4 B b1 b2 b3 b4 C c1 c2 c3 c4 D d1 d2 d3 d4)
|
||||
(A a1 a2 a3 a4 B b1 b2 b3 b4 C c1 c2 c3 c4 D d1 d2 d3 d4 E e1 e2 e3 e4)
|
||||
(A a1 a2 a3 a4 B b1 b2 b3 b4 C c1 c2 c3 c4 D d1 d2 d3 d4 E e1 e2 e3 e4 F f1 f2 f3 f4)
|
||||
(A a1 a2 a3 a4 B b1 b2 b3 b4 C c1 c2 c3 c4 D d1 d2 d3 d4 E e1 e2 e3 e4 F f1 f2 f3 f4 G g1 g2 g3 g4)
|
||||
(A a1 a2 a3 a4 B b1 b2 b3 b4 C c1 c2 c3 c4 D d1 d2 d3 d4 E e1 e2 e3 e4 F f1 f2 f3 f4 G g1 g2 g3 g4 H h1 h2 h3 h4)
|
||||
}
|
||||
|
||||
// Copy the above impls down here for where there's only one argument and it's a
|
||||
@ -758,8 +768,11 @@ where
|
||||
unsafe extern "C" fn invoke<A: RefFromWasmAbi, R: ReturnWasmAbi>(
|
||||
a: usize,
|
||||
b: usize,
|
||||
arg: <A as RefFromWasmAbi>::Abi,
|
||||
) -> <R as ReturnWasmAbi>::Abi {
|
||||
arg1: <A::Abi as WasmAbi>::Prim1,
|
||||
arg2: <A::Abi as WasmAbi>::Prim2,
|
||||
arg3: <A::Abi as WasmAbi>::Prim3,
|
||||
arg4: <A::Abi as WasmAbi>::Prim4,
|
||||
) -> WasmRet<R::Abi> {
|
||||
if a == 0 {
|
||||
throw_str("closure invoked after being dropped");
|
||||
}
|
||||
@ -768,10 +781,10 @@ where
|
||||
// example)
|
||||
let ret = {
|
||||
let f: *const dyn Fn(&A) -> R = FatPtr { fields: (a, b) }.ptr;
|
||||
let arg = <A as RefFromWasmAbi>::ref_from_abi(arg);
|
||||
let arg = <A as RefFromWasmAbi>::ref_from_abi(A::Abi::join(arg1, arg2, arg3, arg4));
|
||||
(*f)(&*arg)
|
||||
};
|
||||
ret.return_abi()
|
||||
ret.return_abi().into()
|
||||
}
|
||||
|
||||
inform(invoke::<A, R> as u32);
|
||||
@ -801,8 +814,11 @@ where
|
||||
unsafe extern "C" fn invoke<A: RefFromWasmAbi, R: ReturnWasmAbi>(
|
||||
a: usize,
|
||||
b: usize,
|
||||
arg: <A as RefFromWasmAbi>::Abi,
|
||||
) -> <R as ReturnWasmAbi>::Abi {
|
||||
arg1: <A::Abi as WasmAbi>::Prim1,
|
||||
arg2: <A::Abi as WasmAbi>::Prim2,
|
||||
arg3: <A::Abi as WasmAbi>::Prim3,
|
||||
arg4: <A::Abi as WasmAbi>::Prim4,
|
||||
) -> WasmRet<R::Abi> {
|
||||
if a == 0 {
|
||||
throw_str("closure invoked recursively or after being dropped");
|
||||
}
|
||||
@ -812,10 +828,10 @@ where
|
||||
let ret = {
|
||||
let f: *const dyn FnMut(&A) -> R = FatPtr { fields: (a, b) }.ptr;
|
||||
let f = f as *mut dyn FnMut(&A) -> R;
|
||||
let arg = <A as RefFromWasmAbi>::ref_from_abi(arg);
|
||||
let arg = <A as RefFromWasmAbi>::ref_from_abi(A::Abi::join(arg1, arg2, arg3, arg4));
|
||||
(*f)(&*arg)
|
||||
};
|
||||
ret.return_abi()
|
||||
ret.return_abi().into()
|
||||
}
|
||||
|
||||
inform(invoke::<A, R> as u32);
|
||||
|
@ -4,12 +4,12 @@ use core::mem;
|
||||
|
||||
use crate::convert::slices::WasmSlice;
|
||||
use crate::convert::RefFromWasmAbi;
|
||||
use crate::convert::{FromWasmAbi, IntoWasmAbi, ReturnWasmAbi};
|
||||
use crate::convert::{FromWasmAbi, IntoWasmAbi, ReturnWasmAbi, WasmAbi, WasmRet};
|
||||
use crate::describe::{inform, WasmDescribe, FUNCTION};
|
||||
use crate::throw_str;
|
||||
|
||||
macro_rules! stack_closures {
|
||||
($( ($cnt:tt $invoke:ident $invoke_mut:ident $($var:ident)*) )*) => ($(
|
||||
($( ($cnt:tt $invoke:ident $invoke_mut:ident $($var:ident $arg1:ident $arg2:ident $arg3:ident $arg4:ident)*) )*) => ($(
|
||||
impl<'a, 'b, $($var,)* R> IntoWasmAbi for &'a (dyn Fn($($var),*) -> R + 'b)
|
||||
where $($var: FromWasmAbi,)*
|
||||
R: ReturnWasmAbi
|
||||
@ -28,8 +28,13 @@ macro_rules! stack_closures {
|
||||
unsafe extern "C" fn $invoke<$($var: FromWasmAbi,)* R: ReturnWasmAbi>(
|
||||
a: usize,
|
||||
b: usize,
|
||||
$($var: <$var as FromWasmAbi>::Abi),*
|
||||
) -> <R as ReturnWasmAbi>::Abi {
|
||||
$(
|
||||
$arg1: <$var::Abi as WasmAbi>::Prim1,
|
||||
$arg2: <$var::Abi as WasmAbi>::Prim2,
|
||||
$arg3: <$var::Abi as WasmAbi>::Prim3,
|
||||
$arg4: <$var::Abi as WasmAbi>::Prim4,
|
||||
)*
|
||||
) -> WasmRet<R::Abi> {
|
||||
if a == 0 {
|
||||
throw_str("closure invoked after being dropped");
|
||||
}
|
||||
@ -38,11 +43,11 @@ macro_rules! stack_closures {
|
||||
let ret = {
|
||||
let f: &dyn Fn($($var),*) -> R = mem::transmute((a, b));
|
||||
$(
|
||||
let $var = <$var as FromWasmAbi>::from_abi($var);
|
||||
let $var = <$var as FromWasmAbi>::from_abi($var::Abi::join($arg1, $arg2, $arg3, $arg4));
|
||||
)*
|
||||
f($($var),*)
|
||||
};
|
||||
ret.return_abi()
|
||||
ret.return_abi().into()
|
||||
}
|
||||
|
||||
impl<'a, $($var,)* R> WasmDescribe for dyn Fn($($var),*) -> R + 'a
|
||||
@ -77,8 +82,13 @@ macro_rules! stack_closures {
|
||||
unsafe extern "C" fn $invoke_mut<$($var: FromWasmAbi,)* R: ReturnWasmAbi>(
|
||||
a: usize,
|
||||
b: usize,
|
||||
$($var: <$var as FromWasmAbi>::Abi),*
|
||||
) -> <R as ReturnWasmAbi>::Abi {
|
||||
$(
|
||||
$arg1: <$var::Abi as WasmAbi>::Prim1,
|
||||
$arg2: <$var::Abi as WasmAbi>::Prim2,
|
||||
$arg3: <$var::Abi as WasmAbi>::Prim3,
|
||||
$arg4: <$var::Abi as WasmAbi>::Prim4,
|
||||
)*
|
||||
) -> WasmRet<R::Abi> {
|
||||
if a == 0 {
|
||||
throw_str("closure invoked recursively or after being dropped");
|
||||
}
|
||||
@ -87,11 +97,11 @@ macro_rules! stack_closures {
|
||||
let ret = {
|
||||
let f: &mut dyn FnMut($($var),*) -> R = mem::transmute((a, b));
|
||||
$(
|
||||
let $var = <$var as FromWasmAbi>::from_abi($var);
|
||||
let $var = <$var as FromWasmAbi>::from_abi($var::Abi::join($arg1, $arg2, $arg3, $arg4));
|
||||
)*
|
||||
f($($var),*)
|
||||
};
|
||||
ret.return_abi()
|
||||
ret.return_abi().into()
|
||||
}
|
||||
|
||||
impl<'a, $($var,)* R> WasmDescribe for dyn FnMut($($var),*) -> R + 'a
|
||||
@ -112,14 +122,14 @@ macro_rules! stack_closures {
|
||||
|
||||
stack_closures! {
|
||||
(0 invoke0 invoke0_mut)
|
||||
(1 invoke1 invoke1_mut A)
|
||||
(2 invoke2 invoke2_mut A B)
|
||||
(3 invoke3 invoke3_mut A B C)
|
||||
(4 invoke4 invoke4_mut A B C D)
|
||||
(5 invoke5 invoke5_mut A B C D E)
|
||||
(6 invoke6 invoke6_mut A B C D E F)
|
||||
(7 invoke7 invoke7_mut A B C D E F G)
|
||||
(8 invoke8 invoke8_mut A B C D E F G H)
|
||||
(1 invoke1 invoke1_mut A a1 a2 a3 a4)
|
||||
(2 invoke2 invoke2_mut A a1 a2 a3 a4 B b1 b2 b3 b4)
|
||||
(3 invoke3 invoke3_mut A a1 a2 a3 a4 B b1 b2 b3 b4 C c1 c2 c3 c4)
|
||||
(4 invoke4 invoke4_mut A a1 a2 a3 a4 B b1 b2 b3 b4 C c1 c2 c3 c4 D d1 d2 d3 d4)
|
||||
(5 invoke5 invoke5_mut A a1 a2 a3 a4 B b1 b2 b3 b4 C c1 c2 c3 c4 D d1 d2 d3 d4 E e1 e2 e3 e4)
|
||||
(6 invoke6 invoke6_mut A a1 a2 a3 a4 B b1 b2 b3 b4 C c1 c2 c3 c4 D d1 d2 d3 d4 E e1 e2 e3 e4 F f1 f2 f3 f4)
|
||||
(7 invoke7 invoke7_mut A a1 a2 a3 a4 B b1 b2 b3 b4 C c1 c2 c3 c4 D d1 d2 d3 d4 E e1 e2 e3 e4 F f1 f2 f3 f4 G g1 g2 g3 g4)
|
||||
(8 invoke8 invoke8_mut A a1 a2 a3 a4 B b1 b2 b3 b4 C c1 c2 c3 c4 D d1 d2 d3 d4 E e1 e2 e3 e4 F f1 f2 f3 f4 G g1 g2 g3 g4 H h1 h2 h3 h4)
|
||||
}
|
||||
|
||||
impl<'a, 'b, A, R> IntoWasmAbi for &'a (dyn Fn(&A) -> R + 'b)
|
||||
@ -144,8 +154,11 @@ where
|
||||
unsafe extern "C" fn invoke1_ref<A: RefFromWasmAbi, R: ReturnWasmAbi>(
|
||||
a: usize,
|
||||
b: usize,
|
||||
arg: <A as RefFromWasmAbi>::Abi,
|
||||
) -> <R as ReturnWasmAbi>::Abi {
|
||||
arg1: <A::Abi as WasmAbi>::Prim1,
|
||||
arg2: <A::Abi as WasmAbi>::Prim2,
|
||||
arg3: <A::Abi as WasmAbi>::Prim3,
|
||||
arg4: <A::Abi as WasmAbi>::Prim4,
|
||||
) -> WasmRet<R::Abi> {
|
||||
if a == 0 {
|
||||
throw_str("closure invoked after being dropped");
|
||||
}
|
||||
@ -153,10 +166,10 @@ unsafe extern "C" fn invoke1_ref<A: RefFromWasmAbi, R: ReturnWasmAbi>(
|
||||
// ensure they're all destroyed as `return_abi` may throw
|
||||
let ret = {
|
||||
let f: &dyn Fn(&A) -> R = mem::transmute((a, b));
|
||||
let arg = <A as RefFromWasmAbi>::ref_from_abi(arg);
|
||||
let arg = <A as RefFromWasmAbi>::ref_from_abi(A::Abi::join(arg1, arg2, arg3, arg4));
|
||||
f(&*arg)
|
||||
};
|
||||
ret.return_abi()
|
||||
ret.return_abi().into()
|
||||
}
|
||||
|
||||
impl<'a, A, R> WasmDescribe for dyn Fn(&A) -> R + 'a
|
||||
@ -196,8 +209,11 @@ where
|
||||
unsafe extern "C" fn invoke1_mut_ref<A: RefFromWasmAbi, R: ReturnWasmAbi>(
|
||||
a: usize,
|
||||
b: usize,
|
||||
arg: <A as RefFromWasmAbi>::Abi,
|
||||
) -> <R as ReturnWasmAbi>::Abi {
|
||||
arg1: <A::Abi as WasmAbi>::Prim1,
|
||||
arg2: <A::Abi as WasmAbi>::Prim2,
|
||||
arg3: <A::Abi as WasmAbi>::Prim3,
|
||||
arg4: <A::Abi as WasmAbi>::Prim4,
|
||||
) -> WasmRet<R::Abi> {
|
||||
if a == 0 {
|
||||
throw_str("closure invoked recursively or after being dropped");
|
||||
}
|
||||
@ -205,10 +221,10 @@ unsafe extern "C" fn invoke1_mut_ref<A: RefFromWasmAbi, R: ReturnWasmAbi>(
|
||||
// ensure they're all destroyed as `return_abi` may throw
|
||||
let ret = {
|
||||
let f: &mut dyn FnMut(&A) -> R = mem::transmute((a, b));
|
||||
let arg = <A as RefFromWasmAbi>::ref_from_abi(arg);
|
||||
let arg = <A as RefFromWasmAbi>::ref_from_abi(A::Abi::join(arg1, arg2, arg3, arg4));
|
||||
f(&*arg)
|
||||
};
|
||||
ret.return_abi()
|
||||
ret.return_abi().into()
|
||||
}
|
||||
|
||||
impl<'a, A, R> WasmDescribe for dyn FnMut(&A) -> R + 'a
|
||||
|
@ -1,7 +1,7 @@
|
||||
use core::char;
|
||||
use core::mem::{self, ManuallyDrop};
|
||||
|
||||
use crate::convert::traits::WasmAbi;
|
||||
use crate::convert::traits::{WasmAbi, WasmPrimitive};
|
||||
use crate::convert::{FromWasmAbi, IntoWasmAbi, LongRefFromWasmAbi, RefFromWasmAbi};
|
||||
use crate::convert::{OptionFromWasmAbi, OptionIntoWasmAbi, ReturnWasmAbi};
|
||||
use crate::{Clamped, JsError, JsValue, UnwrapThrowExt};
|
||||
@ -13,28 +13,53 @@ if_std! {
|
||||
use std::vec::Vec;
|
||||
}
|
||||
|
||||
unsafe impl WasmAbi for () {}
|
||||
// Primitive types can always be passed over the ABI.
|
||||
impl<T: WasmPrimitive> WasmAbi for T {
|
||||
type Prim1 = Self;
|
||||
type Prim2 = ();
|
||||
type Prim3 = ();
|
||||
type Prim4 = ();
|
||||
|
||||
#[repr(C, u32)]
|
||||
pub enum WasmOption<T: WasmAbi> {
|
||||
None,
|
||||
Some(T),
|
||||
#[inline]
|
||||
fn split(self) -> (Self, (), (), ()) {
|
||||
(self, (), (), ())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn join(prim: Self, _: (), _: (), _: ()) -> Self {
|
||||
prim
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: WasmAbi> WasmAbi for WasmOption<T> {}
|
||||
impl<T: WasmAbi<Prim4 = ()>> WasmAbi for Option<T> {
|
||||
/// Whether this `Option` is a `Some` value.
|
||||
type Prim1 = u32;
|
||||
type Prim2 = T::Prim1;
|
||||
type Prim3 = T::Prim2;
|
||||
type Prim4 = T::Prim3;
|
||||
|
||||
impl<Abi: WasmAbi> WasmOption<Abi> {
|
||||
pub fn from_option<T: IntoWasmAbi<Abi = Abi>>(option: Option<T>) -> Self {
|
||||
match option {
|
||||
Some(v) => WasmOption::Some(v.into_abi()),
|
||||
None => WasmOption::None,
|
||||
#[inline]
|
||||
fn split(self) -> (u32, T::Prim1, T::Prim2, T::Prim3) {
|
||||
match self {
|
||||
None => (
|
||||
0,
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
),
|
||||
Some(value) => {
|
||||
let (prim1, prim2, prim3, ()) = value.split();
|
||||
(1, prim1, prim2, prim3)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn into_option<T: FromWasmAbi<Abi = Abi>>(v: Self) -> Option<T> {
|
||||
match v {
|
||||
WasmOption::Some(v) => Some(T::from_abi(v)),
|
||||
WasmOption::None => None,
|
||||
#[inline]
|
||||
fn join(is_some: u32, prim1: T::Prim1, prim2: T::Prim2, prim3: T::Prim3) -> Self {
|
||||
if is_some == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(T::join(prim1, prim2, prim3, ()))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -56,20 +81,20 @@ macro_rules! type_wasm_native {
|
||||
}
|
||||
|
||||
impl IntoWasmAbi for Option<$t> {
|
||||
type Abi = WasmOption<$c>;
|
||||
type Abi = Option<$c>;
|
||||
|
||||
#[inline]
|
||||
fn into_abi(self) -> Self::Abi {
|
||||
WasmOption::from_option(self.map(|v| v as $c))
|
||||
self.map(|v| v as $c)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromWasmAbi for Option<$t> {
|
||||
type Abi = WasmOption<$c>;
|
||||
type Abi = Option<$c>;
|
||||
|
||||
#[inline]
|
||||
unsafe fn from_abi(js: Self::Abi) -> Self {
|
||||
WasmOption::into_option(js).map(|v: $c| v as $t)
|
||||
js.map(|v: $c| v as $t)
|
||||
}
|
||||
}
|
||||
)*)
|
||||
@ -317,70 +342,53 @@ impl IntoWasmAbi for () {
|
||||
}
|
||||
}
|
||||
|
||||
/// This is an encoding of a Result. It can only store things that can be decoded by the JS
|
||||
/// bindings.
|
||||
///
|
||||
/// At the moment, we do not write the exact struct packing layout of everything into the
|
||||
/// glue/descriptions of datatypes, so T cannot be arbitrary. The current requirements of the
|
||||
/// struct unpacker (StructUnpacker), which apply to ResultAbi<T> as a whole, are as follows:
|
||||
///
|
||||
/// - repr(C), of course
|
||||
/// - u32/i32/f32/f64 fields at the "leaf fields" of the "field tree"
|
||||
/// - layout equivalent to a completely flattened repr(C) struct, constructed by an in order
|
||||
/// traversal of all the leaf fields in it.
|
||||
///
|
||||
/// This means that you can't embed struct A(u32, f64) as struct B(u32, A); because the "completely
|
||||
/// flattened" struct AB(u32, u32, f64) would miss the 4 byte padding that is actually present
|
||||
/// within B and then as a consequence also miss the 4 byte padding within A that repr(C) inserts.
|
||||
///
|
||||
/// The enemy is padding. Padding is only required when there is an `f64` field. So the enemy is
|
||||
/// `f64` after anything else, particularly anything arbitrary. There is no smaller sized type, so
|
||||
/// we don't need to worry about 1-byte integers, etc. It's best, therefore, to place your f64s
|
||||
/// first in your structs, that's why we have `abi` first, although here it doesn't matter as the
|
||||
/// other two fields total 8 bytes anyway.
|
||||
///
|
||||
#[repr(C)]
|
||||
pub struct ResultAbi<T> {
|
||||
/// This field is the same size/align as `T`.
|
||||
abi: ResultAbiUnion<T>,
|
||||
/// Order of args here is such that we can pop() the possible error first, deal with it and
|
||||
/// move on. Later fields are popped off the stack first.
|
||||
err: u32,
|
||||
is_err: u32,
|
||||
impl<T: WasmAbi<Prim3 = (), Prim4 = ()>> WasmAbi for Result<T, u32> {
|
||||
type Prim1 = T::Prim1;
|
||||
type Prim2 = T::Prim2;
|
||||
// The order of primitives here is such that we can pop() the possible error
|
||||
// first, deal with it and move on. Later primitives are popped off the
|
||||
// stack first.
|
||||
/// If this `Result` is an `Err`, the error value.
|
||||
type Prim3 = u32;
|
||||
/// Whether this `Result` is an `Err`.
|
||||
type Prim4 = u32;
|
||||
|
||||
#[inline]
|
||||
fn split(self) -> (T::Prim1, T::Prim2, u32, u32) {
|
||||
match self {
|
||||
Ok(value) => {
|
||||
let (prim1, prim2, (), ()) = value.split();
|
||||
(prim1, prim2, 0, 0)
|
||||
}
|
||||
Err(err) => (Default::default(), Default::default(), err, 1),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn join(prim1: T::Prim1, prim2: T::Prim2, err: u32, is_err: u32) -> Self {
|
||||
if is_err == 0 {
|
||||
Ok(T::join(prim1, prim2, (), ()))
|
||||
} else {
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub union ResultAbiUnion<T> {
|
||||
// ManuallyDrop is #[repr(transparent)]
|
||||
ok: std::mem::ManuallyDrop<T>,
|
||||
err: (),
|
||||
}
|
||||
impl<T, E> ReturnWasmAbi for Result<T, E>
|
||||
where
|
||||
T: IntoWasmAbi,
|
||||
E: Into<JsValue>,
|
||||
T::Abi: WasmAbi<Prim3 = (), Prim4 = ()>,
|
||||
{
|
||||
type Abi = Result<T::Abi, u32>;
|
||||
|
||||
unsafe impl<T: WasmAbi> WasmAbi for ResultAbi<T> {}
|
||||
unsafe impl<T: WasmAbi> WasmAbi for ResultAbiUnion<T> {}
|
||||
|
||||
impl<T: IntoWasmAbi, E: Into<JsValue>> ReturnWasmAbi for Result<T, E> {
|
||||
type Abi = ResultAbi<T::Abi>;
|
||||
#[inline]
|
||||
fn return_abi(self) -> Self::Abi {
|
||||
match self {
|
||||
Ok(v) => {
|
||||
let abi = ResultAbiUnion {
|
||||
ok: std::mem::ManuallyDrop::new(v.into_abi()),
|
||||
};
|
||||
ResultAbi {
|
||||
abi,
|
||||
is_err: 0,
|
||||
err: 0,
|
||||
}
|
||||
}
|
||||
Ok(v) => Ok(v.into_abi()),
|
||||
Err(e) => {
|
||||
let jsval = e.into();
|
||||
ResultAbi {
|
||||
abi: ResultAbiUnion { err: () },
|
||||
is_err: 1,
|
||||
err: jsval.into_abi(),
|
||||
}
|
||||
Err(jsval.into_abi())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,13 +20,33 @@ if_std! {
|
||||
use crate::convert::{js_value_vector_from_abi, js_value_vector_into_abi};
|
||||
}
|
||||
|
||||
// note: `WasmAbi` types do not need to be FFI-safe themselves, it's just more
|
||||
// convenient to directly write `WasmSlice` in some of the manually-written FFI
|
||||
// functions in `lib.rs` rather than `WasmRet<WasmSlice>`.
|
||||
#[repr(C)]
|
||||
pub struct WasmSlice {
|
||||
pub ptr: u32,
|
||||
pub len: u32,
|
||||
}
|
||||
|
||||
unsafe impl WasmAbi for WasmSlice {}
|
||||
impl WasmAbi for WasmSlice {
|
||||
/// `self.ptr`
|
||||
type Prim1 = u32;
|
||||
/// `self.len`
|
||||
type Prim2 = u32;
|
||||
type Prim3 = ();
|
||||
type Prim4 = ();
|
||||
|
||||
#[inline]
|
||||
fn split(self) -> (u32, u32, (), ()) {
|
||||
(self.ptr, self.len, (), ())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn join(ptr: u32, len: u32, _: (), _: ()) -> Self {
|
||||
Self { ptr, len }
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn null_slice() -> WasmSlice {
|
||||
@ -34,13 +54,33 @@ fn null_slice() -> WasmSlice {
|
||||
}
|
||||
|
||||
if_std! {
|
||||
#[repr(C)]
|
||||
pub struct WasmMutSlice {
|
||||
pub slice: WasmSlice,
|
||||
pub idx: u32,
|
||||
}
|
||||
|
||||
unsafe impl WasmAbi for WasmMutSlice {}
|
||||
impl WasmAbi for WasmMutSlice {
|
||||
/// `self.slice.ptr`
|
||||
type Prim1 = u32;
|
||||
/// `self.slice.len`
|
||||
type Prim2 = u32;
|
||||
/// `self.idx`
|
||||
type Prim3 = u32;
|
||||
type Prim4 = ();
|
||||
|
||||
#[inline]
|
||||
fn split(self) -> (u32, u32, u32, ()) {
|
||||
(self.slice.ptr, self.slice.len, self.idx, ())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn join(ptr: u32, len: u32, idx: u32, _: ()) -> Self {
|
||||
Self {
|
||||
slice: WasmSlice { ptr, len },
|
||||
idx,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The representation of a mutable slice passed from JS to Rust.
|
||||
pub struct MutSlice<T> {
|
||||
|
@ -117,22 +117,54 @@ pub trait OptionFromWasmAbi: FromWasmAbi {
|
||||
fn is_none(abi: &Self::Abi) -> bool;
|
||||
}
|
||||
|
||||
/// An unsafe trait which represents types that are ABI-safe to pass via wasm
|
||||
/// arguments.
|
||||
/// A trait for any type which maps to a Wasm primitive type when used in FFI
|
||||
/// (`i32`, `i64`, `f32`, or `f64`).
|
||||
///
|
||||
/// This is with the exception of `()` (and other zero-sized types), which are
|
||||
/// also allowed because they're ignored: no arguments actually get added.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This is an unsafe trait to implement as there's no guarantee the type is
|
||||
/// actually safe to transfer across the was boundary, it's up to you to
|
||||
/// guarantee this, so codegen works correctly.
|
||||
pub unsafe trait WasmAbi {}
|
||||
/// This is an unsafe trait to implement as there's no guarantee the type
|
||||
/// actually maps to a primitive type.
|
||||
pub unsafe trait WasmPrimitive: Default {}
|
||||
|
||||
unsafe impl WasmAbi for u32 {}
|
||||
unsafe impl WasmAbi for i32 {}
|
||||
unsafe impl WasmAbi for u64 {}
|
||||
unsafe impl WasmAbi for i64 {}
|
||||
unsafe impl WasmAbi for f32 {}
|
||||
unsafe impl WasmAbi for f64 {}
|
||||
unsafe impl WasmPrimitive for u32 {}
|
||||
unsafe impl WasmPrimitive for i32 {}
|
||||
unsafe impl WasmPrimitive for u64 {}
|
||||
unsafe impl WasmPrimitive for i64 {}
|
||||
unsafe impl WasmPrimitive for f32 {}
|
||||
unsafe impl WasmPrimitive for f64 {}
|
||||
unsafe impl WasmPrimitive for () {}
|
||||
|
||||
/// A trait which represents types that can be passed across the Wasm ABI
|
||||
/// boundary, by being split into multiple Wasm primitive types.
|
||||
///
|
||||
/// Up to 4 primitives are supported; if you don't want to use all of them, you
|
||||
/// can set the rest to `()`, which will cause them to be ignored.
|
||||
///
|
||||
/// You need to be careful how many primitives you use, however:
|
||||
/// `Result<T, JsValue>` uses up 2 primitives to store the error, and so it
|
||||
/// doesn't work if `T` uses more than 2 primitives.
|
||||
///
|
||||
/// So, if you're adding support for a type that needs 3 or more primitives and
|
||||
/// is able to be returned, you have to add another primitive here.
|
||||
///
|
||||
/// There's already one type that uses 3 primitives: `&mut [T]`. However, it
|
||||
/// can't be returned anyway, so it doesn't matter that
|
||||
/// `Result<&mut [T], JsValue>` wouldn't work.
|
||||
pub trait WasmAbi {
|
||||
type Prim1: WasmPrimitive;
|
||||
type Prim2: WasmPrimitive;
|
||||
type Prim3: WasmPrimitive;
|
||||
type Prim4: WasmPrimitive;
|
||||
|
||||
/// Splits this type up into primitives to be sent over the ABI.
|
||||
fn split(self) -> (Self::Prim1, Self::Prim2, Self::Prim3, Self::Prim4);
|
||||
/// Reconstructs this type from primitives received over the ABI.
|
||||
fn join(prim1: Self::Prim1, prim2: Self::Prim2, prim3: Self::Prim3, prim4: Self::Prim4)
|
||||
-> Self;
|
||||
}
|
||||
|
||||
/// A trait representing how to interpret the return value of a function for
|
||||
/// the wasm ABI.
|
||||
@ -179,3 +211,41 @@ if_std! {
|
||||
unsafe fn vector_from_abi(js: Self::Abi) -> Box<[Self]>;
|
||||
}
|
||||
}
|
||||
|
||||
/// A repr(C) struct containing all of the primitives of a `WasmAbi` type, in
|
||||
/// order.
|
||||
///
|
||||
/// This is used as the return type of imported/exported functions. `WasmAbi`
|
||||
/// types aren't guaranteed to be FFI-safe, so we can't return them directly:
|
||||
/// instead we return this.
|
||||
///
|
||||
/// If all but one of the primitives is `()`, this corresponds to returning the
|
||||
/// remaining primitive directly, otherwise a return pointer is used.
|
||||
#[repr(C)]
|
||||
pub struct WasmRet<T: WasmAbi> {
|
||||
prim1: T::Prim1,
|
||||
prim2: T::Prim2,
|
||||
prim3: T::Prim3,
|
||||
prim4: T::Prim4,
|
||||
}
|
||||
|
||||
impl<T: WasmAbi> From<T> for WasmRet<T> {
|
||||
fn from(value: T) -> Self {
|
||||
let (prim1, prim2, prim3, prim4) = value.split();
|
||||
Self {
|
||||
prim1,
|
||||
prim2,
|
||||
prim3,
|
||||
prim4,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ideally this'd just be an `Into<T>` implementation, but unfortunately that
|
||||
// doesn't work because of the orphan rule.
|
||||
impl<T: WasmAbi> WasmRet<T> {
|
||||
/// Joins the components of this `WasmRet` back into the type they represent.
|
||||
pub fn join(self) -> T {
|
||||
T::join(self.prim1, self.prim2, self.prim3, self.prim4)
|
||||
}
|
||||
}
|
||||
|
11
src/lib.rs
11
src/lib.rs
@ -18,7 +18,7 @@ use core::ops::{
|
||||
};
|
||||
use core::u32;
|
||||
|
||||
use crate::convert::{FromWasmAbi, WasmSlice};
|
||||
use crate::convert::{FromWasmAbi, WasmRet, WasmSlice};
|
||||
|
||||
macro_rules! if_std {
|
||||
($($i:item)*) => ($(
|
||||
@ -71,7 +71,6 @@ pub mod describe;
|
||||
|
||||
mod cast;
|
||||
pub use crate::cast::{JsCast, JsObject};
|
||||
use convert::WasmOption;
|
||||
|
||||
if_std! {
|
||||
extern crate std;
|
||||
@ -265,7 +264,7 @@ impl JsValue {
|
||||
/// `None`.
|
||||
#[inline]
|
||||
pub fn as_f64(&self) -> Option<f64> {
|
||||
unsafe { FromWasmAbi::from_abi(__wbindgen_number_get(self.idx)) }
|
||||
unsafe { __wbindgen_number_get(self.idx).join() }
|
||||
}
|
||||
|
||||
/// Tests whether this JS value is a JS string.
|
||||
@ -910,7 +909,7 @@ macro_rules! big_numbers {
|
||||
}
|
||||
|
||||
fn bigint_get_as_i64(v: &JsValue) -> Option<i64> {
|
||||
unsafe { Option::from_abi(__wbindgen_bigint_get_as_i64(v.idx)) }
|
||||
unsafe { __wbindgen_bigint_get_as_i64(v.idx).join() }
|
||||
}
|
||||
|
||||
macro_rules! try_from_for_num64 {
|
||||
@ -1057,10 +1056,10 @@ externs! {
|
||||
fn __wbindgen_ge(a: u32, b: u32) -> u32;
|
||||
fn __wbindgen_gt(a: u32, b: u32) -> u32;
|
||||
|
||||
fn __wbindgen_number_get(idx: u32) -> WasmOption<f64>;
|
||||
fn __wbindgen_number_get(idx: u32) -> WasmRet<Option<f64>>;
|
||||
fn __wbindgen_boolean_get(idx: u32) -> u32;
|
||||
fn __wbindgen_string_get(idx: u32) -> WasmSlice;
|
||||
fn __wbindgen_bigint_get_as_i64(idx: u32) -> WasmOption<i64>;
|
||||
fn __wbindgen_bigint_get_as_i64(idx: u32) -> WasmRet<Option<i64>>;
|
||||
|
||||
fn __wbindgen_debug_string(ret: *mut [usize; 2], idx: u32) -> ();
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user