Skip to content

Commit

Permalink
Make wasm-bindgen compatible with the standard C ABI (rustwasm#3595)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
Liamolucko authored Sep 12, 2023
1 parent 2c62271 commit 51e89eb
Show file tree
Hide file tree
Showing 12 changed files with 424 additions and 200 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
124 changes: 86 additions & 38 deletions crates/backend/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -142,15 +143,15 @@ 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! {
{
#program
#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())
}
}
})
Expand Down Expand Up @@ -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;
Expand All @@ -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()
}
};
})
Expand All @@ -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]
Expand All @@ -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;
}
Expand Down Expand Up @@ -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),*)
)
};
});
}
Expand Down Expand Up @@ -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();
Expand All @@ -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;
Expand Down Expand Up @@ -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 {
Expand All @@ -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;
Expand All @@ -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 {
Expand All @@ -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 {
Expand Down Expand Up @@ -1421,23 +1439,27 @@ 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> = {
fn init() -> #ty {
#[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(););
Expand Down Expand Up @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions crates/cli/tests/reference/anyref-import-catch.wat
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions crates/cli/tests/reference/builder.wat
Original file line number Diff line number Diff line change
@@ -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))
Expand Down
4 changes: 2 additions & 2 deletions crates/cli/tests/reference/constructor.wat
Original file line number Diff line number Diff line change
@@ -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))
Expand Down
Loading

0 comments on commit 51e89eb

Please sign in to comment.