maniac_runtime/generator/
rt.rs

1//! # generator run time support
2//!
3//! generator run time context management
4//!
5use std::any::Any;
6use std::cell::Cell;
7use std::mem::MaybeUninit;
8use std::ptr;
9
10use crate::generator::reg_context::RegContext;
11
12thread_local! {
13    // each thread has it's own generator context stack
14    static ROOT_CONTEXT_P: Cell<*mut Context> = const { Cell::new(ptr::null_mut()) };
15}
16
17/// yield panic error types
18#[derive(Debug, Copy, Clone, Eq, PartialEq)]
19pub enum Error {
20    /// Done panic
21    Done,
22    /// Cancel panic
23    Cancel,
24    /// Type mismatch panic
25    TypeErr,
26    /// Stack overflow panic
27    StackErr,
28    /// Wrong Context panic
29    ContextErr,
30}
31
32/// generator context
33#[repr(C)]
34#[repr(align(128))]
35pub struct Context {
36    /// generator regs context
37    pub regs: RegContext,
38    /// child context
39    child: *mut Context,
40    /// parent context
41    pub parent: *mut Context,
42    /// passed in para for send
43    pub para: MaybeUninit<*mut dyn Any>,
44    /// this is just a buffer for the return value
45    pub ret: MaybeUninit<*mut dyn Any>,
46    /// track generator ref, yield will -1, send will +1
47    pub _ref: usize,
48    /// context local storage
49    pub local_data: *mut u8,
50    /// propagate panic
51    pub err: Option<Box<dyn Any + Send>>,
52    /// cached stack guard for fast path
53    pub stack_guard: (usize, usize),
54}
55
56impl Context {
57    /// return a default generator context
58    pub fn new() -> Context {
59        Context {
60            regs: RegContext::empty(),
61            para: MaybeUninit::zeroed(),
62            ret: MaybeUninit::zeroed(),
63            _ref: 1, // none zero means it's not running
64            err: None,
65            child: ptr::null_mut(),
66            parent: ptr::null_mut(),
67            local_data: ptr::null_mut(),
68            stack_guard: (0, 0),
69        }
70    }
71
72    /// judge it's generator context
73    #[inline]
74    pub fn is_generator(&self) -> bool {
75        !std::ptr::eq(self.parent, self)
76    }
77
78    /// get current generator send para
79    #[inline]
80    pub fn get_para<A>(&mut self) -> Option<A>
81    where
82        A: Any,
83    {
84        let para = unsafe {
85            let para_ptr = *self.para.as_mut_ptr();
86            assert!(!para_ptr.is_null());
87            &mut *para_ptr
88        };
89        match para.downcast_mut::<Option<A>>() {
90            Some(v) => v.take(),
91            None => type_error::<A>("get yield type mismatch error detected"),
92        }
93    }
94
95    /// get coroutine send para
96    #[inline]
97    pub fn co_get_para<A>(&mut self) -> Option<A> {
98        let para = unsafe {
99            let para_ptr = *self.para.as_mut_ptr();
100            debug_assert!(!para_ptr.is_null());
101            &mut *(para_ptr as *mut Option<A>)
102        };
103        para.take()
104    }
105
106    // /// set current generator send para
107    // #[inline]
108    // pub fn set_para<A>(&self, data: A)
109    // where
110    //     A: Any,
111    // {
112    //     let para = unsafe { &mut *self.para };
113    //     match para.downcast_mut::<Option<A>>() {
114    //         Some(v) => *v = Some(data),
115    //         None => type_error::<A>("set yield type mismatch error detected"),
116    //     }
117    // }
118
119    /// set coroutine send para
120    /// without check the data type for coroutine performance reason
121    #[inline]
122    pub fn co_set_para<A>(&mut self, data: A) {
123        let para = unsafe {
124            let para_ptr = *self.para.as_mut_ptr();
125            debug_assert!(!para_ptr.is_null());
126            &mut *(para_ptr as *mut Option<A>)
127        };
128        *para = Some(data);
129    }
130
131    /// set current generator return value
132    #[inline]
133    pub fn set_ret<T>(&mut self, v: T)
134    where
135        T: Any,
136    {
137        let ret = unsafe {
138            let ret_ptr = *self.ret.as_mut_ptr();
139            assert!(!ret_ptr.is_null());
140            &mut *ret_ptr
141        };
142        match ret.downcast_mut::<Option<T>>() {
143            Some(r) => *r = Some(v),
144            None => type_error::<T>("yield type mismatch error detected"),
145        }
146    }
147
148    /// set coroutine return value
149    /// without check the data type for coroutine performance reason
150    #[inline]
151    pub fn co_set_ret<T>(&mut self, v: T) {
152        let ret = unsafe {
153            let ret_ptr = *self.ret.as_mut_ptr();
154            debug_assert!(!ret_ptr.is_null());
155            &mut *(ret_ptr as *mut Option<T>)
156        };
157        *ret = Some(v);
158    }
159}
160
161/// Coroutine managing environment
162pub struct ContextStack {
163    pub(crate) root: *mut Context,
164}
165
166impl ContextStack {
167    #[cold]
168    fn init_root() -> *mut Context {
169        let root = Box::leak(Box::new(Context::new()));
170        root.parent = root; // init top to current
171        ROOT_CONTEXT_P.set(root);
172        root
173    }
174
175    /// get the current context stack
176    pub fn current() -> ContextStack {
177        let mut root = ROOT_CONTEXT_P.get();
178
179        if root.is_null() {
180            root = Self::init_root();
181        }
182
183        // for windows and macos release version
184        // seems add these two lines could fix the bug!!!
185        // the MAY project test could fail due to this bug
186        // this bug is appeared since rust 1.89 (I have no clue yet)
187        #[cfg(all(not(debug_assertions), any(windows, target_os = "macos")))]
188        {
189            let _thread = std::thread::current();
190            let _thread = std::thread::current();
191        }
192
193        ContextStack { root }
194    }
195
196    /// get the top context
197    #[inline]
198    pub fn top(&self) -> &'static mut Context {
199        unsafe {
200            let root = &*self.root;
201            &mut *root.parent
202        }
203    }
204
205    /// get the coroutine context
206    #[inline]
207    pub fn co_ctx(&self) -> Option<&'static mut Context> {
208        // search from top
209        let mut ctx = self.top();
210
211        while !std::ptr::eq(ctx, self.root) {
212            if !ctx.local_data.is_null() {
213                return Some(ctx);
214            }
215            ctx = unsafe { &mut *ctx.parent };
216        }
217        // not find any coroutine
218        None
219    }
220
221    /// push the context to the thread context list
222    #[inline]
223    pub fn push_context(&self, ctx: *mut Context) {
224        let root = unsafe { &mut *self.root };
225        let ctx = unsafe { &mut *ctx };
226        let top = unsafe { &mut *root.parent };
227        let new_top = ctx.parent;
228
229        // link top and new ctx
230        top.child = ctx;
231        ctx.parent = top;
232
233        // save the new top
234        root.parent = new_top;
235    }
236
237    /// pop the context from the thread context list and return it's parent context
238    #[inline]
239    pub fn pop_context(&self, ctx: *mut Context) -> &'static mut Context {
240        let root = unsafe { &mut *self.root };
241        let ctx = unsafe { &mut *ctx };
242        let parent = unsafe { &mut *ctx.parent };
243
244        // save the old top in ctx's parent
245        ctx.parent = root.parent;
246        // unlink ctx and it's parent
247        parent.child = ptr::null_mut();
248
249        // save the new top
250        root.parent = parent;
251
252        parent
253    }
254}
255
256#[inline]
257#[cold]
258fn type_error<A>(msg: &str) -> ! {
259    log::error!("{msg}, expected type: {}", std::any::type_name::<A>());
260    std::panic::panic_any(Error::TypeErr)
261}
262
263/// check the current context if it's generator
264#[inline]
265pub fn is_generator() -> bool {
266    let env = ContextStack::current();
267    let root = unsafe { &mut *env.root };
268    !root.child.is_null()
269}
270
271/// get the current context local data
272/// only coroutine support local data
273#[inline]
274pub fn get_local_data() -> *mut u8 {
275    let env = ContextStack::current();
276    env.co_ctx().map_or(ptr::null_mut(), |ctx| ctx.local_data)
277}
278
279pub mod guard {
280    use crate::generator::is_generator;
281    use crate::generator::rt::ContextStack;
282    use crate::generator::stack::sys::page_size;
283    use std::ops::Range;
284
285    pub type Guard = Range<usize>;
286
287    pub fn current() -> Guard {
288        assert!(is_generator());
289        let guard = unsafe { (*(*ContextStack::current().root).child).stack_guard };
290
291        guard.0 - page_size()..guard.1
292    }
293}
294
295#[cfg(test)]
296mod test {
297    use super::is_generator;
298
299    #[test]
300    fn test_is_context() {
301        // this is the root context
302        assert!(!is_generator());
303    }
304
305    #[test]
306    fn test_overflow() {
307        use crate::generator::*;
308        use std::panic::catch_unwind;
309
310        // test signal mask
311        for _ in 0..2 {
312            let result = catch_unwind(|| {
313                let mut g = Gn::new_scoped(move |_s: Scope<(), ()>| {
314                    let guard = super::guard::current();
315
316                    // make sure the compiler does not apply any optimization on it
317                    std::hint::black_box(unsafe { *(guard.start as *const usize) });
318
319                    eprintln!("entered unreachable code");
320                    std::process::abort();
321                });
322
323                g.next();
324            });
325
326            assert!(matches!(
327                result.map_err(|err| *err.downcast::<Error>().unwrap()),
328                Err(Error::StackErr)
329            ));
330        }
331    }
332}