Skip to content

Commit a11a497

Browse files
Davidsterdaxpedda
authored andcommitted
Improve the performance of 'string enums' (rustwasm#3915)
1 parent 852ea7c commit a11a497

File tree

16 files changed

+298
-74
lines changed

16 files changed

+298
-74
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
* Generate JS bindings for WebIDL dictionary setters instead of using `Reflect`. This increases the size of the Web API bindings but should be more performant. Also, importing getters/setters from JS now supports specifying the JS attribute name as a string, e.g. `#[wasm_bindgen(method, setter = "x-cdm-codecs")]`.
1414
[#3898](https://github.com/rustwasm/wasm-bindgen/pull/3898)
1515

16+
* Greatly improve the performance of sending WebIDL 'string enums' across the JavaScript boundary by converting the enum variant string to/from an int.
17+
[#3915](https://github.com/rustwasm/wasm-bindgen/pull/3915)
18+
1619
### Fixed
1720

1821
* Fix `catch` not being thread-safe.

benchmarks/index.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -277,12 +277,12 @@ <h1>wasm-bindgen benchmarks</h1>
277277

278278
<tr>
279279
<td>
280-
Call a custom JS function with an enum value parameter
280+
Call a custom JS function with a string enum value parameter
281281

282282
<a class='about-open' href='#'>(?)</a>
283283

284284
<p class='about'>
285-
Benchmarks the speed of passing enum values to javascript
285+
Benchmarks the speed of passing string enums to javascript
286286
</p>
287287
</td>
288288

crates/backend/src/ast.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ pub enum ImportKind {
163163
/// Importing a type/class
164164
Type(ImportType),
165165
/// Importing a JS enum
166-
Enum(ImportEnum),
166+
Enum(StringEnum),
167167
}
168168

169169
/// A function being imported from JS
@@ -302,10 +302,10 @@ pub struct ImportType {
302302
pub wasm_bindgen: Path,
303303
}
304304

305-
/// The metadata for an Enum being imported
305+
/// The metadata for a String Enum
306306
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
307307
#[derive(Clone)]
308-
pub struct ImportEnum {
308+
pub struct StringEnum {
309309
/// The Rust enum's visibility
310310
pub vis: syn::Visibility,
311311
/// The Rust enum's identifiers
@@ -404,7 +404,7 @@ pub struct StructField {
404404
pub wasm_bindgen: Path,
405405
}
406406

407-
/// Information about an Enum being exported
407+
/// The metadata for an Enum
408408
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
409409
#[derive(Clone)]
410410
pub struct Enum {

crates/backend/src/codegen.rs

Lines changed: 71 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::ast;
22
use crate::encode;
33
use crate::Diagnostic;
44
use once_cell::sync::Lazy;
5-
use proc_macro2::{Ident, Literal, Span, TokenStream};
5+
use proc_macro2::{Ident, Span, TokenStream};
66
use quote::format_ident;
77
use quote::quote_spanned;
88
use quote::{quote, ToTokens};
@@ -1015,117 +1015,136 @@ impl ToTokens for ast::ImportType {
10151015
}
10161016
}
10171017

1018-
impl ToTokens for ast::ImportEnum {
1018+
impl ToTokens for ast::StringEnum {
10191019
fn to_tokens(&self, tokens: &mut TokenStream) {
10201020
let vis = &self.vis;
1021-
let name = &self.name;
1022-
let expect_string = format!("attempted to convert invalid {} into JSValue", name);
1021+
let enum_name = &self.name;
1022+
let name_str = enum_name.to_string();
1023+
let name_len = name_str.len() as u32;
1024+
let name_chars = name_str.chars().map(u32::from);
10231025
let variants = &self.variants;
1024-
let variant_strings = &self.variant_values;
1026+
let variant_count = self.variant_values.len() as u32;
1027+
let variant_values = &self.variant_values;
1028+
let variant_indices = (0..variant_count).collect::<Vec<_>>();
1029+
let invalid = variant_count;
1030+
let hole = variant_count + 1;
10251031
let attrs = &self.rust_attrs;
10261032

1027-
let mut current_idx: usize = 0;
1028-
let variant_indexes: Vec<Literal> = variants
1029-
.iter()
1030-
.map(|_| {
1031-
let this_index = current_idx;
1032-
current_idx += 1;
1033-
Literal::usize_unsuffixed(this_index)
1034-
})
1035-
.collect();
1036-
1037-
// Borrow variant_indexes because we need to use it multiple times inside the quote! macro
1038-
let variant_indexes_ref = &variant_indexes;
1033+
let invalid_to_str_msg = format!(
1034+
"Converting an invalid string enum ({}) back to a string is currently not supported",
1035+
enum_name
1036+
);
10391037

10401038
// A vector of EnumName::VariantName tokens for this enum
10411039
let variant_paths: Vec<TokenStream> = self
10421040
.variants
10431041
.iter()
1044-
.map(|v| quote!(#name::#v).into_token_stream())
1042+
.map(|v| quote!(#enum_name::#v).into_token_stream())
10451043
.collect();
10461044

10471045
// Borrow variant_paths because we need to use it multiple times inside the quote! macro
10481046
let variant_paths_ref = &variant_paths;
10491047

10501048
let wasm_bindgen = &self.wasm_bindgen;
10511049

1050+
let describe_variants = self.variant_values.iter().map(|variant_value| {
1051+
let length = variant_value.len() as u32;
1052+
let chars = variant_value.chars().map(u32::from);
1053+
quote! {
1054+
inform(#length);
1055+
#(inform(#chars);)*
1056+
}
1057+
});
1058+
10521059
(quote! {
10531060
#(#attrs)*
1054-
#vis enum #name {
1055-
#(#variants = #variant_indexes_ref,)*
1061+
#[non_exhaustive]
1062+
#[repr(u32)]
1063+
#vis enum #enum_name {
1064+
#(#variants = #variant_indices,)*
10561065
#[automatically_derived]
10571066
#[doc(hidden)]
1058-
__Nonexhaustive,
1067+
__Invalid
10591068
}
10601069

10611070
#[automatically_derived]
1062-
impl #name {
1063-
fn from_str(s: &str) -> Option<#name> {
1071+
impl #enum_name {
1072+
fn from_str(s: &str) -> Option<#enum_name> {
10641073
match s {
1065-
#(#variant_strings => Some(#variant_paths_ref),)*
1074+
#(#variant_values => Some(#variant_paths_ref),)*
10661075
_ => None,
10671076
}
10681077
}
10691078

10701079
fn to_str(&self) -> &'static str {
10711080
match self {
1072-
#(#variant_paths_ref => #variant_strings,)*
1073-
#name::__Nonexhaustive => panic!(#expect_string),
1081+
#(#variant_paths_ref => #variant_values,)*
1082+
#enum_name::__Invalid => panic!(#invalid_to_str_msg),
10741083
}
10751084
}
10761085

1077-
#vis fn from_js_value(obj: &#wasm_bindgen::JsValue) -> Option<#name> {
1086+
#vis fn from_js_value(obj: &#wasm_bindgen::JsValue) -> Option<#enum_name> {
10781087
obj.as_string().and_then(|obj_str| Self::from_str(obj_str.as_str()))
10791088
}
10801089
}
10811090

1082-
// It should really be using &str for all of these, but that requires some major changes to cli-support
10831091
#[automatically_derived]
1084-
impl #wasm_bindgen::describe::WasmDescribe for #name {
1085-
fn describe() {
1086-
<#wasm_bindgen::JsValue as #wasm_bindgen::describe::WasmDescribe>::describe()
1087-
}
1088-
}
1089-
1090-
#[automatically_derived]
1091-
impl #wasm_bindgen::convert::IntoWasmAbi for #name {
1092-
type Abi = <#wasm_bindgen::JsValue as #wasm_bindgen::convert::IntoWasmAbi>::Abi;
1092+
impl #wasm_bindgen::convert::IntoWasmAbi for #enum_name {
1093+
type Abi = u32;
10931094

10941095
#[inline]
1095-
fn into_abi(self) -> Self::Abi {
1096-
<#wasm_bindgen::JsValue as #wasm_bindgen::convert::IntoWasmAbi>::into_abi(self.into())
1096+
fn into_abi(self) -> u32 {
1097+
self as u32
10971098
}
10981099
}
10991100

11001101
#[automatically_derived]
1101-
impl #wasm_bindgen::convert::FromWasmAbi for #name {
1102-
type Abi = <#wasm_bindgen::JsValue as #wasm_bindgen::convert::FromWasmAbi>::Abi;
1102+
impl #wasm_bindgen::convert::FromWasmAbi for #enum_name {
1103+
type Abi = u32;
11031104

1104-
unsafe fn from_abi(js: Self::Abi) -> Self {
1105-
let s = <#wasm_bindgen::JsValue as #wasm_bindgen::convert::FromWasmAbi>::from_abi(js);
1106-
#name::from_js_value(&s).unwrap_or(#name::__Nonexhaustive)
1105+
unsafe fn from_abi(val: u32) -> Self {
1106+
match val {
1107+
#(#variant_indices => #variant_paths_ref,)*
1108+
#invalid => #enum_name::__Invalid,
1109+
_ => unreachable!("The JS binding should only ever produce a valid value or the specific 'invalid' value"),
1110+
}
11071111
}
11081112
}
11091113

11101114
#[automatically_derived]
1111-
impl #wasm_bindgen::convert::OptionIntoWasmAbi for #name {
1115+
impl #wasm_bindgen::convert::OptionFromWasmAbi for #enum_name {
11121116
#[inline]
1113-
fn none() -> Self::Abi { <::js_sys::Object as #wasm_bindgen::convert::OptionIntoWasmAbi>::none() }
1117+
fn is_none(val: &u32) -> bool { *val == #hole }
11141118
}
11151119

11161120
#[automatically_derived]
1117-
impl #wasm_bindgen::convert::OptionFromWasmAbi for #name {
1121+
impl #wasm_bindgen::convert::OptionIntoWasmAbi for #enum_name {
11181122
#[inline]
1119-
fn is_none(abi: &Self::Abi) -> bool { <::js_sys::Object as #wasm_bindgen::convert::OptionFromWasmAbi>::is_none(abi) }
1123+
fn none() -> Self::Abi { #hole }
1124+
}
1125+
1126+
#[automatically_derived]
1127+
impl #wasm_bindgen::describe::WasmDescribe for #enum_name {
1128+
fn describe() {
1129+
use #wasm_bindgen::describe::*;
1130+
inform(STRING_ENUM);
1131+
inform(#name_len);
1132+
#(inform(#name_chars);)*
1133+
inform(#variant_count);
1134+
#(#describe_variants)*
1135+
}
11201136
}
11211137

11221138
#[automatically_derived]
1123-
impl From<#name> for #wasm_bindgen::JsValue {
1124-
fn from(obj: #name) -> #wasm_bindgen::JsValue {
1125-
#wasm_bindgen::JsValue::from(obj.to_str())
1139+
impl #wasm_bindgen::__rt::core::convert::From<#enum_name> for
1140+
#wasm_bindgen::JsValue
1141+
{
1142+
fn from(val: #enum_name) -> Self {
1143+
#wasm_bindgen::JsValue::from_str(val.to_str())
11261144
}
11271145
}
1128-
}).to_tokens(tokens);
1146+
})
1147+
.to_tokens(tokens);
11291148
}
11301149
}
11311150

crates/backend/src/encode.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -325,8 +325,8 @@ fn shared_import_type<'a>(i: &'a ast::ImportType, intern: &'a Interner) -> Impor
325325
}
326326
}
327327

328-
fn shared_import_enum<'a>(_i: &'a ast::ImportEnum, _intern: &'a Interner) -> ImportEnum {
329-
ImportEnum {}
328+
fn shared_import_enum<'a>(_i: &'a ast::StringEnum, _intern: &'a Interner) -> StringEnum {
329+
StringEnum {}
330330
}
331331

332332
fn shared_struct<'a>(s: &'a ast::Struct, intern: &'a Interner) -> Struct<'a> {

crates/cli-support/src/descriptor.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ tys! {
3434
EXTERNREF
3535
NAMED_EXTERNREF
3636
ENUM
37+
STRING_ENUM
3738
RUST_STRUCT
3839
CHAR
3940
OPTIONAL
@@ -67,7 +68,16 @@ pub enum Descriptor {
6768
String,
6869
Externref,
6970
NamedExternref(String),
70-
Enum { name: String, hole: u32 },
71+
Enum {
72+
name: String,
73+
hole: u32,
74+
},
75+
StringEnum {
76+
name: String,
77+
invalid: u32,
78+
hole: u32,
79+
variant_values: Vec<String>,
80+
},
7181
RustStruct(String),
7282
Char,
7383
Option(Box<Descriptor>),
@@ -156,6 +166,19 @@ impl Descriptor {
156166
let hole = get(data);
157167
Descriptor::Enum { name, hole }
158168
}
169+
STRING_ENUM => {
170+
let name = get_string(data);
171+
let variant_count = get(data);
172+
let invalid = variant_count;
173+
let hole = variant_count + 1;
174+
let variant_values = (0..variant_count).map(|_| get_string(data)).collect();
175+
Descriptor::StringEnum {
176+
name,
177+
invalid,
178+
hole,
179+
variant_values,
180+
}
181+
}
159182
RUST_STRUCT => {
160183
let name = get_string(data);
161184
Descriptor::RustStruct(name)

0 commit comments

Comments
 (0)