Skip to content

Commit 90cb201

Browse files
committed
Overhaul how type information gets to the CLI
This commit is a complete overhaul of how the `#[wasm_bindgen]` macro communicates type information to the CLI tool, and it's done in a somewhat... unconventional fashion. Today we've got a problem where the generated JS needs to understand the types of each function exported or imported. This understanding is what enables it to generate the appropriate JS wrappers and such. We want to, however, be quite flexible and extensible in types that are supported across the boundary, which means that internally we rely on the trait system to resolve what's what. Communicating the type information historically was done by creating a four byte "descriptor" and using associated type projections to communicate that to the CLI tool. Unfortunately four bytes isn't a lot of space to cram information like arguments to a generic function, tuple types, etc. In general this just wasn't flexible enough and the way custom references were treated was also already a bit of a hack. This commit takes a radical step of creating a **descriptor function** for each function imported/exported. The really crazy part is that the `wasm-bindgen` CLI tool now embeds a wasm interpreter and executes these functions when the CLI tool is invoked. By allowing arbitrary functions to get executed it's now *much* easier to inform `wasm-bindgen` about complicated structures of types. Rest assured though that all these descriptor functions are automatically unexported and gc'd away, so this should not have any impact on binary sizes A new internal trait, `WasmDescribe`, is added to represent a description of all types, sort of like a serialization of the structure of a type that `wasm-bindgen` can understand. This works by calling a special exported function with a `u32` value a bunch of times. This means that when we run a descriptor we effectively get a `Vec<u32>` in the `wasm-bindgen` CLI tool. This list of integers can then be parsed into a rich `enum` for the JS generation to work with. This commit currently only retains feature parity with the previous implementation. I hope to soon solve issues like #123, #104, and #111 with this support.
1 parent eb9a652 commit 90cb201

File tree

15 files changed

+1167
-921
lines changed

15 files changed

+1167
-921
lines changed

crates/backend/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ quote = '0.5'
1515
proc-macro2 = { version = "0.3", features = ["nightly"] }
1616
wasm-bindgen-shared = { path = "../shared", version = "=0.2.2" }
1717
syn = { version = '0.13', features = ['full'] }
18+
serde_json = "1.0"

crates/backend/src/ast.rs

Lines changed: 117 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
use literal::{self, Literal};
2-
use proc_macro2::Span;
31
use quote::{ToTokens, Tokens};
42
use shared;
53
use syn;
@@ -404,22 +402,14 @@ impl Program {
404402
})
405403
}
406404

407-
pub fn literal(&self, dst: &mut Tokens) -> usize {
408-
let mut tmp = Tokens::new();
409-
let cnt = {
410-
let mut a = literal::LiteralBuilder::new(&mut tmp);
411-
Literal::literal(self, &mut a);
412-
a.finish()
413-
};
414-
let cnt = cnt as u32;
415-
(quote! {
416-
(#cnt >> 0) as u8,
417-
(#cnt >> 8) as u8,
418-
(#cnt >> 16) as u8,
419-
(#cnt >> 24) as u8
420-
}).to_tokens(dst);
421-
tmp.to_tokens(dst);
422-
(cnt as usize) + 4
405+
pub fn shared(&self) -> shared::Program {
406+
shared::Program {
407+
exports: self.exports.iter().map(|a| a.shared()).collect(),
408+
enums: self.enums.iter().map(|a| a.shared()).collect(),
409+
imports: self.imports.iter().map(|a| a.shared()).collect(),
410+
version: shared::version(),
411+
schema_version: shared::SCHEMA_VERSION.to_string(),
412+
}
423413
}
424414
}
425415

@@ -511,6 +501,12 @@ impl Function {
511501
mutable,
512502
)
513503
}
504+
505+
fn shared(&self) -> shared::Function {
506+
shared::Function {
507+
name: self.name.as_ref().to_string(),
508+
}
509+
}
514510
}
515511

516512
pub fn extract_path_ident(path: &syn::Path) -> Option<syn::Ident> {
@@ -539,14 +535,59 @@ impl Export {
539535
syn::Ident::from(generated_name)
540536
}
541537

542-
pub fn export_name(&self) -> syn::LitStr {
543-
let name = match self.class {
538+
pub fn export_name(&self) -> String {
539+
match self.class {
544540
Some(class) => {
545541
shared::struct_function_export_name(class.as_ref(), self.function.name.as_ref())
546542
}
547543
None => shared::free_function_export_name(self.function.name.as_ref()),
548-
};
549-
syn::LitStr::new(&name, Span::call_site())
544+
}
545+
}
546+
547+
fn shared(&self) -> shared::Export {
548+
shared::Export {
549+
class: self.class.map(|s| s.as_ref().to_string()),
550+
method: self.method,
551+
function: self.function.shared(),
552+
}
553+
}
554+
}
555+
556+
impl Enum {
557+
fn shared(&self) -> shared::Enum {
558+
shared::Enum {
559+
name: self.name.as_ref().to_string(),
560+
variants: self.variants.iter().map(|v| v.shared()).collect(),
561+
}
562+
}
563+
}
564+
565+
impl Variant {
566+
fn shared(&self) -> shared::EnumVariant {
567+
shared::EnumVariant {
568+
name: self.name.as_ref().to_string(),
569+
value: self.value,
570+
}
571+
}
572+
}
573+
574+
impl Import {
575+
fn shared(&self) -> shared::Import {
576+
shared::Import {
577+
module: self.module.clone(),
578+
js_namespace: self.js_namespace.map(|s| s.as_ref().to_string()),
579+
kind: self.kind.shared(),
580+
}
581+
}
582+
}
583+
584+
impl ImportKind {
585+
fn shared(&self) -> shared::ImportKind {
586+
match *self {
587+
ImportKind::Function(ref f) => shared::ImportKind::Function(f.shared()),
588+
ImportKind::Static(ref f) => shared::ImportKind::Static(f.shared()),
589+
ImportKind::Type(ref f) => shared::ImportKind::Type(f.shared()),
590+
}
550591
}
551592
}
552593

@@ -560,6 +601,60 @@ impl ImportFunction {
560601
assert!(name.starts_with("set_"), "setters must start with `set_`");
561602
name[4..].to_string()
562603
}
604+
605+
fn shared(&self) -> shared::ImportFunction {
606+
let mut method = false;
607+
let mut js_new = false;
608+
let mut class_name = None;
609+
match self.kind {
610+
ImportFunctionKind::Method { ref class, .. } => {
611+
method = true;
612+
class_name = Some(class);
613+
}
614+
ImportFunctionKind::JsConstructor { ref class, .. } => {
615+
js_new = true;
616+
class_name = Some(class);
617+
}
618+
ImportFunctionKind::Normal => {}
619+
}
620+
let mut getter = None;
621+
let mut setter = None;
622+
623+
if let Some(s) = self.function.opts.getter() {
624+
let s = s.map(|s| s.to_string());
625+
getter = Some(s.unwrap_or_else(|| self.infer_getter_property()));
626+
}
627+
if let Some(s) = self.function.opts.setter() {
628+
let s = s.map(|s| s.to_string());
629+
setter = Some(s.unwrap_or_else(|| self.infer_setter_property()));
630+
}
631+
shared::ImportFunction {
632+
shim: self.shim.as_ref().to_string(),
633+
catch: self.function.opts.catch(),
634+
method,
635+
js_new,
636+
structural: self.function.opts.structural(),
637+
getter,
638+
setter,
639+
class: class_name.cloned(),
640+
function: self.function.shared(),
641+
}
642+
}
643+
}
644+
645+
impl ImportStatic {
646+
fn shared(&self) -> shared::ImportStatic {
647+
shared::ImportStatic {
648+
name: self.js_name.as_ref().to_string(),
649+
shim: self.shim.as_ref().to_string(),
650+
}
651+
}
652+
}
653+
654+
impl ImportType {
655+
fn shared(&self) -> shared::ImportType {
656+
shared::ImportType { }
657+
}
563658
}
564659

565660
impl Struct {

0 commit comments

Comments
 (0)