"Coroutines".

This commit is contained in:
losfair 2019-01-02 00:58:49 +08:00
parent e2b4527106
commit 1da4c3d4dc

View File

@ -17,17 +17,33 @@ extern "C" {
pub struct StackContext<T, R> { pub struct StackContext<T, R> {
addr: *mut u8, addr: *mut u8,
size: usize, thread_context: Box<ThreadContext<T, R>>,
thread_context: ThreadContext<T, R>,
} }
struct ThreadContext<T, R> { pub struct ThreadContext<T, R> {
target: fn(T) -> R, target: fn(&mut ThreadContext<T, R>, T) -> R,
param: Option<T>, param: Option<T>,
ret: Option<R>, ret: Option<R>,
jmp_buffer: [c_int; SETJMP_BUFFER_LEN], jmp_buffer: [c_int; SETJMP_BUFFER_LEN],
} }
impl<T, R> ThreadContext<T, R> {
/// do_yield swaps the current state with the saved one.
///
/// This is marked as safe because we assume that:
/// - A ThreadContext can only be constructed from within this module.
/// - This function is never inlined so that the compiler consider the whole ThreadContext as possibly modified after calling this.
#[inline(never)] // prevent compiler from looking into do_yield when performing optimizations.
pub fn do_yield(&mut self) {
unsafe {
let mut yield_point = self.jmp_buffer;
if setjmp(&mut self.jmp_buffer) == 0 {
longjmp(&mut yield_point, 1);
}
}
}
}
struct PthreadAttr { struct PthreadAttr {
inner: pthread_attr_t, inner: pthread_attr_t,
} }
@ -56,36 +72,46 @@ impl Drop for PthreadAttr {
} }
impl<T, R> StackContext<T, R> { impl<T, R> StackContext<T, R> {
pub unsafe fn new(addr: *mut u8, size: usize, f: fn(T) -> R, param: T) -> StackContext<T, R> { /// Creates a new stack context.
///
/// This function is unsafe because `addr` and `size` must be ensured by the user to be valid.
pub unsafe fn new(
addr: *mut u8,
f: fn(&mut ThreadContext<T, R>, T) -> R,
param: T,
) -> StackContext<T, R> {
extern "C" fn run_context<T, R>(ctx: *mut c_void) -> *mut c_void { extern "C" fn run_context<T, R>(ctx: *mut c_void) -> *mut c_void {
unsafe { unsafe {
let ctx = &mut *(ctx as *mut ThreadContext<T, R>); let ctx = &mut *(ctx as *mut ThreadContext<T, R>);
if setjmp(&mut ctx.jmp_buffer) != 0 { if setjmp(&mut ctx.jmp_buffer) != 0 {
ctx.ret = Some((ctx.target)(ctx.param.take().unwrap())); let target = ctx.target;
let param = ctx.param.take().unwrap();
let ret = (target)(ctx, param);
ctx.ret = Some(ret);
ctx.do_yield();
} }
} }
::std::ptr::null_mut() ::std::ptr::null_mut()
} }
let mut attr = PthreadAttr::new(); let mut attr = PthreadAttr::new();
if pthread_attr_setstack(&mut attr.inner, addr as *mut c_void, size as size_t) != 0 {
panic!( // The value 65536 is chosen arbitrarily. Any reasonable value for stack size works here.
"pthread_attr_setstack failed, addr = {:?}, size = {}", if pthread_attr_setstack(&mut attr.inner, addr as *mut c_void, 65536) != 0 {
addr, size panic!("pthread_attr_setstack failed, addr = {:?}", addr);
);
} }
let mut thread_ctx = ThreadContext { let mut thread_ctx = Box::new(ThreadContext {
target: f, target: f,
param: Some(param), param: Some(param),
ret: None, ret: None,
jmp_buffer: ::std::mem::uninitialized(), jmp_buffer: ::std::mem::uninitialized(),
}; });
let mut pthread_handle: pthread_t = ::std::mem::uninitialized(); let mut pthread_handle: pthread_t = ::std::mem::uninitialized();
if pthread_create( if pthread_create(
&mut pthread_handle, &mut pthread_handle,
&mut attr.inner, &mut attr.inner,
run_context::<T, R>, run_context::<T, R>,
&mut thread_ctx as *mut ThreadContext<T, R> as *mut c_void, thread_ctx.as_mut() as *mut ThreadContext<T, R> as *mut c_void,
) != 0 ) != 0
{ {
panic!("pthread_create failed"); panic!("pthread_create failed");
@ -93,10 +119,20 @@ impl<T, R> StackContext<T, R> {
pthread_join(pthread_handle, ::std::ptr::null_mut()); pthread_join(pthread_handle, ::std::ptr::null_mut());
StackContext { StackContext {
addr: addr, addr: addr,
size: size,
thread_context: thread_ctx, thread_context: thread_ctx,
} }
} }
/// Continues execution of the current stack context.
///
/// This function is unsafe because nested stack context is not supported and may cause UB.
pub unsafe fn next(mut self) -> Result<R, Self> {
self.thread_context.do_yield();
match self.thread_context.ret {
Some(x) => Ok(x),
None => Err(self),
}
}
} }
#[cfg(test)] #[cfg(test)]
@ -105,16 +141,31 @@ mod tests {
#[test] #[test]
fn test_stack_context() { fn test_stack_context() {
fn some_fn(_: ()) {} fn some_fn(ctx: &mut ThreadContext<&mut i32, ()>, out: &mut i32) {
for i in 0..100i32 {
*out += i;
ctx.do_yield();
}
}
let mut buf: Vec<u8> = Vec::with_capacity(65536); let mut buf: Vec<u8> = Vec::with_capacity(65536);
unsafe { buf.set_len(65536) }; unsafe { buf.set_len(65536) };
let ctx = unsafe {
let mut sum: i32 = 0;
let mut ctx = Some(unsafe {
StackContext::new( StackContext::new(
((buf.as_mut_ptr().offset(buf.len() as isize) as usize) & (!4095usize)) as _, ((buf.as_mut_ptr().offset(buf.len() as isize) as usize) & (!4095usize)) as _,
buf.len(),
some_fn, some_fn,
(), &mut sum,
) )
}; });
loop {
match unsafe { ctx.take().unwrap().next() } {
Ok(x) => break,
Err(x) => {
ctx = Some(x);
}
}
}
assert_eq!(sum, 4950);
} }
} }