Skip to content

Instantly share code, notes, and snippets.

@sug0
Created December 1, 2019 18:57
Show Gist options
  • Select an option

  • Save sug0/494cca824ccf7d56750e25b1f8b98b75 to your computer and use it in GitHub Desktop.

Select an option

Save sug0/494cca824ccf7d56750e25b1f8b98b75 to your computer and use it in GitHub Desktop.
Rust coroutines based around setjmp/longjmp... Why has God abandoned us?!
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