mirror of
https://github.com/rustwasm/wasm-bindgen.git
synced 2025-01-07 05:31:37 +03:00
Merge pull request #101 from rustwasm/closures
Initial support for closures
This commit is contained in:
commit
ee93122c5b
@ -32,7 +32,10 @@ members = [
|
||||
"examples/math",
|
||||
"examples/performance",
|
||||
"examples/wasm-in-wasm",
|
||||
"examples/closures",
|
||||
]
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
opt-level = 'z'
|
||||
panic = 'abort'
|
||||
|
44
DESIGN.md
44
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
|
||||
|
77
README.md
77
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<Fn()>);
|
||||
}
|
||||
```
|
||||
|
||||
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<FnMut() -> 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
|
||||
|
@ -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<bool>) {
|
||||
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::<Vec<_>>();
|
||||
|
||||
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<syn::Ident> {
|
||||
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<BindgenAttr>,
|
||||
@ -719,12 +763,12 @@ impl syn::synom::Synom for BindgenAttr {
|
||||
}
|
||||
|
||||
fn extract_first_ty_param(ty: Option<&Type>) -> Option<Option<Type>> {
|
||||
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<Option<Type>> {
|
||||
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, ()> {
|
||||
|
@ -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! { () };
|
||||
|
@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<u32, String>,
|
||||
pub imported_names: HashSet<String>,
|
||||
pub exported_classes: HashMap<String, ExportedClass>,
|
||||
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::<Vec<_>>()
|
||||
.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::<Vec<_>>()
|
||||
.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");
|
||||
|
@ -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(())
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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.
|
||||
|
4
examples/closures/.gitignore
vendored
Normal file
4
examples/closures/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
package-lock.json
|
||||
closures.js
|
||||
closures_bg.js
|
||||
closures_bg.wasm
|
10
examples/closures/Cargo.toml
Normal file
10
examples/closures/Cargo.toml
Normal file
@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "closures"
|
||||
version = "0.1.0"
|
||||
authors = ["Alex Crichton <alex@alexcrichton.com>"]
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
wasm-bindgen = { path = "../.." }
|
20
examples/closures/README.md
Normal file
20
examples/closures/README.md
Normal file
@ -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
|
12
examples/closures/build.sh
Executable file
12
examples/closures/build.sh
Executable file
@ -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
|
40
examples/closures/index.html
Normal file
40
examples/closures/index.html
Normal file
@ -0,0 +1,40 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta content="text/html;charset=utf-8" http-equiv="Content-Type"/>
|
||||
<style>
|
||||
#green-square {
|
||||
background: green;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
text-align: center;
|
||||
line-height: 200px;
|
||||
color: white;
|
||||
}
|
||||
#green-square span {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id='loading'>
|
||||
Loading...
|
||||
</div>
|
||||
<div id='script' style='display:none'>
|
||||
<p>
|
||||
The current time is:
|
||||
<span id='current-time'>...</span>
|
||||
</p>
|
||||
|
||||
<div id='green-square'>
|
||||
<span>Click me!</span>
|
||||
</div>
|
||||
<p>
|
||||
You've clicked the green square
|
||||
<span id='num-clicks'>0</span>
|
||||
times
|
||||
</p>
|
||||
</div>
|
||||
<script src='./index.js'></script>
|
||||
</body>
|
||||
</html>
|
4
examples/closures/index.js
Normal file
4
examples/closures/index.js
Normal file
@ -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());
|
10
examples/closures/package.json
Normal file
10
examples/closures/package.json
Normal file
@ -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"
|
||||
}
|
||||
}
|
94
examples/closures/src/lib.rs
Normal file
94
examples/closures/src/lib.rs
Normal file
@ -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<FnMut()>, 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<FnMut()>);
|
||||
#[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");
|
||||
}
|
10
examples/closures/webpack.config.js
Normal file
10
examples/closures/webpack.config.js
Normal file
@ -0,0 +1,10 @@
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
entry: "./index.js",
|
||||
output: {
|
||||
path: path.resolve(__dirname, "dist"),
|
||||
filename: "index.js",
|
||||
},
|
||||
mode: "development"
|
||||
};
|
335
src/closure.rs
Normal file
335
src/closure.rs
Normal file
@ -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<FnMut()>, time: u32);
|
||||
///
|
||||
/// #[wasm_bindgen(js_namespace = console)]
|
||||
/// fn log(s: &str);
|
||||
/// }
|
||||
///
|
||||
/// #[wasm_bindgen]
|
||||
/// pub struct ClosureHandle(Closure<FnMut()>);
|
||||
///
|
||||
/// #[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<T: WasmShim + ?Sized> {
|
||||
_inner: T::Wrapper,
|
||||
js: ManuallyDrop<JsValue>,
|
||||
}
|
||||
|
||||
impl<T> Closure<T>
|
||||
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<F>(t: F) -> Closure<T>
|
||||
where F: Unsize<T> + '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<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())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<T> ToRefWasmBoundary for Closure<T>
|
||||
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<T> Drop for Closure<T>
|
||||
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: U) -> Self::Wrapper where U: Unsize<Self> + 'static;
|
||||
#[doc(hidden)]
|
||||
fn data(t: &Self::Wrapper) -> [u32; 2];
|
||||
}
|
||||
|
||||
union RawPtr<T: ?Sized> {
|
||||
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($($var),*)>;
|
||||
|
||||
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::<Fn($($var),*)> { data: [a, b] }.ptr)($($var),*)
|
||||
}
|
||||
shim::<$($var),*> as u32
|
||||
}
|
||||
|
||||
fn factory() -> unsafe extern fn(u32, u32, u32) -> u32 {
|
||||
super::$arity
|
||||
}
|
||||
|
||||
fn wrap<U>(u: U) -> Self::Wrapper where U: Unsize<Self> + 'static {
|
||||
Box::new(u) as Box<Self>
|
||||
}
|
||||
|
||||
fn data(t: &Self::Wrapper) -> [u32; 2] {
|
||||
unsafe {
|
||||
RawPtr::<Self> { 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<Fn($($var),*) -> 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::<Fn($($var),*) -> 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: U) -> Self::Wrapper where U: Unsize<Self> + 'static {
|
||||
Box::new(u) as Box<Self>
|
||||
}
|
||||
|
||||
fn data(t: &Self::Wrapper) -> [u32; 2] {
|
||||
unsafe {
|
||||
RawPtr::<Self> { ptr: &**t }.data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FnMut with no return
|
||||
unsafe impl<$($var: WasmAbi),*> WasmShim for FnMut($($var),*) {
|
||||
const DESCRIPTOR: Descriptor = DESCRIPTOR_FUNC;
|
||||
type Wrapper = Box<WasmRefCell<FnMut($($var),*)>>;
|
||||
|
||||
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<FnMut($($var),*)> = 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: U) -> Self::Wrapper where U: Unsize<Self> + 'static {
|
||||
Box::new(WasmRefCell::new(u)) as Box<_>
|
||||
}
|
||||
|
||||
fn data(t: &Self::Wrapper) -> [u32; 2] {
|
||||
unsafe {
|
||||
RawPtr::<WasmRefCell<Self>> { 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<WasmRefCell<FnMut($($var),*) -> 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<FnMut($($var),*) -> 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: U) -> Self::Wrapper where U: Unsize<Self> + 'static {
|
||||
Box::new(WasmRefCell::new(u)) as Box<_>
|
||||
}
|
||||
|
||||
fn data(t: &Self::Wrapper) -> [u32; 2] {
|
||||
unsafe {
|
||||
RawPtr::<WasmRefCell<Self>> { 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
|
||||
}
|
@ -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
|
||||
}
|
||||
|
37
src/lib.rs
37
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<T> {
|
||||
pub struct WasmRefCell<T: ?Sized> {
|
||||
borrow: Cell<usize>,
|
||||
value: UnsafeCell<T>,
|
||||
}
|
||||
|
||||
impl<T> WasmRefCell<T> {
|
||||
pub fn new(value: T) -> WasmRefCell<T> {
|
||||
impl<T: ?Sized> WasmRefCell<T> {
|
||||
pub fn new(value: T) -> WasmRefCell<T> 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<usize>,
|
||||
}
|
||||
|
||||
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<usize>,
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
325
tests/closures.rs
Normal file
325
tests/closures.rs
Normal file
@ -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()>);
|
||||
fn call2(a: &Closure<FnMut(u32) -> 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()>);
|
||||
fn call2(a: &Closure<Fn(u32)>);
|
||||
fn call3(a: &Closure<Fn(u32, u32)>);
|
||||
fn call4(a: &Closure<Fn(u32, u32, u32)>);
|
||||
fn call5(a: &Closure<Fn(u32, u32, u32, u32)>);
|
||||
fn call6(a: &Closure<Fn(u32, u32, u32, u32, u32)>);
|
||||
fn call7(a: &Closure<Fn(u32, u32, u32, u32, u32, u32)>);
|
||||
fn call8(a: &Closure<Fn(u32, u32, u32, u32, u32, u32, u32)>);
|
||||
|
||||
#[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<Fn()>);
|
||||
#[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<FnMut()>);
|
||||
#[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();
|
||||
}
|
Loading…
Reference in New Issue
Block a user