From 7ffad26cfd28f1d04e3033725fa16fc296f63b33 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sun, 17 Jul 2016 03:11:33 +0000 Subject: [PATCH] Implement the safe Generator abstraction. close #24 --- README.md | 19 ++++- benches/generator.rs | 18 +++++ src/context.rs | 2 +- src/generator.rs | 176 +++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 37 +++++++-- tests/generator.rs | 40 ++++++++++ 6 files changed, 280 insertions(+), 12 deletions(-) create mode 100644 benches/generator.rs create mode 100644 src/generator.rs create mode 100644 tests/generator.rs diff --git a/README.md b/README.md index 91e2cf8..eea24dd 100644 --- a/README.md +++ b/README.md @@ -8,10 +8,21 @@ # libfringe - libfringe is a low-level green threading library for Rust. - It's usable in freestanding environments (like kernels), - but it can also provide an easy-to-use stack allocator using - your operating system's memory mapping facility. +libfringe is a library implementing lightweight context switches, +without relying on kernel services. It can be used in hosted environments +(using `std`) as well as on bare metal (using `core`). + +It provides high-level, safe abstractions: + * an implementation of internal iterators, also known as generators, + [Generator](https://edef1c.github.io/libfringe/fringe/generator/struct.Generator.html). + +It also provides low-level, *very* unsafe building blocks: + * a flexible, low-level context-swapping mechanism, + [Context](https://edef1c.github.io/libfringe/fringe/struct.Context.html); + * a trait that can be implemented by stack allocators, + [Stack](https://edef1c.github.io/libfringe/fringe/struct.Stack.html); + * a stack allocator based on anonymous memory mappings with guard pages, + [OsStack](https://edef1c.github.io/libfringe/fringe/struct.OsStack.html). ## Performance diff --git a/benches/generator.rs b/benches/generator.rs new file mode 100644 index 0000000..d1a6957 --- /dev/null +++ b/benches/generator.rs @@ -0,0 +1,18 @@ +// This file is part of libfringe, a low-level green threading library. +// Copyright (c) whitequark +// See the LICENSE file included in this distribution. +#![feature(test)] +extern crate test; +extern crate fringe; + +use fringe::{OsStack, Generator}; + +#[bench] +fn generate(b: &mut test::Bencher) { + let stack = OsStack::new(0).unwrap(); + let mut gen = Generator::new(stack, move |yielder| { + for i in 1.. { yielder.generate(i) } + }); + + b.iter(|| test::black_box(gen.next())); +} diff --git a/src/context.rs b/src/context.rs index 7572480..6c78241 100644 --- a/src/context.rs +++ b/src/context.rs @@ -43,7 +43,7 @@ impl Context where Stack: stack::Stack { } impl Context where OldStack: stack::Stack { - /// Switches to in_ctx, saving the current thread of execution to out_ctx. + /// Switches to `in_ctx`, saving the current thread of execution to `out_ctx`. #[inline(always)] pub unsafe fn swap(old_ctx: *mut Context, new_ctx: *const Context, diff --git a/src/generator.rs b/src/generator.rs new file mode 100644 index 0000000..0517e6c --- /dev/null +++ b/src/generator.rs @@ -0,0 +1,176 @@ +// This file is part of libfringe, a low-level green threading library. +// Copyright (c) whitequark +// See the LICENSE file included in this distribution. + +//! Generators. +//! +//! Generators allow repeatedly suspending the execution of a function, +//! returning a value to the caller, and resuming the suspended function +//! afterwards. + +use core::marker::PhantomData; +use core::iter::Iterator; +use core::{ptr, mem}; + +use stack; +use context; + +#[derive(Debug, Clone, Copy)] +pub enum State { + /// Generator can be resumed. This is the initial state. + Suspended, + /// Generator cannot be resumed. This is the state of the generator after + /// the generator function has returned. + Finished +} + +/// Generator wraps a function and allows suspending its execution more than +/// once, return a value each time. +/// +/// It implements the Iterator trait. The first time `next()` is called, +/// the function is called as `f(yielder)`; every time `next()` is called afterwards, +/// the function is resumed. In either case, it runs until it suspends its execution +/// through `yielder.generate(val)`), in which case `next()` returns `Some(val)`, or +/// returns, in which case `next()` returns `None`. `next()` will return `None` +/// every time after that. +/// +/// After the generator function returns, it is safe to reclaim the generator +/// stack using `unwrap()`. +/// +/// `state()` can be used to determine whether the generator function has returned; +/// the state is `State::Suspended` after creation and suspension, and `State::Finished` +/// once the generator function returns. +/// +/// # Example +/// +/// ``` +/// use fringe::{OsStack, Generator}; +/// +/// let stack = OsStack::new(0).unwrap(); +/// let mut gen = Generator::new(stack, move |yielder| { +/// for i in 1..4 { +/// yielder.generate(i); +/// } +/// }); +/// println!("{:?}", gen.next()); // prints Some(1) +/// println!("{:?}", gen.next()); // prints Some(2) +/// println!("{:?}", gen.next()); // prints Some(3) +/// println!("{:?}", gen.next()); // prints None +/// ``` +#[derive(Debug)] +pub struct Generator { + state: State, + context: context::Context, + phantom: PhantomData +} + +impl Generator + where Item: Send, Stack: stack::Stack { + /// Creates a new generator. + pub fn new(stack: Stack, f: F) -> Generator + where F: FnOnce(&mut Yielder) + Send { + unsafe extern "C" fn generator_wrapper(info: usize) -> ! + where Item: Send, Stack: stack::Stack, F: FnOnce(&mut Yielder) { + // Retrieve our environment from the callee and return control to it. + let (mut yielder, f) = ptr::read(info as *mut (Yielder, F)); + let new_context = context::Context::swap(yielder.context, yielder.context, 0); + // See Yielder::return_. + yielder.context = new_context as *mut context::Context; + // Run the body of the generator. + f(&mut yielder); + // Past this point, the generator has dropped everything it has held. + loop { yielder.return_(None) } + } + + unsafe { + let mut generator = Generator { + state: State::Suspended, + context: context::Context::new(stack, generator_wrapper::), + phantom: PhantomData + }; + + // Transfer environment to the callee. + let mut data = (Yielder::new(&mut generator.context), f); + context::Context::swap(&mut generator.context, &generator.context, + &mut data as *mut (Yielder, F) as usize); + mem::forget(data); + + generator + } + } + + /// Returns the state of the generator. + pub fn state(&self) -> State { self.state } + + /// Extracts the stack from a generator when the generator function has returned. + /// If the generator function has not returned + /// (i.e. `self.state() == State::Suspended`), panics. + pub fn unwrap(self) -> Stack { + match self.state { + State::Suspended => panic!("Argh! Bastard! Don't touch that!"), + State::Finished => unsafe { self.context.unwrap() } + } + } +} + +/// Yielder is an interface provided to every generator through which it +/// returns a value. +#[derive(Debug)] +pub struct Yielder { + context: *mut context::Context, + phantom: PhantomData +} + +impl Yielder + where Item: Send, Stack: stack::Stack { + fn new(context: *mut context::Context) -> Yielder { + Yielder { + context: context, + phantom: PhantomData + } + } + + #[inline(always)] + fn return_(&mut self, mut val: Option) { + unsafe { + let new_context = context::Context::swap(self.context, self.context, + &mut val as *mut Option as usize); + // The generator can be moved (and with it, the context). + // This changes the address of the context. + // Thus, we update it after each swap. + self.context = new_context as *mut context::Context; + // However, between this point and the next time we enter return_ + // the generator cannot be moved, as a &mut Generator is necessary + // to resume the generator function. + } + } + + /// Suspends the generator and returns `Some(item)` from the `next()` + /// invocation that resumed the generator. + #[inline(always)] + pub fn generate(&mut self, item: Item) { + self.return_(Some(item)) + } +} + +impl Iterator for Generator + where Item: Send, Stack: stack::Stack { + type Item = Item; + + /// Resumes the generator and return the next value it yields. + /// If the generator function has returned, returns `None`. + fn next(&mut self) -> Option { + match self.state { + State::Suspended => { + let new_context = &mut self.context as *mut context::Context as usize; + let val = unsafe { + ptr::read(context::Context::swap(&mut self.context, &self.context, new_context) + as *mut Option) + }; + if let None = val { self.state = State::Finished } + val + } + State::Finished => None + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 0631ae0..2bdf621 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,20 +5,43 @@ #![cfg_attr(target_arch = "x86", feature(naked_functions, core_intrinsics))] #![no_std] -//! libfringe is a low-level green threading library. -//! It provides only a context-swapping mechanism. +//! libfringe is a library implementing lightweight context switches, +//! without relying on kernel services. It can be used in hosted environments +//! (using `std`) as well as on bare metal (using `core`). +//! +//! It provides high-level, safe abstractions: +//! +//! * an implementation of internal iterators, also known as generators, +//! [Generator](generator/struct.Generator.html). +//! +//! It also provides low-level, *very* unsafe building blocks: +//! +//! * a flexible, low-level context-swapping mechanism, +//! [Context](struct.Context.html); +//! * a trait that can be implemented by stack allocators, +//! [Stack](struct.Stack.html); +//! * a stack allocator based on anonymous memory mappings with guard pages, +//! [OsStack](struct.OsStack.html). +//! +//! **FIXME:** not actually safe yet in presence of unwinding + +#[cfg(test)] +#[macro_use] +extern crate std; -pub use context::Context; pub use stack::Stack; +pub use context::Context; +pub use generator::Generator; #[cfg(any(unix, windows))] pub use os::Stack as OsStack; -mod context; +mod arch; +mod debug; + mod stack; +mod context; +pub mod generator; #[cfg(any(unix, windows))] mod os; - -mod arch; -mod debug; diff --git a/tests/generator.rs b/tests/generator.rs new file mode 100644 index 0000000..76010ce --- /dev/null +++ b/tests/generator.rs @@ -0,0 +1,40 @@ +// This file is part of libfringe, a low-level green threading library. +// Copyright (c) whitequark +// See the LICENSE file included in this distribution. +extern crate fringe; + +use fringe::OsStack; +use fringe::generator::Generator; + +#[test] +fn generator() { + let stack = OsStack::new(0).unwrap(); + let mut gen = Generator::new(stack, move |yielder| { + for i in 1..4 { + yielder.generate(i); + } + }); + assert_eq!(gen.next(), Some(1)); + assert_eq!(gen.next(), Some(2)); + assert_eq!(gen.next(), Some(3)); + assert_eq!(gen.next(), None); +} + +#[test] +fn move_after_new() { + let stack = OsStack::new(0).unwrap(); + let mut gen = Generator::new(stack, move |yielder| { + for i in 1..4 { + yielder.generate(i); + } + }); + assert_eq!(gen.next(), Some(1)); + + #[inline(never)] + fn rest(mut gen: Generator) { + assert_eq!(gen.next(), Some(2)); + assert_eq!(gen.next(), Some(3)); + assert_eq!(gen.next(), None); + } + rest(gen); +}