Skip to content

Commit 51e89eb

Browse files
authored
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
1 parent 2c62271 commit 51e89eb

File tree

12 files changed

+424
-200
lines changed

12 files changed

+424
-200
lines changed

CHANGELOG.md

+11
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,17 @@
7979
When exported constructors return `Self`.
8080
[#3562](https://github.com/rustwasm/wasm-bindgen/pull/3562)
8181

82+
* Made `wasm-bindgen` forwards-compatible with the standard C ABI.
83+
[#3595](https://github.com/rustwasm/wasm-bindgen/pull/3595)
84+
85+
* Changed the design of the internal `WasmAbi` trait. Rather than marking a type
86+
which can be passed directly as a parameter/result to/from JS, it now lets
87+
types specify how they can be split into / recreated from multiple primitive
88+
types which are then passed to/from JS.
89+
`WasmPrimitive` now serves the old function of `WasmAbi`, minus allowing
90+
`#[repr(C)]` types.
91+
[#3595](https://github.com/rustwasm/wasm-bindgen/pull/3595)
92+
8293
### Fixed
8394

8495
* Fixed bindings and comments for `Atomics.wait`.

crates/backend/src/codegen.rs

+86-38
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use crate::encode;
33
use crate::Diagnostic;
44
use once_cell::sync::Lazy;
55
use proc_macro2::{Ident, Literal, Span, TokenStream};
6+
use quote::format_ident;
67
use quote::quote_spanned;
78
use quote::{quote, ToTokens};
89
use std::collections::{HashMap, HashSet};
@@ -142,15 +143,15 @@ impl TryToTokens for ast::LinkToModule {
142143
let link_function_name = self.0.link_function_name(0);
143144
let name = Ident::new(&link_function_name, Span::call_site());
144145
let wasm_bindgen = &self.0.wasm_bindgen;
145-
let abi_ret = quote! { <std::string::String as #wasm_bindgen::convert::FromWasmAbi>::Abi };
146+
let abi_ret = quote! { #wasm_bindgen::convert::WasmRet<<std::string::String as #wasm_bindgen::convert::FromWasmAbi>::Abi> };
146147
let extern_fn = extern_fn(&name, &[], &[], &[], abi_ret);
147148
(quote! {
148149
{
149150
#program
150151
#extern_fn
151152

152153
unsafe {
153-
<std::string::String as #wasm_bindgen::convert::FromWasmAbi>::from_abi(#name())
154+
<std::string::String as #wasm_bindgen::convert::FromWasmAbi>::from_abi(#name().join())
154155
}
155156
}
156157
})
@@ -401,7 +402,7 @@ impl ToTokens for ast::StructField {
401402
#[cfg_attr(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))), no_mangle)]
402403
#[doc(hidden)]
403404
pub unsafe extern "C" fn #getter(js: u32)
404-
-> <#ty as #wasm_bindgen::convert::IntoWasmAbi>::Abi
405+
-> #wasm_bindgen::convert::WasmRet<<#ty as #wasm_bindgen::convert::IntoWasmAbi>::Abi>
405406
{
406407
use #wasm_bindgen::__rt::{WasmRefCell, assert_not_null};
407408
use #wasm_bindgen::convert::IntoWasmAbi;
@@ -412,7 +413,7 @@ impl ToTokens for ast::StructField {
412413
let js = js as *mut WasmRefCell<#struct_name>;
413414
assert_not_null(js);
414415
let val = #val;
415-
<#ty as IntoWasmAbi>::into_abi(val)
416+
<#ty as IntoWasmAbi>::into_abi(val).into()
416417
}
417418
};
418419
})
@@ -432,6 +433,9 @@ impl ToTokens for ast::StructField {
432433
return;
433434
}
434435

436+
let abi = quote! { <#ty as #wasm_bindgen::convert::FromWasmAbi>::Abi };
437+
let (args, names) = splat(wasm_bindgen, &Ident::new("val", rust_name.span()), &abi);
438+
435439
(quote! {
436440
#[cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))]
437441
#[automatically_derived]
@@ -440,13 +444,14 @@ impl ToTokens for ast::StructField {
440444
#[doc(hidden)]
441445
pub unsafe extern "C" fn #setter(
442446
js: u32,
443-
val: <#ty as #wasm_bindgen::convert::FromWasmAbi>::Abi,
447+
#(#args,)*
444448
) {
445449
use #wasm_bindgen::__rt::{WasmRefCell, assert_not_null};
446450
use #wasm_bindgen::convert::FromWasmAbi;
447451

448452
let js = js as *mut WasmRefCell<#struct_name>;
449453
assert_not_null(js);
454+
let val = <#abi as #wasm_bindgen::convert::WasmAbi>::join(#(#names),*);
450455
let val = <#ty as FromWasmAbi>::from_abi(val);
451456
(*js).borrow_mut().#rust_name = val;
452457
}
@@ -525,52 +530,61 @@ impl TryToTokens for ast::Export {
525530
elem,
526531
..
527532
}) => {
528-
args.push(quote! {
529-
#ident: <#elem as #wasm_bindgen::convert::RefMutFromWasmAbi>::Abi
530-
});
533+
let abi = quote! { <#elem as #wasm_bindgen::convert::RefMutFromWasmAbi>::Abi };
534+
let (prim_args, prim_names) = splat(wasm_bindgen, &ident, &abi);
535+
args.extend(prim_args);
531536
arg_conversions.push(quote! {
532537
let mut #ident = unsafe {
533538
<#elem as #wasm_bindgen::convert::RefMutFromWasmAbi>
534-
::ref_mut_from_abi(#ident)
539+
::ref_mut_from_abi(
540+
<#abi as #wasm_bindgen::convert::WasmAbi>::join(#(#prim_names),*)
541+
)
535542
};
536543
let #ident = &mut *#ident;
537544
});
538545
}
539546
syn::Type::Reference(syn::TypeReference { elem, .. }) => {
540547
if self.function.r#async {
541-
args.push(quote! {
542-
#ident: <#elem as #wasm_bindgen::convert::LongRefFromWasmAbi>::Abi
543-
});
548+
let abi =
549+
quote! { <#elem as #wasm_bindgen::convert::LongRefFromWasmAbi>::Abi };
550+
let (prim_args, prim_names) = splat(wasm_bindgen, &ident, &abi);
551+
args.extend(prim_args);
544552
arg_conversions.push(quote! {
545553
let #ident = unsafe {
546554
<#elem as #wasm_bindgen::convert::LongRefFromWasmAbi>
547-
::long_ref_from_abi(#ident)
555+
::long_ref_from_abi(
556+
<#abi as #wasm_bindgen::convert::WasmAbi>::join(#(#prim_names),*)
557+
)
548558
};
549559
let #ident = <<#elem as #wasm_bindgen::convert::LongRefFromWasmAbi>
550560
::Anchor as core::borrow::Borrow<#elem>>
551561
::borrow(&#ident);
552562
});
553563
} else {
554-
args.push(quote! {
555-
#ident: <#elem as #wasm_bindgen::convert::RefFromWasmAbi>::Abi
556-
});
564+
let abi = quote! { <#elem as #wasm_bindgen::convert::RefFromWasmAbi>::Abi };
565+
let (prim_args, prim_names) = splat(wasm_bindgen, &ident, &abi);
566+
args.extend(prim_args);
557567
arg_conversions.push(quote! {
558568
let #ident = unsafe {
559569
<#elem as #wasm_bindgen::convert::RefFromWasmAbi>
560-
::ref_from_abi(#ident)
570+
::ref_from_abi(
571+
<#abi as #wasm_bindgen::convert::WasmAbi>::join(#(#prim_names),*)
572+
)
561573
};
562574
let #ident = &*#ident;
563575
});
564576
}
565577
}
566578
_ => {
567-
args.push(quote! {
568-
#ident: <#ty as #wasm_bindgen::convert::FromWasmAbi>::Abi
569-
});
579+
let abi = quote! { <#ty as #wasm_bindgen::convert::FromWasmAbi>::Abi };
580+
let (prim_args, prim_names) = splat(wasm_bindgen, &ident, &abi);
581+
args.extend(prim_args);
570582
arg_conversions.push(quote! {
571583
let #ident = unsafe {
572584
<#ty as #wasm_bindgen::convert::FromWasmAbi>
573-
::from_abi(#ident)
585+
::from_abi(
586+
<#abi as #wasm_bindgen::convert::WasmAbi>::join(#(#prim_names),*)
587+
)
574588
};
575589
});
576590
}
@@ -642,7 +656,7 @@ impl TryToTokens for ast::Export {
642656
}
643657

644658
let projection = quote! { <#ret_ty as #wasm_bindgen::convert::ReturnWasmAbi> };
645-
let convert_ret = quote! { #projection::return_abi(#ret) };
659+
let convert_ret = quote! { #projection::return_abi(#ret).into() };
646660
let describe_ret = quote! {
647661
<#ret_ty as WasmDescribe>::describe();
648662
<#inner_ret_ty as WasmDescribe>::describe();
@@ -664,7 +678,7 @@ impl TryToTokens for ast::Export {
664678
all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))),
665679
export_name = #export_name,
666680
)]
667-
pub unsafe extern "C" fn #generated_name(#(#args),*) -> #projection::Abi {
681+
pub unsafe extern "C" fn #generated_name(#(#args),*) -> #wasm_bindgen::convert::WasmRet<#projection::Abi> {
668682
#start_check
669683

670684
let #ret = #call;
@@ -1147,10 +1161,11 @@ impl TryToTokens for ast::ImportFunction {
11471161
),
11481162
};
11491163

1150-
abi_argument_names.push(name.clone());
1151-
abi_arguments.push(quote! {
1152-
#name: <#ty as #wasm_bindgen::convert::IntoWasmAbi>::Abi
1153-
});
1164+
let abi = quote! { <#ty as #wasm_bindgen::convert::IntoWasmAbi>::Abi };
1165+
let (prim_args, prim_names) = splat(wasm_bindgen, &name, &abi);
1166+
abi_arguments.extend(prim_args);
1167+
abi_argument_names.extend(prim_names.iter().cloned());
1168+
11541169
let var = if i == 0 && is_method {
11551170
quote! { self }
11561171
} else {
@@ -1160,6 +1175,7 @@ impl TryToTokens for ast::ImportFunction {
11601175
arg_conversions.push(quote! {
11611176
let #name = <#ty as #wasm_bindgen::convert::IntoWasmAbi>
11621177
::into_abi(#var);
1178+
let (#(#prim_names),*) = <#abi as #wasm_bindgen::convert::WasmAbi>::split(#name);
11631179
});
11641180
}
11651181
let abi_ret;
@@ -1173,12 +1189,13 @@ impl TryToTokens for ast::ImportFunction {
11731189
}
11741190
Some(ref ty) => {
11751191
if self.function.r#async {
1176-
abi_ret =
1177-
quote! { <js_sys::Promise as #wasm_bindgen::convert::FromWasmAbi>::Abi };
1192+
abi_ret = quote! {
1193+
#wasm_bindgen::convert::WasmRet<<js_sys::Promise as #wasm_bindgen::convert::FromWasmAbi>::Abi>
1194+
};
11781195
let future = quote! {
11791196
#wasm_bindgen_futures::JsFuture::from(
11801197
<js_sys::Promise as #wasm_bindgen::convert::FromWasmAbi>
1181-
::from_abi(#ret_ident)
1198+
::from_abi(#ret_ident.join())
11821199
).await
11831200
};
11841201
convert_ret = if self.catch {
@@ -1188,22 +1205,23 @@ impl TryToTokens for ast::ImportFunction {
11881205
};
11891206
} else {
11901207
abi_ret = quote! {
1191-
<#ty as #wasm_bindgen::convert::FromWasmAbi>::Abi
1208+
#wasm_bindgen::convert::WasmRet<<#ty as #wasm_bindgen::convert::FromWasmAbi>::Abi>
11921209
};
11931210
convert_ret = quote! {
11941211
<#ty as #wasm_bindgen::convert::FromWasmAbi>
1195-
::from_abi(#ret_ident)
1212+
::from_abi(#ret_ident.join())
11961213
};
11971214
}
11981215
}
11991216
None => {
12001217
if self.function.r#async {
1201-
abi_ret =
1202-
quote! { <js_sys::Promise as #wasm_bindgen::convert::FromWasmAbi>::Abi };
1218+
abi_ret = quote! {
1219+
#wasm_bindgen::convert::WasmRet<<js_sys::Promise as #wasm_bindgen::convert::FromWasmAbi>::Abi>
1220+
};
12031221
let future = quote! {
12041222
#wasm_bindgen_futures::JsFuture::from(
12051223
<js_sys::Promise as #wasm_bindgen::convert::FromWasmAbi>
1206-
::from_abi(#ret_ident)
1224+
::from_abi(#ret_ident.join())
12071225
).await
12081226
};
12091227
convert_ret = if self.catch {
@@ -1421,23 +1439,27 @@ impl ToTokens for ast::ImportStatic {
14211439
let shim_name = &self.shim;
14221440
let vis = &self.vis;
14231441
let wasm_bindgen = &self.wasm_bindgen;
1442+
1443+
let abi_ret = quote! {
1444+
#wasm_bindgen::convert::WasmRet<<#ty as #wasm_bindgen::convert::FromWasmAbi>::Abi>
1445+
};
14241446
(quote! {
14251447
#[automatically_derived]
14261448
#vis static #name: #wasm_bindgen::JsStatic<#ty> = {
14271449
fn init() -> #ty {
14281450
#[link(wasm_import_module = "__wbindgen_placeholder__")]
14291451
#[cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))]
14301452
extern "C" {
1431-
fn #shim_name() -> <#ty as #wasm_bindgen::convert::FromWasmAbi>::Abi;
1453+
fn #shim_name() -> #abi_ret;
14321454
}
14331455

14341456
#[cfg(not(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi")))))]
1435-
unsafe fn #shim_name() -> <#ty as wasm_bindgen::convert::FromWasmAbi>::Abi {
1457+
unsafe fn #shim_name() -> #abi_ret {
14361458
panic!("cannot access imported statics on non-wasm targets")
14371459
}
14381460

14391461
unsafe {
1440-
<#ty as #wasm_bindgen::convert::FromWasmAbi>::from_abi(#shim_name())
1462+
<#ty as #wasm_bindgen::convert::FromWasmAbi>::from_abi(#shim_name().join())
14411463
}
14421464
}
14431465
thread_local!(static _VAL: #ty = init(););
@@ -1540,6 +1562,32 @@ fn extern_fn(
15401562
}
15411563
}
15421564

1565+
/// Splats an argument with the given name and ABI type into 4 arguments, one
1566+
/// for each primitive that the ABI type splits into.
1567+
///
1568+
/// Returns an `(args, names)` pair, where `args` is the list of arguments to
1569+
/// be inserted into the function signature, and `names` is a list of the names
1570+
/// of those arguments.
1571+
fn splat(
1572+
wasm_bindgen: &syn::Path,
1573+
name: &Ident,
1574+
abi: &TokenStream,
1575+
) -> (Vec<TokenStream>, Vec<Ident>) {
1576+
let mut args = Vec::new();
1577+
let mut names = Vec::new();
1578+
1579+
for n in 1..=4 {
1580+
let arg_name = format_ident!("{name}_{n}");
1581+
let prim_name = format_ident!("Prim{n}");
1582+
args.push(quote! {
1583+
#arg_name: <#abi as #wasm_bindgen::convert::WasmAbi>::#prim_name
1584+
});
1585+
names.push(arg_name);
1586+
}
1587+
1588+
(args, names)
1589+
}
1590+
15431591
/// Converts `span` into a stream of tokens, and attempts to ensure that `input`
15441592
/// has all the appropriate span information so errors in it point to `span`.
15451593
fn respan(input: TokenStream, span: &dyn ToTokens) -> TokenStream {

crates/cli/tests/reference/anyref-import-catch.wat

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
(type (;3;) (func (param i32) (result i32)))
66
(import "./reference_test_bg.js" "__wbindgen_init_externref_table" (func (;0;) (type 0)))
77
(func $__wbindgen_exn_store (;1;) (type 2) (param i32))
8-
(func $__externref_table_dealloc (;2;) (type 2) (param i32))
9-
(func $exported (;3;) (type 2) (param i32))
8+
(func $exported (;2;) (type 2) (param i32))
9+
(func $__externref_table_dealloc (;3;) (type 2) (param i32))
1010
(func $__externref_table_alloc (;4;) (type 1) (result i32))
1111
(func $__wbindgen_add_to_stack_pointer (;5;) (type 3) (param i32) (result i32))
1212
(table (;0;) 128 externref)

crates/cli/tests/reference/builder.wat

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
(module
22
(type (;0;) (func (result i32)))
33
(type (;1;) (func (param i32)))
4-
(func $__wbg_classbuilder_free (;0;) (type 1) (param i32))
5-
(func $classbuilder_builder (;1;) (type 0) (result i32))
4+
(func $classbuilder_builder (;0;) (type 0) (result i32))
5+
(func $__wbg_classbuilder_free (;1;) (type 1) (param i32))
66
(memory (;0;) 17)
77
(export "memory" (memory 0))
88
(export "__wbg_classbuilder_free" (func $__wbg_classbuilder_free))

crates/cli/tests/reference/constructor.wat

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
(module
22
(type (;0;) (func (result i32)))
33
(type (;1;) (func (param i32)))
4-
(func $__wbg_classconstructor_free (;0;) (type 1) (param i32))
5-
(func $classconstructor_new (;1;) (type 0) (result i32))
4+
(func $classconstructor_new (;0;) (type 0) (result i32))
5+
(func $__wbg_classconstructor_free (;1;) (type 1) (param i32))
66
(memory (;0;) 17)
77
(export "memory" (memory 0))
88
(export "__wbg_classconstructor_free" (func $__wbg_classconstructor_free))

0 commit comments

Comments
 (0)