Created
December 1, 2019 18:57
-
-
Save sug0/494cca824ccf7d56750e25b1f8b98b75 to your computer and use it in GitHub Desktop.
Rust coroutines based around setjmp/longjmp... Why has God abandoned us?!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| use std::ffi::c_void; | |
| use std::os::raw::c_int; | |
| extern "C" { | |
| fn setjmp(env: *mut c_void) -> c_int; | |
| fn longjmp(env: *mut c_void, val: c_int); | |
| } | |
| const JMP_BUF_SIZ: usize = 512; | |
| struct JmpBuf { | |
| inner: [u8; JMP_BUF_SIZ] | |
| } | |
| #[derive(Debug, Eq, PartialEq, Copy, Clone, Ord, PartialOrd, Hash)] | |
| enum CoroutineStatus { | |
| Suspended, | |
| Running, | |
| Dead, | |
| } | |
| struct Coroutine<Y> { | |
| status: CoroutineStatus, | |
| from: JmpBuf, | |
| to: JmpBuf, | |
| ret: Option<Y>, | |
| } | |
| fn main() { | |
| let mut co = Coroutine::new(); | |
| co.start(|co| { | |
| // yield a dummy value | |
| co.yield_val(-1); | |
| // actual coroutine values | |
| for i in 0..10 { | |
| co.yield_val(i) | |
| } | |
| }); | |
| while let Some(i) = co.resume() { | |
| println!("{}", i) | |
| } | |
| assert!(co.status() == CoroutineStatus::Dead) | |
| } | |
| impl JmpBuf { | |
| const fn new() -> Self { | |
| JmpBuf { inner: [0; JMP_BUF_SIZ] } | |
| } | |
| unsafe fn set(&mut self) -> i32 { | |
| setjmp(self.get_env()) as i32 | |
| } | |
| unsafe fn jmp(&mut self, val: i32) { | |
| longjmp(self.get_env(), val as c_int) | |
| } | |
| fn get_env(&mut self) -> *mut c_void { | |
| &mut self.inner[0] as *mut u8 as *mut c_void | |
| } | |
| } | |
| impl Clone for JmpBuf { | |
| fn clone(&self) -> Self { | |
| JmpBuf { inner: self.inner.clone() } | |
| } | |
| } | |
| impl<Y> Coroutine<Y> { | |
| const fn new() -> Self { | |
| Coroutine { | |
| status: CoroutineStatus::Dead, | |
| from: JmpBuf::new(), | |
| to: JmpBuf::new(), | |
| ret: None, | |
| } | |
| } | |
| fn status(&self) -> CoroutineStatus { | |
| self.status | |
| } | |
| fn start<F>(&mut self, co: F) -> Option<Y> | |
| where F: FnOnce(&mut Self), | |
| { | |
| if self.status != CoroutineStatus::Dead { | |
| panic!("coroutine already running") | |
| } | |
| self.status = CoroutineStatus::Running; | |
| let ctx = unsafe { self.from.set() }; | |
| if ctx != 0 { | |
| self.status = CoroutineStatus::Suspended; | |
| return self.ret.take() | |
| } | |
| co(self); | |
| self.status = CoroutineStatus::Dead; | |
| None | |
| } | |
| fn resume(&mut self) -> Option<Y> { | |
| match self.status { | |
| CoroutineStatus::Running => panic!("resume on running coroutine"), | |
| CoroutineStatus::Dead => None, | |
| CoroutineStatus::Suspended => { | |
| self.status = CoroutineStatus::Running; | |
| // save context to jump back here | |
| let ctx = unsafe { self.from.set() }; | |
| if ctx != 0 { | |
| self.status = CoroutineStatus::Suspended; | |
| return self.ret.take() | |
| } | |
| // if we return back here, the coroutine is dead | |
| unsafe { self.to.jmp(1) }; | |
| self.status = CoroutineStatus::Dead; | |
| None | |
| } | |
| } | |
| } | |
| fn yield_val(&mut self, val: Y) { | |
| if self.status != CoroutineStatus::Running { | |
| panic!("can't call yield on a non running coroutine") | |
| } | |
| // save the yielded value | |
| self.ret = Some(val); | |
| // save context to jump back here | |
| let ctx = unsafe { self.to.set() }; | |
| if ctx != 0 { | |
| // resume from where we left off | |
| return | |
| } | |
| // jump back to the caller | |
| unsafe { self.from.jmp(1) } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment