Skip to content

Commit f367a42

Browse files
authored
Merge pull request #1417 from alexcrichton/closure-ref
Support 1-reference argument closures
2 parents c5f18b6 + d48f499 commit f367a42

File tree

4 files changed

+332
-3
lines changed

4 files changed

+332
-3
lines changed

src/closure.rs

+148-3
Original file line numberDiff line numberDiff line change
@@ -519,7 +519,7 @@ where
519519
/// This trait is not stable and it's not recommended to use this in bounds or
520520
/// implement yourself.
521521
#[doc(hidden)]
522-
pub unsafe trait WasmClosure: 'static {
522+
pub unsafe trait WasmClosure {
523523
fn describe();
524524
}
525525

@@ -541,7 +541,7 @@ macro_rules! doit {
541541
($(
542542
($($var:ident)*)
543543
)*) => ($(
544-
unsafe impl<$($var,)* R> WasmClosure for Fn($($var),*) -> R
544+
unsafe impl<$($var,)* R> WasmClosure for Fn($($var),*) -> R + 'static
545545
where $($var: FromWasmAbi + 'static,)*
546546
R: ReturnWasmAbi + 'static,
547547
{
@@ -587,7 +587,7 @@ macro_rules! doit {
587587
}
588588
}
589589

590-
unsafe impl<$($var,)* R> WasmClosure for FnMut($($var),*) -> R
590+
unsafe impl<$($var,)* R> WasmClosure for FnMut($($var),*) -> R + 'static
591591
where $($var: FromWasmAbi + 'static,)*
592592
R: ReturnWasmAbi + 'static,
593593
{
@@ -696,3 +696,148 @@ doit! {
696696
(A B C D E F)
697697
(A B C D E F G)
698698
}
699+
700+
// Copy the above impls down here for where there's only one argument and it's a
701+
// reference. We could add more impls for more kinds of references, but it
702+
// becomes a combinatorial explosion quickly. Let's see how far we can get with
703+
// just this one! Maybe someone else can figure out voodoo so we don't have to
704+
// duplicate.
705+
706+
unsafe impl<A, R> WasmClosure for Fn(&A) -> R
707+
where A: RefFromWasmAbi,
708+
R: ReturnWasmAbi + 'static,
709+
{
710+
fn describe() {
711+
#[allow(non_snake_case)]
712+
unsafe extern "C" fn invoke<A: RefFromWasmAbi, R: ReturnWasmAbi>(
713+
a: usize,
714+
b: usize,
715+
arg: <A as RefFromWasmAbi>::Abi,
716+
) -> <R as ReturnWasmAbi>::Abi {
717+
if a == 0 {
718+
throw_str("closure invoked recursively or destroyed already");
719+
}
720+
// Make sure all stack variables are converted before we
721+
// convert `ret` as it may throw (for `Result`, for
722+
// example)
723+
let ret = {
724+
let f: *const Fn(&A) -> R =
725+
FatPtr { fields: (a, b) }.ptr;
726+
let mut _stack = GlobalStack::new();
727+
let arg = <A as RefFromWasmAbi>::ref_from_abi(arg, &mut _stack);
728+
(*f)(&*arg)
729+
};
730+
ret.return_abi(&mut GlobalStack::new())
731+
}
732+
733+
inform(invoke::<A, R> as u32);
734+
735+
unsafe extern fn destroy<A: RefFromWasmAbi, R: ReturnWasmAbi>(
736+
a: usize,
737+
b: usize,
738+
) {
739+
debug_assert!(a != 0, "should never destroy a Fn whose pointer is 0");
740+
drop(Box::from_raw(FatPtr::<Fn(&A) -> R> {
741+
fields: (a, b)
742+
}.ptr));
743+
}
744+
inform(destroy::<A, R> as u32);
745+
746+
<&Self>::describe();
747+
}
748+
}
749+
750+
unsafe impl<A, R> WasmClosure for FnMut(&A) -> R
751+
where A: RefFromWasmAbi,
752+
R: ReturnWasmAbi + 'static,
753+
{
754+
fn describe() {
755+
#[allow(non_snake_case)]
756+
unsafe extern "C" fn invoke<A: RefFromWasmAbi, R: ReturnWasmAbi>(
757+
a: usize,
758+
b: usize,
759+
arg: <A as RefFromWasmAbi>::Abi,
760+
) -> <R as ReturnWasmAbi>::Abi {
761+
if a == 0 {
762+
throw_str("closure invoked recursively or destroyed already");
763+
}
764+
// Make sure all stack variables are converted before we
765+
// convert `ret` as it may throw (for `Result`, for
766+
// example)
767+
let ret = {
768+
let f: *const FnMut(&A) -> R =
769+
FatPtr { fields: (a, b) }.ptr;
770+
let f = f as *mut FnMut(&A) -> R;
771+
let mut _stack = GlobalStack::new();
772+
let arg = <A as RefFromWasmAbi>::ref_from_abi(arg, &mut _stack);
773+
(*f)(&*arg)
774+
};
775+
ret.return_abi(&mut GlobalStack::new())
776+
}
777+
778+
inform(invoke::<A, R> as u32);
779+
780+
unsafe extern fn destroy<A: RefFromWasmAbi, R: ReturnWasmAbi>(
781+
a: usize,
782+
b: usize,
783+
) {
784+
debug_assert!(a != 0, "should never destroy a FnMut whose pointer is 0");
785+
drop(Box::from_raw(FatPtr::<FnMut(&A) -> R> {
786+
fields: (a, b)
787+
}.ptr));
788+
}
789+
inform(destroy::<A, R> as u32);
790+
791+
<&mut Self>::describe();
792+
}
793+
}
794+
795+
#[allow(non_snake_case)]
796+
impl<T, A, R> WasmClosureFnOnce<(&A,), R> for T
797+
where T: 'static + FnOnce(&A) -> R,
798+
A: RefFromWasmAbi + 'static,
799+
R: ReturnWasmAbi + 'static
800+
{
801+
type FnMut = FnMut(&A) -> R;
802+
803+
fn into_fn_mut(self) -> Box<Self::FnMut> {
804+
let mut me = Some(self);
805+
Box::new(move |arg| {
806+
let me = me.take().expect_throw("FnOnce called more than once");
807+
me(arg)
808+
})
809+
}
810+
811+
fn into_js_function(self) -> JsValue {
812+
use std::rc::Rc;
813+
use crate::__rt::WasmRefCell;
814+
815+
let mut me = Some(self);
816+
817+
let rc1 = Rc::new(WasmRefCell::new(None));
818+
let rc2 = rc1.clone();
819+
820+
let closure = Closure::wrap(Box::new(move |arg: &A| {
821+
// Invoke ourself and get the result.
822+
let me = me.take().expect_throw("FnOnce called more than once");
823+
let result = me(arg);
824+
825+
// And then drop the `Rc` holding this function's `Closure`
826+
// alive.
827+
debug_assert_eq!(Rc::strong_count(&rc2), 1);
828+
let option_closure = rc2.borrow_mut().take();
829+
debug_assert!(option_closure.is_some());
830+
drop(option_closure);
831+
832+
result
833+
}) as Box<FnMut(&A) -> R>);
834+
835+
let js_val = closure.as_ref().clone();
836+
837+
*rc1.borrow_mut() = Some(closure);
838+
debug_assert_eq!(Rc::strong_count(&rc1), 2);
839+
drop(rc1);
840+
841+
js_val
842+
}
843+
}

src/convert/closures.rs

+95
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use core::mem;
22

33
use crate::convert::slices::WasmSlice;
44
use crate::convert::{FromWasmAbi, GlobalStack, IntoWasmAbi, ReturnWasmAbi, Stack};
5+
use crate::convert::RefFromWasmAbi;
56
use crate::describe::{inform, WasmDescribe, FUNCTION};
67
use crate::throw_str;
78

@@ -117,3 +118,97 @@ stack_closures! {
117118
(6 invoke6 invoke6_mut A B C D E F)
118119
(7 invoke7 invoke7_mut A B C D E F G)
119120
}
121+
122+
impl<'a, 'b, A, R> IntoWasmAbi for &'a (Fn(&A) -> R + 'b)
123+
where A: RefFromWasmAbi,
124+
R: ReturnWasmAbi
125+
{
126+
type Abi = WasmSlice;
127+
128+
fn into_abi(self, _extra: &mut Stack) -> WasmSlice {
129+
unsafe {
130+
let (a, b): (usize, usize) = mem::transmute(self);
131+
WasmSlice { ptr: a as u32, len: b as u32 }
132+
}
133+
}
134+
}
135+
136+
#[allow(non_snake_case)]
137+
unsafe extern "C" fn invoke1_ref<A: RefFromWasmAbi, R: ReturnWasmAbi>(
138+
a: usize,
139+
b: usize,
140+
arg: <A as RefFromWasmAbi>::Abi,
141+
) -> <R as ReturnWasmAbi>::Abi {
142+
if a == 0 {
143+
throw_str("closure invoked recursively or destroyed already");
144+
}
145+
// Scope all local variables before we call `return_abi` to
146+
// ensure they're all destroyed as `return_abi` may throw
147+
let ret = {
148+
let f: &Fn(&A) -> R = mem::transmute((a, b));
149+
let mut _stack = GlobalStack::new();
150+
let arg = <A as RefFromWasmAbi>::ref_from_abi(arg, &mut _stack);
151+
f(&*arg)
152+
};
153+
ret.return_abi(&mut GlobalStack::new())
154+
}
155+
156+
impl<'a, A, R> WasmDescribe for Fn(&A) -> R + 'a
157+
where A: RefFromWasmAbi,
158+
R: ReturnWasmAbi,
159+
{
160+
fn describe() {
161+
inform(FUNCTION);
162+
inform(invoke1_ref::<A, R> as u32);
163+
inform(1);
164+
<&A as WasmDescribe>::describe();
165+
<R as WasmDescribe>::describe();
166+
}
167+
}
168+
169+
impl<'a, 'b, A, R> IntoWasmAbi for &'a mut (FnMut(&A) -> R + 'b)
170+
where A: RefFromWasmAbi,
171+
R: ReturnWasmAbi
172+
{
173+
type Abi = WasmSlice;
174+
175+
fn into_abi(self, _extra: &mut Stack) -> WasmSlice {
176+
unsafe {
177+
let (a, b): (usize, usize) = mem::transmute(self);
178+
WasmSlice { ptr: a as u32, len: b as u32 }
179+
}
180+
}
181+
}
182+
183+
#[allow(non_snake_case)]
184+
unsafe extern "C" fn invoke1_mut_ref<A: RefFromWasmAbi, R: ReturnWasmAbi>(
185+
a: usize,
186+
b: usize,
187+
arg: <A as RefFromWasmAbi>::Abi,
188+
) -> <R as ReturnWasmAbi>::Abi {
189+
if a == 0 {
190+
throw_str("closure invoked recursively or destroyed already");
191+
}
192+
// Scope all local variables before we call `return_abi` to
193+
// ensure they're all destroyed as `return_abi` may throw
194+
let ret = {
195+
let f: &mut FnMut(&A) -> R = mem::transmute((a, b));
196+
let mut _stack = GlobalStack::new();
197+
let arg = <A as RefFromWasmAbi>::ref_from_abi(arg, &mut _stack);
198+
f(&*arg)
199+
};
200+
ret.return_abi(&mut GlobalStack::new())
201+
}
202+
203+
impl<'a, A, R> WasmDescribe for FnMut(&A) -> R + 'a
204+
where A: RefFromWasmAbi,
205+
R: ReturnWasmAbi
206+
{
207+
fn describe() {
208+
inform(FUNCTION);
209+
inform(invoke1_mut_ref::<A, R> as u32);
210+
inform(1);
211+
<&A as WasmDescribe>::describe();
212+
<R as WasmDescribe>::describe();
213+
}
214+
}

tests/wasm/closures.js

+6
Original file line numberDiff line numberDiff line change
@@ -113,3 +113,9 @@ exports.calling_it_throws = a => {
113113
};
114114

115115
exports.call_val = f => f();
116+
117+
exports.pass_reference_first_arg_twice = (a, b, c) => {
118+
b(a);
119+
c(a);
120+
a.free();
121+
};

tests/wasm/closures.rs

+83
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,18 @@ extern "C" {
9090

9191
#[wasm_bindgen(js_name = calling_it_throws)]
9292
fn call_val_throws(f: &JsValue) -> bool;
93+
94+
fn pass_reference_first_arg_twice(
95+
a: RefFirstArgument,
96+
b: &Closure<FnMut(&RefFirstArgument)>,
97+
c: &Closure<FnMut(&RefFirstArgument)>,
98+
);
99+
#[wasm_bindgen(js_name = pass_reference_first_arg_twice)]
100+
fn pass_reference_first_arg_twice2(
101+
a: RefFirstArgument,
102+
b: &mut FnMut(&RefFirstArgument),
103+
c: &mut FnMut(&RefFirstArgument),
104+
);
93105
}
94106

95107
#[wasm_bindgen_test]
@@ -439,3 +451,74 @@ fn test_closure_returner() {
439451
Ok(o)
440452
}
441453
}
454+
455+
#[wasm_bindgen]
456+
pub struct RefFirstArgument {
457+
contents: u32,
458+
}
459+
460+
#[wasm_bindgen_test]
461+
fn reference_as_first_argument_builds_at_all() {
462+
#[wasm_bindgen]
463+
extern "C" {
464+
fn ref_first_arg1(a: &Fn(&JsValue));
465+
fn ref_first_arg2(a: &mut FnMut(&JsValue));
466+
fn ref_first_arg3(a: &Closure<Fn(&JsValue)>);
467+
fn ref_first_arg4(a: &Closure<FnMut(&JsValue)>);
468+
fn ref_first_custom1(a: &Fn(&RefFirstArgument));
469+
fn ref_first_custom2(a: &mut FnMut(&RefFirstArgument));
470+
fn ref_first_custom3(a: &Closure<Fn(&RefFirstArgument)>);
471+
fn ref_first_custom4(a: &Closure<FnMut(&RefFirstArgument)>);
472+
}
473+
474+
Closure::wrap(Box::new(|_: &JsValue| ()) as Box<Fn(&JsValue)>);
475+
Closure::wrap(Box::new(|_: &JsValue| ()) as Box<FnMut(&JsValue)>);
476+
Closure::once(|_: &JsValue| ());
477+
Closure::once_into_js(|_: &JsValue| ());
478+
Closure::wrap(Box::new(|_: &RefFirstArgument| ()) as Box<Fn(&RefFirstArgument)>);
479+
Closure::wrap(Box::new(|_: &RefFirstArgument| ()) as Box<FnMut(&RefFirstArgument)>);
480+
Closure::once(|_: &RefFirstArgument| ());
481+
Closure::once_into_js(|_: &RefFirstArgument| ());
482+
}
483+
484+
#[wasm_bindgen_test]
485+
fn reference_as_first_argument_works() {
486+
let a = Rc::new(Cell::new(0));
487+
let b = {
488+
let a = a.clone();
489+
Closure::once(move |x: &RefFirstArgument| {
490+
assert_eq!(a.get(), 0);
491+
assert_eq!(x.contents, 3);
492+
a.set(a.get() + 1);
493+
})
494+
};
495+
let c = {
496+
let a = a.clone();
497+
Closure::once(move |x: &RefFirstArgument| {
498+
assert_eq!(a.get(), 1);
499+
assert_eq!(x.contents, 3);
500+
a.set(a.get() + 1);
501+
})
502+
};
503+
pass_reference_first_arg_twice(RefFirstArgument { contents: 3 }, &b, &c);
504+
assert_eq!(a.get(), 2);
505+
}
506+
507+
#[wasm_bindgen_test]
508+
fn reference_as_first_argument_works2() {
509+
let a = Cell::new(0);
510+
pass_reference_first_arg_twice2(
511+
RefFirstArgument { contents: 3 },
512+
&mut |x: &RefFirstArgument| {
513+
assert_eq!(a.get(), 0);
514+
assert_eq!(x.contents, 3);
515+
a.set(a.get() + 1);
516+
},
517+
&mut |x: &RefFirstArgument| {
518+
assert_eq!(a.get(), 1);
519+
assert_eq!(x.contents, 3);
520+
a.set(a.get() + 1);
521+
},
522+
);
523+
assert_eq!(a.get(), 2);
524+
}

0 commit comments

Comments
 (0)