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:
Liam Murphy 2023-09-12 22:24:36 +10:00 committed by GitHub
parent 2c622715c9
commit 51e89ebee5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 424 additions and 200 deletions

View File

@ -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`.

View File

@ -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 {

View File

@ -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)

View File

@ -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))

View File

@ -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))

View File

@ -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.

View File

@ -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);

View File

@ -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

View File

@ -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())
}
}
}

View File

@ -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> {

View File

@ -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)
}
}

View File

@ -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) -> ();