From c0cad447c1b1742f59ca6de01d7b0def1c341156 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 4 Apr 2018 08:24:19 -0700 Subject: [PATCH 1/6] Initial support for closures This commit starts wasm-bindgen down the path of supporting closures. We discussed this at the recent Rust All-Hands but I ended up needing to pretty significantly scale back the ambitions of what closures are supported. This commit is just the initial support and provides only a small amount of support but will hopefully provide a good basis for future implementations. Specifically this commit adds support for passing `&Fn(...)` to an *imported function*, but nothing elese. The `&Fn` type can have any lifetime and the JS object is invalidated as soon as the import returns. The arguments and return value of `Fn` must currently implement the `WasmAbi` trait, aka they can't require any conversions like strings/types/etc. I'd like to soon expand this to `&mut FnMut` as well as `'static` closures that can be passed around for a long time in JS, but for now I'm putting that off until later. I'm not currently sure how to implement richer argument types, but hopefully that can be figured out at some point! --- crates/backend/src/ast.rs | 112 ++++++++++++++++++++++++---------- crates/backend/src/codegen.rs | 48 ++++++++------- crates/backend/src/literal.rs | 29 ++++++--- crates/cli-support/src/js.rs | 54 ++++++++++++++++ crates/cli-support/src/lib.rs | 1 + crates/shared/src/lib.rs | 4 +- src/convert.rs | 73 +++++++++++++++++++++- tests/closures.rs | 90 +++++++++++++++++++++++++++ 8 files changed, 347 insertions(+), 64 deletions(-) create mode 100644 tests/closures.rs diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index ece288fe5..3b945c0b0 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -81,10 +81,25 @@ pub struct Variant { pub value: u32, } -pub enum Type { - ByRef(syn::Type), - ByMutRef(syn::Type), - ByValue(syn::Type), +pub struct Type { + pub ty: syn::Type, + pub kind: TypeKind, + pub loc: TypeLocation, +} + +#[derive(Copy, Clone)] +pub enum TypeKind { + ByRef, + ByMutRef, + ByValue, +} + +#[derive(Copy, Clone)] +pub enum TypeLocation { + ImportArgument, + ImportRet, + ExportArgument, + ExportRet, } impl Program { @@ -197,6 +212,7 @@ impl Program { opts, method.vis, true, + false, ); self.exports.push(Export { class: Some(class), @@ -284,7 +300,15 @@ impl Program { pub fn push_foreign_fn(&mut self, f: syn::ForeignItemFn, opts: BindgenAttrs) -> ImportKind { let js_name = opts.js_name().unwrap_or(f.ident); - let mut wasm = Function::from_decl(js_name, f.decl, f.attrs, opts, f.vis, false).0; + let mut wasm = Function::from_decl( + js_name, + f.decl, + f.attrs, + opts, + f.vis, + false, + true, + ).0; if wasm.opts.catch() { // TODO: this assumes a whole bunch: // @@ -301,11 +325,7 @@ impl Program { let class = wasm.arguments .get(0) .expect("methods must have at least one argument"); - let class = match *class { - Type::ByRef(ref t) | Type::ByValue(ref t) => t, - Type::ByMutRef(_) => panic!("first method argument cannot be mutable ref"), - }; - let class_name = match *class { + let class_name = match class.ty { syn::Type::Path(syn::TypePath { qself: None, ref path, @@ -317,11 +337,11 @@ impl Program { ImportFunctionKind::Method { class: class_name.as_ref().to_string(), - ty: class.clone(), + ty: class.ty.clone(), } } else if wasm.opts.constructor() { let class = match wasm.ret { - Some(Type::ByValue(ref t)) => t, + Some(Type { ref ty, kind: TypeKind::ByValue, .. }) => ty, _ => panic!("constructor returns must be bare types"), }; let class_name = match *class { @@ -416,7 +436,15 @@ impl Function { panic!("can only bindgen safe functions"); } - Function::from_decl(input.ident, input.decl, input.attrs, opts, input.vis, false).0 + Function::from_decl( + input.ident, + input.decl, + input.attrs, + opts, + input.vis, + false, + false, + ).0 } pub fn from_decl( @@ -426,6 +454,7 @@ impl Function { opts: BindgenAttrs, vis: syn::Visibility, allow_self: bool, + import: bool, ) -> (Function, Option) { if decl.variadic.is_some() { panic!("can't bindgen variadic functions") @@ -449,12 +478,24 @@ impl Function { } _ => panic!("arguments cannot be `self` or ignored"), }) - .map(|arg| Type::from(&arg.ty)) + .map(|arg| { + Type::from(&arg.ty, if import { + TypeLocation::ImportArgument + } else { + TypeLocation::ExportArgument + }) + }) .collect::>(); let ret = match decl.output { syn::ReturnType::Default => None, - syn::ReturnType::Type(_, ref t) => Some(Type::from(t)), + syn::ReturnType::Type(_, ref t) => { + Some(Type::from(t, if import { + TypeLocation::ImportRet + } else { + TypeLocation::ExportRet + })) + } }; ( @@ -486,19 +527,6 @@ pub fn extract_path_ident(path: &syn::Path) -> Option { path.segments.first().map(|v| v.value().ident) } -impl Type { - pub fn from(ty: &syn::Type) -> Type { - if let syn::Type::Reference(ref r) = *ty { - return if r.mutability.is_some() { - Type::ByMutRef((*r.elem).clone()) - } else { - Type::ByRef((*r.elem).clone()) - } - } - Type::ByValue(ty.clone()) - } -} - impl Export { pub fn rust_symbol(&self) -> syn::Ident { let mut generated_name = format!("__wasm_bindgen_generated"); @@ -540,6 +568,22 @@ impl Struct { } } +impl Type { + pub fn from(ty: &syn::Type, loc: TypeLocation) -> Type { + let (ty, kind) = match *ty { + syn::Type::Reference(ref r) => { + if r.mutability.is_some() { + ((*r.elem).clone(), TypeKind::ByMutRef) + } else { + ((*r.elem).clone(), TypeKind::ByRef) + } + } + _ => (ty.clone(), TypeKind::ByValue), + }; + Type { loc, ty, kind } + } +} + #[derive(Default)] pub struct BindgenAttrs { attrs: Vec, @@ -719,12 +763,12 @@ impl syn::synom::Synom for BindgenAttr { } fn extract_first_ty_param(ty: Option<&Type>) -> Option> { - let ty = match ty { + let t = match ty { Some(t) => t, None => return Some(None), }; - let ty = match *ty { - Type::ByValue(ref t) => t, + let ty = match *t { + Type { ref ty, kind: TypeKind::ByValue, .. } => ty, _ => return None, }; let path = match *ty { @@ -747,7 +791,11 @@ fn extract_first_ty_param(ty: Option<&Type>) -> Option> { syn::Type::Tuple(ref t) if t.elems.len() == 0 => return Some(None), _ => {} } - Some(Some(Type::from(ty))) + Some(Some(Type { + ty: ty.clone(), + kind: TypeKind::ByValue, + loc: t.loc, + })) } fn term<'a>(cursor: syn::buffer::Cursor<'a>, name: &str) -> syn::synom::PResult<'a, ()> { diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs index 8ea9e1c6e..9841c006f 100644 --- a/crates/backend/src/codegen.rs +++ b/crates/backend/src/codegen.rs @@ -210,8 +210,9 @@ impl ToTokens for ast::Export { for (i, ty) in self.function.arguments.iter().enumerate() { let i = i + offset; let ident = syn::Ident::from(format!("arg{}", i)); - match *ty { - ast::Type::ByValue(ref t) => { + let t = &ty.ty; + match ty.kind { + ast::TypeKind::ByValue => { args.push(quote! { #ident: <#t as ::wasm_bindgen::convert::WasmBoundary>::Abi }); @@ -222,25 +223,25 @@ impl ToTokens for ast::Export { }; }); } - ast::Type::ByRef(ref ty) => { + ast::TypeKind::ByRef => { args.push(quote! { - #ident: <#ty as ::wasm_bindgen::convert::FromRefWasmBoundary>::Abi + #ident: <#t as ::wasm_bindgen::convert::FromRefWasmBoundary>::Abi }); arg_conversions.push(quote! { let #ident = unsafe { - <#ty as ::wasm_bindgen::convert::FromRefWasmBoundary> + <#t as ::wasm_bindgen::convert::FromRefWasmBoundary> ::from_abi_ref(#ident, &mut __stack) }; let #ident = &*#ident; }); } - ast::Type::ByMutRef(ref ty) => { + ast::TypeKind::ByMutRef => { args.push(quote! { - #ident: <#ty as ::wasm_bindgen::convert::FromRefMutWasmBoundary>::Abi + #ident: <#t as ::wasm_bindgen::convert::FromRefMutWasmBoundary>::Abi }); arg_conversions.push(quote! { let mut #ident = unsafe { - <#ty as ::wasm_bindgen::convert::FromRefMutWasmBoundary> + <#t as ::wasm_bindgen::convert::FromRefMutWasmBoundary> ::from_abi_ref_mut(#ident, &mut __stack) }; let #ident = &mut *#ident; @@ -252,19 +253,19 @@ impl ToTokens for ast::Export { let ret_ty; let convert_ret; match self.function.ret { - Some(ast::Type::ByValue(ref t)) => { + Some(ast::Type { ref ty, kind: ast::TypeKind::ByValue, .. }) => { ret_ty = quote! { - -> <#t as ::wasm_bindgen::convert::WasmBoundary>::Abi + -> <#ty as ::wasm_bindgen::convert::WasmBoundary>::Abi }; convert_ret = quote! { - <#t as ::wasm_bindgen::convert::WasmBoundary> + <#ty as ::wasm_bindgen::convert::WasmBoundary> ::into_abi(#ret, &mut unsafe { ::wasm_bindgen::convert::GlobalStack::new() }) }; } - Some(ast::Type::ByMutRef(_)) - | Some(ast::Type::ByRef(_)) => { + Some(ast::Type { kind: ast::TypeKind::ByMutRef, .. }) | + Some(ast::Type { kind: ast::TypeKind::ByRef, .. }) => { panic!("can't return a borrowed ref"); } None => { @@ -432,8 +433,9 @@ impl ToTokens for ast::ImportFunction { }); for (i, (ty, name)) in self.function.arguments.iter().zip(names).enumerate() { - match *ty { - ast::Type::ByValue(ref t) => { + let t = &ty.ty; + match ty.kind { + ast::TypeKind::ByValue => { abi_argument_names.push(name); abi_arguments.push(quote! { #name: <#t as ::wasm_bindgen::convert::WasmBoundary>::Abi @@ -448,8 +450,8 @@ impl ToTokens for ast::ImportFunction { ::into_abi(#var, &mut __stack); }); } - ast::Type::ByMutRef(_) => panic!("urgh mut"), - ast::Type::ByRef(ref t) => { + ast::TypeKind::ByMutRef => panic!("urgh mut"), + ast::TypeKind::ByRef => { abi_argument_names.push(name); abi_arguments.push(quote! { #name: u32 }); let var = if i == 0 && is_method { @@ -467,20 +469,22 @@ impl ToTokens for ast::ImportFunction { let abi_ret; let mut convert_ret; match self.function.ret { - Some(ast::Type::ByValue(ref t)) => { + Some(ast::Type { ref ty, kind: ast::TypeKind::ByValue, .. }) => { abi_ret = quote! { - <#t as ::wasm_bindgen::convert::WasmBoundary>::Abi + <#ty as ::wasm_bindgen::convert::WasmBoundary>::Abi }; convert_ret = quote! { - <#t as ::wasm_bindgen::convert::WasmBoundary> + <#ty as ::wasm_bindgen::convert::WasmBoundary> ::from_abi( #ret_ident, &mut ::wasm_bindgen::convert::GlobalStack::new(), ) }; } - Some(ast::Type::ByRef(_)) - | Some(ast::Type::ByMutRef(_)) => panic!("can't return a borrowed ref"), + Some(ast::Type { kind: ast::TypeKind::ByRef, .. }) | + Some(ast::Type { kind: ast::TypeKind::ByMutRef, .. }) => { + panic!("can't return a borrowed ref") + } None => { abi_ret = quote! { () }; convert_ret = quote! { () }; diff --git a/crates/backend/src/literal.rs b/crates/backend/src/literal.rs index 992577b4a..24e6d06e7 100644 --- a/crates/backend/src/literal.rs +++ b/crates/backend/src/literal.rs @@ -139,18 +139,31 @@ impl Literal for ast::Function { impl Literal for ast::Type { fn literal(&self, a: &mut LiteralBuilder) { - match *self { - ast::Type::ByValue(ref t) => { + let t = &self.ty; + match self.kind { + ast::TypeKind::ByValue => { a.as_char(quote! { <#t as ::wasm_bindgen::convert::WasmBoundary>::DESCRIPTOR }); } - ast::Type::ByRef(ref ty) | ast::Type::ByMutRef(ref ty) => { - // TODO: this assumes `ToRef*` and `FromRef*` use the same - // descriptor. - a.as_char(quote! { - <#ty as ::wasm_bindgen::convert::FromRefWasmBoundary>::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 + }); + } + } } } } diff --git a/crates/cli-support/src/js.rs b/crates/cli-support/src/js.rs index 9a48df561..3a30d0324 100644 --- a/crates/cli-support/src/js.rs +++ b/crates/cli-support/src/js.rs @@ -19,6 +19,7 @@ pub struct Context<'a> { pub custom_type_names: HashMap, pub imported_names: HashSet, pub exported_classes: HashMap, + pub function_table_needed: bool, } #[derive(Default)] @@ -246,6 +247,7 @@ impl<'a> Context<'a> { ); self.unexport_unused_internal_exports(); + self.export_table(); (js, self.typescript.clone()) } @@ -313,6 +315,22 @@ impl<'a> Context<'a> { } } + fn export_table(&mut self) { + if !self.function_table_needed { + return + } + for section in self.module.sections_mut() { + let exports = match *section { + Section::Export(ref mut s) => s, + _ => continue, + }; + let entry = ExportEntry::new("__wbg_function_table".to_string(), + Internal::Table(0)); + exports.entries_mut().push(entry); + break + } + } + fn rewrite_imports(&mut self, module_name: &str) { for (name, contents) in self._rewrite_imports(module_name) { self.export(&name, &contents); @@ -1404,6 +1422,7 @@ impl<'a, 'b> SubContext<'a, 'b> { let mut abi_args = Vec::new(); let mut extra = String::new(); + let mut finally = String::new(); let mut next_global = 0; for (i, arg) in import.function.arguments.iter().enumerate() { @@ -1419,6 +1438,30 @@ impl<'a, 'b> SubContext<'a, 'b> { self.cx.expose_get_object(); format!("getObject(arg{})", i) } + shared::TYPE_STACK_FUNC0 | + shared::TYPE_STACK_FUNC1 => { + 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) => { @@ -1585,6 +1628,17 @@ impl<'a, 'b> SubContext<'a, 'b> { } else { invoc }; + let invoc = if finally.len() > 0 { + format!(" + try {{ + {} + }} finally {{ + {} + }} + ", invoc, finally) + } else { + invoc + }; dst.push_str(&abi_args.join(", ")); dst.push_str(") {\n"); diff --git a/crates/cli-support/src/lib.rs b/crates/cli-support/src/lib.rs index ad0e3b62d..a8015d407 100644 --- a/crates/cli-support/src/lib.rs +++ b/crates/cli-support/src/lib.rs @@ -94,6 +94,7 @@ impl Bindgen { exported_classes: Default::default(), config: &self, module: &mut module, + function_table_needed: false, }; for program in programs.iter() { cx.add_custom_type_names(program); diff --git a/crates/shared/src/lib.rs b/crates/shared/src/lib.rs index 30126d9f3..87b56c6a6 100644 --- a/crates/shared/src/lib.rs +++ b/crates/shared/src/lib.rs @@ -146,8 +146,10 @@ 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_CUSTOM_START: u32 = 24; +pub const TYPE_CUSTOM_START: u32 = 26; pub const TYPE_CUSTOM_REF_FLAG: u32 = 1; pub fn name_to_descriptor(name: &str) -> u32 { diff --git a/src/convert.rs b/src/convert.rs index e5069247d..07866e6d0 100644 --- a/src/convert.rs +++ b/src/convert.rs @@ -3,7 +3,7 @@ use std::ops::{Deref, DerefMut}; use std::slice; use std::str; -use super::JsValue; +use {JsValue, throw}; #[derive(PartialEq, Eq, Copy, Clone)] pub struct Descriptor { @@ -21,6 +21,9 @@ 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 trait WasmBoundary { type Abi: WasmAbi; const DESCRIPTOR: Descriptor; @@ -337,3 +340,71 @@ impl Stack for GlobalStack { pub unsafe extern fn __wbindgen_global_argument_ptr() -> *mut u32 { GLOBAL_STACK.as_mut_ptr() } + +macro_rules! stack_closures { + ($( + ($($var:ident)*) => $descriptor:ident + )*) => ($( + impl<'a, $($var,)* R> ToRefWasmBoundary for Fn($($var),*) -> R + 'a + where $($var: WasmAbi,)* + R: WasmAbi + { + type Abi = u32; + const DESCRIPTOR: Descriptor = $descriptor; + + fn to_abi_ref(&self, extra: &mut Stack) -> u32 { + #[allow(non_snake_case)] + unsafe extern fn invoke<$($var,)* R>( + a: usize, + b: usize, + $($var: $var),* + ) -> R { + if a == 0 { + throw("stack closure has been destroyed already"); + } + let f: &Fn($($var),*) -> R = mem::transmute((a, b)); + f($($var),*) + } + unsafe { + let (a, b): (usize, usize) = mem::transmute(self); + extra.push(a as u32); + extra.push(b as u32); + invoke::<$($var,)* R> as u32 + } + } + } + + impl<'a, $($var,)*> ToRefWasmBoundary for Fn($($var),*) + 'a + where $($var: WasmAbi,)* + { + type Abi = u32; + const DESCRIPTOR: Descriptor = $descriptor; + + fn to_abi_ref(&self, extra: &mut Stack) -> u32 { + #[allow(non_snake_case)] + unsafe extern fn invoke<$($var,)* >( + a: usize, + b: usize, + $($var: $var),* + ) { + if a == 0 { + throw("stack closure has been destroyed already"); + } + let f: &Fn($($var),*) = mem::transmute((a, b)); + f($($var),*) + } + unsafe { + let (a, b): (usize, usize) = mem::transmute(self); + extra.push(a as u32); + extra.push(b as u32); + invoke::<$($var,)*> as u32 + } + } + } + )*) +} + +stack_closures! { + () => DESCRIPTOR_STACK_FUNC0 + (A) => DESCRIPTOR_STACK_FUNC1 +} diff --git a/tests/closures.rs b/tests/closures.rs new file mode 100644 index 000000000..67bd867ae --- /dev/null +++ b/tests/closures.rs @@ -0,0 +1,90 @@ +extern crate test_support; + +#[test] +fn works() { + test_support::project() + .file("src/lib.rs", r#" + #![feature(proc_macro, wasm_custom_section, wasm_import_module)] + + extern crate wasm_bindgen; + + use std::cell::Cell; + use wasm_bindgen::prelude::*; + + #[wasm_bindgen(module = "./test")] + extern { + fn call(a: &Fn()); + fn thread(a: &Fn(u32) -> u32) -> u32; + } + + #[wasm_bindgen] + pub fn run() { + let a = Cell::new(false); + call(&|| a.set(true)); + assert!(a.get()); + + assert_eq!(thread(&|a| a + 1), 3); + } + "#) + .file("test.ts", r#" + import { run } from "./out"; + + export function call(a: any) { + a(); + } + + export function thread(a: any) { + return a(2); + } + + export function test() { + run(); + } + "#) + .test(); +} + +#[test] +fn cannot_reuse() { + test_support::project() + .file("src/lib.rs", r#" + #![feature(proc_macro, wasm_custom_section, wasm_import_module)] + + extern crate wasm_bindgen; + + use std::cell::Cell; + use wasm_bindgen::prelude::*; + + #[wasm_bindgen(module = "./test")] + extern { + fn call(a: &Fn()); + #[wasm_bindgen(catch)] + fn call_again() -> Result<(), JsValue>; + } + + #[wasm_bindgen] + pub fn run() { + call(&|| {}); + assert!(call_again().is_err()); + } + "#) + .file("test.ts", r#" + import { run } from "./out"; + + let CACHE: any = null; + + export function call(a: any) { + CACHE = a; + } + + export function call_again() { + CACHE(); + } + + export function test() { + run(); + } + "#) + .test(); +} + From 28d6c1bc12c37f561771a8334e66bc3b464a8709 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 4 Apr 2018 08:47:48 -0700 Subject: [PATCH 2/6] Support stack closures with up to 7 arguments --- crates/cli-support/src/js.rs | 8 +++++++- crates/shared/src/lib.rs | 8 +++++++- src/convert.rs | 12 ++++++++++++ 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/crates/cli-support/src/js.rs b/crates/cli-support/src/js.rs index 3a30d0324..10f3c62a7 100644 --- a/crates/cli-support/src/js.rs +++ b/crates/cli-support/src/js.rs @@ -1439,7 +1439,13 @@ impl<'a, 'b> SubContext<'a, 'b> { format!("getObject(arg{})", i) } shared::TYPE_STACK_FUNC0 | - shared::TYPE_STACK_FUNC1 => { + 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)) diff --git a/crates/shared/src/lib.rs b/crates/shared/src/lib.rs index 87b56c6a6..3c0a1b762 100644 --- a/crates/shared/src/lib.rs +++ b/crates/shared/src/lib.rs @@ -148,8 +148,14 @@ 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_CUSTOM_START: u32 = 26; +pub const TYPE_CUSTOM_START: u32 = 32; pub const TYPE_CUSTOM_REF_FLAG: u32 = 1; pub fn name_to_descriptor(name: &str) -> u32 { diff --git a/src/convert.rs b/src/convert.rs index 07866e6d0..3e4862349 100644 --- a/src/convert.rs +++ b/src/convert.rs @@ -23,6 +23,12 @@ 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 trait WasmBoundary { type Abi: WasmAbi; @@ -407,4 +413,10 @@ 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 } From f7f0d578e7045f540ad46c524e8e7b78b9015c2b Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 5 Apr 2018 18:25:22 -0700 Subject: [PATCH 3/6] Support long-lived closures Docs coming soon! --- crates/cli-support/src/js.rs | 35 +++++ crates/shared/src/lib.rs | 3 +- src/closure.rs | 241 +++++++++++++++++++++++++++++++++++ src/convert.rs | 6 +- src/lib.rs | 36 ++++-- tests/closures.rs | 235 ++++++++++++++++++++++++++++++++++ 6 files changed, 541 insertions(+), 15 deletions(-) create mode 100644 src/closure.rs diff --git a/crates/cli-support/src/js.rs b/crates/cli-support/src/js.rs index 10f3c62a7..72f8b8755 100644 --- a/crates/cli-support/src/js.rs +++ b/crates/cli-support/src/js.rs @@ -220,6 +220,37 @@ impl<'a> Context<'a> { return ptr; }") }); + + for i in 0..8 { + let name = format!("__wbindgen_cb_arity{}", i); + bind(&name, &|me| { + me.expose_add_heap_object(); + me.function_table_needed = true; + let args = (0..i) + .map(|x| format!("arg{}", x)) + .collect::>() + .join(", "); + format!("function(a, b, c) {{ + const cb = function({0}) {{ + return this.f(this.a, this.b {1} {0}); + }}; + cb.a = b; + cb.b = c; + cb.f = wasm.__wbg_function_table.get(a); + let real = cb.bind(cb); + real.original = cb; + return addHeapObject(real); + }}", args, if i == 0 {""} else {","}) + }); + } + bind("__wbindgen_cb_drop", &|me| { + me.expose_drop_ref(); + String::from("function(i) { + let obj = getObject(i).original; + obj.a = obj.b = 0; + dropRef(i); + }") + }); } self.rewrite_imports(module_name); @@ -1438,6 +1469,10 @@ impl<'a, 'b> SubContext<'a, 'b> { 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 | diff --git a/crates/shared/src/lib.rs b/crates/shared/src/lib.rs index 3c0a1b762..c937a9675 100644 --- a/crates/shared/src/lib.rs +++ b/crates/shared/src/lib.rs @@ -154,8 +154,9 @@ 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 = 32; +pub const TYPE_CUSTOM_START: u32 = 40; pub const TYPE_CUSTOM_REF_FLAG: u32 = 1; pub fn name_to_descriptor(name: &str) -> u32 { diff --git a/src/closure.rs b/src/closure.rs new file mode 100644 index 000000000..83825cb36 --- /dev/null +++ b/src/closure.rs @@ -0,0 +1,241 @@ +use std::mem::ManuallyDrop; +use std::marker::{self, Unsize}; + +use {throw, JsValue}; +use convert::*; +use __rt::WasmRefCell; + +pub unsafe trait WasmShim<'a> { + #[doc(hidden)] + const DESCRIPTOR: Descriptor; + #[doc(hidden)] + type Wrapper; + #[doc(hidden)] + fn shim() -> u32; + #[doc(hidden)] + fn factory() -> unsafe extern fn(u32, u32, u32) -> u32; + #[doc(hidden)] + fn wrap(u: U) -> Self::Wrapper where U: Unsize + 'a; + #[doc(hidden)] + fn data(t: &Self::Wrapper) -> [u32; 2]; +} + +union RawPtr { + ptr: *const T, + data: [u32; 2] +} + +macro_rules! doit { + ($( + ($($var:ident)*) => $arity:ident + )*) => ($( + // Fn with no return + unsafe impl<'a, $($var: WasmAbi),*> WasmShim<'a> for Fn($($var),*) + 'a { + const DESCRIPTOR: Descriptor = DESCRIPTOR_FUNC; + type Wrapper = Box; + + fn shim() -> u32 { + #[allow(non_snake_case)] + unsafe extern fn shim<$($var),*>( + a: u32, + b: u32, + $($var:$var),* + ) { + if a == 0 { + throw("closure has been destroyed already"); + } + (*RawPtr:: { data: [a, b] }.ptr)($($var),*) + } + shim::<$($var),*> as u32 + } + + fn factory() -> unsafe extern fn(u32, u32, u32) -> u32 { + super::$arity + } + + fn wrap(u: U) -> Self::Wrapper where U: Unsize + 'a { + Box::new(u) as Box + } + + fn data(t: &Self::Wrapper) -> [u32; 2] { + unsafe { + RawPtr:: { ptr: &**t }.data + } + } + } + + // Fn with a return + unsafe impl<'a, $($var: WasmAbi,)* R: WasmAbi> WasmShim<'a> for Fn($($var),*) -> R + 'a { + const DESCRIPTOR: Descriptor = DESCRIPTOR_FUNC; + type Wrapper = Box R + 'a>; + + fn shim() -> u32 { + #[allow(non_snake_case)] + unsafe extern fn shim<$($var,)* R>( + a: u32, + b: u32, + $($var:$var),* + ) -> R { + if a == 0 { + throw("closure has been destroyed already"); + } + (*RawPtr:: R> { data: [a, b] }.ptr)($($var),*) + } + shim::<$($var,)* R> as u32 + } + + fn factory() -> unsafe extern fn(u32, u32, u32) -> u32 { + super::$arity + } + + fn wrap(u: U) -> Self::Wrapper where U: Unsize + 'a { + Box::new(u) as Box + } + + fn data(t: &Self::Wrapper) -> [u32; 2] { + unsafe { + RawPtr:: { ptr: &**t }.data + } + } + } + + // FnMut with no return + unsafe impl<'a, $($var: WasmAbi),*> WasmShim<'a> for FnMut($($var),*) + 'a { + const DESCRIPTOR: Descriptor = DESCRIPTOR_FUNC; + type Wrapper = Box>; + + fn shim() -> u32 { + #[allow(non_snake_case)] + unsafe extern fn shim<$($var),*>( + a: u32, + b: u32, + $($var:$var),* + ) { + if a == 0 { + throw("closure has been destroyed already"); + } + let ptr: *const WasmRefCell = RawPtr { + data: [a, b], + }.ptr; + let mut ptr = (*ptr).borrow_mut(); + (&mut *ptr)($($var),*) + } + shim::<$($var),*> as u32 + } + + fn factory() -> unsafe extern fn(u32, u32, u32) -> u32 { + super::$arity + } + + fn wrap(u: U) -> Self::Wrapper where U: Unsize + 'a { + Box::new(WasmRefCell::new(u)) as Box<_> + } + + fn data(t: &Self::Wrapper) -> [u32; 2] { + unsafe { + RawPtr::> { ptr: &**t }.data + } + } + } + + // FnMut with a return + unsafe impl<'a, $($var: WasmAbi,)* R: WasmAbi> WasmShim<'a> for FnMut($($var),*) -> R + 'a { + const DESCRIPTOR: Descriptor = DESCRIPTOR_FUNC; + type Wrapper = Box R + 'a>>; + + fn shim() -> u32 { + #[allow(non_snake_case)] + unsafe extern fn shim<$($var,)* R>( + a: u32, + b: u32, + $($var:$var),* + ) -> R { + if a == 0 { + throw("closure has been destroyed already"); + } + let ptr: *const WasmRefCell R> = RawPtr { + data: [a, b], + }.ptr; + let mut ptr = (*ptr).borrow_mut(); + (&mut *ptr)($($var),*) + } + shim::<$($var,)* R> as u32 + } + + fn factory() -> unsafe extern fn(u32, u32, u32) -> u32 { + super::$arity + } + + fn wrap(u: U) -> Self::Wrapper where U: Unsize + 'a { + Box::new(WasmRefCell::new(u)) as Box<_> + } + + fn data(t: &Self::Wrapper) -> [u32; 2] { + unsafe { + RawPtr::> { ptr: &**t }.data + } + } + } + )*) +} + +doit! { + () => __wbindgen_cb_arity0 + (A) => __wbindgen_cb_arity1 + (A B) => __wbindgen_cb_arity2 + (A B C) => __wbindgen_cb_arity3 + (A B C D) => __wbindgen_cb_arity4 + (A B C D E) => __wbindgen_cb_arity5 + (A B C D E F) => __wbindgen_cb_arity6 + (A B C D E F G) => __wbindgen_cb_arity7 +} + +pub struct Closure<'a, T: WasmShim<'a> + ?Sized + 'a> { + _inner: T::Wrapper, + js: ManuallyDrop, + _marker: marker::PhantomData<&'a ()>, +} + +impl<'a, T> Closure<'a, T> + where T: WasmShim<'a> + ?Sized, +{ + pub fn new(t: U) -> Closure<'a, T> + where U: Unsize + 'a + { + Closure::wrap(T::wrap(t)) + } + + pub fn wrap(t: T::Wrapper) -> Closure<'a, T> { + unsafe { + let data = T::data(&t); + let js = T::factory()(T::shim(), data[0], data[1]); + Closure { + _inner: t, + js: ManuallyDrop::new(JsValue::from_abi(js, &mut GlobalStack::new())), + _marker: marker::PhantomData, + } + } + } +} + +impl<'a, T> ToRefWasmBoundary for Closure<'a, T> + where T: WasmShim<'a> + ?Sized, +{ + type Abi = u32; + const DESCRIPTOR: Descriptor = T::DESCRIPTOR; + + fn to_abi_ref(&self, extra: &mut Stack) -> u32 { + self.js.to_abi_ref(extra) + } +} + +impl<'a, T> Drop for Closure<'a, T> + where T: WasmShim<'a> + ?Sized, +{ + fn drop(&mut self) { + unsafe { + let idx = self.js.to_abi_ref(&mut GlobalStack::new()); + super::__wbindgen_cb_drop(idx); + } + } +} diff --git a/src/convert.rs b/src/convert.rs index 3e4862349..360ac5a55 100644 --- a/src/convert.rs +++ b/src/convert.rs @@ -30,6 +30,8 @@ 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 { type Abi: WasmAbi; const DESCRIPTOR: Descriptor; @@ -366,7 +368,7 @@ macro_rules! stack_closures { $($var: $var),* ) -> R { if a == 0 { - throw("stack closure has been destroyed already"); + throw("closure has been destroyed already"); } let f: &Fn($($var),*) -> R = mem::transmute((a, b)); f($($var),*) @@ -394,7 +396,7 @@ macro_rules! stack_closures { $($var: $var),* ) { if a == 0 { - throw("stack closure has been destroyed already"); + throw("closure has been destroyed already"); } let f: &Fn($($var),*) = mem::transmute((a, b)); f($($var),*) diff --git a/src/lib.rs b/src/lib.rs index 3b4c9beed..00ad89479 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,7 +5,7 @@ //! this crate and this crate also provides JS bindings through the `JsValue` //! interface. -#![feature(use_extern_macros, wasm_import_module, try_reserve)] +#![feature(use_extern_macros, wasm_import_module, try_reserve, unsize)] extern crate wasm_bindgen_macro; @@ -23,9 +23,11 @@ use convert::WasmBoundary; pub mod prelude { pub use wasm_bindgen_macro::wasm_bindgen; pub use JsValue; + pub use closure::Closure; } pub mod convert; +pub mod closure; /// Representation of an object owned by JS. /// @@ -230,6 +232,16 @@ extern { fn __wbindgen_is_symbol(idx: u32) -> u32; fn __wbindgen_string_get(idx: u32, len: *mut usize) -> *mut u8; fn __wbindgen_throw(a: *const u8, b: usize) -> !; + + fn __wbindgen_cb_arity0(a: u32, b: u32, c: u32) -> u32; + fn __wbindgen_cb_arity1(a: u32, b: u32, c: u32) -> u32; + fn __wbindgen_cb_arity2(a: u32, b: u32, c: u32) -> u32; + fn __wbindgen_cb_arity3(a: u32, b: u32, c: u32) -> u32; + fn __wbindgen_cb_arity4(a: u32, b: u32, c: u32) -> u32; + fn __wbindgen_cb_arity5(a: u32, b: u32, c: u32) -> u32; + fn __wbindgen_cb_arity6(a: u32, b: u32, c: u32) -> u32; + fn __wbindgen_cb_arity7(a: u32, b: u32, c: u32) -> u32; + fn __wbindgen_cb_drop(idx: u32); } impl Clone for JsValue { @@ -341,13 +353,13 @@ pub mod __rt { /// guard accidental reentrancy, so this vendored version is intended solely /// to not panic in libstd. Instead when it "panics" it calls our `throw` /// function in this crate which raises an error in JS. - pub struct WasmRefCell { + pub struct WasmRefCell { borrow: Cell, value: UnsafeCell, } - impl WasmRefCell { - pub fn new(value: T) -> WasmRefCell { + impl WasmRefCell { + pub fn new(value: T) -> WasmRefCell where T: Sized { WasmRefCell { value: UnsafeCell::new(value), borrow: Cell::new(0), @@ -386,17 +398,17 @@ pub mod __rt { } } - pub fn into_inner(self) -> T { + pub fn into_inner(self) -> T where T: Sized { self.value.into_inner() } } - pub struct Ref<'b, T: 'b> { + pub struct Ref<'b, T: ?Sized + 'b> { value: &'b T, borrow: &'b Cell, } - impl<'b, T> Deref for Ref<'b, T> { + impl<'b, T: ?Sized> Deref for Ref<'b, T> { type Target = T; #[inline] @@ -405,18 +417,18 @@ pub mod __rt { } } - impl<'b, T> Drop for Ref<'b, T> { + impl<'b, T: ?Sized> Drop for Ref<'b, T> { fn drop(&mut self) { self.borrow.set(self.borrow.get() - 1); } } - pub struct RefMut<'b, T: 'b> { + pub struct RefMut<'b, T: ?Sized + 'b> { value: &'b mut T, borrow: &'b Cell, } - impl<'b, T> Deref for RefMut<'b, T> { + impl<'b, T: ?Sized> Deref for RefMut<'b, T> { type Target = T; #[inline] @@ -425,14 +437,14 @@ pub mod __rt { } } - impl<'b, T> DerefMut for RefMut<'b, T> { + impl<'b, T: ?Sized> DerefMut for RefMut<'b, T> { #[inline] fn deref_mut(&mut self) -> &mut T { self.value } } - impl<'b, T> Drop for RefMut<'b, T> { + impl<'b, T: ?Sized> Drop for RefMut<'b, T> { fn drop(&mut self) { self.borrow.set(0); } diff --git a/tests/closures.rs b/tests/closures.rs index 67bd867ae..0fdddfbdc 100644 --- a/tests/closures.rs +++ b/tests/closures.rs @@ -88,3 +88,238 @@ fn cannot_reuse() { .test(); } +#[test] +fn long_lived() { + test_support::project() + .file("src/lib.rs", r#" + #![feature(proc_macro, wasm_custom_section, wasm_import_module)] + + extern crate wasm_bindgen; + + use std::cell::Cell; + use wasm_bindgen::prelude::*; + + #[wasm_bindgen(module = "./test")] + extern { + fn call1(a: &Closure); + fn call2(a: &Closure u32>) -> u32; + } + + #[wasm_bindgen] + pub fn run() { + let hit = Cell::new(false); + let a = Closure::new(|| hit.set(true)); + assert!(!hit.get()); + call1(&a); + assert!(hit.get()); + + let mut hit = false; + { + let a = Closure::new(|x| { + hit = true; + x + 3 + }); + assert_eq!(call2(&a), 5); + } + assert!(hit); + } + "#) + .file("test.ts", r#" + import { run } from "./out"; + + export function call1(a: any) { + a(); + } + + export function call2(a: any) { + return a(2); + } + + export function test() { + run(); + } + "#) + .test(); +} + +#[test] +fn many_arity() { + test_support::project() + .file("src/lib.rs", r#" + #![feature(proc_macro, wasm_custom_section, wasm_import_module)] + + extern crate wasm_bindgen; + + use wasm_bindgen::prelude::*; + + #[wasm_bindgen(module = "./test")] + extern { + fn call1(a: &Closure); + fn call2(a: &Closure); + fn call3(a: &Closure); + fn call4(a: &Closure); + fn call5(a: &Closure); + fn call6(a: &Closure); + fn call7(a: &Closure); + fn call8(a: &Closure); + + #[wasm_bindgen(js_name = call1)] + fn stack1(a: &Fn()); + #[wasm_bindgen(js_name = call2)] + fn stack2(a: &Fn(u32)); + #[wasm_bindgen(js_name = call3)] + fn stack3(a: &Fn(u32, u32)); + #[wasm_bindgen(js_name = call4)] + fn stack4(a: &Fn(u32, u32, u32)); + #[wasm_bindgen(js_name = call5)] + fn stack5(a: &Fn(u32, u32, u32, u32)); + #[wasm_bindgen(js_name = call6)] + fn stack6(a: &Fn(u32, u32, u32, u32, u32)); + #[wasm_bindgen(js_name = call7)] + fn stack7(a: &Fn(u32, u32, u32, u32, u32, u32)); + #[wasm_bindgen(js_name = call8)] + fn stack8(a: &Fn(u32, u32, u32, u32, u32, u32, u32)); + } + + #[wasm_bindgen] + pub fn run() { + call1(&Closure::new(|| {})); + call2(&Closure::new(|a| assert_eq!(a, 1))); + call3(&Closure::new(|a, b| assert_eq!((a, b), (1, 2)))); + call4(&Closure::new(|a, b, c| assert_eq!((a, b, c), (1, 2, 3)))); + call5(&Closure::new(|a, b, c, d| { + assert_eq!((a, b, c, d), (1, 2, 3, 4)) + })); + call6(&Closure::new(|a, b, c, d, e| { + assert_eq!((a, b, c, d, e), (1, 2, 3, 4, 5)) + })); + call7(&Closure::new(|a, b, c, d, e, f| { + assert_eq!((a, b, c, d, e, f), (1, 2, 3, 4, 5, 6)) + })); + call8(&Closure::new(|a, b, c, d, e, f, g| { + assert_eq!((a, b, c, d, e, f, g), (1, 2, 3, 4, 5, 6, 7)) + })); + + stack1(&(|| {})); + stack2(&(|a| assert_eq!(a, 1))); + stack3(&(|a, b| assert_eq!((a, b), (1, 2)))); + stack4(&(|a, b, c| assert_eq!((a, b, c), (1, 2, 3)))); + stack5(&(|a, b, c, d| { + assert_eq!((a, b, c, d), (1, 2, 3, 4)) + })); + stack6(&(|a, b, c, d, e| { + assert_eq!((a, b, c, d, e), (1, 2, 3, 4, 5)) + })); + stack7(&(|a, b, c, d, e, f| { + assert_eq!((a, b, c, d, e, f), (1, 2, 3, 4, 5, 6)) + })); + stack8(&(|a, b, c, d, e, f, g| { + assert_eq!((a, b, c, d, e, f, g), (1, 2, 3, 4, 5, 6, 7)) + })); + } + "#) + .file("test.ts", r#" + import { run } from "./out"; + + export function call1(a: any) { a() } + export function call2(a: any) { a(1) } + export function call3(a: any) { a(1, 2) } + export function call4(a: any) { a(1, 2, 3) } + export function call5(a: any) { a(1, 2, 3, 4) } + export function call6(a: any) { a(1, 2, 3, 4, 5) } + export function call7(a: any) { a(1, 2, 3, 4, 5, 6) } + export function call8(a: any) { a(1, 2, 3, 4, 5, 6, 7) } + + export function test() { + run(); + } + "#) + .test(); +} + +#[test] +fn long_lived_dropping() { + test_support::project() + .file("src/lib.rs", r#" + #![feature(proc_macro, wasm_custom_section, wasm_import_module)] + + extern crate wasm_bindgen; + + use std::cell::Cell; + use wasm_bindgen::prelude::*; + + #[wasm_bindgen(module = "./test")] + extern { + fn cache(a: &Closure); + #[wasm_bindgen(catch)] + fn call() -> Result<(), JsValue>; + } + + #[wasm_bindgen] + pub fn run() { + let hit = Cell::new(false); + let a = Closure::new(|| hit.set(true)); + cache(&a); + assert!(!hit.get()); + assert!(call().is_ok()); + assert!(hit.get()); + drop(a); + assert!(call().is_err()); + } + "#) + .file("test.ts", r#" + import { run } from "./out"; + + let CACHE: any = null; + + export function cache(a: any) { CACHE = a; } + export function call() { CACHE() } + + export function test() { + run(); + } + "#) + .test(); +} + +#[test] +fn long_fnmut_recursive() { + test_support::project() + .file("src/lib.rs", r#" + #![feature(proc_macro, wasm_custom_section, wasm_import_module)] + + extern crate wasm_bindgen; + + use std::cell::Cell; + use wasm_bindgen::prelude::*; + + #[wasm_bindgen(module = "./test")] + extern { + fn cache(a: &Closure); + #[wasm_bindgen(catch)] + fn call() -> Result<(), JsValue>; + } + + #[wasm_bindgen] + pub fn run() { + let a = Closure::new(|| { + assert!(call().is_err()); + }); + cache(&a); + assert!(call().is_ok()); + } + "#) + .file("test.ts", r#" + import { run } from "./out"; + + let CACHE: any = null; + + export function cache(a: any) { CACHE = a; } + export function call() { CACHE() } + + export function test() { + run(); + } + "#) + .test(); +} From 66bdd92fa2194887e99b548bc12efcf79840799e Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 5 Apr 2018 18:30:37 -0700 Subject: [PATCH 4/6] More aggressively gc module As soon as we've removed unneeded exports immediately run a gc pass to ensure that we don't bind functions in JS that don't actually end up getting needed. --- crates/cli-support/src/js.rs | 20 ++++++++++++++++++-- crates/cli-support/src/lib.rs | 5 +---- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/crates/cli-support/src/js.rs b/crates/cli-support/src/js.rs index 72f8b8755..ee5290339 100644 --- a/crates/cli-support/src/js.rs +++ b/crates/cli-support/src/js.rs @@ -2,8 +2,10 @@ use std::collections::{HashSet, HashMap}; use std::fmt::Write; use std::mem; -use shared; use parity_wasm::elements::*; +use parity_wasm; +use shared; +use wasm_gc; use super::Bindgen; @@ -61,6 +63,8 @@ impl<'a> Context<'a> { } pub fn finalize(&mut self, module_name: &str) -> (String, String) { + self.unexport_unused_internal_exports(); + self.gc(); self.write_classes(); { let mut bind = |name: &str, f: &Fn(&mut Self) -> String| { @@ -277,8 +281,8 @@ impl<'a> Context<'a> { footer = self.footer, ); - self.unexport_unused_internal_exports(); self.export_table(); + self.gc(); (js, self.typescript.clone()) } @@ -303,6 +307,7 @@ impl<'a> Context<'a> { let new_name = shared::new_function(&class); if self.wasm_import_needed(&new_name) { + self.expose_add_heap_object(); self.export(&new_name, &format!(" function(ptr) {{ return addHeapObject(new {class}(ptr, token)); @@ -319,6 +324,7 @@ impl<'a> Context<'a> { let new_name = shared::new_function(&class); if self.wasm_import_needed(&new_name) { + self.expose_add_heap_object(); self.export(&new_name, &format!(" function(ptr) {{ return addHeapObject(new {class}(ptr)); @@ -1154,6 +1160,16 @@ impl<'a> Context<'a> { } "); } + + fn gc(&mut self) { + let module = mem::replace(self.module, Module::default()); + let wasm_bytes = parity_wasm::serialize(module).unwrap(); + let bytes = wasm_gc::Config::new() + .demangle(false) + .gc(&wasm_bytes) + .unwrap(); + *self.module = deserialize_buffer(&bytes).unwrap(); + } } impl<'a, 'b> SubContext<'a, 'b> { diff --git a/crates/cli-support/src/lib.rs b/crates/cli-support/src/lib.rs index a8015d407..0060fd925 100644 --- a/crates/cli-support/src/lib.rs +++ b/crates/cli-support/src/lib.rs @@ -129,10 +129,7 @@ impl Bindgen { let wasm_bytes = parity_wasm::serialize(module).map_err(|e| { Error(format!("{:?}", e)) })?; - let bytes = wasm_gc::Config::new() - .demangle(false) - .gc(&wasm_bytes)?; - File::create(&wasm_path)?.write_all(&bytes)?; + File::create(&wasm_path)?.write_all(&wasm_bytes)?; Ok(()) } From 176060cc8a19d0d8159952bb787998ec2ab214b2 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 6 Apr 2018 08:05:47 -0700 Subject: [PATCH 5/6] Aggressively optimize for size in release mode --- Cargo.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 82907be90..b78940d74 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,3 +36,5 @@ members = [ [profile.release] lto = true +opt-level = 'z' +panic = 'abort' From a3e5485b86c578beee37963a35bb3b3b0aadade5 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 6 Apr 2018 08:49:21 -0700 Subject: [PATCH 6/6] Add examples/documentation for closures --- Cargo.toml | 1 + DESIGN.md | 44 ++++++ README.md | 77 +++++++++- crates/cli-support/src/js.rs | 6 + examples/README.md | 2 + examples/closures/.gitignore | 4 + examples/closures/Cargo.toml | 10 ++ examples/closures/README.md | 20 +++ examples/closures/build.sh | 12 ++ examples/closures/index.html | 40 +++++ examples/closures/index.js | 4 + examples/closures/package.json | 10 ++ examples/closures/src/lib.rs | 94 ++++++++++++ examples/closures/webpack.config.js | 10 ++ src/closure.rs | 226 ++++++++++++++++++++-------- src/lib.rs | 1 + 16 files changed, 493 insertions(+), 68 deletions(-) create mode 100644 examples/closures/.gitignore create mode 100644 examples/closures/Cargo.toml create mode 100644 examples/closures/README.md create mode 100755 examples/closures/build.sh create mode 100644 examples/closures/index.html create mode 100644 examples/closures/index.js create mode 100644 examples/closures/package.json create mode 100644 examples/closures/src/lib.rs create mode 100644 examples/closures/webpack.config.js diff --git a/Cargo.toml b/Cargo.toml index b78940d74..f1422b48a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ members = [ "examples/math", "examples/performance", "examples/wasm-in-wasm", + "examples/closures", ] [profile.release] diff --git a/DESIGN.md b/DESIGN.md index 63395b2c0..0db86779d 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -872,6 +872,7 @@ Under the hood this generates shims that do a bunch of translation, but it suffices to say that a call in wasm to `foo` should always return appropriately. + ## Customizing import behavior The `#[wasm_bindgen]` macro supports a good amount of configuration for @@ -1037,6 +1038,49 @@ possibilities! All of these functions will call `console.log` in Rust, but each identifier will have only one signature in Rust. +## Closures + +Closures are a particularly tricky topic in wasm-bindgen right now. They use +somewhat advanced language features to currently be implemented and *still* the +amount of functionality you can use is quite limiting. + +Most of the implementation details of closures can be found in `src/convert.rs` +and `src/closure.rs`, effectively the `ToRefWasmBoundary` implementations for +closure types. Stack closures are pretty straightforward in that they pass +a function pointer and a data pointer to JS. This function pointer is accessed +via the exported `WebAssembly.Table` in JS, and the data pointer is passed along +eventually when the JS closure is invoked. + +Stack closures currently only support `Fn` because there's no great location to +insert a `RefCell` for types like `FnMut`. This restriction may be lift-able +though in the future... + +Long-lived closures are a bit more complicated. The general idea there is: + +* First you create a `Closure`. This manufactures a JS callback and "passes it" + to Rust so Rust can store it. +* Next you later pass it as `&Closure<...>` to JS. This extracts the callback + from Rust and passes it to JS. +* Finally you eventually drop the Rust `Closure` which invalidates the JS + closure. + +Creation of the initial JS function is done with a bunch of +`__wbindgen_cb_arityN` functions. These functions create a JS closure with the +given arity (number of arguments). This isn't really that scalable unfortunately +and also means that it's very difficult to support richer types one day. Unsure +how to solve this. + +The `ToRefWasmBoundary` is quite straightforward for `Closure` as it just plucks +out the JS closure and passes it along. The real meat comes down to the +`WasmShim` internal trait. This is implemented for all the *unsized* closure +types to avoid running afoul with coherence. Each trait impl defines a shim +function to be invokeable from JS as well as the ability to wrap up the sized +verion (aka transition from `F: FnMut()` to `FnMut()`). Impls for `FnMut` also +embed the `RefCell` internally. + +The `WasmShim` design is basically the first thing that got working today. It's +not great and will likely change in the future to hopefully be more flexible! + ## Wrapping up That's currently at least what `wasm-bindgen` has to offer! If you've got more diff --git a/README.md b/README.md index d7d838cde..a7401831e 100644 --- a/README.md +++ b/README.md @@ -24,8 +24,8 @@ Notable features of this project includes: * Importing JS functionality in to Rust such as [DOM manipulation][dom-ex], [console logging][console-log], or [performance monitoring][perf-ex]. * [Exporting Rust functionality][smorg-ex] to JS such as classes, functions, etc. -* Working with rich types like strings, numbers, classes, and objects rather - than simply `u32` and floats. +* Working with rich types like strings, numbers, classes, closures, and objects + rather than simply `u32` and floats. This project is still relatively new but feedback is of course always welcome! If you're curious about the design plus even more information about @@ -397,6 +397,79 @@ export class Awesome { booted.then(main); ``` +## Closures + +The `#[wasm_bindgen]` attribute supports a limited subset of Rust closures being +passed to JS at this time. There are plans to expand this support currently but +it's not clear how to proceed unfortunately. In any case some examples of what +you can do are: + +```rust +#[wasm_bindgen] +extern { + fn foo(a: &Fn()); // must be `Fn`, not `FnMut` +} +``` + +Here a function `foo` is imported from JS where the first argument is a *stack +closure*. You can call this function with a `&Fn()` argument and JS will receive +a JS function. When the `foo` function returns, however, the JS function will be +invalidated and any future usage of it will raise an exception. + +Closures also support arguments and return values native to the wasm type +system, aka f32/u32: + +```rust +#[wasm_bindgen] +extern { + fn bar(a: &Fn(u32, f32) -> f64); +} +``` + +At this time [types like strings aren't supported][cbstr] unfortunately. + +[cbstr]: https://github.com/rustwasm/wasm-bindgen/issues/104 + +Sometimes the stack behavior of these closures is not desired. For example you'd +like to schedule a closure to be run on the next turn of the event loop in JS +through `setTimeout`. For this you want the imported function to return but the +JS closure still needs to be valid! + +To support this use case you can also do: + +```rust +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +extern { + fn baz(a: &Closure); +} +``` + +The `Closure` type is defined in the `wasm_bindgen` crate and represents a "long +lived" closure. The JS closure passed to `baz` is still valid after `baz` +returns, and the validity of the JS closure is tied to the lifetime of the +`Closure` in Rust. Once `Closure` is dropped it will deallocate its internal +memory and invalidate the corresponding JS function. + +Unlike stack closures a `Closure` supports `FnMut`: + +```rust +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +extern { + fn another(a: &Closure u32>); +} +``` + +Like stack closures, however, only wasm types like u32/f32 are supported today. + +At this time you cannot [pass a JS closure to Rust][cbjs], you can only pass a +Rust closure to JS in limited circumstances. + +[cbjs]: https://github.com/rustwasm/wasm-bindgen/issues/103 + ## Feature reference Here this section will attempt to be a reference for the various features diff --git a/crates/cli-support/src/js.rs b/crates/cli-support/src/js.rs index ee5290339..fbc820cd3 100644 --- a/crates/cli-support/src/js.rs +++ b/crates/cli-support/src/js.rs @@ -255,6 +255,12 @@ impl<'a> Context<'a> { dropRef(i); }") }); + bind("__wbindgen_cb_forget", &|me| { + me.expose_drop_ref(); + String::from("function(i) { + dropRef(i); + }") + }); } self.rewrite_imports(module_name); diff --git a/examples/README.md b/examples/README.md index 2834b24e5..da64b936a 100644 --- a/examples/README.md +++ b/examples/README.md @@ -22,3 +22,5 @@ The examples here are: operations in Rust * `wasm-in-wasm` - how to interact with namespaced APIs like `WebAssembly.Module` and shows off creation of a WebAssembly module from Rust +* `closures` - an example of how to invoke functions like `setInterval` or use + the `onclick` property in conjunction with closures. diff --git a/examples/closures/.gitignore b/examples/closures/.gitignore new file mode 100644 index 000000000..3e0dcd948 --- /dev/null +++ b/examples/closures/.gitignore @@ -0,0 +1,4 @@ +package-lock.json +closures.js +closures_bg.js +closures_bg.wasm diff --git a/examples/closures/Cargo.toml b/examples/closures/Cargo.toml new file mode 100644 index 000000000..3fafb3481 --- /dev/null +++ b/examples/closures/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "closures" +version = "0.1.0" +authors = ["Alex Crichton "] + +[lib] +crate-type = ["cdylib"] + +[dependencies] +wasm-bindgen = { path = "../.." } diff --git a/examples/closures/README.md b/examples/closures/README.md new file mode 100644 index 000000000..b3ee97283 --- /dev/null +++ b/examples/closures/README.md @@ -0,0 +1,20 @@ +# Closure examples + +This directory is an example of using the `#[wasm_bindgen]` macro with closures +to interact with the DOM. + +You can build the example with: + +``` +$ ./build.sh +``` + +(or running the commands on Windows manually) + +and then opening up `index.html` in a web browser should show a hello message on +the web page generated by the wasm. + +For more information about this example be sure to check out +[`hello_world`][hello] which also has more comments about caveats and such. + +[hello]: https://github.com/alexcrichton/wasm-bindgen/tree/master/examples/hello_world diff --git a/examples/closures/build.sh b/examples/closures/build.sh new file mode 100755 index 000000000..438e8331a --- /dev/null +++ b/examples/closures/build.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +# For more coments about what's going on here, see the `hello_world` example + +set -ex + +cargo +nightly build --target wasm32-unknown-unknown +cargo +nightly run --manifest-path ../../crates/cli/Cargo.toml \ + --bin wasm-bindgen -- \ + ../../target/wasm32-unknown-unknown/debug/closures.wasm --out-dir . +#npm install +npm run serve diff --git a/examples/closures/index.html b/examples/closures/index.html new file mode 100644 index 000000000..e0b1d36a6 --- /dev/null +++ b/examples/closures/index.html @@ -0,0 +1,40 @@ + + + + + + +
+ Loading... +
+ + + + diff --git a/examples/closures/index.js b/examples/closures/index.js new file mode 100644 index 000000000..3035ac7ab --- /dev/null +++ b/examples/closures/index.js @@ -0,0 +1,4 @@ +// For more comments about what's going on here, check out the `hello_world` +// example +const rust = import("./closures"); +rust.then(m => m.run()); diff --git a/examples/closures/package.json b/examples/closures/package.json new file mode 100644 index 000000000..408b462e6 --- /dev/null +++ b/examples/closures/package.json @@ -0,0 +1,10 @@ +{ + "scripts": { + "serve": "webpack-dev-server" + }, + "devDependencies": { + "webpack": "^4.0.1", + "webpack-cli": "^2.0.10", + "webpack-dev-server": "^3.1.0" + } +} diff --git a/examples/closures/src/lib.rs b/examples/closures/src/lib.rs new file mode 100644 index 000000000..b49178451 --- /dev/null +++ b/examples/closures/src/lib.rs @@ -0,0 +1,94 @@ +#![feature(proc_macro, wasm_custom_section, wasm_import_module)] + +extern crate wasm_bindgen; + +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +extern { + // Binding for the `setInverval` method in JS. This function takes a "long + // lived" closure as the first argument so we use `Closure` instead of + // a bare `&Fn()` which only surives for that one stack frame. + // + // The second argument is then the interval and the return value is how we + // clear this interval. We're not going to clear our interval in this + // example though so the return value is ignored. + #[wasm_bindgen(js_name = setInterval)] + fn set_interval(cb: &Closure, delay: u32) -> f64; + + // Bindings for JS `Date` so we can update our local timer + type Date; + #[wasm_bindgen(constructor)] + fn new() -> Date; + #[wasm_bindgen(method, js_name = toLocaleString)] + fn to_locale_string(this: &Date) -> String; + + // Bindings for `document` and various methods of updating HTML elements. + // Like with the `dom` example these'll ideally be upstream in a generated + // crate one day but for now we manually define them. + type HTMLDocument; + static document: HTMLDocument; + #[wasm_bindgen(method, js_name = getElementById)] + fn get_element_by_id(this: &HTMLDocument, id: &str) -> Element; + #[wasm_bindgen(method, js_name = getElementById)] + fn get_html_element_by_id(this: &HTMLDocument, id: &str) -> HTMLElement; + + type Element; + #[wasm_bindgen(method, setter = innerHTML)] + fn set_inner_html(this: &Element, html: &str); + + type HTMLElement; + #[wasm_bindgen(method, setter)] + fn set_onclick(this: &HTMLElement, cb: &Closure); + #[wasm_bindgen(method, getter)] + fn style(this: &HTMLElement) -> CSS2Properties; + + type CSS2Properties; + #[wasm_bindgen(method, setter)] + fn set_display(this: &CSS2Properties, display: &str); +} + +#[wasm_bindgen] +pub fn run() { + // Set up a clock on our page and update it each second to ensure it's got + // an accurate date. + let a = Closure::new(update_time); + set_interval(&a, 1000); + update_time(); + fn update_time() { + document.get_element_by_id("current-time") + .set_inner_html(&Date::new().to_locale_string()); + } + + // We also want to count the number of times that our green square has been + // clicked. Our callback will update the `#num-clicks` div + let square = document.get_html_element_by_id("green-square"); + let mut clicks = 0; + let b = Closure::new(move || { + clicks += 1; + document.get_element_by_id("num-clicks") + .set_inner_html(&clicks.to_string()); + }); + square.set_onclick(&b); + + // The instances of `Closure` that we created will invalidate their + // corresponding JS callback whenever they're dropped, so if we were to + // normally return from `run` then both of our registered closures will + // raise exceptions when invoked. + // + // Normally we'd store these handles to later get dropped at an appropriate + // time but for now we want these to be global handlers so we use the + // `forget` method to drop them without invalidating the closure. Note that + // this is leaking memory in Rust, so this should be done judiciously! + a.forget(); + b.forget(); + + // And finally now that our demo is ready to go let's switch things up so + // everything is displayed and our loading prompt is hidden. + document.get_html_element_by_id("loading") + .style() + .set_display("none"); + document.get_html_element_by_id("script") + .style() + .set_display("block"); +} diff --git a/examples/closures/webpack.config.js b/examples/closures/webpack.config.js new file mode 100644 index 000000000..5910e2ae1 --- /dev/null +++ b/examples/closures/webpack.config.js @@ -0,0 +1,10 @@ +const path = require('path'); + +module.exports = { + entry: "./index.js", + output: { + path: path.resolve(__dirname, "dist"), + filename: "index.js", + }, + mode: "development" +}; diff --git a/src/closure.rs b/src/closure.rs index 83825cb36..65a464c90 100644 --- a/src/closure.rs +++ b/src/closure.rs @@ -1,11 +1,155 @@ -use std::mem::ManuallyDrop; -use std::marker::{self, Unsize}; +//! Support for long-lived closures in `wasm-bindgen` +//! +//! This module defines the `Closure` type which is used to pass "owned +//! closures" from Rust to JS. Some more details can be found on the `Closure` +//! type itself. + +use std::mem::{self, ManuallyDrop}; +use std::marker::Unsize; use {throw, JsValue}; use convert::*; use __rt::WasmRefCell; -pub unsafe trait WasmShim<'a> { +/// A handle to both a closure in Rust as well as JS closure which will invoke +/// the Rust closure. +/// +/// A `Closure` is the primary way that a `'static` lifetime closure is +/// transferred from Rust to JS. `Closure` currently requires that the closures +/// it's created with have the `'static` lifetime in Rust for soundness reasons. +/// +/// This type is a "handle" in the sense that whenever it is dropped it will +/// invalidate the JS closure that it refers to. Any usage of the closure in JS +/// after the `Closure` has been dropped will raise an exception. It's then up +/// to you to arrange for `Closure` to be properly deallocate at an appropriate +/// location in your program. +/// +/// The type parameter on `Closure` is the type of closure that this represents. +/// Currently this can only be the `Fn` and `FnMut` traits with up to 7 +/// arguments (and an optional return value). The arguments/return value of the +/// trait must be numbers like `u32` for now, although this restriction may be +/// lifted in the future! +/// +/// # Example +/// +/// ```rust,no_run +/// #[wasm_bindgen] +/// extern { +/// fn setTimeout(closure: &Closure, time: u32); +/// +/// #[wasm_bindgen(js_namespace = console)] +/// fn log(s: &str); +/// } +/// +/// #[wasm_bindgen] +/// pub struct ClosureHandle(Closure); +/// +/// #[wasm_bindgen] +/// pub fn run() -> ClosureHandle { +/// // First up we use `Closure::new` to wrap up a Rust closure and create +/// a JS closure. +/// let cb = Closure::new(|| { +/// log("timeout elapsed!"); +/// }); +/// +/// // Next we pass this via reference to the `setTimeout` function, and +/// // `setTimeout` gets a handle to the corresponding JS closure. +/// setTimeout(&cb, 1_000); +/// +/// // If we were to drop `cb` here it would cause an exception to be raised +/// // when the timeout elapses. Instead we *return* our handle back to JS +/// // so JS can tell us later when it would like to deallocate this handle. +/// ClosureHandle(cb) +/// } +/// ``` +pub struct Closure { + _inner: T::Wrapper, + js: ManuallyDrop, +} + +impl Closure + where T: WasmShim + ?Sized, +{ + /// Creates a new instance of `Closure` from the provided Rust closure. + /// + /// Note that the closure provided here, `F`, has a few requirements + /// associated with it: + /// + /// * It must implement `Fn` or `FnMut` + /// * It must be `'static`, aka no stack references (use the `move` keyword) + /// * It can have at most 7 arguments + /// * Its arguments and return values are all wasm types like u32/f64. + /// + /// This is unfortunately pretty restrictive for now but hopefully some of + /// these restrictions can be lifted in the future! + pub fn new(t: F) -> Closure + where F: Unsize + 'static + { + Closure::wrap(T::wrap(t)) + } + + /// A mostly internal function to wrap a boxed closure inside a `Closure` + /// type. + /// + /// This is the function where the JS closure is manufactured. + pub fn wrap(t: T::Wrapper) -> Closure { + unsafe { + let data = T::data(&t); + let js = T::factory()(T::shim(), data[0], data[1]); + Closure { + _inner: t, + js: ManuallyDrop::new(JsValue::from_abi(js, &mut GlobalStack::new())), + } + } + } + + /// Leaks this `Closure` to ensure it remains valid for the duration of the + /// entire program. + /// + /// > **Note**: this function will leak memory. It should be used sparingly + /// > to ensure the memory leak doesn't affect the program too much. + /// + /// When a `Closure` is dropped it will invalidate the associated JS + /// closure, but this isn't always desired. Some callbacks are alive for + /// the entire duration of the program, so this can be used to conveniently + /// leak this instance of `Closure` while performing as much internal + /// cleanup as it can. + pub fn forget(self) { + unsafe { + super::__wbindgen_cb_forget(self.js.to_abi_ref(&mut GlobalStack::new())); + mem::forget(self); + } + } +} + +// `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) + } +} + +impl Drop for Closure + where T: WasmShim + ?Sized, +{ + fn drop(&mut self) { + unsafe { + let idx = self.js.to_abi_ref(&mut GlobalStack::new()); + super::__wbindgen_cb_drop(idx); + } + } +} + +/// An internal trait for the `Closure` type. +/// +/// 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; #[doc(hidden)] @@ -15,7 +159,7 @@ pub unsafe trait WasmShim<'a> { #[doc(hidden)] fn factory() -> unsafe extern fn(u32, u32, u32) -> u32; #[doc(hidden)] - fn wrap(u: U) -> Self::Wrapper where U: Unsize + 'a; + fn wrap(u: U) -> Self::Wrapper where U: Unsize + 'static; #[doc(hidden)] fn data(t: &Self::Wrapper) -> [u32; 2]; } @@ -30,9 +174,9 @@ macro_rules! doit { ($($var:ident)*) => $arity:ident )*) => ($( // Fn with no return - unsafe impl<'a, $($var: WasmAbi),*> WasmShim<'a> for Fn($($var),*) + 'a { + unsafe impl<$($var: WasmAbi),*> WasmShim for Fn($($var),*) { const DESCRIPTOR: Descriptor = DESCRIPTOR_FUNC; - type Wrapper = Box; + type Wrapper = Box; fn shim() -> u32 { #[allow(non_snake_case)] @@ -53,7 +197,7 @@ macro_rules! doit { super::$arity } - fn wrap(u: U) -> Self::Wrapper where U: Unsize + 'a { + fn wrap(u: U) -> Self::Wrapper where U: Unsize + 'static { Box::new(u) as Box } @@ -65,9 +209,9 @@ macro_rules! doit { } // Fn with a return - unsafe impl<'a, $($var: WasmAbi,)* R: WasmAbi> WasmShim<'a> for Fn($($var),*) -> R + 'a { + unsafe impl<$($var: WasmAbi,)* R: WasmAbi> WasmShim for Fn($($var),*) -> R { const DESCRIPTOR: Descriptor = DESCRIPTOR_FUNC; - type Wrapper = Box R + 'a>; + type Wrapper = Box R>; fn shim() -> u32 { #[allow(non_snake_case)] @@ -88,7 +232,7 @@ macro_rules! doit { super::$arity } - fn wrap(u: U) -> Self::Wrapper where U: Unsize + 'a { + fn wrap(u: U) -> Self::Wrapper where U: Unsize + 'static { Box::new(u) as Box } @@ -100,9 +244,9 @@ macro_rules! doit { } // FnMut with no return - unsafe impl<'a, $($var: WasmAbi),*> WasmShim<'a> for FnMut($($var),*) + 'a { + unsafe impl<$($var: WasmAbi),*> WasmShim for FnMut($($var),*) { const DESCRIPTOR: Descriptor = DESCRIPTOR_FUNC; - type Wrapper = Box>; + type Wrapper = Box>; fn shim() -> u32 { #[allow(non_snake_case)] @@ -127,7 +271,7 @@ macro_rules! doit { super::$arity } - fn wrap(u: U) -> Self::Wrapper where U: Unsize + 'a { + fn wrap(u: U) -> Self::Wrapper where U: Unsize + 'static { Box::new(WasmRefCell::new(u)) as Box<_> } @@ -139,9 +283,9 @@ macro_rules! doit { } // FnMut with a return - unsafe impl<'a, $($var: WasmAbi,)* R: WasmAbi> WasmShim<'a> for FnMut($($var),*) -> R + 'a { + unsafe impl<$($var: WasmAbi,)* R: WasmAbi> WasmShim for FnMut($($var),*) -> R { const DESCRIPTOR: Descriptor = DESCRIPTOR_FUNC; - type Wrapper = Box R + 'a>>; + type Wrapper = Box R>>; fn shim() -> u32 { #[allow(non_snake_case)] @@ -166,7 +310,7 @@ macro_rules! doit { super::$arity } - fn wrap(u: U) -> Self::Wrapper where U: Unsize + 'a { + fn wrap(u: U) -> Self::Wrapper where U: Unsize + 'static { Box::new(WasmRefCell::new(u)) as Box<_> } @@ -189,53 +333,3 @@ doit! { (A B C D E F) => __wbindgen_cb_arity6 (A B C D E F G) => __wbindgen_cb_arity7 } - -pub struct Closure<'a, T: WasmShim<'a> + ?Sized + 'a> { - _inner: T::Wrapper, - js: ManuallyDrop, - _marker: marker::PhantomData<&'a ()>, -} - -impl<'a, T> Closure<'a, T> - where T: WasmShim<'a> + ?Sized, -{ - pub fn new(t: U) -> Closure<'a, T> - where U: Unsize + 'a - { - Closure::wrap(T::wrap(t)) - } - - pub fn wrap(t: T::Wrapper) -> Closure<'a, T> { - unsafe { - let data = T::data(&t); - let js = T::factory()(T::shim(), data[0], data[1]); - Closure { - _inner: t, - js: ManuallyDrop::new(JsValue::from_abi(js, &mut GlobalStack::new())), - _marker: marker::PhantomData, - } - } - } -} - -impl<'a, T> ToRefWasmBoundary for Closure<'a, T> - where T: WasmShim<'a> + ?Sized, -{ - type Abi = u32; - const DESCRIPTOR: Descriptor = T::DESCRIPTOR; - - fn to_abi_ref(&self, extra: &mut Stack) -> u32 { - self.js.to_abi_ref(extra) - } -} - -impl<'a, T> Drop for Closure<'a, T> - where T: WasmShim<'a> + ?Sized, -{ - fn drop(&mut self) { - unsafe { - let idx = self.js.to_abi_ref(&mut GlobalStack::new()); - super::__wbindgen_cb_drop(idx); - } - } -} diff --git a/src/lib.rs b/src/lib.rs index 00ad89479..1773dcbfa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -242,6 +242,7 @@ extern { fn __wbindgen_cb_arity6(a: u32, b: u32, c: u32) -> u32; fn __wbindgen_cb_arity7(a: u32, b: u32, c: u32) -> u32; fn __wbindgen_cb_drop(idx: u32); + fn __wbindgen_cb_forget(idx: u32); } impl Clone for JsValue {