closure_ffi/
bare_closure.rs

1//! Provides the [`BareFnOnce`], [`BareFnMut`] and [`BareFn`] wrapper types which allow closures to
2//! be called through context-free unsafe bare functions.
3
4// Provide a re-export of Box that proc macros can use no matter the no_std state
5#[doc(hidden)]
6#[cfg(feature = "no_std")]
7pub use alloc::Box;
8use core::{marker::PhantomData, mem::ManuallyDrop};
9#[doc(hidden)]
10pub use std::boxed::Box;
11
12#[cfg(feature = "proc_macros")]
13#[doc(inline)]
14pub use closure_ffi_proc_macros::bare_dyn;
15#[cfg(feature = "bundled_jit_alloc")]
16use jit_alloc::GlobalJitAlloc;
17
18use crate::{
19    arch::{create_thunk, ThunkInfo},
20    cc,
21    jit_alloc::{self, JitAlloc, JitAllocError},
22    thunk::{FnMutThunk, FnOnceThunk, FnThunk},
23};
24
25macro_rules! cc_shorthand {
26    ($fn_name:ident, $trait_ident:ident, $cc_ty:ty, $cc_name:literal $(,$cfg:meta)?) => {
27        $(#[cfg(any($cfg, doc))])?
28        #[doc = "Create a bare function thunk using the "]
29        #[doc = $cc_name]
30        #[doc = "calling convention for `fun`."]
31        ///
32        /// The W^X memory required is allocated using the global JIT allocator.
33        #[inline]
34        pub fn $fn_name(fun: F) -> Self
35        where
36            ($cc_ty, F): $trait_ident<$cc_ty, B>,
37        {
38            Self::new(<$cc_ty>::default(), fun)
39        }
40    };
41}
42
43macro_rules! cc_shorthand_in {
44    ($fn_name:ident, $trait_ident:ident, $cc_ty:ty, $cc_name:literal $(,$cfg:meta)?) => {
45        $(#[cfg(any($cfg, doc))])?
46        #[doc = "Create a bare function thunk using the "]
47        #[doc = $cc_name]
48        #[doc = "calling convention for `fun`."]
49        ///
50        /// The W^X memory required is allocated using the provided JIT allocator.
51        #[inline]
52        pub fn $fn_name(fun: F, jit_alloc: A) -> Self
53        where
54            ($cc_ty, F): $trait_ident<$cc_ty, B>,
55        {
56            Self::new_in(<$cc_ty>::default(), fun, jit_alloc)
57        }
58    };
59}
60
61macro_rules! bare_closure_impl {
62    (
63        $ty_name:ident,
64        $trait_ident:ident,
65        $thunk_template:ident,
66        $bare_toggle:meta,
67        $bare_receiver:ty,
68        $fn_trait_doc:literal,
69        $safety_doc:literal
70    ) => {
71        #[cfg(feature = "bundled_jit_alloc")]
72        #[cfg_attr(feature = "build-docs", doc(cfg(all())))]
73        /// Wrapper around a
74        #[doc = $fn_trait_doc]
75        /// closure which exposes a bare function thunk that can invoke it without
76        /// additional arguments.
77        #[allow(dead_code)]
78        pub struct $ty_name<B: Copy, F, A: JitAlloc = GlobalJitAlloc> {
79            thunk_info: ThunkInfo,
80            jit_alloc: A,
81            // We can't directly own the closure, even through an UnsafeCell.
82            // Otherwise, holding a reference to a BareFnMut while the bare function is
83            // being called would be UB! So we reclaim the pointer in the Drop impl.
84            closure: *mut F,
85            phantom: PhantomData<B>,
86        }
87
88        #[cfg(not(feature = "bundled_jit_alloc"))]
89        /// Wrapper around a
90        #[doc = $fn_trait_doc]
91        /// closure which exposes a bare function thunk that can invoke it without
92        /// additional arguments.
93        #[allow(dead_code)]
94        pub struct $ty_name<B: Copy, F, A: JitAlloc> {
95            thunk_info: ThunkInfo,
96            jit_alloc: A,
97            closure: *mut F,
98            phantom: PhantomData<B>,
99        }
100
101        // SAFETY: F and A can be moved to other threads
102        unsafe impl<B: Copy, F: Send, A: JitAlloc + Send> Send for $ty_name<B, F, A> {}
103        // SAFETY: F and A can borrowed by other threads
104        unsafe impl<B: Copy, F: Sync, A: JitAlloc + Sync> Sync for $ty_name<B, F, A> {}
105
106        impl<B: Copy, F, A: JitAlloc> $ty_name<B, F, A> {
107            /// Wraps `fun`, producing a bare function with calling convention
108            /// `cconv`.
109            ///
110            /// Uses the provided JIT allocator to allocate the W^X memory used to create the thunk.
111            #[allow(unused_variables)]
112            #[deprecated(since = "0.3.0", note = "please use `try_new_in` instead")]
113            pub fn with_jit_alloc<CC>(
114                cconv: CC,
115                fun: F,
116                jit_alloc: A,
117            ) -> Result<Self, JitAllocError>
118            where
119                (CC, F): $trait_ident<CC, B>,
120            {
121                Self::try_new_in(cconv, fun, jit_alloc)
122            }
123
124            /// Wraps `fun`, producing a bare function with calling convention
125            /// `cconv`.
126            ///
127            /// Uses the provided JIT allocator to allocate the W^X memory used to create the thunk.
128            #[allow(unused_variables)]
129            pub fn try_new_in<CC>(cconv: CC, fun: F, jit_alloc: A) -> Result<Self, JitAllocError>
130            where
131                (CC, F): $trait_ident<CC, B>,
132            {
133                let closure = Box::into_raw(Box::new(fun));
134
135                // SAFETY:
136                // - thunk_template pointer obtained from the correct source
137                // - `closure` is a valid pointer to `fun`
138                let thunk_info = unsafe {
139                    create_thunk(<(CC, F)>::$thunk_template, closure as *const _, &jit_alloc)?
140                };
141                Ok(Self {
142                    thunk_info,
143                    jit_alloc,
144                    closure,
145                    phantom: PhantomData,
146                })
147            }
148
149            /// Wraps `fun`, producing a bare function with calling convention
150            /// `cconv`.
151            ///
152            /// Uses `jit_alloc` to allocate the W^X memory used to create the thunk.
153            ///
154            /// # Panics
155            /// If the provided JIT allocator fails to allocate memory. For a non-panicking
156            /// version, see `try_new_in`.
157            #[allow(unused_variables)]
158            pub fn new_in<CC>(cconv: CC, fun: F, jit_alloc: A) -> Self
159            where
160                (CC, F): $trait_ident<CC, B>,
161            {
162                Self::try_new_in(cconv, fun, jit_alloc).unwrap()
163            }
164
165            cc_shorthand_in!(new_c_in, $trait_ident, cc::C, "C");
166
167            cc_shorthand_in!(new_system_in, $trait_ident, cc::System, "system");
168
169            cc_shorthand!(
170                new_sysv64_in,
171                $trait_ident,
172                cc::Sysv64,
173                "sysv64",
174                all(not(windows), target_arch = "x86_64")
175            );
176
177            cc_shorthand_in!(
178                new_aapcs_in,
179                $trait_ident,
180                cc::Aapcs,
181                "aapcs",
182                any(doc, target_arch = "arm", target_arch = "aarch64")
183            );
184
185            cc_shorthand_in!(
186                new_fastcall_in,
187                $trait_ident,
188                cc::Fastcall,
189                "fastcall",
190                all(windows, any(target_arch = "x86_64", target_arch = "x86"))
191            );
192
193            cc_shorthand_in!(
194                new_stdcall_in,
195                $trait_ident,
196                cc::Stdcall,
197                "stdcall",
198                all(windows, any(target_arch = "x86_64", target_arch = "x86"))
199            );
200
201            cc_shorthand_in!(
202                new_cdecl_in,
203                $trait_ident,
204                cc::Cdecl,
205                "cdecl",
206                all(windows, any(target_arch = "x86_64", target_arch = "x86"))
207            );
208
209            cc_shorthand_in!(
210                new_thiscall_in,
211                $trait_ident,
212                cc::Thiscall,
213                "thiscall",
214                all(windows, target_arch = "x86")
215            );
216
217            cc_shorthand_in!(
218                new_win64_in,
219                $trait_ident,
220                cc::Win64,
221                "win64",
222                all(windows, target_arch = "x86_64")
223            );
224
225            #[$bare_toggle]
226            /// Return a bare function pointer that invokes the underlying closure.
227            ///
228            /// # Safety
229            /// While this method is safe, the returned function pointer is not. In particular, it
230            /// must not be called when:
231            /// - The lifetime of `self` has expired, or `self` has been dropped.
232            #[doc = $safety_doc]
233            #[inline]
234            pub fn bare(self: $bare_receiver) -> B {
235                // SAFETY: B is a bare function pointer
236                unsafe { std::mem::transmute_copy(&self.thunk_info.thunk) }
237            }
238
239            /// Leak the underlying closure, returning the unsafe bare function pointer that invokes
240            /// it.
241            ///
242            /// `self` must be `'static` for this method to be called.
243            ///
244            /// # Safety
245            /// While this method is safe, the returned function pointer is not. In particular, it
246            /// must not be called when:
247            #[doc = $safety_doc]
248            #[inline]
249            pub fn leak(self) -> B
250            where
251                Self: 'static,
252            {
253                let no_drop = ManuallyDrop::new(self);
254                // SAFETY: B is a bare function pointer
255                unsafe { std::mem::transmute_copy(&no_drop.thunk_info.thunk) }
256            }
257        }
258
259        impl<B: Copy, F, A: JitAlloc> Drop for $ty_name<B, F, A> {
260            fn drop(&mut self) {
261                // Don't panic on allocator failures for safety reasons
262                // SAFETY:
263                // - The caller of `bare()` promised not to call through the thunk after
264                // the lifetime of self expires
265                // - alloc_base is RX memory previously allocated by jit_alloc which has not been
266                // freed yet
267                unsafe { self.jit_alloc.release(self.thunk_info.alloc_base).ok() };
268
269                // Free the closure
270                // SAFETY:
271                // - The caller of `bare()` promised not to call through the thunk after
272                // the lifetime of self expires, so no borrow on closure exists
273                drop(unsafe { Box::from_raw(self.closure) })
274            }
275        }
276
277        #[cfg(any(test, feature = "bundled_jit_alloc"))]
278        impl<B: Copy, F> $ty_name<B, F, GlobalJitAlloc> {
279            /// Wraps `fun`, producing a bare function with calling convention `cconv`.
280            ///
281            /// The W^X memory required is allocated using the global JIT allocator.
282            #[inline]
283            pub fn new<CC>(cconv: CC, fun: F) -> Self
284            where
285                (CC, F): $trait_ident<CC, B>,
286            {
287                Self::new_in(cconv, fun, Default::default())
288            }
289
290            cc_shorthand!(new_c, $trait_ident, cc::C, "C");
291
292            cc_shorthand!(new_system, $trait_ident, cc::System, "system");
293
294            cc_shorthand!(
295                new_sysv64,
296                $trait_ident,
297                cc::Sysv64,
298                "sysv64",
299                all(not(windows), target_arch = "x86_64")
300            );
301
302            cc_shorthand!(
303                new_aapcs,
304                $trait_ident,
305                cc::Aapcs,
306                "aapcs",
307                any(doc, target_arch = "arm", target_arch = "aarch64")
308            );
309
310            cc_shorthand!(
311                new_fastcall,
312                $trait_ident,
313                cc::Fastcall,
314                "fastcall",
315                all(windows, any(target_arch = "x86_64", target_arch = "x86"))
316            );
317
318            cc_shorthand!(
319                new_stdcall,
320                $trait_ident,
321                cc::Stdcall,
322                "stdcall",
323                all(windows, any(target_arch = "x86_64", target_arch = "x86"))
324            );
325
326            cc_shorthand!(
327                new_cdecl,
328                $trait_ident,
329                cc::Cdecl,
330                "cdecl",
331                all(windows, any(target_arch = "x86_64", target_arch = "x86"))
332            );
333
334            cc_shorthand!(
335                new_thiscall,
336                $trait_ident,
337                cc::Thiscall,
338                "thiscall",
339                all(windows, target_arch = "x86")
340            );
341
342            cc_shorthand!(
343                new_win64,
344                $trait_ident,
345                cc::Win64,
346                "win64",
347                all(windows, target_arch = "x86_64")
348            );
349        }
350    };
351}
352
353// TODO:
354// BareFnOnce still needs work.
355// In particular, to avoid leaks we need to have the compiler generated thunk
356// call `release` on the allocator after it's done running, then drop the allocator.
357// Then, to avoid double frees we need `bare` to be taken by value.
358//
359// At the moment, we simply force leaking for `BareFnOnce` by omitting `bare()`.
360
361bare_closure_impl!(
362    BareFnOnce,
363    FnOnceThunk,
364    THUNK_TEMPLATE_ONCE,
365    cfg(any()),
366    Self,
367    "[`FnOnce`]",
368    "- The function has been called before.\n
369- The closure is not `Send`, if calling from a different thread than the current one."
370);
371
372bare_closure_impl!(
373    BareFnMut,
374    FnMutThunk,
375    THUNK_TEMPLATE_MUT,
376    cfg(all()),
377    &Self,
378    "[`FnMut`]",
379    "- A borrow induced by a previous call is still active.\n
380- The closure is not `Sync`, if calling from a different thread than the current one."
381);
382bare_closure_impl!(
383    BareFn,
384    FnThunk,
385    THUNK_TEMPLATE,
386    cfg(all()),
387    &Self,
388    "[`Fn`]",
389    "- The closure is not `Sync`, if calling from a different thread than the current one."
390);
391
392#[cfg(test)]
393mod tests {
394    #[test]
395    fn test_fn_once() {
396        use super::BareFnOnce;
397
398        let value = "test".to_owned();
399        let bare_closure = BareFnOnce::new_c(move |n: usize| value + &n.to_string());
400
401        // bare() not available on `BareFnOnce` yet
402        let bare = bare_closure.leak();
403
404        let result = unsafe { bare(5) };
405        assert_eq!(&result, "test5");
406    }
407
408    #[test]
409    fn test_fn_mut() {
410        use super::BareFnMut;
411
412        let mut value = "0".to_owned();
413        let bare_closure = BareFnMut::new_c(|n: usize| {
414            value += &n.to_string();
415            value.clone()
416        });
417
418        let bare = bare_closure.bare();
419
420        let result = unsafe { bare(1) };
421        assert_eq!(&result, "01");
422
423        let result = unsafe { bare(2) };
424        assert_eq!(&result, "012");
425    }
426
427    #[test]
428    fn test_fn() {
429        use super::BareFn;
430
431        let cell = core::cell::RefCell::new("0".to_owned());
432        let bare_closure = BareFn::new_c(|n: usize| {
433            *cell.borrow_mut() += &n.to_string();
434            cell.borrow().clone()
435        });
436
437        let bare = bare_closure.bare();
438
439        let result = unsafe { bare(1) };
440        assert_eq!(&result, "01");
441
442        let result = unsafe { bare(2) };
443        assert_eq!(&result, "012");
444    }
445}