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