Implement the safe Generator abstraction.

close #24
This commit is contained in:
whitequark 2016-07-17 03:11:33 +00:00 committed by edef
parent 758088c673
commit 7ffad26cfd
6 changed files with 280 additions and 12 deletions

View File

@ -8,10 +8,21 @@
# libfringe # libfringe
libfringe is a low-level green threading library for Rust. libfringe is a library implementing lightweight context switches,
It's usable in freestanding environments (like kernels), without relying on kernel services. It can be used in hosted environments
but it can also provide an easy-to-use stack allocator using (using `std`) as well as on bare metal (using `core`).
your operating system's memory mapping facility.
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 ## Performance

18
benches/generator.rs Normal file
View File

@ -0,0 +1,18 @@
// This file is part of libfringe, a low-level green threading library.
// Copyright (c) whitequark <whitequark@whitequark.org>
// 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()));
}

View File

@ -43,7 +43,7 @@ impl<Stack> Context<Stack> where Stack: stack::Stack {
} }
impl<OldStack> Context<OldStack> where OldStack: stack::Stack { impl<OldStack> Context<OldStack> 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)] #[inline(always)]
pub unsafe fn swap<NewStack>(old_ctx: *mut Context<OldStack>, pub unsafe fn swap<NewStack>(old_ctx: *mut Context<OldStack>,
new_ctx: *const Context<NewStack>, new_ctx: *const Context<NewStack>,

176
src/generator.rs Normal file
View File

@ -0,0 +1,176 @@
// This file is part of libfringe, a low-level green threading library.
// Copyright (c) whitequark <whitequark@whitequark.org>
// 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<Item: Send, Stack: stack::Stack> {
state: State,
context: context::Context<Stack>,
phantom: PhantomData<Item>
}
impl<Item, Stack> Generator<Item, Stack>
where Item: Send, Stack: stack::Stack {
/// Creates a new generator.
pub fn new<F>(stack: Stack, f: F) -> Generator<Item, Stack>
where F: FnOnce(&mut Yielder<Item, Stack>) + Send {
unsafe extern "C" fn generator_wrapper<Item, Stack, F>(info: usize) -> !
where Item: Send, Stack: stack::Stack, F: FnOnce(&mut Yielder<Item, Stack>) {
// Retrieve our environment from the callee and return control to it.
let (mut yielder, f) = ptr::read(info as *mut (Yielder<Item, Stack>, F));
let new_context = context::Context::swap(yielder.context, yielder.context, 0);
// See Yielder::return_.
yielder.context = new_context as *mut context::Context<Stack>;
// 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::<Item, Stack, F>),
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<Item, Stack>, 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<Item: Send, Stack: stack::Stack> {
context: *mut context::Context<Stack>,
phantom: PhantomData<Item>
}
impl<Item, Stack> Yielder<Item, Stack>
where Item: Send, Stack: stack::Stack {
fn new(context: *mut context::Context<Stack>) -> Yielder<Item, Stack> {
Yielder {
context: context,
phantom: PhantomData
}
}
#[inline(always)]
fn return_(&mut self, mut val: Option<Item>) {
unsafe {
let new_context = context::Context::swap(self.context, self.context,
&mut val as *mut Option<Item> 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<Stack>;
// 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<Item, Stack> Iterator for Generator<Item, Stack>
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<Self::Item> {
match self.state {
State::Suspended => {
let new_context = &mut self.context as *mut context::Context<Stack> as usize;
let val = unsafe {
ptr::read(context::Context::swap(&mut self.context, &self.context, new_context)
as *mut Option<Item>)
};
if let None = val { self.state = State::Finished }
val
}
State::Finished => None
}
}
}

View File

@ -5,20 +5,43 @@
#![cfg_attr(target_arch = "x86", feature(naked_functions, core_intrinsics))] #![cfg_attr(target_arch = "x86", feature(naked_functions, core_intrinsics))]
#![no_std] #![no_std]
//! libfringe is a low-level green threading library. //! libfringe is a library implementing lightweight context switches,
//! It provides only a context-swapping mechanism. //! 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 stack::Stack;
pub use context::Context;
pub use generator::Generator;
#[cfg(any(unix, windows))] #[cfg(any(unix, windows))]
pub use os::Stack as OsStack; pub use os::Stack as OsStack;
mod context; mod arch;
mod debug;
mod stack; mod stack;
mod context;
pub mod generator;
#[cfg(any(unix, windows))] #[cfg(any(unix, windows))]
mod os; mod os;
mod arch;
mod debug;

40
tests/generator.rs Normal file
View File

@ -0,0 +1,40 @@
// This file is part of libfringe, a low-level green threading library.
// Copyright (c) whitequark <whitequark@whitequark.org>
// 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<u32, OsStack>) {
assert_eq!(gen.next(), Some(2));
assert_eq!(gen.next(), Some(3));
assert_eq!(gen.next(), None);
}
rest(gen);
}