diff --git a/Cargo.toml b/Cargo.toml index 82907be90..f1422b48a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,10 @@ members = [ "examples/math", "examples/performance", "examples/wasm-in-wasm", + "examples/closures", ] [profile.release] lto = true +opt-level = 'z' +panic = 'abort' 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/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..fbc820cd3 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; @@ -19,6 +21,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)] @@ -60,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| { @@ -219,6 +224,43 @@ 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); + }") + }); + bind("__wbindgen_cb_forget", &|me| { + me.expose_drop_ref(); + String::from("function(i) { + dropRef(i); + }") + }); } self.rewrite_imports(module_name); @@ -245,7 +287,8 @@ impl<'a> Context<'a> { footer = self.footer, ); - self.unexport_unused_internal_exports(); + self.export_table(); + self.gc(); (js, self.typescript.clone()) } @@ -270,6 +313,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)); @@ -286,6 +330,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)); @@ -313,6 +358,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); @@ -1105,6 +1166,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> { @@ -1404,6 +1475,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 +1491,40 @@ 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 | + 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) => { @@ -1585,6 +1691,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..0060fd925 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); @@ -128,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(()) } diff --git a/crates/shared/src/lib.rs b/crates/shared/src/lib.rs index 30126d9f3..c937a9675 100644 --- a/crates/shared/src/lib.rs +++ b/crates/shared/src/lib.rs @@ -146,8 +146,17 @@ 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 = 24; +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/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 new file mode 100644 index 000000000..65a464c90 --- /dev/null +++ b/src/closure.rs @@ -0,0 +1,335 @@ +//! 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; + +/// 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)] + 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 + 'static; + #[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<$($var: WasmAbi),*> WasmShim for Fn($($var),*) { + 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 + 'static { + Box::new(u) as Box + } + + fn data(t: &Self::Wrapper) -> [u32; 2] { + unsafe { + RawPtr:: { ptr: &**t }.data + } + } + } + + // Fn with a return + unsafe impl<$($var: WasmAbi,)* R: WasmAbi> WasmShim for Fn($($var),*) -> R { + const DESCRIPTOR: Descriptor = DESCRIPTOR_FUNC; + type Wrapper = Box R>; + + 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 + 'static { + Box::new(u) as Box + } + + fn data(t: &Self::Wrapper) -> [u32; 2] { + unsafe { + RawPtr:: { ptr: &**t }.data + } + } + } + + // FnMut with no return + unsafe impl<$($var: WasmAbi),*> WasmShim for FnMut($($var),*) { + 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 + 'static { + 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<$($var: WasmAbi,)* R: WasmAbi> WasmShim for FnMut($($var),*) -> R { + const DESCRIPTOR: Descriptor = DESCRIPTOR_FUNC; + type Wrapper = Box R>>; + + 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 + 'static { + 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 +} diff --git a/src/convert.rs b/src/convert.rs index e5069247d..360ac5a55 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,17 @@ 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 { type Abi: WasmAbi; const DESCRIPTOR: Descriptor; @@ -337,3 +348,77 @@ 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("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("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 + (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 +} diff --git a/src/lib.rs b/src/lib.rs index 3b4c9beed..1773dcbfa 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,17 @@ 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); + fn __wbindgen_cb_forget(idx: u32); } impl Clone for JsValue { @@ -341,13 +354,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 +399,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 +418,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 +438,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 new file mode 100644 index 000000000..0fdddfbdc --- /dev/null +++ b/tests/closures.rs @@ -0,0 +1,325 @@ +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(); +} + +#[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(); +}