mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-04-11 22:56:06 +00:00
Merge pull request #879 from alexcrichton/closure-zst
Improve codegen for `Closure<T>`
This commit is contained in:
commit
a7e9da0a81
@ -226,26 +226,32 @@ impl ClosureDescriptors {
|
|||||||
|
|
||||||
let (js, _ts, _js_doc) = {
|
let (js, _ts, _js_doc) = {
|
||||||
let mut builder = Js2Rust::new("", input);
|
let mut builder = Js2Rust::new("", input);
|
||||||
|
builder.prelude("this.cnt++;");
|
||||||
if closure.mutable {
|
if closure.mutable {
|
||||||
builder
|
builder
|
||||||
.prelude("let a = this.a;\n")
|
.prelude("let a = this.a;\n")
|
||||||
.prelude("this.a = 0;\n")
|
.prelude("this.a = 0;\n")
|
||||||
.rust_argument("a")
|
.rust_argument("a")
|
||||||
|
.rust_argument("b")
|
||||||
.finally("this.a = a;\n");
|
.finally("this.a = a;\n");
|
||||||
} else {
|
} else {
|
||||||
builder.rust_argument("this.a");
|
builder.rust_argument("this.a")
|
||||||
|
.rust_argument("b");
|
||||||
}
|
}
|
||||||
|
builder.finally("if (this.cnt-- == 1) d(this.a, b);");
|
||||||
builder
|
builder
|
||||||
.process(&closure.function)?
|
.process(&closure.function)?
|
||||||
.finish("function", "this.f")
|
.finish("function", "f")
|
||||||
};
|
};
|
||||||
input.expose_add_heap_object();
|
input.expose_add_heap_object();
|
||||||
input.function_table_needed = true;
|
input.function_table_needed = true;
|
||||||
let body = format!(
|
let body = format!(
|
||||||
"function(ptr, f, _ignored) {{
|
"function(a, b, fi, di, _ignored) {{
|
||||||
let cb = {};
|
const f = wasm.__wbg_function_table.get(fi);
|
||||||
cb.f = wasm.__wbg_function_table.get(f);
|
const d = wasm.__wbg_function_table.get(di);
|
||||||
cb.a = ptr;
|
const cb = {};
|
||||||
|
cb.a = a;
|
||||||
|
cb.cnt = 1;
|
||||||
let real = cb.bind(cb);
|
let real = cb.bind(cb);
|
||||||
real.original = cb;
|
real.original = cb;
|
||||||
return addHeapObject(real);
|
return addHeapObject(real);
|
||||||
|
@ -327,9 +327,13 @@ impl<'a> Context<'a> {
|
|||||||
Ok(String::from(
|
Ok(String::from(
|
||||||
"
|
"
|
||||||
function(i) {
|
function(i) {
|
||||||
let obj = getObject(i).original;
|
const obj = getObject(i).original;
|
||||||
obj.a = obj.b = 0;
|
|
||||||
dropRef(i);
|
dropRef(i);
|
||||||
|
if (obj.cnt-- == 1) {
|
||||||
|
obj.a = 0;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
",
|
",
|
||||||
))
|
))
|
||||||
@ -337,13 +341,7 @@ impl<'a> Context<'a> {
|
|||||||
|
|
||||||
self.bind("__wbindgen_cb_forget", &|me| {
|
self.bind("__wbindgen_cb_forget", &|me| {
|
||||||
me.expose_drop_ref();
|
me.expose_drop_ref();
|
||||||
Ok(String::from(
|
Ok("dropRef".to_string())
|
||||||
"
|
|
||||||
function(i) {
|
|
||||||
dropRef(i);
|
|
||||||
}
|
|
||||||
",
|
|
||||||
))
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
self.bind("__wbindgen_json_parse", &|me| {
|
self.bind("__wbindgen_json_parse", &|me| {
|
||||||
|
@ -347,6 +347,8 @@ impl Interpreter {
|
|||||||
self.descriptor_table_idx = Some(self.stack.pop().unwrap() as u32);
|
self.descriptor_table_idx = Some(self.stack.pop().unwrap() as u32);
|
||||||
self.stack.pop();
|
self.stack.pop();
|
||||||
self.stack.pop();
|
self.stack.pop();
|
||||||
|
self.stack.pop();
|
||||||
|
self.stack.pop();
|
||||||
self.stack.push(0);
|
self.stack.push(0);
|
||||||
} else {
|
} else {
|
||||||
self.call(*idx, sections);
|
self.call(*idx, sections);
|
||||||
|
@ -4,12 +4,10 @@
|
|||||||
//! closures" from Rust to JS. Some more details can be found on the `Closure`
|
//! closures" from Rust to JS. Some more details can be found on the `Closure`
|
||||||
//! type itself.
|
//! type itself.
|
||||||
|
|
||||||
use std::cell::UnsafeCell;
|
|
||||||
#[cfg(feature = "nightly")]
|
#[cfg(feature = "nightly")]
|
||||||
use std::marker::Unsize;
|
use std::marker::Unsize;
|
||||||
use std::mem::{self, ManuallyDrop};
|
use std::mem::{self, ManuallyDrop};
|
||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use JsValue;
|
use JsValue;
|
||||||
use convert::*;
|
use convert::*;
|
||||||
@ -102,7 +100,12 @@ use throw_str;
|
|||||||
/// ```
|
/// ```
|
||||||
pub struct Closure<T: ?Sized> {
|
pub struct Closure<T: ?Sized> {
|
||||||
js: ManuallyDrop<JsValue>,
|
js: ManuallyDrop<JsValue>,
|
||||||
_keep_this_data_alive: Rc<UnsafeCell<Box<T>>>,
|
data: ManuallyDrop<Box<T>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
union FatPtr<T: ?Sized> {
|
||||||
|
ptr: *mut T,
|
||||||
|
fields: (usize, usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Closure<T>
|
impl<T> Closure<T>
|
||||||
@ -135,9 +138,11 @@ impl<T> Closure<T>
|
|||||||
/// type.
|
/// type.
|
||||||
///
|
///
|
||||||
/// This is the function where the JS closure is manufactured.
|
/// This is the function where the JS closure is manufactured.
|
||||||
pub fn wrap(t: Box<T>) -> Closure<T> {
|
pub fn wrap(mut data: Box<T>) -> Closure<T> {
|
||||||
let data = Rc::new(UnsafeCell::new(t));
|
assert_eq!(mem::size_of::<*const T>(), mem::size_of::<FatPtr<T>>());
|
||||||
let ptr = &*data as *const UnsafeCell<Box<T>>;
|
let (a, b) = unsafe {
|
||||||
|
FatPtr { ptr: &mut *data as *mut T }.fields
|
||||||
|
};
|
||||||
|
|
||||||
// Here we need to create a `JsValue` with the data and `T::invoke()`
|
// Here we need to create a `JsValue` with the data and `T::invoke()`
|
||||||
// function pointer. To do that we... take a few unconventional turns.
|
// function pointer. To do that we... take a few unconventional turns.
|
||||||
@ -190,19 +195,27 @@ impl<T> Closure<T>
|
|||||||
|
|
||||||
#[inline(never)]
|
#[inline(never)]
|
||||||
unsafe fn breaks_if_inlined<T: WasmClosure + ?Sized>(
|
unsafe fn breaks_if_inlined<T: WasmClosure + ?Sized>(
|
||||||
ptr: usize,
|
a: usize,
|
||||||
|
b: usize,
|
||||||
invoke: u32,
|
invoke: u32,
|
||||||
|
destroy: u32,
|
||||||
) -> u32 {
|
) -> u32 {
|
||||||
super::__wbindgen_describe_closure(ptr as u32, invoke, describe::<T> as u32)
|
super::__wbindgen_describe_closure(
|
||||||
|
a as u32,
|
||||||
|
b as u32,
|
||||||
|
invoke,
|
||||||
|
destroy,
|
||||||
|
describe::<T> as u32,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
let idx = unsafe {
|
let idx = unsafe {
|
||||||
breaks_if_inlined::<T>(ptr as usize, T::invoke_fn())
|
breaks_if_inlined::<T>(a, b, T::invoke_fn(), T::destroy_fn())
|
||||||
};
|
};
|
||||||
|
|
||||||
Closure {
|
Closure {
|
||||||
js: ManuallyDrop::new(JsValue { idx }),
|
js: ManuallyDrop::new(JsValue { idx }),
|
||||||
_keep_this_data_alive: data,
|
data: ManuallyDrop::new(data),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,7 +236,6 @@ impl<T> Closure<T>
|
|||||||
if idx != !0 {
|
if idx != !0 {
|
||||||
super::__wbindgen_cb_forget(idx);
|
super::__wbindgen_cb_forget(idx);
|
||||||
}
|
}
|
||||||
mem::forget(self);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -270,7 +282,9 @@ impl<T> Drop for Closure<T>
|
|||||||
unsafe {
|
unsafe {
|
||||||
// this will implicitly drop our strong reference in addition to
|
// this will implicitly drop our strong reference in addition to
|
||||||
// invalidating all future invocations of the closure
|
// invalidating all future invocations of the closure
|
||||||
super::__wbindgen_cb_drop(self.js.idx);
|
if super::__wbindgen_cb_drop(self.js.idx) != 0 {
|
||||||
|
ManuallyDrop::drop(&mut self.data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -284,6 +298,7 @@ pub unsafe trait WasmClosure: 'static {
|
|||||||
fn describe();
|
fn describe();
|
||||||
|
|
||||||
fn invoke_fn() -> u32;
|
fn invoke_fn() -> u32;
|
||||||
|
fn destroy_fn() -> u32;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The memory safety here in these implementations below is a bit tricky. We
|
// The memory safety here in these implementations below is a bit tricky. We
|
||||||
@ -315,30 +330,42 @@ macro_rules! doit {
|
|||||||
fn invoke_fn() -> u32 {
|
fn invoke_fn() -> u32 {
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
unsafe extern fn invoke<$($var: FromWasmAbi,)* R: ReturnWasmAbi>(
|
unsafe extern fn invoke<$($var: FromWasmAbi,)* R: ReturnWasmAbi>(
|
||||||
a: *const UnsafeCell<Box<Fn($($var),*) -> R>>,
|
a: usize,
|
||||||
|
b: usize,
|
||||||
$($var: <$var as FromWasmAbi>::Abi),*
|
$($var: <$var as FromWasmAbi>::Abi),*
|
||||||
) -> <R as ReturnWasmAbi>::Abi {
|
) -> <R as ReturnWasmAbi>::Abi {
|
||||||
if a.is_null() {
|
if a == 0 {
|
||||||
throw_str("closure invoked recursively or destroyed already");
|
throw_str("closure invoked recursively or destroyed already");
|
||||||
}
|
}
|
||||||
// Make sure all stack variables are converted before we
|
// Make sure all stack variables are converted before we
|
||||||
// convert `ret` as it may throw (for `Result`, for
|
// convert `ret` as it may throw (for `Result`, for
|
||||||
// example)
|
// example)
|
||||||
let ret = {
|
let ret = {
|
||||||
let a = Rc::from_raw(a);
|
let f: *const Fn($($var),*) -> R =
|
||||||
let my_handle = a.clone();
|
FatPtr { fields: (a, b) }.ptr;
|
||||||
drop(Rc::into_raw(a));
|
|
||||||
let f: &Fn($($var),*) -> R = &**my_handle.get();
|
|
||||||
let mut _stack = GlobalStack::new();
|
let mut _stack = GlobalStack::new();
|
||||||
$(
|
$(
|
||||||
let $var = <$var as FromWasmAbi>::from_abi($var, &mut _stack);
|
let $var = <$var as FromWasmAbi>::from_abi($var, &mut _stack);
|
||||||
)*
|
)*
|
||||||
f($($var),*)
|
(*f)($($var),*)
|
||||||
};
|
};
|
||||||
ret.return_abi(&mut GlobalStack::new())
|
ret.return_abi(&mut GlobalStack::new())
|
||||||
}
|
}
|
||||||
invoke::<$($var,)* R> as u32
|
invoke::<$($var,)* R> as u32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn destroy_fn() -> u32 {
|
||||||
|
unsafe extern fn destroy<$($var: FromWasmAbi,)* R: ReturnWasmAbi>(
|
||||||
|
a: usize,
|
||||||
|
b: usize,
|
||||||
|
) {
|
||||||
|
debug_assert!(a != 0);
|
||||||
|
drop(Box::from_raw(FatPtr::<Fn($($var,)*) -> R> {
|
||||||
|
fields: (a, b)
|
||||||
|
}.ptr));
|
||||||
|
}
|
||||||
|
destroy::<$($var,)* R> as u32
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl<$($var,)* R> WasmClosure for FnMut($($var),*) -> R
|
unsafe impl<$($var,)* R> WasmClosure for FnMut($($var),*) -> R
|
||||||
@ -352,30 +379,43 @@ macro_rules! doit {
|
|||||||
fn invoke_fn() -> u32 {
|
fn invoke_fn() -> u32 {
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
unsafe extern fn invoke<$($var: FromWasmAbi,)* R: ReturnWasmAbi>(
|
unsafe extern fn invoke<$($var: FromWasmAbi,)* R: ReturnWasmAbi>(
|
||||||
a: *const UnsafeCell<Box<FnMut($($var),*) -> R>>,
|
a: usize,
|
||||||
|
b: usize,
|
||||||
$($var: <$var as FromWasmAbi>::Abi),*
|
$($var: <$var as FromWasmAbi>::Abi),*
|
||||||
) -> <R as ReturnWasmAbi>::Abi {
|
) -> <R as ReturnWasmAbi>::Abi {
|
||||||
if a.is_null() {
|
if a == 0 {
|
||||||
throw_str("closure invoked recursively or destroyed already");
|
throw_str("closure invoked recursively or destroyed already");
|
||||||
}
|
}
|
||||||
// Make sure all stack variables are converted before we
|
// Make sure all stack variables are converted before we
|
||||||
// convert `ret` as it may throw (for `Result`, for
|
// convert `ret` as it may throw (for `Result`, for
|
||||||
// example)
|
// example)
|
||||||
let ret = {
|
let ret = {
|
||||||
let a = Rc::from_raw(a);
|
let f: *const FnMut($($var),*) -> R =
|
||||||
let my_handle = a.clone();
|
FatPtr { fields: (a, b) }.ptr;
|
||||||
drop(Rc::into_raw(a));
|
let f = f as *mut FnMut($($var),*) -> R;
|
||||||
let f: &mut FnMut($($var),*) -> R = &mut **my_handle.get();
|
|
||||||
let mut _stack = GlobalStack::new();
|
let mut _stack = GlobalStack::new();
|
||||||
$(
|
$(
|
||||||
let $var = <$var as FromWasmAbi>::from_abi($var, &mut _stack);
|
let $var = <$var as FromWasmAbi>::from_abi($var, &mut _stack);
|
||||||
)*
|
)*
|
||||||
f($($var),*)
|
(*f)($($var),*)
|
||||||
};
|
};
|
||||||
ret.return_abi(&mut GlobalStack::new())
|
ret.return_abi(&mut GlobalStack::new())
|
||||||
}
|
}
|
||||||
invoke::<$($var,)* R> as u32
|
invoke::<$($var,)* R> as u32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn destroy_fn() -> u32 {
|
||||||
|
unsafe extern fn destroy<$($var: FromWasmAbi,)* R: ReturnWasmAbi>(
|
||||||
|
a: usize,
|
||||||
|
b: usize,
|
||||||
|
) {
|
||||||
|
debug_assert!(a != 0);
|
||||||
|
drop(Box::from_raw(FatPtr::<FnMut($($var,)*) -> R> {
|
||||||
|
fields: (a, b)
|
||||||
|
}.ptr));
|
||||||
|
}
|
||||||
|
destroy::<$($var,)* R> as u32
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)*)
|
)*)
|
||||||
}
|
}
|
||||||
|
@ -445,11 +445,11 @@ externs! {
|
|||||||
fn __wbindgen_throw(a: *const u8, b: usize) -> !;
|
fn __wbindgen_throw(a: *const u8, b: usize) -> !;
|
||||||
fn __wbindgen_rethrow(a: u32) -> !;
|
fn __wbindgen_rethrow(a: u32) -> !;
|
||||||
|
|
||||||
fn __wbindgen_cb_drop(idx: u32) -> ();
|
fn __wbindgen_cb_drop(idx: u32) -> u32;
|
||||||
fn __wbindgen_cb_forget(idx: u32) -> ();
|
fn __wbindgen_cb_forget(idx: u32) -> ();
|
||||||
|
|
||||||
fn __wbindgen_describe(v: u32) -> ();
|
fn __wbindgen_describe(v: u32) -> ();
|
||||||
fn __wbindgen_describe_closure(a: u32, b: u32, c: u32) -> u32;
|
fn __wbindgen_describe_closure(a: u32, b: u32, c: u32, d: u32, e: u32) -> u32;
|
||||||
|
|
||||||
fn __wbindgen_json_parse(ptr: *const u8, len: usize) -> u32;
|
fn __wbindgen_json_parse(ptr: *const u8, len: usize) -> u32;
|
||||||
fn __wbindgen_json_serialize(idx: u32, ptr: *mut *mut u8) -> usize;
|
fn __wbindgen_json_serialize(idx: u32, ptr: *mut *mut u8) -> usize;
|
||||||
|
@ -91,3 +91,9 @@ exports.string_arguments_call = a => {
|
|||||||
exports.string_ret_call = a => {
|
exports.string_ret_call = a => {
|
||||||
assert.strictEqual(a('foo'), 'foobar');
|
assert.strictEqual(a('foo'), 'foobar');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let DROP_DURING_CALL = null;
|
||||||
|
exports.drop_during_call_save = f => {
|
||||||
|
DROP_DURING_CALL = f;
|
||||||
|
};
|
||||||
|
exports.drop_during_call_call = () => DROP_DURING_CALL();
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
#![cfg(feature = "nightly")]
|
#![cfg(feature = "nightly")]
|
||||||
|
|
||||||
use std::cell::Cell;
|
use std::cell::{Cell, RefCell};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
use wasm_bindgen_test::*;
|
use wasm_bindgen_test::*;
|
||||||
@ -60,6 +60,9 @@ extern "C" {
|
|||||||
fn string_arguments_call(a: &mut FnMut(String));
|
fn string_arguments_call(a: &mut FnMut(String));
|
||||||
|
|
||||||
fn string_ret_call(a: &mut FnMut(String) -> 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]
|
#[wasm_bindgen_test]
|
||||||
@ -206,3 +209,59 @@ fn string_ret() {
|
|||||||
});
|
});
|
||||||
assert!(x);
|
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); }
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user