crossbeam_epoch/
deferred.rs

1use alloc::boxed::Box;
2use core::fmt;
3use core::marker::PhantomData;
4use core::mem::{self, MaybeUninit};
5use core::ptr;
6
7/// Number of words a piece of `Data` can hold.
8///
9/// Three words should be enough for the majority of cases. For example, you can fit inside it the
10/// function pointer together with a fat pointer representing an object that needs to be destroyed.
11const DATA_WORDS: usize = 3;
12
13/// Some space to keep a `FnOnce()` object on the stack.
14type Data = [usize; DATA_WORDS];
15
16/// A `FnOnce()` that is stored inline if small, or otherwise boxed on the heap.
17///
18/// This is a handy way of keeping an unsized `FnOnce()` within a sized structure.
19pub(crate) struct Deferred {
20    call: unsafe fn(*mut u8),
21    data: Data,
22    _marker: PhantomData<*mut ()>, // !Send + !Sync
23}
24
25impl fmt::Debug for Deferred {
26    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
27        f.pad("Deferred { .. }")
28    }
29}
30
31impl Deferred {
32    /// Constructs a new `Deferred` from a `FnOnce()`.
33    pub(crate) fn new<F: FnOnce()>(f: F) -> Self {
34        let size = mem::size_of::<F>();
35        let align = mem::align_of::<F>();
36
37        unsafe {
38            if size <= mem::size_of::<Data>() && align <= mem::align_of::<Data>() {
39                let mut data = MaybeUninit::<Data>::uninit();
40                ptr::write(data.as_mut_ptr() as *mut F, f);
41
42                unsafe fn call<F: FnOnce()>(raw: *mut u8) {
43                    let f: F = ptr::read(raw as *mut F);
44                    f();
45                }
46
47                Deferred {
48                    call: call::<F>,
49                    data: data.assume_init(),
50                    _marker: PhantomData,
51                }
52            } else {
53                let b: Box<F> = Box::new(f);
54                let mut data = MaybeUninit::<Data>::uninit();
55                ptr::write(data.as_mut_ptr() as *mut Box<F>, b);
56
57                unsafe fn call<F: FnOnce()>(raw: *mut u8) {
58                    // It's safe to cast `raw` from `*mut u8` to `*mut Box<F>`, because `raw` is
59                    // originally derived from `*mut Box<F>`.
60                    #[allow(clippy::cast_ptr_alignment)]
61                    let b: Box<F> = ptr::read(raw as *mut Box<F>);
62                    (*b)();
63                }
64
65                Deferred {
66                    call: call::<F>,
67                    data: data.assume_init(),
68                    _marker: PhantomData,
69                }
70            }
71        }
72    }
73
74    /// Calls the function.
75    #[inline]
76    pub(crate) fn call(mut self) {
77        let call = self.call;
78        unsafe { call(&mut self.data as *mut Data as *mut u8) };
79    }
80}
81
82#[cfg(all(test, not(crossbeam_loom)))]
83mod tests {
84    use super::Deferred;
85    use std::cell::Cell;
86
87    #[test]
88    fn on_stack() {
89        let fired = &Cell::new(false);
90        let a = [0usize; 1];
91
92        let d = Deferred::new(move || {
93            drop(a);
94            fired.set(true);
95        });
96
97        assert!(!fired.get());
98        d.call();
99        assert!(fired.get());
100    }
101
102    #[test]
103    fn on_heap() {
104        let fired = &Cell::new(false);
105        let a = [0usize; 10];
106
107        let d = Deferred::new(move || {
108            drop(a);
109            fired.set(true);
110        });
111
112        assert!(!fired.get());
113        d.call();
114        assert!(fired.get());
115    }
116
117    #[test]
118    fn string() {
119        let a = "hello".to_string();
120        let d = Deferred::new(move || assert_eq!(a, "hello"));
121        d.call();
122    }
123
124    #[test]
125    fn boxed_slice_i32() {
126        let a: Box<[i32]> = vec![2, 3, 5, 7].into_boxed_slice();
127        let d = Deferred::new(move || assert_eq!(*a, [2, 3, 5, 7]));
128        d.call();
129    }
130
131    #[test]
132    fn long_slice_usize() {
133        let a: [usize; 5] = [2, 3, 5, 7, 11];
134        let d = Deferred::new(move || assert_eq!(a, [2, 3, 5, 7, 11]));
135        d.call();
136    }
137}