wasm-bindgen/tests/wasm/closures.rs
Alex Crichton 8ba41cce6e Improve codegen for Closure<T>
This commit improves the codegen for `Closure<T>`, primarily for ZST
where the closure doesn't actually capture anything. Previously
`wasm-bindgen` would unconditionally allocate an `Rc` for a fat pointer,
meaning that it would always hit the allocator even when the `Box<T>`
didn't actually contain an allocation. Now the reference count for the
closure is stored on the JS object rather than in Rust.

Some more advanced tests were added along the way to ensure that
functionality didn't regress, and otherwise the calling convention for
`Closure` changed a good deal but should still be the same user-facing.
The primary change was that the reference count reaching zero may cause
JS to need to run the destructor. It simply returns this information in
`Drop for Closure` and otherwise when calling it now also retains a
function pointer that runs the destructor.

Closes #874
2018-09-29 07:00:53 -07:00

268 lines
7.3 KiB
Rust

#![cfg(feature = "nightly")]
use std::cell::{Cell, RefCell};
use std::rc::Rc;
use wasm_bindgen::prelude::*;
use wasm_bindgen_test::*;
#[wasm_bindgen(module = "tests/wasm/closures.js")]
extern "C" {
fn works_call(a: &Fn());
fn works_thread(a: &Fn(u32) -> u32) -> u32;
fn cannot_reuse_call(a: &Fn());
#[wasm_bindgen(catch)]
fn cannot_reuse_call_again() -> Result<(), JsValue>;
fn long_lived_call1(a: &Closure<Fn()>);
fn long_lived_call2(a: &Closure<FnMut(u32) -> u32>) -> u32;
fn many_arity_call1(a: &Closure<Fn()>);
fn many_arity_call2(a: &Closure<Fn(u32)>);
fn many_arity_call3(a: &Closure<Fn(u32, u32)>);
fn many_arity_call4(a: &Closure<Fn(u32, u32, u32)>);
fn many_arity_call5(a: &Closure<Fn(u32, u32, u32, u32)>);
fn many_arity_call6(a: &Closure<Fn(u32, u32, u32, u32, u32)>);
fn many_arity_call7(a: &Closure<Fn(u32, u32, u32, u32, u32, u32)>);
fn many_arity_call8(a: &Closure<Fn(u32, u32, u32, u32, u32, u32, u32)>);
#[wasm_bindgen(js_name = many_arity_call1)]
fn many_arity_stack1(a: &Fn());
#[wasm_bindgen(js_name = many_arity_call2)]
fn many_arity_stack2(a: &Fn(u32));
#[wasm_bindgen(js_name = many_arity_call3)]
fn many_arity_stack3(a: &Fn(u32, u32));
#[wasm_bindgen(js_name = many_arity_call4)]
fn many_arity_stack4(a: &Fn(u32, u32, u32));
#[wasm_bindgen(js_name = many_arity_call5)]
fn many_arity_stack5(a: &Fn(u32, u32, u32, u32));
#[wasm_bindgen(js_name = many_arity_call6)]
fn many_arity_stack6(a: &Fn(u32, u32, u32, u32, u32));
#[wasm_bindgen(js_name = many_arity_call7)]
fn many_arity_stack7(a: &Fn(u32, u32, u32, u32, u32, u32));
#[wasm_bindgen(js_name = many_arity_call8)]
fn many_arity_stack8(a: &Fn(u32, u32, u32, u32, u32, u32, u32));
fn long_lived_dropping_cache(a: &Closure<Fn()>);
#[wasm_bindgen(catch)]
fn long_lived_dropping_call() -> Result<(), JsValue>;
fn long_fnmut_recursive_cache(a: &Closure<FnMut()>);
#[wasm_bindgen(catch)]
fn long_fnmut_recursive_call() -> Result<(), JsValue>;
fn fnmut_call(a: &mut FnMut());
fn fnmut_thread(a: &mut FnMut(u32) -> u32) -> u32;
fn fnmut_bad_call(a: &mut FnMut());
#[wasm_bindgen(catch)]
fn fnmut_bad_again(a: bool) -> Result<(), JsValue>;
fn string_arguments_call(a: &mut FnMut(String));
fn string_ret_call(a: &mut FnMut(String) -> String);
fn drop_during_call_save(a: &Closure<Fn()>);
fn drop_during_call_call();
}
#[wasm_bindgen_test]
fn works() {
let a = Cell::new(false);
works_call(&|| a.set(true));
assert!(a.get());
assert_eq!(works_thread(&|a| a + 1), 3);
}
#[wasm_bindgen_test]
fn cannot_reuse() {
cannot_reuse_call(&|| {});
assert!(cannot_reuse_call_again().is_err());
}
#[wasm_bindgen_test]
fn long_lived() {
let hit = Rc::new(Cell::new(false));
let hit2 = hit.clone();
let a = Closure::new(move || hit2.set(true));
assert!(!hit.get());
long_lived_call1(&a);
assert!(hit.get());
let hit = Rc::new(Cell::new(false));
{
let hit = hit.clone();
let a = Closure::new(move |x| {
hit.set(true);
x + 3
});
assert_eq!(long_lived_call2(&a), 5);
}
assert!(hit.get());
}
#[wasm_bindgen_test]
fn many_arity() {
many_arity_call1(&Closure::new(|| {}));
many_arity_call2(&Closure::new(|a| assert_eq!(a, 1)));
many_arity_call3(&Closure::new(|a, b| assert_eq!((a, b), (1, 2))));
many_arity_call4(&Closure::new(|a, b, c| assert_eq!((a, b, c), (1, 2, 3))));
many_arity_call5(&Closure::new(|a, b, c, d| {
assert_eq!((a, b, c, d), (1, 2, 3, 4))
}));
many_arity_call6(&Closure::new(|a, b, c, d, e| {
assert_eq!((a, b, c, d, e), (1, 2, 3, 4, 5))
}));
many_arity_call7(&Closure::new(|a, b, c, d, e, f| {
assert_eq!((a, b, c, d, e, f), (1, 2, 3, 4, 5, 6))
}));
many_arity_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))
}));
many_arity_stack1(&(|| {}));
many_arity_stack2(&(|a| assert_eq!(a, 1)));
many_arity_stack3(&(|a, b| assert_eq!((a, b), (1, 2))));
many_arity_stack4(&(|a, b, c| assert_eq!((a, b, c), (1, 2, 3))));
many_arity_stack5(&(|a, b, c, d| assert_eq!((a, b, c, d), (1, 2, 3, 4))));
many_arity_stack6(&(|a, b, c, d, e| assert_eq!((a, b, c, d, e), (1, 2, 3, 4, 5))));
many_arity_stack7(&(|a, b, c, d, e, f| assert_eq!((a, b, c, d, e, f), (1, 2, 3, 4, 5, 6))));
many_arity_stack8(
&(|a, b, c, d, e, f, g| assert_eq!((a, b, c, d, e, f, g), (1, 2, 3, 4, 5, 6, 7))),
);
}
#[wasm_bindgen_test]
fn long_lived_dropping() {
let hit = Rc::new(Cell::new(false));
let hit2 = hit.clone();
let a = Closure::new(move || hit2.set(true));
long_lived_dropping_cache(&a);
assert!(!hit.get());
assert!(long_lived_dropping_call().is_ok());
assert!(hit.get());
drop(a);
assert!(long_lived_dropping_call().is_err());
}
#[wasm_bindgen_test]
fn long_fnmut_recursive() {
let a = Closure::new(|| {
assert!(long_fnmut_recursive_call().is_err());
});
long_fnmut_recursive_cache(&a);
assert!(long_fnmut_recursive_call().is_ok());
}
#[wasm_bindgen_test]
fn fnmut() {
let mut a = false;
fnmut_call(&mut || a = true);
assert!(a);
let mut x = false;
assert_eq!(
fnmut_thread(&mut |a| {
x = true;
a + 1
}),
3
);
assert!(x);
}
#[wasm_bindgen_test]
fn fnmut_bad() {
let mut x = true;
let mut hits = 0;
fnmut_bad_call(&mut || {
hits += 1;
if fnmut_bad_again(hits == 1).is_err() {
return;
}
x = false;
});
assert_eq!(hits, 1);
assert!(x);
assert!(fnmut_bad_again(true).is_err());
}
#[wasm_bindgen_test]
fn string_arguments() {
let mut x = false;
string_arguments_call(&mut |s| {
assert_eq!(s, "foo");
x = true;
});
assert!(x);
}
#[wasm_bindgen_test]
fn string_ret() {
let mut x = false;
string_ret_call(&mut |mut s| {
assert_eq!(s, "foo");
s.push_str("bar");
x = true;
s
});
assert!(x);
}
#[wasm_bindgen_test]
fn drop_drops() {
static mut HIT: bool = false;
struct A;
impl Drop for A {
fn drop(&mut self) {
unsafe { HIT = true; }
}
}
let a = A;
let x: Closure<Fn()> = Closure::new(move || drop(&a));
drop(x);
unsafe { assert!(HIT); }
}
#[wasm_bindgen_test]
fn drop_during_call_ok() {
static mut HIT: bool = false;
struct A;
impl Drop for A {
fn drop(&mut self) {
unsafe { HIT = true; }
}
}
let rc = Rc::new(RefCell::new(None));
let rc2 = rc.clone();
let x = 3;
let a = A;
let x: Closure<Fn()> = Closure::new(move || {
// "drop ourselves"
drop(rc2.borrow_mut().take().unwrap());
// `A` should not have been destroyed as a result
unsafe { assert!(!HIT); }
// allocate some heap memory to try to paper over our `3`
drop(String::from("1234567890"));
// make sure our closure memory is still valid
assert_eq!(x, 3);
// make sure `A` is bound to our closure environment.
drop(&a);
unsafe { assert!(!HIT); }
});
drop_during_call_save(&x);
*rc.borrow_mut() = Some(x);
drop(rc);
unsafe { assert!(!HIT); }
drop_during_call_call();
unsafe { assert!(HIT); }
}