diff --git a/CHANGELOG.md b/CHANGELOG.md index 536b2a8ba64..cce1b9d2852 100644 --- a/CHANGELOG.md +++ b/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`. diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs index 837245d5e6d..4e3d6ca8d9a 100644 --- a/crates/backend/src/codegen.rs +++ b/crates/backend/src/codegen.rs @@ -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! { ::Abi }; + let abi_ret = quote! { #wasm_bindgen::convert::WasmRet<::Abi> }; let extern_fn = extern_fn(&name, &[], &[], &[], abi_ret); (quote! { { @@ -150,7 +151,7 @@ impl TryToTokens for ast::LinkToModule { #extern_fn unsafe { - ::from_abi(#name()) + ::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! { ::Abi }; + abi_ret = quote! { + #wasm_bindgen::convert::WasmRet<::Abi> + }; let future = quote! { #wasm_bindgen_futures::JsFuture::from( - ::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! { ::Abi }; + abi_ret = quote! { + #wasm_bindgen::convert::WasmRet<::Abi> + }; let future = quote! { #wasm_bindgen_futures::JsFuture::from( - ::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, Vec) { + 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 { diff --git a/crates/cli/tests/reference/anyref-import-catch.wat b/crates/cli/tests/reference/anyref-import-catch.wat index 48071a315d7..ed8e5683402 100644 --- a/crates/cli/tests/reference/anyref-import-catch.wat +++ b/crates/cli/tests/reference/anyref-import-catch.wat @@ -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) diff --git a/crates/cli/tests/reference/builder.wat b/crates/cli/tests/reference/builder.wat index cd38fc7fc6a..c6761d480fd 100644 --- a/crates/cli/tests/reference/builder.wat +++ b/crates/cli/tests/reference/builder.wat @@ -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)) diff --git a/crates/cli/tests/reference/constructor.wat b/crates/cli/tests/reference/constructor.wat index a5596470321..bad14a1de88 100644 --- a/crates/cli/tests/reference/constructor.wat +++ b/crates/cli/tests/reference/constructor.wat @@ -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)) diff --git a/guide/src/contributing/design/rust-type-conversions.md b/guide/src/contributing/design/rust-type-conversions.md index 7af5f1b4dca..89776893f21 100644 --- a/guide/src/contributing/design/rust-type-conversions.md +++ b/guide/src/contributing/design/rust-type-conversions.md @@ -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. - diff --git a/src/closure.rs b/src/closure.rs index a498d6d2513..319f4c63c24 100644 --- a/src/closure.rs +++ b/src/closure.rs @@ -559,7 +559,7 @@ pub trait IntoWasmClosure { 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),* - ) -> ::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 { 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),* - ) -> ::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 { 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: usize, b: usize, - arg: ::Abi, - ) -> ::Abi { + arg1: ::Prim1, + arg2: ::Prim2, + arg3: ::Prim3, + arg4: ::Prim4, + ) -> WasmRet { 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 = ::ref_from_abi(arg); + let arg = ::ref_from_abi(A::Abi::join(arg1, arg2, arg3, arg4)); (*f)(&*arg) }; - ret.return_abi() + ret.return_abi().into() } inform(invoke:: as u32); @@ -801,8 +814,11 @@ where unsafe extern "C" fn invoke( a: usize, b: usize, - arg: ::Abi, - ) -> ::Abi { + arg1: ::Prim1, + arg2: ::Prim2, + arg3: ::Prim3, + arg4: ::Prim4, + ) -> WasmRet { 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 = ::ref_from_abi(arg); + let arg = ::ref_from_abi(A::Abi::join(arg1, arg2, arg3, arg4)); (*f)(&*arg) }; - ret.return_abi() + ret.return_abi().into() } inform(invoke:: as u32); diff --git a/src/convert/closures.rs b/src/convert/closures.rs index 02527173d37..8227a6b622f 100644 --- a/src/convert/closures.rs +++ b/src/convert/closures.rs @@ -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),* - ) -> ::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 { 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),* - ) -> ::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 { 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: usize, b: usize, - arg: ::Abi, -) -> ::Abi { + arg1: ::Prim1, + arg2: ::Prim2, + arg3: ::Prim3, + arg4: ::Prim4, +) -> WasmRet { if a == 0 { throw_str("closure invoked after being dropped"); } @@ -153,10 +166,10 @@ unsafe extern "C" fn invoke1_ref( // ensure they're all destroyed as `return_abi` may throw let ret = { let f: &dyn Fn(&A) -> R = mem::transmute((a, b)); - let arg = ::ref_from_abi(arg); + let arg = ::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: usize, b: usize, - arg: ::Abi, -) -> ::Abi { + arg1: ::Prim1, + arg2: ::Prim2, + arg3: ::Prim3, + arg4: ::Prim4, +) -> WasmRet { if a == 0 { throw_str("closure invoked recursively or after being dropped"); } @@ -205,10 +221,10 @@ unsafe extern "C" fn invoke1_mut_ref( // 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 = ::ref_from_abi(arg); + let arg = ::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 diff --git a/src/convert/impls.rs b/src/convert/impls.rs index 637d9d979ad..8c0c3c014bb 100644 --- a/src/convert/impls.rs +++ b/src/convert/impls.rs @@ -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 WasmAbi for T { + type Prim1 = Self; + type Prim2 = (); + type Prim3 = (); + type Prim4 = (); -#[repr(C, u32)] -pub enum WasmOption { - None, - Some(T), + #[inline] + fn split(self) -> (Self, (), (), ()) { + (self, (), (), ()) + } + + #[inline] + fn join(prim: Self, _: (), _: (), _: ()) -> Self { + prim + } } -unsafe impl WasmAbi for WasmOption {} +impl> WasmAbi for Option { + /// Whether this `Option` is a `Some` value. + type Prim1 = u32; + type Prim2 = T::Prim1; + type Prim3 = T::Prim2; + type Prim4 = T::Prim3; -impl WasmOption { - pub fn from_option>(option: Option) -> 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>(v: Self) -> Option { - 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 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 { - /// This field is the same size/align as `T`. - abi: ResultAbiUnion, - /// 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> WasmAbi for Result { + 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; -#[repr(C)] -pub union ResultAbiUnion { - // ManuallyDrop is #[repr(transparent)] - ok: std::mem::ManuallyDrop, - err: (), + #[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) + } + } } -unsafe impl WasmAbi for ResultAbi {} -unsafe impl WasmAbi for ResultAbiUnion {} +impl ReturnWasmAbi for Result +where + T: IntoWasmAbi, + E: Into, + T::Abi: WasmAbi, +{ + type Abi = Result; -impl> ReturnWasmAbi for Result { - type Abi = ResultAbi; #[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()) } } } diff --git a/src/convert/slices.rs b/src/convert/slices.rs index b356df5eb27..b051e3814a0 100644 --- a/src/convert/slices.rs +++ b/src/convert/slices.rs @@ -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`. #[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 { diff --git a/src/convert/traits.rs b/src/convert/traits.rs index dc74caae15a..fd06c3adba3 100644 --- a/src/convert/traits.rs +++ b/src/convert/traits.rs @@ -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 {} - -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 {} +/// 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 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` 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 { + prim1: T::Prim1, + prim2: T::Prim2, + prim3: T::Prim3, + prim4: T::Prim4, +} + +impl From for WasmRet { + fn from(value: T) -> Self { + let (prim1, prim2, prim3, prim4) = value.split(); + Self { + prim1, + prim2, + prim3, + prim4, + } + } +} + +// Ideally this'd just be an `Into` implementation, but unfortunately that +// doesn't work because of the orphan rule. +impl WasmRet { + /// 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) + } +} diff --git a/src/lib.rs b/src/lib.rs index 44844f63b8b..f188e023ddf 100644 --- a/src/lib.rs +++ b/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 { - 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 { - 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; + fn __wbindgen_number_get(idx: u32) -> WasmRet>; fn __wbindgen_boolean_get(idx: u32) -> u32; fn __wbindgen_string_get(idx: u32) -> WasmSlice; - fn __wbindgen_bigint_get_as_i64(idx: u32) -> WasmOption; + fn __wbindgen_bigint_get_as_i64(idx: u32) -> WasmRet>; fn __wbindgen_debug_string(ret: *mut [usize; 2], idx: u32) -> ();