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