1use std::any::Any;
22use std::cell::Cell;
23use std::panic::{AssertUnwindSafe, catch_unwind, resume_unwind};
24
25const UCONTEXT_BYTES: usize = 5120;
27const STACK_BYTES: usize = 4 << 20;
31const POOL_MAX: usize = 32;
34const GUARD_BYTES: usize = 4096;
35
36#[repr(C, align(64))]
37struct UContext([u8; UCONTEXT_BYTES]);
38
39unsafe extern "C" {
40 fn getcontext(ucp: *mut UContext) -> i32;
41 fn makecontext(ucp: *mut UContext, func: extern "C" fn(), argc: i32, ...);
42 fn swapcontext(oucp: *mut UContext, ucp: *const UContext) -> i32;
43 fn mmap(addr: *mut u8, len: usize, prot: i32, flags: i32, fd: i32, off: i64) -> *mut u8;
44 fn munmap(addr: *mut u8, len: usize) -> i32;
45 fn mprotect(addr: *mut u8, len: usize, prot: i32) -> i32;
46}
47
48const PROT_NONE: i32 = 0;
49const PROT_RW: i32 = 3;
50const MAP_PRIVATE_ANON_STACK: i32 = 0x02 | 0x20 | 0x20000 | 0x4000; const MAP_FAILED: *mut u8 = !0usize as *mut u8;
52
53#[derive(Clone, Copy, Debug, PartialEq, Eq)]
55pub enum FiberState {
56 Yielded,
58 Finished,
60}
61
62struct Inner {
63 fiber_ctx: UContext,
65 caller_ctx: UContext,
67 entry: Option<Box<dyn FnOnce()>>,
68 panic: Option<Box<dyn Any + Send>>,
69 done: bool,
70 map: *mut u8,
71}
72
73pub struct Fiber {
75 inner: Box<Inner>,
76}
77
78thread_local! {
79 static POOL: std::cell::RefCell<Vec<usize>> = const { std::cell::RefCell::new(Vec::new()) };
81 static CURRENT: Cell<*mut Inner> = const { Cell::new(std::ptr::null_mut()) };
83}
84
85extern "C" fn trampoline() {
86 let inner = CURRENT.with(Cell::get);
87 let f = unsafe { (*inner).entry.take() };
89 if let Some(f) = f
90 && let Err(p) = catch_unwind(AssertUnwindSafe(f))
91 {
92 unsafe { (*inner).panic = Some(p) };
94 }
95 unsafe {
97 (*inner).done = true;
98 swapcontext(&raw mut (*inner).fiber_ctx, &raw const (*inner).caller_ctx);
99 }
100 unreachable!("a finished fiber was resumed");
101}
102
103impl Fiber {
104 pub fn new(entry: impl FnOnce() + 'static) -> Option<Fiber> {
107 let total = STACK_BYTES + GUARD_BYTES;
108 let pooled = POOL.with(|p| p.borrow_mut().pop());
109 let map = match pooled {
110 Some(m) => m as *mut u8,
111 None => {
112 let map = unsafe {
114 mmap(
115 std::ptr::null_mut(),
116 total,
117 PROT_RW,
118 MAP_PRIVATE_ANON_STACK,
119 -1,
120 0,
121 )
122 };
123 if map == MAP_FAILED {
124 return None;
125 }
126 unsafe { mprotect(map, GUARD_BYTES, PROT_NONE) };
128 map
129 }
130 };
131 let mut inner = Box::new(Inner {
132 fiber_ctx: UContext([0; UCONTEXT_BYTES]),
133 caller_ctx: UContext([0; UCONTEXT_BYTES]),
134 entry: Some(Box::new(entry)),
135 panic: None,
136 done: false,
137 map,
138 });
139 unsafe {
142 if getcontext(&raw mut inner.fiber_ctx) != 0 {
143 release_stack(map);
144 return None;
145 }
146 set_stack(
147 &mut inner.fiber_ctx,
148 map.add(GUARD_BYTES),
149 STACK_BYTES,
150 std::ptr::null_mut(),
151 );
152 makecontext(&raw mut inner.fiber_ctx, trampoline, 0);
153 }
154 Some(Fiber { inner })
155 }
156
157 pub fn resume(&mut self) -> FiberState {
160 assert!(!self.inner.done, "resume of a finished fiber");
161 let inner: *mut Inner = &raw mut *self.inner;
162 let prev = CURRENT.with(|c| c.replace(inner));
163 unsafe { swapcontext(&raw mut (*inner).caller_ctx, &raw const (*inner).fiber_ctx) };
165 CURRENT.with(|c| c.set(prev));
166 if let Some(p) = self.inner.panic.take() {
167 resume_unwind(p);
168 }
169 if self.inner.done {
170 FiberState::Finished
171 } else {
172 FiberState::Yielded
173 }
174 }
175
176 pub fn is_finished(&self) -> bool {
178 self.inner.done
179 }
180}
181
182impl Drop for Fiber {
183 fn drop(&mut self) {
184 release_stack(self.inner.map);
188 }
189}
190
191fn release_stack(map: *mut u8) {
194 let kept = POOL.with(|p| {
195 let mut p = p.borrow_mut();
196 let room = p.len() < POOL_MAX;
197 if room {
198 p.push(map as usize);
199 }
200 room
201 });
202 if !kept {
203 unsafe { munmap(map, STACK_BYTES + GUARD_BYTES) };
205 }
206}
207
208pub fn yield_now() -> bool {
211 let inner = CURRENT.with(Cell::get);
212 if inner.is_null() {
213 return false;
214 }
215 unsafe { swapcontext(&raw mut (*inner).fiber_ctx, &raw const (*inner).caller_ctx) };
217 true
218}
219
220pub fn on_fiber() -> bool {
222 !CURRENT.with(Cell::get).is_null()
223}
224
225unsafe fn set_stack(uc: &mut UContext, sp: *mut u8, size: usize, link: *mut UContext) {
234 let w = uc.0.as_mut_ptr().cast::<usize>();
235 unsafe {
237 w.add(1).write(link as usize); w.add(2).write(sp as usize); w.add(3).write(0); w.add(4).write(size); }
242}
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247 use std::rc::Rc;
248
249 #[test]
250 fn runs_to_completion_without_yielding() {
251 let hit = Rc::new(Cell::new(0));
252 let h = hit.clone();
253 let mut f = Fiber::new(move || h.set(7)).unwrap();
254 assert!(!on_fiber());
255 assert_eq!(f.resume(), FiberState::Finished);
256 assert_eq!(hit.get(), 7);
257 assert!(f.is_finished());
258 }
259
260 #[test]
261 fn yields_and_resumes_in_order() {
262 let log = Rc::new(std::cell::RefCell::new(Vec::new()));
263 let l = log.clone();
264 let mut f = Fiber::new(move || {
265 assert!(on_fiber());
266 l.borrow_mut().push(1);
267 assert!(yield_now());
268 l.borrow_mut().push(3);
269 yield_now();
270 l.borrow_mut().push(5);
271 })
272 .unwrap();
273 assert_eq!(f.resume(), FiberState::Yielded);
274 log.borrow_mut().push(2);
275 assert_eq!(f.resume(), FiberState::Yielded);
276 log.borrow_mut().push(4);
277 assert_eq!(f.resume(), FiberState::Finished);
278 assert_eq!(*log.borrow(), [1, 2, 3, 4, 5]);
279 }
280
281 #[test]
282 fn interleaved_fibers_keep_their_own_stacks() {
283 let out = Rc::new(std::cell::RefCell::new(Vec::new()));
284 let mk = |tag: i32| {
285 let o = out.clone();
286 Fiber::new(move || {
287 let local = [tag; 64];
288 yield_now();
289 o.borrow_mut().push(local.iter().sum::<i32>());
290 })
291 .unwrap()
292 };
293 let (mut a, mut b) = (mk(1), mk(2));
294 a.resume();
295 b.resume();
296 assert_eq!(b.resume(), FiberState::Finished);
298 assert_eq!(a.resume(), FiberState::Finished);
299 assert_eq!(*out.borrow(), [128, 64]);
300 }
301
302 #[test]
303 fn yield_on_main_stack_is_a_no_op() {
304 assert!(!yield_now());
305 }
306
307 #[test]
308 fn panic_is_re_raised_on_the_resumer() {
309 let mut f = Fiber::new(|| panic!("boom")).unwrap();
310 let r = catch_unwind(AssertUnwindSafe(|| f.resume()));
311 assert!(r.is_err());
312 }
313
314 #[test]
315 fn nested_fibers() {
316 let log = Rc::new(std::cell::RefCell::new(Vec::new()));
317 let l = log.clone();
318 let mut outer = Fiber::new(move || {
319 let l2 = l.clone();
320 let mut inner = Fiber::new(move || {
321 l2.borrow_mut().push("in1");
322 yield_now();
323 l2.borrow_mut().push("in2");
324 })
325 .unwrap();
326 inner.resume();
327 l.borrow_mut().push("out1");
328 yield_now();
329 inner.resume();
330 l.borrow_mut().push("out2");
331 })
332 .unwrap();
333 outer.resume();
334 outer.resume();
335 assert_eq!(*log.borrow(), ["in1", "out1", "in2", "out2"]);
336 }
337}