diff --git a/crates/backend/Cargo.toml b/crates/backend/Cargo.toml index f9a3edab..a4ea93bc 100644 --- a/crates/backend/Cargo.toml +++ b/crates/backend/Cargo.toml @@ -15,3 +15,4 @@ quote = '0.5' proc-macro2 = { version = "0.3", features = ["nightly"] } wasm-bindgen-shared = { path = "../shared", version = "=0.2.2" } syn = { version = '0.13', features = ['full'] } +serde_json = "1.0" diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index 3b945c0b..b29f140d 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -1,5 +1,3 @@ -use literal::{self, Literal}; -use proc_macro2::Span; use quote::{ToTokens, Tokens}; use shared; use syn; @@ -404,22 +402,14 @@ impl Program { }) } - pub fn literal(&self, dst: &mut Tokens) -> usize { - let mut tmp = Tokens::new(); - let cnt = { - let mut a = literal::LiteralBuilder::new(&mut tmp); - Literal::literal(self, &mut a); - a.finish() - }; - let cnt = cnt as u32; - (quote! { - (#cnt >> 0) as u8, - (#cnt >> 8) as u8, - (#cnt >> 16) as u8, - (#cnt >> 24) as u8 - }).to_tokens(dst); - tmp.to_tokens(dst); - (cnt as usize) + 4 + pub fn shared(&self) -> shared::Program { + shared::Program { + exports: self.exports.iter().map(|a| a.shared()).collect(), + enums: self.enums.iter().map(|a| a.shared()).collect(), + imports: self.imports.iter().map(|a| a.shared()).collect(), + version: shared::version(), + schema_version: shared::SCHEMA_VERSION.to_string(), + } } } @@ -511,6 +501,12 @@ impl Function { mutable, ) } + + fn shared(&self) -> shared::Function { + shared::Function { + name: self.name.as_ref().to_string(), + } + } } pub fn extract_path_ident(path: &syn::Path) -> Option { @@ -539,14 +535,59 @@ impl Export { syn::Ident::from(generated_name) } - pub fn export_name(&self) -> syn::LitStr { - let name = match self.class { + pub fn export_name(&self) -> String { + match self.class { Some(class) => { shared::struct_function_export_name(class.as_ref(), self.function.name.as_ref()) } None => shared::free_function_export_name(self.function.name.as_ref()), - }; - syn::LitStr::new(&name, Span::call_site()) + } + } + + fn shared(&self) -> shared::Export { + shared::Export { + class: self.class.map(|s| s.as_ref().to_string()), + method: self.method, + function: self.function.shared(), + } + } +} + +impl Enum { + fn shared(&self) -> shared::Enum { + shared::Enum { + name: self.name.as_ref().to_string(), + variants: self.variants.iter().map(|v| v.shared()).collect(), + } + } +} + +impl Variant { + fn shared(&self) -> shared::EnumVariant { + shared::EnumVariant { + name: self.name.as_ref().to_string(), + value: self.value, + } + } +} + +impl Import { + fn shared(&self) -> shared::Import { + shared::Import { + module: self.module.clone(), + js_namespace: self.js_namespace.map(|s| s.as_ref().to_string()), + kind: self.kind.shared(), + } + } +} + +impl ImportKind { + fn shared(&self) -> shared::ImportKind { + match *self { + ImportKind::Function(ref f) => shared::ImportKind::Function(f.shared()), + ImportKind::Static(ref f) => shared::ImportKind::Static(f.shared()), + ImportKind::Type(ref f) => shared::ImportKind::Type(f.shared()), + } } } @@ -560,6 +601,60 @@ impl ImportFunction { assert!(name.starts_with("set_"), "setters must start with `set_`"); name[4..].to_string() } + + fn shared(&self) -> shared::ImportFunction { + let mut method = false; + let mut js_new = false; + let mut class_name = None; + match self.kind { + ImportFunctionKind::Method { ref class, .. } => { + method = true; + class_name = Some(class); + } + ImportFunctionKind::JsConstructor { ref class, .. } => { + js_new = true; + class_name = Some(class); + } + ImportFunctionKind::Normal => {} + } + let mut getter = None; + let mut setter = None; + + if let Some(s) = self.function.opts.getter() { + let s = s.map(|s| s.to_string()); + getter = Some(s.unwrap_or_else(|| self.infer_getter_property())); + } + if let Some(s) = self.function.opts.setter() { + let s = s.map(|s| s.to_string()); + setter = Some(s.unwrap_or_else(|| self.infer_setter_property())); + } + shared::ImportFunction { + shim: self.shim.as_ref().to_string(), + catch: self.function.opts.catch(), + method, + js_new, + structural: self.function.opts.structural(), + getter, + setter, + class: class_name.cloned(), + function: self.function.shared(), + } + } +} + +impl ImportStatic { + fn shared(&self) -> shared::ImportStatic { + shared::ImportStatic { + name: self.js_name.as_ref().to_string(), + shim: self.shim.as_ref().to_string(), + } + } +} + +impl ImportType { + fn shared(&self) -> shared::ImportType { + shared::ImportType { } + } } impl Struct { diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs index 9841c006..297c4b84 100644 --- a/crates/backend/src/codegen.rs +++ b/crates/backend/src/codegen.rs @@ -4,8 +4,9 @@ use std::env; use std::sync::atomic::{ATOMIC_USIZE_INIT, AtomicUsize, Ordering}; use ast; +use proc_macro2::Span; use quote::{ToTokens, Tokens}; -use proc_macro2::Literal; +use serde_json; use shared; use syn; @@ -50,6 +51,7 @@ impl ToTokens for ast::Program { } _ => i.kind.to_tokens(tokens), } + DescribeImport(&i.kind).to_tokens(tokens); } for e in self.enums.iter() { e.to_tokens(tokens); @@ -73,14 +75,26 @@ impl ToTokens for ast::Program { ); let generated_static_name = syn::Ident::from(generated_static_name); - let mut generated_static_value = Tokens::new(); - let generated_static_length = self.literal(&mut generated_static_value); + let description = serde_json::to_string(&self.shared()).unwrap(); + + // Each JSON blob is prepended with the length of the JSON blob so when + // all these sections are concatenated in the final wasm file we know + // how to extract all the JSON pieces, so insert the byte length here. + let generated_static_length = description.len() + 4; + let mut bytes = vec![ + (description.len() >> 0) as u8, + (description.len() >> 8) as u8, + (description.len() >> 16) as u8, + (description.len() >> 24) as u8, + ]; + bytes.extend_from_slice(description.as_bytes()); + let generated_static_value = syn::LitByteStr::new(&bytes, Span::call_site()); (quote! { #[allow(non_upper_case_globals)] #[wasm_custom_section = "__wasm_bindgen_unstable"] const #generated_static_name: [u8; #generated_static_length] = - [#generated_static_value]; + *#generated_static_value; }).to_tokens(tokens); } } @@ -88,18 +102,22 @@ impl ToTokens for ast::Program { impl ToTokens for ast::Struct { fn to_tokens(&self, tokens: &mut Tokens) { let name = &self.name; + let name_len = name.as_ref().len() as u32; + let name_chars = name.as_ref().chars().map(|c| c as u32); let new_fn = syn::Ident::from(shared::new_function(self.name.as_ref())); let free_fn = syn::Ident::from(shared::free_function(self.name.as_ref())); - let c = shared::name_to_descriptor(name.as_ref()); - let descriptor = Literal::byte_string(format!("{:4}", c).as_bytes()); - let borrowed_descriptor = Literal::byte_string(format!("{:4}", c + 1).as_bytes()); (quote! { + impl ::wasm_bindgen::describe::WasmDescribe for #name { + fn describe() { + use wasm_bindgen::describe::*; + inform(RUST_STRUCT); + inform(#name_len); + #(inform(#name_chars);)* + } + } + impl ::wasm_bindgen::convert::WasmBoundary for #name { type Abi = u32; - const DESCRIPTOR: ::wasm_bindgen::convert::Descriptor = - ::wasm_bindgen::convert::Descriptor { - __x: *#descriptor - }; fn into_abi(self, _extra: &mut ::wasm_bindgen::convert::Stack) -> u32 @@ -120,10 +138,6 @@ impl ToTokens for ast::Struct { impl ::wasm_bindgen::convert::FromRefWasmBoundary for #name { type Abi = u32; - const DESCRIPTOR: ::wasm_bindgen::convert::Descriptor = - ::wasm_bindgen::convert::Descriptor { - __x: *#borrowed_descriptor - }; type RefAnchor = ::wasm_bindgen::__rt::Ref<'static, #name>; unsafe fn from_abi_ref( @@ -138,10 +152,6 @@ impl ToTokens for ast::Struct { impl ::wasm_bindgen::convert::FromRefMutWasmBoundary for #name { type Abi = u32; - const DESCRIPTOR: ::wasm_bindgen::convert::Descriptor = - ::wasm_bindgen::convert::Descriptor { - __x: *#borrowed_descriptor - }; type RefAnchor = ::wasm_bindgen::__rt::RefMut<'static, #name>; unsafe fn from_abi_ref_mut( @@ -273,6 +283,15 @@ impl ToTokens for ast::Export { convert_ret = quote!{}; } } + let describe_ret = match self.function.ret { + Some(ast::Type { ref ty, .. }) => { + quote! { + inform(1); + <#ty as WasmDescribe>::describe(); + } + } + None => quote! { inform(0); }, + }; let name = self.function.name; let receiver = match self.class { @@ -286,6 +305,10 @@ impl ToTokens for ast::Export { Some(class) => quote! { #class::#name }, None => quote!{ #name }, }; + let descriptor_name = format!("__wbindgen_describe_{}", export_name); + let descriptor_name = syn::Ident::from(descriptor_name); + let nargs = self.function.arguments.len() as u32; + let argtys = self.function.arguments.iter(); let tokens = quote! { #[export_name = #export_name] @@ -301,11 +324,46 @@ impl ToTokens for ast::Export { }; #convert_ret } + + // In addition to generating the shim function above which is what + // our generated JS will invoke, we *also* generate a "descriptor" + // shim. This descriptor shim uses the `WasmDescribe` trait to + // programmatically describe the type signature of the generated + // shim above. This in turn is then used to inform the + // `wasm-bindgen` CLI tool exactly what types and such it should be + // using in JS. + // + // Note that this descriptor function is a purely an internal detail + // of `#[wasm_bindgen]` and isn't intended to be exported to anyone + // or actually part of the final was binary. Additionally, this is + // literally executed when the `wasm-bindgen` tool executes. + // + // In any case, there's complications in `wasm-bindgen` to handle + // this, but the tl;dr; is that this is stripped from the final wasm + // binary along with anything it references. + #[no_mangle] + pub extern fn #descriptor_name() { + use wasm_bindgen::describe::*; + inform(FUNCTION); + inform(#nargs); + #(<#argtys as WasmDescribe>::describe();)* + #describe_ret + } }; tokens.to_tokens(into); } } +impl ToTokens for ast::ImportKind { + fn to_tokens(&self, tokens: &mut Tokens) { + match *self { + ast::ImportKind::Function(ref f) => f.to_tokens(tokens), + ast::ImportKind::Static(ref s) => s.to_tokens(tokens), + ast::ImportKind::Type(ref t) => t.to_tokens(tokens), + } + } +} + impl ToTokens for ast::ImportType { fn to_tokens(&self, tokens: &mut Tokens) { let vis = &self.vis; @@ -316,12 +374,15 @@ impl ToTokens for ast::ImportType { obj: ::wasm_bindgen::JsValue, } + impl ::wasm_bindgen::describe::WasmDescribe for #name { + fn describe() { + ::wasm_bindgen::JsValue::describe(); + } + } + impl ::wasm_bindgen::convert::WasmBoundary for #name { type Abi = <::wasm_bindgen::JsValue as ::wasm_bindgen::convert::WasmBoundary>::Abi; - const DESCRIPTOR: ::wasm_bindgen::convert::Descriptor = - <::wasm_bindgen::JsValue as ::wasm_bindgen::convert::WasmBoundary> - ::DESCRIPTOR; fn into_abi(self, extra: &mut ::wasm_bindgen::convert::Stack) -> Self::Abi { self.obj.into_abi(extra) @@ -338,9 +399,6 @@ impl ToTokens for ast::ImportType { impl ::wasm_bindgen::convert::ToRefWasmBoundary for #name { type Abi = <::wasm_bindgen::JsValue as ::wasm_bindgen::convert::ToRefWasmBoundary>::Abi; - const DESCRIPTOR: ::wasm_bindgen::convert::Descriptor = - <::wasm_bindgen::JsValue as ::wasm_bindgen::convert::ToRefWasmBoundary> - ::DESCRIPTOR; fn to_abi_ref(&self, extra: &mut ::wasm_bindgen::convert::Stack) -> u32 { self.obj.to_abi_ref(extra) @@ -350,9 +408,6 @@ impl ToTokens for ast::ImportType { impl ::wasm_bindgen::convert::FromRefWasmBoundary for #name { type Abi = <::wasm_bindgen::JsValue as ::wasm_bindgen::convert::ToRefWasmBoundary>::Abi; - const DESCRIPTOR: ::wasm_bindgen::convert::Descriptor = - <::wasm_bindgen::JsValue as ::wasm_bindgen::convert::ToRefWasmBoundary> - ::DESCRIPTOR; type RefAnchor = ::std::mem::ManuallyDrop<#name>; unsafe fn from_abi_ref( @@ -381,16 +436,6 @@ impl ToTokens for ast::ImportType { } } -impl ToTokens for ast::ImportKind { - fn to_tokens(&self, tokens: &mut Tokens) { - match *self { - ast::ImportKind::Function(ref f) => f.to_tokens(tokens), - ast::ImportKind::Static(ref s) => s.to_tokens(tokens), - ast::ImportKind::Type(ref t) => t.to_tokens(tokens), - } - } -} - impl ToTokens for ast::ImportFunction { fn to_tokens(&self, tokens: &mut Tokens) { let mut class_ty = None; @@ -552,6 +597,7 @@ impl ToTokens for ast::ImportFunction { #convert_ret } } + }; if let Some(class) = class_ty { @@ -566,36 +612,52 @@ impl ToTokens for ast::ImportFunction { } } +// See comment above in ast::Export for what's going on here. +struct DescribeImport<'a>(&'a ast::ImportKind); + +impl<'a> ToTokens for DescribeImport<'a> { + fn to_tokens(&self, tokens: &mut Tokens) { + let f = match *self.0 { + ast::ImportKind::Function(ref f) => f, + ast::ImportKind::Static(_) => return, + ast::ImportKind::Type(_) => return, + }; + let describe_name = format!("__wbindgen_describe_{}", f.shim); + let describe_name = syn::Ident::from(describe_name); + let argtys = f.function.arguments.iter(); + let nargs = f.function.arguments.len() as u32; + let inform_ret = match f.function.ret { + Some(ref t) => quote! { inform(1); <#t as WasmDescribe>::describe(); }, + None => quote! { inform(0); }, + }; + (quote! { + #[no_mangle] + #[allow(non_snake_case)] + pub extern fn #describe_name() { + use wasm_bindgen::describe::*; + inform(FUNCTION); + inform(#nargs); + #(<#argtys as WasmDescribe>::describe();)* + #inform_ret + } + }).to_tokens(tokens); + } +} + impl ToTokens for ast::Enum { fn to_tokens(&self, into: &mut Tokens) { let enum_name = &self.name; - let descriptor = format!("{:4}", shared::TYPE_ENUM); - let descriptor = Literal::byte_string(descriptor.as_bytes()); - let incoming_u32 = quote! { n }; - let enum_name_as_string = enum_name.to_string(); let cast_clauses = self.variants.iter().map(|variant| { let variant_name = &variant.name; quote! { - if #incoming_u32 == #enum_name::#variant_name as u32 { + if js == #enum_name::#variant_name as u32 { #enum_name::#variant_name } } }); (quote! { - impl #enum_name { - fn from_u32(#incoming_u32: u32) -> #enum_name { - #(#cast_clauses else)* { - wasm_bindgen::throw(&format!("Could not cast {} as {}", #incoming_u32, #enum_name_as_string)); - } - } - } - impl ::wasm_bindgen::convert::WasmBoundary for #enum_name { type Abi = u32; - const DESCRIPTOR: ::wasm_bindgen::convert::Descriptor = - ::wasm_bindgen::convert::Descriptor { - __x: *#descriptor, - }; fn into_abi(self, _extra: &mut ::wasm_bindgen::convert::Stack) -> u32 { self as u32 @@ -605,7 +667,16 @@ impl ToTokens for ast::Enum { js: u32, _extra: &mut ::wasm_bindgen::convert::Stack, ) -> Self { - #enum_name::from_u32(js) + #(#cast_clauses else)* { + wasm_bindgen::throw("invalid enum value passed") + } + } + } + + impl ::wasm_bindgen::describe::WasmDescribe for #enum_name { + fn describe() { + use wasm_bindgen::describe::*; + inform(ENUM); } } }).to_tokens(into); @@ -641,3 +712,19 @@ impl ToTokens for ast::ImportStatic { }).to_tokens(into); } } + +impl ToTokens for ast::Type { + fn to_tokens(&self, into: &mut Tokens) { + match self.kind { + ast::TypeKind::ByValue => {} + ast::TypeKind::ByRef => { + syn::token::And::default().to_tokens(into); + } + ast::TypeKind::ByMutRef => { + syn::token::And::default().to_tokens(into); + syn::token::Mut::default().to_tokens(into); + } + } + self.ty.to_tokens(into); + } +} diff --git a/crates/backend/src/lib.rs b/crates/backend/src/lib.rs index 89fb6775..ab9d46ac 100644 --- a/crates/backend/src/lib.rs +++ b/crates/backend/src/lib.rs @@ -5,9 +5,9 @@ extern crate proc_macro2; extern crate quote; #[macro_use] extern crate syn; +extern crate serde_json; extern crate wasm_bindgen_shared as shared; pub mod ast; mod codegen; -mod literal; diff --git a/crates/backend/src/literal.rs b/crates/backend/src/literal.rs deleted file mode 100644 index 24e6d06e..00000000 --- a/crates/backend/src/literal.rs +++ /dev/null @@ -1,296 +0,0 @@ -use ast; -use quote::{ToTokens, Tokens}; -use shared; -use std::collections::BTreeSet; - -pub struct LiteralBuilder<'a> { - dst: &'a mut Tokens, - cnt: usize, -} - -impl<'a> LiteralBuilder<'a> { - pub fn new(dst: &'a mut Tokens) -> LiteralBuilder<'a> { - LiteralBuilder { dst, cnt: 0 } - } - - pub fn finish(self) -> usize { - self.cnt - } - - fn byte(&mut self, b: u8) { - ::syn::token::Comma::default().to_tokens(self.dst); - self.cnt += 1; - b.to_tokens(self.dst); - } - - fn append(&mut self, s: &str) { - for &b in s.as_bytes() { - self.byte(b); - } - } - - fn str(&mut self, s: &str) { - self.append("\""); - self.append(s); - self.append("\""); - } - - fn bool(&mut self, v: bool) { - if v { - self.append("true") - } else { - self.append("false") - } - } - - fn u32(&mut self, s: u32) { - self.append(&s.to_string()) - } - - fn as_char(&mut self, tokens: Tokens) { - (quote! { - ,(#tokens).__x[0] - ,(#tokens).__x[1] - ,(#tokens).__x[2] - ,(#tokens).__x[3] - }).to_tokens(self.dst); - self.cnt += 4; - } - - pub fn fields(&mut self, fields: &[(&str, &Fn(&mut Self))]) { - self.append("{"); - for (i, &(field, cb)) in fields.iter().enumerate() { - if i > 0 { - self.append(","); - } - self.str(field); - self.append(":"); - cb(self); - } - self.append("}"); - } - - pub fn list_of<'b, T, U>(&mut self, list: T) - where - T: IntoIterator, - U: 'b + Literal, - { - self.list(list, U::literal) - } - - fn list(&mut self, list: T, mut cb: F) - where - F: FnMut(T::Item, &mut Self), - T: IntoIterator, - { - self.append("["); - for (i, element) in list.into_iter().enumerate() { - if i > 0 { - self.append(","); - } - cb(element, self); - } - self.append("]"); - } -} - -pub trait Literal { - fn literal(&self, a: &mut LiteralBuilder); -} - -impl Literal for ast::Program { - fn literal(&self, a: &mut LiteralBuilder) { - a.fields(&[ - ("exports", &|a| a.list_of(&self.exports)), - ("imports", &|a| a.list_of(&self.imports)), - ("enums", &|a| a.list_of(&self.enums)), - ("custom_type_names", &|a| { - let names = self.exports - .iter() - .filter_map(|e| e.class) - .chain(self.structs.iter().map(|s| s.name)) - .collect::>(); - a.list(&names, |s, a| { - let val = shared::name_to_descriptor(s.as_ref()); - a.fields(&[ - ("descriptor", &|a| a.u32(val)), - ("name", &|a| a.str(s.as_ref())), - ]); - }) - }), - ("version", &|a| a.str(&shared::version())), - ("schema_version", &|a| a.str(&shared::SCHEMA_VERSION)), - ]); - } -} - -impl Literal for ast::Function { - fn literal(&self, a: &mut LiteralBuilder) { - a.fields(&[ - ("name", &|a| a.str(self.name.as_ref())), - ("arguments", &|a| a.list_of(&self.arguments)), - ("ret", &|a| match self.ret { - Some(ref s) => s.literal(a), - None => a.append("null"), - }), - ]); - } -} - -impl Literal for ast::Type { - fn literal(&self, a: &mut LiteralBuilder) { - let t = &self.ty; - match self.kind { - ast::TypeKind::ByValue => { - a.as_char(quote! { - <#t as ::wasm_bindgen::convert::WasmBoundary>::DESCRIPTOR - }); - } - ast::TypeKind::ByRef | - ast::TypeKind::ByMutRef => { - match self.loc { - ast::TypeLocation::ImportArgument | - ast::TypeLocation::ExportRet => { - a.as_char(quote! { - <#t as ::wasm_bindgen::convert::ToRefWasmBoundary> - ::DESCRIPTOR - }); - } - ast::TypeLocation::ImportRet | - ast::TypeLocation::ExportArgument => { - a.as_char(quote! { - <#t as ::wasm_bindgen::convert::FromRefWasmBoundary> - ::DESCRIPTOR - }); - } - } - } - } - } -} - -impl Literal for ast::Export { - fn literal(&self, a: &mut LiteralBuilder) { - a.fields(&[ - ("class", &|a| match self.class { - Some(ref s) => a.str(s.as_ref()), - None => a.append("null"), - }), - ("method", &|a| a.bool(self.method)), - ("function", &|a| self.function.literal(a)), - ]); - } -} - -impl Literal for ast::Import { - fn literal(&self, a: &mut LiteralBuilder) { - a.fields(&[ - ("module", &|a| match self.module { - Some(ref s) => a.str(s), - None => a.append("null"), - }), - ("js_namespace", &|a| match self.js_namespace { - Some(ref s) => a.str(s.as_ref()), - None => a.append("null"), - }), - ("kind", &|a| self.kind.literal(a)), - ]); - } -} - -impl Literal for ast::ImportKind { - fn literal(&self, a: &mut LiteralBuilder) { - match *self { - ast::ImportKind::Function(ref f) => f.literal(a), - ast::ImportKind::Static(ref s) => s.literal(a), - ast::ImportKind::Type(ref t) => t.literal(a), - } - } -} - -impl Literal for ast::ImportFunction { - fn literal(&self, a: &mut LiteralBuilder) { - let mut method = false; - let mut js_new = false; - let mut class_name = None; - match self.kind { - ast::ImportFunctionKind::Method { ref class, .. } => { - method = true; - class_name = Some(class); - } - ast::ImportFunctionKind::JsConstructor { ref class, .. } => { - js_new = true; - class_name = Some(class); - } - ast::ImportFunctionKind::Normal => {} - } - - let mut getter = None; - let mut setter = None; - let structural = self.function.opts.structural(); - - if let Some(s) = self.function.opts.getter() { - let s = s.map(|s| s.to_string()); - getter = Some(s.unwrap_or_else(|| self.infer_getter_property())); - } - if let Some(s) = self.function.opts.setter() { - let s = s.map(|s| s.to_string()); - setter = Some(s.unwrap_or_else(|| self.infer_setter_property())); - } - a.fields(&[ - ("kind", &|a| a.str("function")), - ("catch", &|a| a.bool(self.function.opts.catch())), - ("method", &|a| a.bool(method)), - ("js_new", &|a| a.bool(js_new)), - ("structural", &|a| a.bool(structural)), - ("shim", &|a| a.str(self.shim.as_ref())), - ("getter", &|a| match getter { - Some(ref s) => a.str(s), - None => a.append("null"), - }), - ("setter", &|a| match setter { - Some(ref s) => a.str(s), - None => a.append("null"), - }), - ("function", &|a| self.function.literal(a)), - ("class", &|a| match class_name { - Some(s) => a.str(s), - None => a.append("null"), - }), - ]); - } -} - -impl Literal for ast::Enum { - fn literal(&self, a: &mut LiteralBuilder) { - a.fields(&[ - ("name", &|a| a.str(self.name.as_ref())), - ("variants", &|a| a.list_of(&self.variants)), - ]); - } -} - -impl Literal for ast::Variant { - fn literal(&self, a: &mut LiteralBuilder) { - a.fields(&[ - ("name", &|a| a.str(self.name.as_ref())), - ("value", &|a| a.append(&format!("{}", self.value))), - ]) - } -} - -impl Literal for ast::ImportStatic { - fn literal(&self, a: &mut LiteralBuilder) { - a.fields(&[ - ("kind", &|a| a.str("static")), - ("name", &|a| a.str(self.js_name.as_ref())), - ("shim", &|a| a.str(self.shim.as_ref())), - ]) - } -} - -impl Literal for ast::ImportType { - fn literal(&self, a: &mut LiteralBuilder) { - a.fields(&[("kind", &|a| a.str("type"))]) - } -} diff --git a/crates/cli-support/Cargo.toml b/crates/cli-support/Cargo.toml index 4845362d..60f7571b 100644 --- a/crates/cli-support/Cargo.toml +++ b/crates/cli-support/Cargo.toml @@ -16,3 +16,4 @@ parity-wasm = "0.27" serde_json = "1.0" wasm-bindgen-shared = { path = "../shared", version = '=0.2.2' } wasm-gc-api = "0.1" +wasmi = "0.1" diff --git a/crates/cli-support/src/descriptor.rs b/crates/cli-support/src/descriptor.rs new file mode 100644 index 00000000..5d590cc8 --- /dev/null +++ b/crates/cli-support/src/descriptor.rs @@ -0,0 +1,285 @@ +use std::char; + +macro_rules! tys { + ($($a:ident)*) => (tys! { @ ($($a)*) 0 }); + (@ () $v:expr) => {}; + (@ ($a:ident $($b:ident)*) $v:expr) => { + const $a: u32 = $v; + tys!(@ ($($b)*) $v+1); + } +} + +// NB: this list must be kept in sync with `src/describe.rs` +tys! { + I8 + U8 + I16 + U16 + I32 + U32 + I64 + U64 + F32 + F64 + BOOLEAN + FUNCTION + CLOSURE + STRING + REF + REFMUT + SLICE + VECTOR + ANYREF + ENUM + RUST_STRUCT +} + +#[derive(Debug)] +pub enum Descriptor { + I8, + U8, + I16, + U16, + I32, + U32, + I64, + U64, + F32, + F64, + Boolean, + Function(Box), + Closure(Box), + Ref(Box), + RefMut(Box), + Slice(Box), + Vector(Box), + String, + Anyref, + Enum, + RustStruct(String), +} + +#[derive(Debug)] +pub struct Function { + pub arguments: Vec, + pub ret: Option, +} + +#[derive(Copy, Clone)] +pub enum VectorKind { + I8, + U8, + I16, + U16, + I32, + U32, + F32, + F64, + String, + Anyref, +} + +impl Descriptor { + pub fn decode(mut data: &[u32]) -> Descriptor { + let descriptor = Descriptor::_decode(&mut data); + assert!(data.is_empty()); + descriptor + } + + fn _decode(data: &mut &[u32]) -> Descriptor { + match get(data) { + I8 => Descriptor::I8, + I16 => Descriptor::I16, + I32 => Descriptor::I32, + I64 => Descriptor::I64, + U8 => Descriptor::U8, + U16 => Descriptor::U16, + U32 => Descriptor::U32, + U64 => Descriptor::U64, + F32 => Descriptor::F32, + F64 => Descriptor::F64, + BOOLEAN => Descriptor::Boolean, + FUNCTION => Descriptor::Function(Box::new(Function::decode(data))), + CLOSURE => { + assert_eq!(get(data), FUNCTION); + Descriptor::Closure(Box::new(Function::decode(data))) + } + REF => Descriptor::Ref(Box::new(Descriptor::_decode(data))), + REFMUT => Descriptor::RefMut(Box::new(Descriptor::_decode(data))), + SLICE => Descriptor::Slice(Box::new(Descriptor::_decode(data))), + VECTOR => Descriptor::Vector(Box::new(Descriptor::_decode(data))), + STRING => Descriptor::String, + ANYREF => Descriptor::Anyref, + ENUM => Descriptor::Enum, + RUST_STRUCT => { + let name = (0..get(data)) + .map(|_| char::from_u32(get(data)).unwrap()) + .collect(); + Descriptor::RustStruct(name) + } + other => panic!("unknown descriptor: {}", other), + } + } + + pub fn unwrap_function(&self) -> &Function { + match *self { + Descriptor::Function(ref f) => f, + _ => panic!("not a function"), + } + } + + pub fn is_number(&self) -> bool { + match *self { + Descriptor::I8 | + Descriptor::U8 | + Descriptor::I16 | + Descriptor::U16 | + Descriptor::I32 | + Descriptor::U32 | + Descriptor::I64 | + Descriptor::U64 | + Descriptor::F32 | + Descriptor::F64 | + Descriptor::Enum => true, + _ => return false, + } + } + + pub fn is_ref_anyref(&self) -> bool { + match *self { + Descriptor::Ref(ref s) => s.is_anyref(), + _ => return false, + } + } + + pub fn ref_closure(&self) -> Option<&Function> { + match *self { + Descriptor::Ref(ref s) => s.closure(), + _ => None, + } + } + + pub fn closure(&self) -> Option<&Function> { + match *self { + Descriptor::Closure(ref s) => Some(s), + _ => None, + } + } + + pub fn is_anyref(&self) -> bool { + match *self { + Descriptor::Anyref => true, + _ => false, + } + } + + pub fn vector_kind(&self) -> Option { + let inner = match *self { + Descriptor::String => return Some(VectorKind::String), + Descriptor::Vector(ref d) => &**d, + Descriptor::Ref(ref d) => { + match **d { + Descriptor::Slice(ref d) => &**d, + Descriptor::String => return Some(VectorKind::String), + _ => return None, + } + } + _ => return None, + }; + match *inner { + Descriptor::I8 => Some(VectorKind::I8), + Descriptor::I16 => Some(VectorKind::I16), + Descriptor::I32 => Some(VectorKind::I32), + Descriptor::U8 => Some(VectorKind::U8), + Descriptor::U16 => Some(VectorKind::U16), + Descriptor::U32 => Some(VectorKind::U32), + Descriptor::F32 => Some(VectorKind::F32), + Descriptor::F64 => Some(VectorKind::F64), + Descriptor::Anyref => Some(VectorKind::Anyref), + _ => None + } + } + + pub fn rust_struct(&self) -> Option<&str> { + let inner = match *self { + Descriptor::Ref(ref d) => &**d, + Descriptor::RefMut(ref d) => &**d, + ref d => d, + }; + match *inner { + Descriptor::RustStruct(ref s) => Some(s), + _ => None, + } + } + + pub fn stack_closure(&self) -> Option<&Function> { + let inner = match *self { + Descriptor::Ref(ref d) => &**d, + _ => return None, + }; + match *inner { + Descriptor::Function(ref f) => Some(f), + _ => None, + } + } + + pub fn is_by_ref(&self) -> bool { + match *self { + Descriptor::Ref(_) | + Descriptor::RefMut(_) => true, + _ => false, + } + } +} + +fn get(a: &mut &[u32]) -> u32 { + let ret = a[0]; + *a = &a[1..]; + ret +} + +impl Function { + fn decode(data: &mut &[u32]) -> Function { + let arguments = (0..get(data)) + .map(|_| Descriptor::_decode(data)) + .collect::>(); + let ret = if get(data) == 0 { + None + } else { + Some(Descriptor::_decode(data)) + }; + Function { arguments, ret } + } +} + +impl VectorKind { + pub fn js_ty(&self) -> &str { + match *self { + VectorKind::String => "string", + VectorKind::I8 => "Int8Array", + VectorKind::U8 => "Uint8Array", + VectorKind::I16 => "Int16Array", + VectorKind::U16 => "Uint16Array", + VectorKind::I32 => "Int32Array", + VectorKind::U32 => "Uint32Array", + VectorKind::F32 => "Float32Array", + VectorKind::F64 => "Float64Array", + VectorKind::Anyref => "any[]", + } + } + + pub fn size(&self) -> usize { + match *self { + VectorKind::String => 1, + VectorKind::I8 => 1, + VectorKind::U8 => 1, + VectorKind::I16 => 2, + VectorKind::U16 => 2, + VectorKind::I32 => 4, + VectorKind::U32 => 4, + VectorKind::F32 => 4, + VectorKind::F64 => 8, + VectorKind::Anyref => 4, + } + } +} diff --git a/crates/cli-support/src/js.rs b/crates/cli-support/src/js.rs index 2e678f71..6b00b6b6 100644 --- a/crates/cli-support/src/js.rs +++ b/crates/cli-support/src/js.rs @@ -8,6 +8,7 @@ use shared; use wasm_gc; use super::Bindgen; +use descriptor::{Descriptor, VectorKind}; pub struct Context<'a> { pub globals: String, @@ -18,10 +19,10 @@ pub struct Context<'a> { pub required_internal_exports: HashSet<&'static str>, pub config: &'a Bindgen, pub module: &'a mut Module, - pub custom_type_names: HashMap, pub imported_names: HashSet, pub exported_classes: HashMap, pub function_table_needed: bool, + pub run_descriptor: &'a Fn(&str) -> Vec, } #[derive(Default)] @@ -36,16 +37,6 @@ pub struct SubContext<'a, 'b: 'a> { } impl<'a> Context<'a> { - pub fn add_custom_type_names(&mut self, program: &shared::Program) { - for custom in program.custom_type_names.iter() { - let prev = self.custom_type_names.insert(custom.descriptor, - custom.name.clone()); - if let Some(prev) = prev { - assert_eq!(prev, custom.name); - } - } - } - fn export(&mut self, name: &str, contents: &str) { let contents = contents.trim(); let global = if self.config.nodejs { @@ -794,16 +785,16 @@ impl<'a> Context<'a> { self.expose_get_array_u32_from_wasm(); self.expose_get_object(); self.globals.push_str(&format!(" - function getArrayJsValueFromWasm(ptr, len) {{ - const mem = getUint32Memory(); - const slice = mem.slice(ptr / 4, ptr / 4 + len); - const result = [] - for (ptr in slice) {{ - result.push(getObject(ptr)) - }} - return result; + function getArrayJsValueFromWasm(ptr, len) {{ + const mem = getUint32Memory(); + const slice = mem.slice(ptr / 4, ptr / 4 + len); + const result = []; + for (ptr in slice) {{ + result.push(getObject(ptr)) }} - ")); + return result; + }} + ")); } fn expose_get_array_i8_from_wasm(&mut self) { @@ -1043,26 +1034,24 @@ impl<'a> Context<'a> { }) } - fn custom_type_name(&self, c: u32) -> &str { - let c = c & !shared::TYPE_CUSTOM_REF_FLAG; - &self.custom_type_names[&c] - } - - fn pass_to_wasm_function(&mut self, ty: &VectorType) -> &'static str { - match ty.kind { + fn pass_to_wasm_function(&mut self, t: VectorKind) -> &'static str { + match t { VectorKind::String => { self.expose_pass_string_to_wasm(); "passStringToWasm" } - VectorKind::I8 | VectorKind::U8 => { + VectorKind::I8 | + VectorKind::U8 => { self.expose_pass_array8_to_wasm(); "passArray8ToWasm" } - VectorKind::I16 | VectorKind::U16 => { + VectorKind::U16 | + VectorKind::I16 => { self.expose_pass_array16_to_wasm(); "passArray16ToWasm" } - VectorKind::I32 | VectorKind::U32 => { + VectorKind::I32 | + VectorKind::U32 => { self.expose_pass_array32_to_wasm(); "passArray32ToWasm" } @@ -1074,12 +1063,14 @@ impl<'a> Context<'a> { self.expose_pass_array_f64_to_wasm(); "passArrayF64ToWasm" } - VectorKind::JsValue => panic!("Cannot pass Vec to function") + VectorKind::Anyref => { + panic!("cannot pass list of JsValue to wasm yet") + } } } - fn expose_get_vector_from_wasm(&mut self, ty: &VectorType) -> &'static str { - match ty.kind { + fn expose_get_vector_from_wasm(&mut self, ty: VectorKind) -> &'static str { + match ty { VectorKind::String => { self.expose_get_string_from_wasm(); "getStringFromWasm" @@ -1116,7 +1107,7 @@ impl<'a> Context<'a> { self.expose_get_array_f64_from_wasm(); "getArrayF64FromWasm" } - VectorKind::JsValue => { + VectorKind::Anyref => { self.expose_get_array_js_value_from_wasm(); "getArrayJsValueFromWasm" } @@ -1200,6 +1191,107 @@ impl<'a> Context<'a> { .unwrap(); *self.module = deserialize_buffer(&bytes).unwrap(); } + + fn describe(&self, name: &str) -> Descriptor { + let name = format!("__wbindgen_describe_{}", name); + let ret = (self.run_descriptor)(&name); + Descriptor::decode(&ret) + } + + fn return_from_rust(&mut self, ty: &Option, dst_ts: &mut String) + -> String + { + let ty = match *ty { + Some(ref t) => t, + None => { + dst_ts.push_str(": void"); + return format!("return ret;") + } + }; + + if ty.is_ref_anyref() { + dst_ts.push_str(": any"); + self.expose_get_object(); + return format!("return getObject(ret);") + } + + if ty.is_by_ref() { + panic!("cannot return references from Rust to JS yet") + } + + if let Some(ty) = ty.vector_kind() { + dst_ts.push_str(": "); + dst_ts.push_str(ty.js_ty()); + let f = self.expose_get_vector_from_wasm(ty); + self.expose_get_global_argument(); + self.required_internal_exports.insert("__wbindgen_free"); + return format!(" + const len = getGlobalArgument(0); + const realRet = {}(ret, len); + wasm.__wbindgen_free(ret, len * {}); + return realRet; + ", f, ty.size()) + } + + if let Some(name) = ty.rust_struct() { + dst_ts.push_str(": "); + dst_ts.push_str(name); + return if self.config.debug { + format!("return new {name}(ret, token);", name = name) + } else { + format!("return new {name}(ret);", name = name) + } + } + + if ty.is_number() { + dst_ts.push_str(": number"); + return format!("return ret;") + } + + match *ty { + Descriptor::Boolean => { + dst_ts.push_str(": boolean"); + format!("return ret !== 0;") + } + Descriptor::Anyref => { + dst_ts.push_str(": any"); + self.expose_take_object(); + format!("return takeObject(ret);") + } + _ => panic!("unsupported return from Rust to JS {:?}", ty), + } + } + + fn return_from_js(&mut self, ty: &Option, invoc: &str) -> String { + let ty = match *ty { + Some(ref t) => t, + None => return invoc.to_string(), + }; + if ty.is_by_ref() { + panic!("cannot return a reference from JS to Rust") + } + if let Some(ty) = ty.vector_kind() { + let f = self.pass_to_wasm_function(ty); + self.expose_uint32_memory(); + self.expose_set_global_argument(); + return format!(" + const [retptr, retlen] = {}({}); + setGlobalArgument(retlen, 0); + return retptr; + ", f, invoc) + } + if ty.is_number() { + return format!("return {};", invoc) + } + match *ty { + Descriptor::Boolean => format!("return {} ? 1 : 0;", invoc), + Descriptor::Anyref => { + self.expose_add_heap_object(); + format!("return addHeapObject({});", invoc) + } + _ => panic!("unimplemented return from JS to Rust: {:?}", ty), + } + } } impl<'a, 'b> SubContext<'a, 'b> { @@ -1255,6 +1347,8 @@ impl<'a, 'b> SubContext<'a, 'b> { wasm_name: &str, is_method: bool, function: &shared::Function) -> (String, String) { + let descriptor = self.cx.describe(wasm_name); + let desc_function = descriptor.unwrap_function(); let mut dst = String::from("("); let mut dst_ts = format!("{}(", function.name); let mut passed_args = String::new(); @@ -1266,7 +1360,7 @@ impl<'a, 'b> SubContext<'a, 'b> { } let mut global_idx = 0; - for (i, arg) in function.arguments.iter().enumerate() { + for (i, arg) in desc_function.arguments.iter().enumerate() { let name = format!("arg{}", i); if i > 0 { dst.push_str(", "); @@ -1281,163 +1375,97 @@ impl<'a, 'b> SubContext<'a, 'b> { } passed_args.push_str(arg); }; + + if let Some(kind) = arg.vector_kind() { + dst_ts.push_str(": "); + dst_ts.push_str(kind.js_ty()); + let func = self.cx.pass_to_wasm_function(kind); + self.cx.expose_set_global_argument(); + arg_conversions.push_str(&format!("\ + const [ptr{i}, len{i}] = {func}({arg}); + setGlobalArgument(len{i}, {global_idx}); + ", i = i, func = func, arg = name, global_idx = global_idx)); + global_idx += 1; + pass(&format!("ptr{}", i)); + if arg.is_by_ref() { + destructors.push_str(&format!("\n\ + wasm.__wbindgen_free(ptr{i}, len{i} * {size});\n\ + ", i = i, size = kind.size())); + self.cx.required_internal_exports.insert( + "__wbindgen_free", + ); + } + continue + } + + if let Some(s) = arg.rust_struct() { + dst_ts.push_str(&format!(": {}", s)); + if self.cx.config.debug { + self.cx.expose_assert_class(); + arg_conversions.push_str(&format!("\ + _assertClass({arg}, {struct_}); + ", arg = name, struct_ = s)); + } + + if arg.is_by_ref() { + pass(&format!("{}.ptr", name)); + } else { + arg_conversions.push_str(&format!("\ + const ptr{i} = {arg}.ptr; + {arg}.ptr = 0; + ", i = i, arg = name)); + pass(&format!("ptr{}", i)); + } + continue + } + match *arg { - shared::TYPE_ENUM | shared::TYPE_NUMBER => { + ref d if d.is_number() => { dst_ts.push_str(": number"); if self.cx.config.debug { self.cx.expose_assert_num(); arg_conversions.push_str(&format!("_assertNum({});\n", name)); } - pass(&name) + pass(&name); + continue } - shared::TYPE_BOOLEAN => { + Descriptor::Boolean => { dst_ts.push_str(": boolean"); if self.cx.config.debug { self.cx.expose_assert_bool(); arg_conversions.push_str(&format!("\ _assertBoolean({name}); ", name = name)); - } else {} - pass(&format!("arg{i} ? 1 : 0", i = i)) + } + pass(&format!("arg{i} ? 1 : 0", i = i)); + continue } - shared::TYPE_JS_OWNED => { + Descriptor::Anyref => { dst_ts.push_str(": any"); self.cx.expose_add_heap_object(); - arg_conversions.push_str(&format!("\ - const idx{i} = addHeapObject({arg}); - ", i = i, arg = name)); - pass(&format!("idx{}", i)); + pass(&format!("addHeapObject({})", name)); + continue } - shared::TYPE_JS_REF => { + ref r if r.is_ref_anyref() => { dst_ts.push_str(": any"); self.cx.expose_borrowed_objects(); - arg_conversions.push_str(&format!("\ - const idx{i} = addBorrowedObject({arg}); - ", i = i, arg = name)); destructors.push_str("stack.pop();\n"); - pass(&format!("idx{}", i)); - } - other => { - match VectorType::from(other) { - Some(ty) => { - dst_ts.push_str(": "); - dst_ts.push_str(ty.js_ty()); - let func = self.cx.pass_to_wasm_function(&ty); - self.cx.expose_set_global_argument(); - arg_conversions.push_str(&format!("\ - const [ptr{i}, len{i}] = {func}({arg}); - setGlobalArgument(len{i}, {global_idx}); - ", i = i, func = func, arg = name, global_idx = global_idx)); - global_idx += 1; - pass(&format!("ptr{}", i)); - if !ty.owned { - destructors.push_str(&format!("\n\ - wasm.__wbindgen_free(ptr{i}, len{i} * {size});\n\ - ", i = i, size = ty.size())); - self.cx.required_internal_exports.insert( - "__wbindgen_free", - ); - } - } - None => { - let s = self.cx.custom_type_name(other).to_string(); - dst_ts.push_str(&format!(": {}", s)); - if self.cx.config.debug { - self.cx.expose_assert_class(); - arg_conversions.push_str(&format!("\ - _assertClass({arg}, {struct_}); - ", arg = name, struct_ = s)); - } - - if other & shared::TYPE_CUSTOM_REF_FLAG != 0 { - pass(&format!("{}.ptr", name)); - } else { - arg_conversions.push_str(&format!("\ - const ptr{i} = {arg}.ptr; - {arg}.ptr = 0; - ", i = i, arg = name)); - pass(&format!("ptr{}", i)); - } - } - } + pass(&format!("addBorrowedObject({})", name)); + continue } + _ => {} } + panic!("unsupported argument to rust function {:?}", arg) } dst.push_str(")"); dst_ts.push_str(")"); - let convert_ret = match function.ret { - None => { - dst_ts.push_str(": void"); - format!("return ret;") - } - Some(shared::TYPE_ENUM) => { - dst_ts.push_str(": number"); - format!("return ret;") - } - Some(shared::TYPE_NUMBER) => { - dst_ts.push_str(": number"); - format!("return ret;") - } - Some(shared::TYPE_BOOLEAN) => { - dst_ts.push_str(": boolean"); - format!("return ret !== 0;") - } - Some(shared::TYPE_JS_OWNED) => { - dst_ts.push_str(": any"); - self.cx.expose_take_object(); - format!("return takeObject(ret);") - } - Some(shared::TYPE_JS_REF) => { - dst_ts.push_str(": any"); - self.cx.expose_get_object(); - format!("return getObject(ret);") - } - Some(other) => { - match VectorType::from(other) { - Some(ty) => { - if !ty.owned { - panic!("cannot return slices yet"); - } - dst_ts.push_str(": "); - dst_ts.push_str(ty.js_ty()); - let f = self.cx.expose_get_vector_from_wasm(&ty); - self.cx.expose_get_global_argument(); - self.cx.required_internal_exports.insert( - "__wbindgen_free", - ); - format!(" - const len = getGlobalArgument(0); - const realRet = {}(ret, len); - wasm.__wbindgen_free(ret, len * {}); - return realRet; - ", f, ty.size()) - } - None => { - if other & shared::TYPE_CUSTOM_REF_FLAG != 0 { - panic!("cannot return references yet"); - } - let name = self.cx.custom_type_name(other); - dst_ts.push_str(": "); - dst_ts.push_str(name); - if self.cx.config.debug { - format!("\ - return new {name}(ret, token); - ", name = name) - } else { - format!("\ - return new {name}(ret); - ", name = name) - } - } - } - } - }; + let convert_ret = self.cx.return_from_rust(&desc_function.ret, &mut dst_ts); dst_ts.push_str(";"); dst.push_str(" {\n "); dst.push_str(&arg_conversions); if destructors.len() == 0 { dst.push_str(&format!("\ - const ret = wasm.{}({passed}); + const ret = wasm.{f}({passed}); {convert_ret} ", f = wasm_name, @@ -1491,6 +1519,9 @@ impl<'a, 'b> SubContext<'a, 'b> { pub fn generate_import_function(&mut self, info: &shared::Import, import: &shared::ImportFunction) { + let descriptor = self.cx.describe(&import.shim); + let desc_function = descriptor.unwrap_function(); + let mut dst = String::new(); dst.push_str("function("); @@ -1501,89 +1532,86 @@ impl<'a, 'b> SubContext<'a, 'b> { let mut finally = String::new(); let mut next_global = 0; - for (i, arg) in import.function.arguments.iter().enumerate() { + for (i, arg) in desc_function.arguments.iter().enumerate() { abi_args.push(format!("arg{}", i)); + + if let Some(ty) = arg.vector_kind() { + let f = self.cx.expose_get_vector_from_wasm(ty); + self.cx.expose_get_global_argument(); + extra.push_str(&format!(" + let len{0} = getGlobalArgument({next_global}); + let v{0} = {func}(arg{0}, len{0}); + ", i, func = f, next_global = next_global)); + next_global += 1; + + if !arg.is_by_ref() { + extra.push_str(&format!(" + wasm.__wbindgen_free(arg{0}, len{0} * {size}); + ", i, size = ty.size())); + self.cx.required_internal_exports.insert( + "__wbindgen_free" + ); + } + invoc_args.push(format!("v{}", i)); + continue + } + + if let Some(s) = arg.rust_struct() { + if arg.is_by_ref() { + panic!("cannot invoke JS functions with custom ref types yet") + } + let assign = if self.cx.config.debug { + format!("let c{0} = new {class}(arg{0}, token);", i, class = s) + } else { + format!("let c{0} = new {class}(arg{0});", i, class = s) + }; + extra.push_str(&assign); + invoc_args.push(format!("c{}", i)); + continue + } + + if let Some(f) = arg.stack_closure() { + let args = (0..f.arguments.len()) + .map(|i| format!("arg{}", i)) + .collect::>() + .join(", "); + self.cx.expose_get_global_argument(); + self.cx.function_table_needed = true; + let sep = if f.arguments.len() == 0 {""} else {","}; + extra.push_str(&format!(" + let cb{0} = function({args}) {{ + return this.f(this.a, this.b {sep} {args}); + }}; + cb{0}.f = wasm.__wbg_function_table.get(arg{0}); + cb{0}.a = getGlobalArgument({next_global}); + cb{0}.b = getGlobalArgument({next_global} + 1); + ", i, next_global = next_global, args = args, sep = sep)); + next_global += 2; + finally.push_str(&format!(" + cb{0}.a = cb{0}.b = 0; + ", i)); + invoc_args.push(format!("cb{0}.bind(cb{0})", i)); + continue + } + + if let Some(_f) = arg.ref_closure() { + self.cx.expose_get_object(); + invoc_args.push(format!("getObject(arg{})", i)); + continue + } + let invoc_arg = match *arg { - shared::TYPE_NUMBER => format!("arg{}", i), - shared::TYPE_BOOLEAN => format!("arg{} !== 0", i), - shared::TYPE_JS_OWNED => { + ref d if d.is_number() => format!("arg{}", i), + Descriptor::Boolean => format!("arg{} !== 0", i), + Descriptor::Anyref => { self.cx.expose_take_object(); format!("takeObject(arg{})", i) } - shared::TYPE_JS_REF => { + ref d if d.is_ref_anyref() => { self.cx.expose_get_object(); format!("getObject(arg{})", i) } - shared::TYPE_FUNC => { - self.cx.expose_get_object(); - format!("getObject(arg{})", i) - } - shared::TYPE_STACK_FUNC0 | - shared::TYPE_STACK_FUNC1 | - shared::TYPE_STACK_FUNC2 | - shared::TYPE_STACK_FUNC3 | - shared::TYPE_STACK_FUNC4 | - shared::TYPE_STACK_FUNC5 | - shared::TYPE_STACK_FUNC6 | - shared::TYPE_STACK_FUNC7 => { - let nargs = *arg - shared::TYPE_STACK_FUNC0; - let args = (0..nargs) - .map(|i| format!("arg{}", i)) - .collect::>() - .join(", "); - self.cx.expose_get_global_argument(); - self.cx.function_table_needed = true; - let sep = if nargs == 0 {""} else {","}; - extra.push_str(&format!(" - let cb{0} = function({args}) {{ - return this.f(this.a, this.b {sep} {args}); - }}; - cb{0}.f = wasm.__wbg_function_table.get(arg{0}); - cb{0}.a = getGlobalArgument({next_global}); - cb{0}.b = getGlobalArgument({next_global} + 1); - ", i, next_global = next_global, args = args, sep = sep)); - next_global += 2; - finally.push_str(&format!(" - cb{0}.a = cb{0}.b = 0; - ", i)); - format!("cb{0}.bind(cb{0})", i) - } - other => { - match VectorType::from(other) { - Some(ty) => { - let f = self.cx.expose_get_vector_from_wasm(&ty); - self.cx.expose_get_global_argument(); - extra.push_str(&format!(" - let len{0} = getGlobalArgument({next_global}); - let v{0} = {func}(arg{0}, len{0}); - ", i, func = f, next_global = next_global)); - next_global += 1; - - if ty.owned { - extra.push_str(&format!(" - wasm.__wbindgen_free(arg{0}, len{0} * {size}); - ", i, size = ty.size())); - self.cx.required_internal_exports.insert( - "__wbindgen_free" - ); - } - format!("v{}", i) - } - None => { - if other & shared::TYPE_CUSTOM_REF_FLAG != 0 { - panic!("cannot import custom ref types yet") - } - let s = self.cx.custom_type_name(other).to_string(); - let assign = if self.cx.config.debug { - format!("let c{0} = new {class}(arg{0}, token);", i, class = s) - } else { - format!("let c{0} = new {class}(arg{0});", i, class = s) - }; - extra.push_str(&assign); - format!("c{}", i) - } - } - } + _ => panic!("unimplemented argument type in imported function: {:?}", arg), }; invoc_args.push(invoc_arg); } @@ -1670,33 +1698,7 @@ impl<'a, 'b> SubContext<'a, 'b> { } }; let invoc = format!("{}({})", invoc, invoc_args); - let invoc = match import.function.ret { - Some(shared::TYPE_NUMBER) => format!("return {};", invoc), - Some(shared::TYPE_BOOLEAN) => format!("return {} ? 1 : 0;", invoc), - Some(shared::TYPE_JS_OWNED) => { - self.cx.expose_add_heap_object(); - format!("return addHeapObject({});", invoc) - } - Some(other) => { - match VectorType::from(other) { - Some(ty) => { - if !ty.owned { - panic!("cannot return borrowed slices in imports"); - } - let f = self.cx.pass_to_wasm_function(&ty); - self.cx.expose_uint32_memory(); - self.cx.expose_set_global_argument(); - format!(" - const [retptr, retlen] = {}({}); - setGlobalArgument(retlen, 0); - return retptr; - ", f, invoc) - } - None => panic!("unimplemented return type in import"), - } - } - None => invoc, - }; + let invoc = self.cx.return_from_js(&desc_function.ret, &invoc); let invoc = if import.catch { self.cx.expose_uint32_memory(); @@ -1776,119 +1778,3 @@ impl<'a, 'b> SubContext<'a, 'b> { } } } - -#[derive(Debug)] -struct VectorType { - owned: bool, - kind: VectorKind, -} - -#[derive(Debug)] -enum VectorKind { - String, - I8, - U8, - I16, - U16, - I32, - U32, - F32, - F64, - JsValue, -} - -impl VectorType { - fn from(desc: u32) -> Option { - let ty = match desc { - shared::TYPE_BORROWED_STR => { - VectorType { owned: false, kind: VectorKind::String } - } - shared::TYPE_STRING => { - VectorType { owned: true, kind: VectorKind::String } - } - shared::TYPE_VECTOR_U8 => { - VectorType { owned: true, kind: VectorKind::U8 } - } - shared::TYPE_VECTOR_I8 => { - VectorType { owned: true, kind: VectorKind::I8 } - } - shared::TYPE_SLICE_U8 => { - VectorType { owned: false, kind: VectorKind::U8 } - } - shared::TYPE_SLICE_I8 => { - VectorType { owned: false, kind: VectorKind::I8 } - } - shared::TYPE_VECTOR_U16 => { - VectorType { owned: true, kind: VectorKind::U16 } - } - shared::TYPE_VECTOR_I16 => { - VectorType { owned: true, kind: VectorKind::I16 } - } - shared::TYPE_SLICE_U16 => { - VectorType { owned: false, kind: VectorKind::U16 } - } - shared::TYPE_SLICE_I16 => { - VectorType { owned: false, kind: VectorKind::I16 } - } - shared::TYPE_VECTOR_U32 => { - VectorType { owned: true, kind: VectorKind::U32 } - } - shared::TYPE_VECTOR_I32 => { - VectorType { owned: true, kind: VectorKind::I32 } - } - shared::TYPE_SLICE_U32 => { - VectorType { owned: false, kind: VectorKind::U32 } - } - shared::TYPE_SLICE_I32 => { - VectorType { owned: false, kind: VectorKind::I32 } - } - shared::TYPE_VECTOR_F32 => { - VectorType { owned: true, kind: VectorKind::F32 } - } - shared::TYPE_VECTOR_F64 => { - VectorType { owned: true, kind: VectorKind::F64 } - } - shared::TYPE_SLICE_F32 => { - VectorType { owned: false, kind: VectorKind::F32 } - } - shared::TYPE_SLICE_F64 => { - VectorType { owned: false, kind: VectorKind::F64 } - } - shared::TYPE_VECTOR_JSVALUE => { - VectorType { owned: true, kind: VectorKind::JsValue } - } - _ => return None - }; - Some(ty) - } - - fn js_ty(&self) -> &str { - match self.kind { - VectorKind::String => "string", - VectorKind::I8 => "Int8Array", - VectorKind::U8 => "Uint8Array", - VectorKind::I16 => "Int16Array", - VectorKind::U16 => "Uint16Array", - VectorKind::I32 => "Int32Array", - VectorKind::U32 => "Uint32Array", - VectorKind::F32 => "Float32Array", - VectorKind::F64 => "Float64Array", - VectorKind::JsValue => "any[]", - } - } - - fn size(&self) -> usize { - match self.kind { - VectorKind::String => 1, - VectorKind::I8 => 1, - VectorKind::U8 => 1, - VectorKind::I16 => 2, - VectorKind::U16 => 2, - VectorKind::I32 => 4, - VectorKind::U32 => 4, - VectorKind::F32 => 4, - VectorKind::F64 => 8, - VectorKind::JsValue => 4, - } - } -} diff --git a/crates/cli-support/src/lib.rs b/crates/cli-support/src/lib.rs index 3c257052..a4f0fb3e 100644 --- a/crates/cli-support/src/lib.rs +++ b/crates/cli-support/src/lib.rs @@ -2,8 +2,10 @@ extern crate parity_wasm; extern crate wasm_bindgen_shared as shared; extern crate serde_json; extern crate wasm_gc; +extern crate wasmi; use std::collections::BTreeSet; +use std::fmt; use std::fs::File; use std::io::Write; use std::path::{Path, PathBuf}; @@ -11,6 +13,7 @@ use std::path::{Path, PathBuf}; use parity_wasm::elements::*; mod js; +mod descriptor; pub mod wasm2es6js; pub struct Bindgen { @@ -90,11 +93,26 @@ impl Bindgen { None => panic!("must have a path input for now"), }; let stem = input.file_stem().unwrap().to_str().unwrap(); - let mut module = parity_wasm::deserialize_file(input).map_err(|e| { - Error(format!("{:?}", e)) - })?; + let mut module = parity_wasm::deserialize_file(input)?; let programs = extract_programs(&mut module); + // Here we're actually instantiating the module we've parsed above for + // execution. Why, you might be asking, are we executing wasm code? A + // good question! + // + // Transmitting information from `#[wasm_bindgen]` here to the CLI tool + // is pretty tricky. Specifically information about the types involved + // with a function signature (especially generic ones) can be hefty to + // translate over. As a result, the macro emits a bunch of shims which, + // when executed, will describe to us what the types look like. + // + // This means that whenever we encounter an import or export we'll + // execute a shim function which informs us about its type so we can + // then generate the appropriate bindings. + let instance = wasmi::Module::from_parity_wasm_module(module.clone())?; + let instance = wasmi::ModuleInstance::new(&imodule, &MyResolver)?; + let instance = instance.not_started_instance(); + let (js, ts) = { let mut cx = js::Context { globals: String::new(), @@ -103,16 +121,20 @@ impl Bindgen { typescript: format!("/* tslint:disable */\n"), exposed_globals: Default::default(), required_internal_exports: Default::default(), - custom_type_names: Default::default(), imported_names: Default::default(), exported_classes: Default::default(), config: &self, module: &mut module, function_table_needed: false, + run_descriptor: &|name| { + let mut v = MyExternals(Vec::new()); + let ret = imodulei + .invoke_export(name, &[], &mut v) + .expect("failed to run export"); + assert!(ret.is_none()); + v.0 + }, }; - for program in programs.iter() { - cx.add_custom_type_names(program); - } for program in programs.iter() { js::SubContext { program, @@ -233,3 +255,102 @@ to open an issue at https://github.com/alexcrichton/wasm-bindgen/issues! }); return ret } + +struct MyResolver; + +impl wasmi::ImportResolver for MyResolver { + fn resolve_func( + &self, + module_name: &str, + field_name: &str, + signature: &wasmi::Signature + ) -> Result { + // Route our special "describe" export to 1 and everything else to 0. + // That way whenever the function 1 is invoked we know what to do and + // when 0 is invoked (by accident) we'll trap and produce an error. + let idx = (module_name == "__wbindgen_placeholder__" && + field_name == "__wbindgen_describe") as usize; + Ok(wasmi::FuncInstance::alloc_host(signature.clone(), idx)) + } + + fn resolve_global( + &self, + _module_name: &str, + _field_name: &str, + descriptor: &wasmi::GlobalDescriptor + ) -> Result { + // dummy implementation to ensure instantiation succeeds + let val = match descriptor.value_type() { + wasmi::ValueType::I32 => wasmi::RuntimeValue::I32(0), + wasmi::ValueType::I64 => wasmi::RuntimeValue::I64(0), + wasmi::ValueType::F32 => wasmi::RuntimeValue::F32(0.0), + wasmi::ValueType::F64 => wasmi::RuntimeValue::F64(0.0), + }; + Ok(wasmi::GlobalInstance::alloc(val, descriptor.is_mutable())) + } + + fn resolve_memory( + &self, + _module_name: &str, + _field_name: &str, + descriptor: &wasmi::MemoryDescriptor, + ) -> Result { + // dummy implementation to ensure instantiation succeeds + use wasmi::memory_units::Pages; + let initial = Pages(descriptor.initial() as usize); + let maximum = descriptor.maximum().map(|i| Pages(i as usize)); + wasmi::MemoryInstance::alloc(initial, maximum) + } + + fn resolve_table( + &self, + _module_name: &str, + _field_name: &str, + descriptor: &wasmi::TableDescriptor + ) -> Result { + // dummy implementation to ensure instantiation succeeds + let initial = descriptor.initial(); + let maximum = descriptor.maximum(); + wasmi::TableInstance::alloc(initial, maximum) + } +} + +struct MyExternals(Vec); +#[derive(Debug)] +struct MyError(String); + +impl wasmi::Externals for MyExternals { + fn invoke_index( + &mut self, + index: usize, + args: wasmi::RuntimeArgs + ) -> Result, wasmi::Trap> { + macro_rules! bail { + ($($t:tt)*) => ({ + let s = MyError(format!($($t)*)); + return Err(wasmi::Trap::new(wasmi::TrapKind::Host(Box::new(s)))) + }) + } + // We only recognize one function here which was mapped to the index 1 + // by the resolver above. + if index != 1 { + bail!("only __wbindgen_describe can be run at this time") + } + if args.len() != 1 { + bail!("must have exactly one argument"); + } + match args.nth_value_checked(0)? { + wasmi::RuntimeValue::I32(i) => self.0.push(i as u32), + _ => bail!("expected one argument of i32 type"), + } + Ok(None) + } +} + +impl wasmi::HostError for MyError {} + +impl fmt::Display for MyError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(f) + } +} diff --git a/crates/shared/Cargo.toml b/crates/shared/Cargo.toml index ea445d68..90fd3d18 100644 --- a/crates/shared/Cargo.toml +++ b/crates/shared/Cargo.toml @@ -14,4 +14,3 @@ dependency. [dependencies] serde_derive = "1" serde = "1" -fnv = "1" diff --git a/crates/shared/src/lib.rs b/crates/shared/src/lib.rs index c937a967..3d73ad63 100644 --- a/crates/shared/src/lib.rs +++ b/crates/shared/src/lib.rs @@ -1,29 +1,25 @@ #[macro_use] extern crate serde_derive; -extern crate fnv; -use std::hash::{Hash, Hasher}; +pub const SCHEMA_VERSION: &str = "2"; -pub const SCHEMA_VERSION: &str = "1"; - -#[derive(Deserialize)] +#[derive(Deserialize, Serialize)] pub struct Program { pub exports: Vec, pub enums: Vec, pub imports: Vec, - pub custom_type_names: Vec, pub version: String, pub schema_version: String, } -#[derive(Deserialize)] +#[derive(Deserialize, Serialize)] pub struct Import { pub module: Option, pub js_namespace: Option, pub kind: ImportKind, } -#[derive(Deserialize)] +#[derive(Deserialize, Serialize)] #[serde(tag = "kind", rename_all = "lowercase")] pub enum ImportKind { Function(ImportFunction), @@ -31,10 +27,9 @@ pub enum ImportKind { Type(ImportType), } -#[derive(Deserialize)] +#[derive(Deserialize, Serialize)] pub struct ImportFunction { pub shim: String, - pub module: Option, pub catch: bool, pub method: bool, pub js_new: bool, @@ -45,47 +40,38 @@ pub struct ImportFunction { pub function: Function, } -#[derive(Deserialize)] +#[derive(Deserialize, Serialize)] pub struct ImportStatic { - pub module: Option, pub name: String, pub shim: String, } -#[derive(Deserialize)] +#[derive(Deserialize, Serialize)] pub struct ImportType { } -#[derive(Deserialize)] +#[derive(Deserialize, Serialize)] pub struct Export { pub class: Option, pub method: bool, pub function: Function, } -#[derive(Deserialize)] +#[derive(Deserialize, Serialize)] pub struct Enum { pub name: String, pub variants: Vec, } -#[derive(Deserialize)] +#[derive(Deserialize, Serialize)] pub struct EnumVariant { pub name: String, pub value: u32 } -#[derive(Deserialize)] +#[derive(Deserialize, Serialize)] pub struct Function { pub name: String, - pub arguments: Vec, - pub ret: Option, -} - -#[derive(Deserialize)] -pub struct CustomTypeName { - pub descriptor: u32, - pub name: String, } pub fn new_function(struct_name: &str) -> String { @@ -120,53 +106,6 @@ pub fn struct_function_export_name(struct_: &str, f: &str) -> String { return name } -pub type Type = u32; - -pub const TYPE_VECTOR_JSVALUE: u32 = 0; -pub const TYPE_ENUM: u32 = 1; -pub const TYPE_NUMBER: u32 = 2; -pub const TYPE_BORROWED_STR: u32 = 3; -pub const TYPE_STRING: u32 = 4; -pub const TYPE_BOOLEAN: u32 = 5; -pub const TYPE_SLICE_U8: u32 = 6; -pub const TYPE_VECTOR_U8: u32 = 7; -pub const TYPE_SLICE_I8: u32 = 8; -pub const TYPE_VECTOR_I8: u32 = 9; -pub const TYPE_SLICE_U16: u32 = 10; -pub const TYPE_VECTOR_U16: u32 = 11; -pub const TYPE_SLICE_I16: u32 = 12; -pub const TYPE_VECTOR_I16: u32 = 13; -pub const TYPE_SLICE_U32: u32 = 14; -pub const TYPE_VECTOR_U32: u32 = 15; -pub const TYPE_SLICE_I32: u32 = 16; -pub const TYPE_VECTOR_I32: u32 = 17; -pub const TYPE_SLICE_F32: u32 = 18; -pub const TYPE_VECTOR_F32: u32 = 19; -pub const TYPE_SLICE_F64: u32 = 20; -pub const TYPE_VECTOR_F64: u32 = 21; -pub const TYPE_JS_OWNED: u32 = 22; -pub const TYPE_JS_REF: u32 = 23; -pub const TYPE_STACK_FUNC0: u32 = 24; -pub const TYPE_STACK_FUNC1: u32 = 25; -pub const TYPE_STACK_FUNC2: u32 = 26; -pub const TYPE_STACK_FUNC3: u32 = 27; -pub const TYPE_STACK_FUNC4: u32 = 28; -pub const TYPE_STACK_FUNC5: u32 = 29; -pub const TYPE_STACK_FUNC6: u32 = 30; -pub const TYPE_STACK_FUNC7: u32 = 31; -pub const TYPE_FUNC: u32 = 32; - -pub const TYPE_CUSTOM_START: u32 = 40; -pub const TYPE_CUSTOM_REF_FLAG: u32 = 1; - -pub fn name_to_descriptor(name: &str) -> u32 { - const MAX: u32 = 10_000; - let mut h = fnv::FnvHasher::default(); - name.hash(&mut h); - (((h.finish() as u32) % (MAX - TYPE_CUSTOM_START)) + TYPE_CUSTOM_START) & !1 - -} - pub fn version() -> String { let mut v = env!("CARGO_PKG_VERSION").to_string(); if let Some(s) = option_env!("WBG_VERSION") { diff --git a/src/closure.rs b/src/closure.rs index 65a464c9..56c1d995 100644 --- a/src/closure.rs +++ b/src/closure.rs @@ -9,6 +9,7 @@ use std::marker::Unsize; use {throw, JsValue}; use convert::*; +use describe::*; use __rt::WasmRefCell; /// A handle to both a closure in Rust as well as JS closure which will invoke @@ -122,12 +123,20 @@ impl Closure } } +impl WasmDescribe for Closure + where T: WasmShim + ?Sized, +{ + fn describe() { + inform(CLOSURE); + T::describe(); + } +} + // `Closure` can only be passed by reference to imports. impl ToRefWasmBoundary for Closure where T: WasmShim + ?Sized, { type Abi = u32; - const DESCRIPTOR: Descriptor = T::DESCRIPTOR; fn to_abi_ref(&self, extra: &mut Stack) -> u32 { self.js.to_abi_ref(extra) @@ -149,9 +158,7 @@ impl Drop for Closure /// /// This trait is not stable and it's not recommended to use this in bounds or /// implement yourself. -pub unsafe trait WasmShim { - #[doc(hidden)] - const DESCRIPTOR: Descriptor; +pub unsafe trait WasmShim: WasmDescribe { #[doc(hidden)] type Wrapper; #[doc(hidden)] @@ -174,8 +181,9 @@ macro_rules! doit { ($($var:ident)*) => $arity:ident )*) => ($( // Fn with no return - unsafe impl<$($var: WasmAbi),*> WasmShim for Fn($($var),*) { - const DESCRIPTOR: Descriptor = DESCRIPTOR_FUNC; + unsafe impl<$($var),*> WasmShim for Fn($($var),*) + where $($var: WasmAbi + WasmDescribe,)* + { type Wrapper = Box; fn shim() -> u32 { @@ -209,8 +217,10 @@ macro_rules! doit { } // Fn with a return - unsafe impl<$($var: WasmAbi,)* R: WasmAbi> WasmShim for Fn($($var),*) -> R { - const DESCRIPTOR: Descriptor = DESCRIPTOR_FUNC; + unsafe impl<$($var,)* R> WasmShim for Fn($($var),*) -> R + where $($var: WasmAbi + WasmDescribe,)* + R: WasmAbi + WasmDescribe, + { type Wrapper = Box R>; fn shim() -> u32 { @@ -244,8 +254,9 @@ macro_rules! doit { } // FnMut with no return - unsafe impl<$($var: WasmAbi),*> WasmShim for FnMut($($var),*) { - const DESCRIPTOR: Descriptor = DESCRIPTOR_FUNC; + unsafe impl<$($var),*> WasmShim for FnMut($($var),*) + where $($var: WasmAbi + WasmDescribe,)* + { type Wrapper = Box>; fn shim() -> u32 { @@ -283,8 +294,10 @@ macro_rules! doit { } // FnMut with a return - unsafe impl<$($var: WasmAbi,)* R: WasmAbi> WasmShim for FnMut($($var),*) -> R { - const DESCRIPTOR: Descriptor = DESCRIPTOR_FUNC; + unsafe impl<$($var,)* R> WasmShim for FnMut($($var),*) -> R + where $($var: WasmAbi + WasmDescribe,)* + R: WasmAbi + WasmDescribe, + { type Wrapper = Box R>>; fn shim() -> u32 { diff --git a/src/convert.rs b/src/convert.rs index 360ac5a5..6eae11dd 100644 --- a/src/convert.rs +++ b/src/convert.rs @@ -1,9 +1,13 @@ +//! This is mostly an internal module, no stability guarantees are provied. Use +//! at your own risk. + use std::mem::{self, ManuallyDrop}; use std::ops::{Deref, DerefMut}; use std::slice; use std::str; use {JsValue, throw}; +use describe::*; #[derive(PartialEq, Eq, Copy, Clone)] pub struct Descriptor { @@ -11,54 +15,29 @@ pub struct Descriptor { pub __x: [u8; 4], } -// keep in sync with shared/src/lib.rs TYPE constants -// pub const DESCRIPTOR_CUSTOM_REF_FLAG: Descriptor = Descriptor { __x: *b" 1", }; -pub const DESCRIPTOR_NUMBER: Descriptor = Descriptor { __x: *b" 2", }; -pub const DESCRIPTOR_BORROWED_STR: Descriptor = Descriptor { __x: *b" 3", }; -pub const DESCRIPTOR_STRING: Descriptor = Descriptor { __x: *b" 4", }; -pub const DESCRIPTOR_BOOLEAN: Descriptor = Descriptor { __x: *b" 5", }; - -pub const DESCRIPTOR_JS_OWNED: Descriptor = Descriptor { __x: *b" 22", }; -pub const DESCRIPTOR_JS_REF: Descriptor = Descriptor { __x: *b" 23", }; - -pub const DESCRIPTOR_STACK_FUNC0: Descriptor = Descriptor { __x: *b" 24", }; -pub const DESCRIPTOR_STACK_FUNC1: Descriptor = Descriptor { __x: *b" 25", }; -pub const DESCRIPTOR_STACK_FUNC2: Descriptor = Descriptor { __x: *b" 26", }; -pub const DESCRIPTOR_STACK_FUNC3: Descriptor = Descriptor { __x: *b" 27", }; -pub const DESCRIPTOR_STACK_FUNC4: Descriptor = Descriptor { __x: *b" 28", }; -pub const DESCRIPTOR_STACK_FUNC5: Descriptor = Descriptor { __x: *b" 29", }; -pub const DESCRIPTOR_STACK_FUNC6: Descriptor = Descriptor { __x: *b" 30", }; -pub const DESCRIPTOR_STACK_FUNC7: Descriptor = Descriptor { __x: *b" 31", }; - -pub const DESCRIPTOR_FUNC: Descriptor = Descriptor { __x: *b" 32", }; - -pub trait WasmBoundary { +pub trait WasmBoundary: WasmDescribe { type Abi: WasmAbi; - const DESCRIPTOR: Descriptor; fn into_abi(self, extra: &mut Stack) -> Self::Abi; unsafe fn from_abi(js: Self::Abi, extra: &mut Stack) -> Self; } -pub trait FromRefWasmBoundary { +pub trait FromRefWasmBoundary: WasmDescribe { type Abi: WasmAbi; - const DESCRIPTOR: Descriptor; type RefAnchor: Deref; unsafe fn from_abi_ref(js: Self::Abi, extra: &mut Stack) -> Self::RefAnchor; } -pub trait FromRefMutWasmBoundary { +pub trait FromRefMutWasmBoundary: WasmDescribe { type Abi: WasmAbi; - const DESCRIPTOR: Descriptor; type RefAnchor: DerefMut; unsafe fn from_abi_ref_mut(js: Self::Abi, extra: &mut Stack) -> Self::RefAnchor; } -pub trait ToRefWasmBoundary { +pub trait ToRefWasmBoundary: WasmDescribe { type Abi: WasmAbi; - const DESCRIPTOR: Descriptor; fn to_abi_ref(&self, extra: &mut Stack) -> u32; } @@ -78,6 +57,8 @@ pub unsafe trait WasmAbi {} unsafe impl WasmAbi for u32 {} unsafe impl WasmAbi for u64 {} +unsafe impl WasmAbi for i32 {} +unsafe impl WasmAbi for i64 {} unsafe impl WasmAbi for f32 {} unsafe impl WasmAbi for f64 {} @@ -85,7 +66,6 @@ macro_rules! simple { ($($t:tt)*) => ($( impl WasmBoundary for $t { type Abi = $t; - const DESCRIPTOR: Descriptor = DESCRIPTOR_NUMBER; fn into_abi(self, _extra: &mut Stack) -> $t { self } unsafe fn from_abi(js: $t, _extra: &mut Stack) -> $t { js } @@ -93,13 +73,12 @@ macro_rules! simple { )*) } -simple!(u32 u64 f32 f64); +simple!(u32 u64 i32 i64 f32 f64); macro_rules! as_u32 { ($($t:tt)*) => ($( impl WasmBoundary for $t { type Abi = u32; - const DESCRIPTOR: Descriptor = DESCRIPTOR_NUMBER; fn into_abi(self, _extra: &mut Stack) -> u32 { self as u32 } unsafe fn from_abi(js: u32, _extra: &mut Stack) -> $t { js as $t } @@ -107,11 +86,10 @@ macro_rules! as_u32 { )*) } -as_u32!(i8 u8 i16 u16 i32 isize usize); +as_u32!(i8 u8 i16 u16 isize usize); impl WasmBoundary for bool { type Abi = u32; - const DESCRIPTOR: Descriptor = DESCRIPTOR_BOOLEAN; fn into_abi(self, _extra: &mut Stack) -> u32 { self as u32 } unsafe fn from_abi(js: u32, _extra: &mut Stack) -> bool { js != 0 } @@ -119,7 +97,6 @@ impl WasmBoundary for bool { impl WasmBoundary for *const T { type Abi = u32; - const DESCRIPTOR: Descriptor = DESCRIPTOR_NUMBER; fn into_abi(self, _extra: &mut Stack) -> u32 { self as u32 } unsafe fn from_abi(js: u32, _extra: &mut Stack) -> *const T { js as *const T } @@ -127,17 +104,15 @@ impl WasmBoundary for *const T { impl WasmBoundary for *mut T { type Abi = u32; - const DESCRIPTOR: Descriptor = DESCRIPTOR_NUMBER; fn into_abi(self, _extra: &mut Stack) -> u32 { self as u32 } unsafe fn from_abi(js: u32, _extra: &mut Stack) -> *mut T { js as *mut T } } macro_rules! vectors { - ($($t:ident => ($slice:expr, $owned:expr))*) => ($( + ($($t:ident)*) => ($( impl WasmBoundary for Box<[$t]> { type Abi = u32; - const DESCRIPTOR: Descriptor = Descriptor { __x: *$owned }; fn into_abi(self, extra: &mut Stack) -> u32 { let ptr = self.as_ptr(); @@ -152,11 +127,11 @@ macro_rules! vectors { let len = extra.pop() as usize; Vec::from_raw_parts(ptr, len, len).into_boxed_slice() } + } impl ToRefWasmBoundary for [$t] { type Abi = u32; - const DESCRIPTOR: Descriptor = Descriptor { __x: *$slice }; fn to_abi_ref(&self, extra: &mut Stack) -> u32 { let ptr = self.as_ptr(); @@ -168,7 +143,6 @@ macro_rules! vectors { impl FromRefWasmBoundary for [$t] { type Abi = u32; - const DESCRIPTOR: Descriptor = Descriptor { __x: *$slice }; type RefAnchor = SliceAnchor<$t>; unsafe fn from_abi_ref(js: u32, extra: &mut Stack) -> SliceAnchor<$t> { @@ -194,19 +168,11 @@ impl Deref for SliceAnchor { } vectors! { - u8 => (b" 6", b" 7") - i8 => (b" 8", b" 9") - u16 => (b" 10", b" 11") - i16 => (b" 12", b" 13") - u32 => (b" 14", b" 15") - i32 => (b" 16", b" 17") - f32 => (b" 18", b" 19") - f64 => (b" 20", b" 21") + u8 i8 u16 i16 u32 i32 f32 f64 } impl WasmBoundary for Vec where Box<[T]>: WasmBoundary { type Abi = as WasmBoundary>::Abi; - const DESCRIPTOR: Descriptor = as WasmBoundary>::DESCRIPTOR; fn into_abi(self, extra: &mut Stack) -> Self::Abi { self.into_boxed_slice().into_abi(extra) @@ -219,7 +185,6 @@ impl WasmBoundary for Vec where Box<[T]>: WasmBoundary { impl WasmBoundary for String { type Abi = u32; - const DESCRIPTOR: Descriptor = DESCRIPTOR_STRING; fn into_abi(self, extra: &mut Stack) -> u32 { self.into_bytes().into_abi(extra) @@ -232,7 +197,6 @@ impl WasmBoundary for String { impl ToRefWasmBoundary for str { type Abi = <[u8] as ToRefWasmBoundary>::Abi; - const DESCRIPTOR: Descriptor = DESCRIPTOR_BORROWED_STR; fn to_abi_ref(&self, extra: &mut Stack) -> Self::Abi { self.as_bytes().to_abi_ref(extra) @@ -241,7 +205,6 @@ impl ToRefWasmBoundary for str { impl FromRefWasmBoundary for str { type Abi = <[u8] as ToRefWasmBoundary>::Abi; - const DESCRIPTOR: Descriptor = DESCRIPTOR_BORROWED_STR; type RefAnchor = StrAnchor; unsafe fn from_abi_ref(js: Self::Abi, extra: &mut Stack) -> Self::RefAnchor { @@ -263,7 +226,6 @@ impl Deref for StrAnchor { impl WasmBoundary for JsValue { type Abi = u32; - const DESCRIPTOR: Descriptor = DESCRIPTOR_JS_OWNED; fn into_abi(self, _extra: &mut Stack) -> u32 { let ret = self.idx; @@ -278,7 +240,6 @@ impl WasmBoundary for JsValue { impl ToRefWasmBoundary for JsValue { type Abi = u32; - const DESCRIPTOR: Descriptor = DESCRIPTOR_JS_REF; fn to_abi_ref(&self, _extra: &mut Stack) -> u32 { self.idx } @@ -286,7 +247,6 @@ impl ToRefWasmBoundary for JsValue { impl FromRefWasmBoundary for JsValue { type Abi = u32; - const DESCRIPTOR: Descriptor = DESCRIPTOR_JS_REF; type RefAnchor = ManuallyDrop; unsafe fn from_abi_ref(js: u32, _extra: &mut Stack) -> ManuallyDrop { @@ -296,7 +256,6 @@ impl FromRefWasmBoundary for JsValue { impl WasmBoundary for Box<[JsValue]> { type Abi = u32; - const DESCRIPTOR: Descriptor = Descriptor { __x: *b" 0" }; fn into_abi(self, extra: &mut Stack) -> u32 { let ptr = self.as_ptr(); @@ -350,15 +309,12 @@ pub unsafe extern fn __wbindgen_global_argument_ptr() -> *mut u32 { } macro_rules! stack_closures { - ($( - ($($var:ident)*) => $descriptor:ident - )*) => ($( + ($( ($($var:ident)*) )*) => ($( impl<'a, $($var,)* R> ToRefWasmBoundary for Fn($($var),*) -> R + 'a - where $($var: WasmAbi,)* - R: WasmAbi + where $($var: WasmAbi + WasmDescribe,)* + R: WasmAbi + WasmDescribe { type Abi = u32; - const DESCRIPTOR: Descriptor = $descriptor; fn to_abi_ref(&self, extra: &mut Stack) -> u32 { #[allow(non_snake_case)] @@ -383,10 +339,9 @@ macro_rules! stack_closures { } impl<'a, $($var,)*> ToRefWasmBoundary for Fn($($var),*) + 'a - where $($var: WasmAbi,)* + where $($var: WasmAbi + WasmDescribe,)* { type Abi = u32; - const DESCRIPTOR: Descriptor = $descriptor; fn to_abi_ref(&self, extra: &mut Stack) -> u32 { #[allow(non_snake_case)] @@ -413,12 +368,12 @@ macro_rules! stack_closures { } stack_closures! { - () => DESCRIPTOR_STACK_FUNC0 - (A) => DESCRIPTOR_STACK_FUNC1 - (A B) => DESCRIPTOR_STACK_FUNC2 - (A B C) => DESCRIPTOR_STACK_FUNC3 - (A B C D) => DESCRIPTOR_STACK_FUNC4 - (A B C D E) => DESCRIPTOR_STACK_FUNC5 - (A B C D E F) => DESCRIPTOR_STACK_FUNC6 - (A B C D E F G) => DESCRIPTOR_STACK_FUNC7 + () + (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) } diff --git a/src/describe.rs b/src/describe.rs new file mode 100644 index 00000000..293e480f --- /dev/null +++ b/src/describe.rs @@ -0,0 +1,184 @@ +//! This is an internal module, no stability guarantees are provided. Use at +//! your own risk. + +#![doc(hidden)] + +use JsValue; + +macro_rules! tys { + ($($a:ident)*) => (tys! { @ ($($a)*) 0 }); + (@ () $v:expr) => {}; + (@ ($a:ident $($b:ident)*) $v:expr) => { + pub const $a: u32 = $v; + tys!(@ ($($b)*) $v+1); + } +} + +// NB: this list must be kept in sync with `crates/cli-support/src/descriptor.rs` +tys! { + I8 + U8 + I16 + U16 + I32 + U32 + I64 + U64 + F32 + F64 + BOOLEAN + FUNCTION + CLOSURE + STRING + REF + REFMUT + SLICE + VECTOR + ANYREF + ENUM + RUST_STRUCT +} + +pub fn inform(a: u32) { + unsafe { + super::__wbindgen_describe(a) + } +} + +pub trait WasmDescribe { + fn describe(); +} + +macro_rules! simple { + ($($t:ident => $d:ident)*) => ($( + impl WasmDescribe for $t { + fn describe() { inform($d) } + } + )*) +} + +simple! { + i8 => I8 + u8 => U8 + i16 => I16 + u16 => U16 + i32 => I32 + u32 => U32 + i64 => I64 + u64 => U64 + isize => I32 + usize => U32 + f32 => F32 + f64 => F64 + bool => BOOLEAN + str => STRING + String => STRING + JsValue => ANYREF +} + +impl WasmDescribe for *const T { + fn describe() { inform(I32) } +} + +impl WasmDescribe for *mut T { + fn describe() { inform(I32) } +} + +impl WasmDescribe for [T] { + fn describe() { + inform(SLICE); + T::describe(); + } +} + +impl<'a, T: WasmDescribe + ?Sized> WasmDescribe for &'a T { + fn describe() { + inform(REF); + T::describe(); + } +} + +impl<'a, T: WasmDescribe + ?Sized> WasmDescribe for &'a mut T { + fn describe() { + inform(REFMUT); + T::describe(); + } +} + +impl WasmDescribe for Box<[T]> { + fn describe() { + inform(VECTOR); + T::describe(); + } +} + +impl WasmDescribe for Vec where Box<[T]>: WasmDescribe { + fn describe() { + >::describe(); + } +} + +fn _cnt() -> u32 { 1 } + +macro_rules! doit { + ($( ($($var:ident)*))*) => ($( + impl<'a, $($var,)* R> WasmDescribe for Fn($($var),*) -> R + 'a + where $($var: WasmDescribe,)* + R: WasmDescribe + { + fn describe() { + inform(FUNCTION); + inform(0 $(+ _cnt::<$var>())*); + $(<$var as WasmDescribe>::describe();)* + inform(1); + ::describe(); + } + } + + impl<'a, $($var,)* > WasmDescribe for Fn($($var),*) + 'a + where $($var: WasmDescribe,)* + { + fn describe() { + inform(FUNCTION); + inform(0 $(+ _cnt::<$var>())*); + $(<$var as WasmDescribe>::describe();)* + inform(0); + } + } + + impl<'a, $($var,)* R> WasmDescribe for FnMut($($var),*) -> R + 'a + where $($var: WasmDescribe,)* + R: WasmDescribe + { + fn describe() { + inform(FUNCTION); + inform(0 $(+ _cnt::<$var>())*); + $(<$var as WasmDescribe>::describe();)* + inform(1); + ::describe(); + } + } + + impl<'a, $($var,)* > WasmDescribe for FnMut($($var),*) + 'a + where $($var: WasmDescribe,)* + { + fn describe() { + inform(FUNCTION); + inform(0 $(+ _cnt::<$var>())*); + $(<$var as WasmDescribe>::describe();)* + inform(0); + } + } + )*) +} + +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) +} diff --git a/src/lib.rs b/src/lib.rs index 1773dcbf..a74363d9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,6 +28,7 @@ pub mod prelude { pub mod convert; pub mod closure; +pub mod describe; /// Representation of an object owned by JS. /// @@ -243,6 +244,8 @@ extern { fn __wbindgen_cb_arity7(a: u32, b: u32, c: u32) -> u32; fn __wbindgen_cb_drop(idx: u32); fn __wbindgen_cb_forget(idx: u32); + + fn __wbindgen_describe(v: u32); } impl Clone for JsValue { @@ -297,8 +300,6 @@ impl Deref for JsStatic { fn deref(&self) -> &T { unsafe { (*self.__inner.get()).get_or_insert_with(|| { - assert!(T::DESCRIPTOR == JsValue::DESCRIPTOR, - "only JS values can be imported as statics for now"); (self.__init)() }) }