Reimplement Generator to pass both input and output values around.

This commit is contained in:
whitequark 2016-08-11 21:04:40 +00:00 committed by edef
parent 308df32ac2
commit ddbf9a5afe
6 changed files with 127 additions and 123 deletions

View File

@ -13,7 +13,7 @@ without relying on kernel services. It can be used in hosted environments
(using `std`) as well as on bare metal (using `core`). (using `std`) as well as on bare metal (using `core`).
It provides the following safe abstractions: It provides the following safe abstractions:
* an implementation of internal iterators, also known as generators, * an implementation of generators,
[Generator](https://edef1c.github.io/libfringe/fringe/generator/struct.Generator.html). [Generator](https://edef1c.github.io/libfringe/fringe/generator/struct.Generator.html).
It also provides the necessary low-level building blocks: It also provides the necessary low-level building blocks:

View File

@ -10,9 +10,9 @@ use fringe::{OsStack, Generator};
#[bench] #[bench]
fn generate(b: &mut test::Bencher) { fn generate(b: &mut test::Bencher) {
let stack = OsStack::new(0).unwrap(); let stack = OsStack::new(0).unwrap();
let mut gen = Generator::new(stack, move |yielder| { let mut identity = Generator::new(stack, move |yielder, mut input| {
for i in 1.. { yielder.generate(i) } loop { input = yielder.generate(input) }
}); });
b.iter(|| test::black_box(gen.next())); b.iter(|| test::black_box(identity.resume(test::black_box(0))));
} }

View File

@ -9,11 +9,10 @@
//! afterwards. //! afterwards.
use core::marker::PhantomData; use core::marker::PhantomData;
use core::iter::Iterator;
use core::{ptr, mem}; use core::{ptr, mem};
use stack; use stack;
use context; use context::Context;
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum State { pub enum State {
@ -24,18 +23,20 @@ pub enum State {
Unavailable Unavailable
} }
/// Generator wraps a function and allows suspending its execution more than /// Generator wraps a function and allows suspending its execution more than once, returning
/// once, return a value each time. /// a value each time.
/// ///
/// It implements the Iterator trait. The first time `next()` is called, /// The first time `resume(input0)` is called, the function is called as `f(yielder, input0)`.
/// the function is called as `f(yielder)`; every time `next()` is called afterwards, /// It runs until it suspends its execution through `yielder.generate(output0)`, after which
/// the function is resumed. In either case, it runs until it suspends its execution /// `resume(input0)` returns `output0`. The function can be resumed again using `resume(input1)`,
/// through `yielder.generate(val)`), in which case `next()` returns `Some(val)`, or /// after which `yielder.generate(output0)` returns `input1`, and so on. Once the function returns,
/// returns, in which case `next()` returns `None`. `next()` will return `None` /// the `resume()` call will return `None`, and it will return `None` every time it is called
/// every time after that. /// after that.
/// ///
/// After the generator function returns, it is safe to reclaim the generator /// If the generator function panics, the panic is propagated through the `resume()` call as usual.
/// stack using `unwrap()`. ///
/// After the generator function returns or panics, it is safe to reclaim the generator stack
/// using `unwrap()`.
/// ///
/// `state()` can be used to determine whether the generator function has returned; /// `state()` can be used to determine whether the generator function has returned;
/// the state is `State::Runnable` after creation and suspension, and `State::Unavailable` /// the state is `State::Runnable` after creation and suspension, and `State::Unavailable`
@ -47,28 +48,29 @@ pub enum State {
/// use fringe::{OsStack, Generator}; /// use fringe::{OsStack, Generator};
/// ///
/// let stack = OsStack::new(0).unwrap(); /// let stack = OsStack::new(0).unwrap();
/// let mut gen = Generator::new(stack, move |yielder| { /// let mut add_one = Generator::new(stack, move |yielder, mut input| {
/// for i in 1..4 { /// loop {
/// yielder.generate(i); /// if input == 0 { break }
/// input = yielder.generate(input + 1)
/// } /// }
/// }); /// });
/// println!("{:?}", gen.next()); // prints Some(1) /// println!("{:?}", add_one.resume(2)); // prints Some(3)
/// println!("{:?}", gen.next()); // prints Some(2) /// println!("{:?}", add_one.resume(3)); // prints Some(4)
/// println!("{:?}", gen.next()); // prints Some(3) /// println!("{:?}", add_one.resume(0)); // prints None
/// println!("{:?}", gen.next()); // prints None
/// ``` /// ```
#[derive(Debug)] #[derive(Debug)]
pub struct Generator<Item: Send, Stack: stack::Stack> { pub struct Generator<Input: Send, Output: Send, Stack: stack::Stack> {
state: State, state: State,
context: context::Context<Stack>, context: Context<Stack>,
phantom: PhantomData<*const Item> phantom: (PhantomData<*const Input>, PhantomData<*const Output>)
} }
impl<Item, Stack> Generator<Item, Stack> impl<Input, Output, Stack> Generator<Input, Output, Stack>
where Item: Send, Stack: stack::Stack { where Input: Send, Output: Send, Stack: stack::Stack {
/// Creates a new generator. /// Creates a new generator.
pub fn new<F>(stack: Stack, f: F) -> Generator<Item, Stack> pub fn new<F>(stack: Stack, f: F) -> Generator<Input, Output, Stack>
where Stack: stack::GuardedStack, F: FnOnce(&mut Yielder<Item, Stack>) + Send { where Stack: stack::GuardedStack,
F: FnOnce(&mut Yielder<Input, Output, Stack>, Input) + Send {
unsafe { Generator::unsafe_new(stack, f) } unsafe { Generator::unsafe_new(stack, f) }
} }
@ -77,36 +79,70 @@ impl<Item, Stack> Generator<Item, Stack>
/// This function is unsafe because the generator function can easily violate /// This function is unsafe because the generator function can easily violate
/// memory safety by overflowing the stack. It is useful in environments where /// memory safety by overflowing the stack. It is useful in environments where
/// guarded stacks do not exist, e.g. in absence of an MMU. /// guarded stacks do not exist, e.g. in absence of an MMU.
pub unsafe fn unsafe_new<F>(stack: Stack, f: F) -> Generator<Item, Stack> pub unsafe fn unsafe_new<F>(stack: Stack, f: F) -> Generator<Input, Output, Stack>
where F: FnOnce(&mut Yielder<Item, Stack>) + Send { where F: FnOnce(&mut Yielder<Input, Output, Stack>, Input) + Send {
unsafe extern "C" fn generator_wrapper<Item, Stack, F>(info: usize) -> ! unsafe extern "C" fn generator_wrapper<Input, Output, Stack, F>(env: usize) -> !
where Item: Send, Stack: stack::Stack, F: FnOnce(&mut Yielder<Item, Stack>) { where Input: Send, Output: Send, Stack: stack::Stack,
F: FnOnce(&mut Yielder<Input, Output, Stack>, Input) {
// Retrieve our environment from the callee and return control to it. // 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 (mut yielder, f) = ptr::read(env as *mut (Yielder<Input, Output, Stack>, F));
let new_context = context::Context::swap(yielder.context, yielder.context, 0); let data = Context::swap(yielder.context, yielder.context, 0);
// See Yielder::return_. // See the second half of Yielder::generate_bare.
yielder.context = new_context as *mut context::Context<Stack>; let (new_context, input) = ptr::read(data as *mut (*mut Context<Stack>, Input));
yielder.context = new_context as *mut Context<Stack>;
// Run the body of the generator. // Run the body of the generator.
f(&mut yielder); f(&mut yielder, input);
// Past this point, the generator has dropped everything it has held. // Past this point, the generator has dropped everything it has held.
loop { yielder.return_(None) } loop { yielder.generate_bare(None); }
} }
let mut generator = Generator { let mut generator = Generator {
state: State::Runnable, state: State::Runnable,
context: context::Context::new(stack, generator_wrapper::<Item, Stack, F>), context: Context::new(stack, generator_wrapper::<Input, Output, Stack, F>),
phantom: PhantomData phantom: (PhantomData, PhantomData)
}; };
// Transfer environment to the callee. // Transfer environment to the callee.
let mut data = (Yielder::new(&mut generator.context), f); let mut env = (Yielder::new(&mut generator.context), f);
context::Context::swap(&mut generator.context, &generator.context, Context::swap(&mut generator.context, &generator.context,
&mut data as *mut (Yielder<Item, Stack>, F) as usize); &mut env as *mut (Yielder<Input, Output, Stack>, F) as usize);
mem::forget(data); mem::forget(env);
generator generator
} }
/// Resumes the generator and return the next value it yields.
/// If the generator function has returned, returns `None`.
#[inline]
pub fn resume(&mut self, input: Input) -> Option<Output> {
match self.state {
State::Runnable => {
// Set the state to Unavailable. Since we have exclusive access to the generator,
// the only case where this matters is the generator function panics, after which
// it must not be invocable again.
self.state = State::Unavailable;
// Switch to the generator function, and retrieve the yielded value.
let val = unsafe {
let mut data_in = (&mut self.context as *mut Context<Stack>, input);
let data_out =
ptr::read(Context::swap(&mut self.context, &self.context,
&mut data_in as *mut (*mut Context<Stack>, Input) as usize)
as *mut Option<Output>);
mem::forget(data_in);
data_out
};
// Unless the generator function has returned, it can be switched to again, so
// set the state to Runnable.
if val.is_some() { self.state = State::Runnable }
val
}
State::Unavailable => None
}
}
/// Returns the state of the generator. /// Returns the state of the generator.
#[inline] #[inline]
pub fn state(&self) -> State { self.state } pub fn state(&self) -> State { self.state }
@ -125,72 +161,41 @@ impl<Item, Stack> Generator<Item, Stack>
/// Yielder is an interface provided to every generator through which it /// Yielder is an interface provided to every generator through which it
/// returns a value. /// returns a value.
#[derive(Debug)] #[derive(Debug)]
pub struct Yielder<Item: Send, Stack: stack::Stack> { pub struct Yielder<Input: Send, Output: Send, Stack: stack::Stack> {
context: *mut context::Context<Stack>, context: *mut Context<Stack>,
phantom: PhantomData<Item> phantom: (PhantomData<*const Input>, PhantomData<*const Output>)
} }
impl<Item, Stack> Yielder<Item, Stack> impl<Input, Output, Stack> Yielder<Input, Output, Stack>
where Item: Send, Stack: stack::Stack { where Input: Send, Output: Send, Stack: stack::Stack {
fn new(context: *mut context::Context<Stack>) -> Yielder<Item, Stack> { fn new(context: *mut Context<Stack>) -> Yielder<Input, Output, Stack> {
Yielder { Yielder {
context: context, context: context,
phantom: PhantomData phantom: (PhantomData, PhantomData)
} }
} }
#[inline(always)] #[inline(always)]
fn return_(&mut self, mut val: Option<Item>) { fn generate_bare(&mut self, mut val: Option<Output>) -> Input {
unsafe { unsafe {
let new_context = context::Context::swap(self.context, self.context, let data = Context::swap(self.context, self.context,
&mut val as *mut Option<Item> as usize); &mut val as *mut Option<Output> as usize);
let (new_context, input) = ptr::read(data as *mut (*mut Context<Stack>, Input));
// The generator can be moved (and with it, the context). // The generator can be moved (and with it, the context).
// This changes the address of the context. // This changes the address of the context.
// Thus, we update it after each swap. // Thus, we update it after each swap.
self.context = new_context as *mut context::Context<Stack>; self.context = new_context;
// However, between this point and the next time we enter return_ // However, between this point and the next time we enter generate_bare
// the generator cannot be moved, as a &mut Generator is necessary // the generator cannot be moved, as a &mut Generator is necessary
// to resume the generator function. // to resume the generator function.
input
} }
} }
/// Suspends the generator and returns `Some(item)` from the `next()` /// Suspends the generator and returns `Some(item)` from the `resume()`
/// invocation that resumed the generator. /// invocation that resumed the generator.
#[inline(always)] #[inline(always)]
pub fn generate(&mut self, item: Item) { pub fn generate(&mut self, item: Output) -> Input {
self.return_(Some(item)) self.generate_bare(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`.
#[inline]
fn next(&mut self) -> Option<Self::Item> {
match self.state {
State::Runnable => {
// Set the state to Unavailable. Since we have exclusive access to the generator,
// the only case where this matters is the generator function panics, after which
// it must not be invocable again.
self.state = State::Unavailable;
// Switch to the generator function.
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>)
};
// Unless the generator function has returned, it can be switched to again, so
// set the state to Runnable.
if val.is_some() { self.state = State::Runnable }
val
}
State::Unavailable => None
}
} }
} }

View File

@ -13,7 +13,7 @@
//! //!
//! It provides the following safe abstractions: //! It provides the following safe abstractions:
//! //!
//! * an implementation of internal iterators, also known as generators, //! * an implementation of generators,
//! [Generator](generator/struct.Generator.html). //! [Generator](generator/struct.Generator.html).
//! //!
//! It also provides the necessary low-level building blocks: //! It also provides the necessary low-level building blocks:

View File

@ -20,10 +20,10 @@ extern {
#[ignore] #[ignore]
fn fpe() { fn fpe() {
let stack = OsStack::new(0).unwrap(); let stack = OsStack::new(0).unwrap();
let mut gen = Generator::new(stack, move |yielder| { let mut gen = Generator::new(stack, move |yielder, ()| {
yielder.generate(1.0 / black_box(0.0)); yielder.generate(1.0 / black_box(0.0));
}); });
unsafe { feenableexcept(FE_DIVBYZERO); } unsafe { feenableexcept(FE_DIVBYZERO); }
println!("{:?}", gen.next()); println!("{:?}", gen.resume(()));
} }

View File

@ -6,57 +6,56 @@ extern crate fringe;
use fringe::OsStack; use fringe::OsStack;
use fringe::generator::Generator; use fringe::generator::Generator;
fn new_add_one() -> Generator<i32, i32, OsStack> {
let stack = OsStack::new(0).unwrap();
Generator::new(stack, move |yielder, mut input| {
loop {
if input == 0 { break }
input = yielder.generate(input + 1)
}
})
}
#[test] #[test]
fn generator() { fn generator() {
let stack = OsStack::new(0).unwrap(); let mut add_one = new_add_one();
let mut gen = Generator::new(stack, move |yielder| { assert_eq!(add_one.resume(1), Some(2));
for i in 1..4 { assert_eq!(add_one.resume(2), Some(3));
yielder.generate(i); assert_eq!(add_one.resume(0), None);
}
});
assert_eq!(gen.next(), Some(1));
assert_eq!(gen.next(), Some(2));
assert_eq!(gen.next(), Some(3));
assert_eq!(gen.next(), None);
} }
#[test] #[test]
fn move_after_new() { fn move_after_new() {
let stack = OsStack::new(0).unwrap(); let mut add_one = new_add_one();
let mut gen = Generator::new(stack, move |yielder| { assert_eq!(add_one.resume(1), Some(2));
for i in 1..4 {
yielder.generate(i);
}
});
assert_eq!(gen.next(), Some(1));
#[inline(never)] #[inline(never)]
fn rest(mut gen: Generator<u32, OsStack>) { fn run_moved(mut add_one: Generator<i32, i32, OsStack>) {
assert_eq!(gen.next(), Some(2)); assert_eq!(add_one.resume(2), Some(3));
assert_eq!(gen.next(), Some(3)); assert_eq!(add_one.resume(3), Some(4));
assert_eq!(gen.next(), None); assert_eq!(add_one.resume(0), None);
} }
rest(gen); run_moved(add_one);
} }
#[test] #[test]
#[should_panic] #[should_panic]
fn panic_safety() { fn panic_safety() {
struct Wrapper { struct Wrapper {
gen: Generator<u32, OsStack> gen: Generator<(), (), OsStack>
} }
impl Drop for Wrapper { impl Drop for Wrapper {
fn drop(&mut self) { fn drop(&mut self) {
self.gen.next(); self.gen.resume(());
} }
} }
let stack = OsStack::new(4 << 20).unwrap(); let stack = OsStack::new(4 << 20).unwrap();
let gen = Generator::new(stack, move |_yielder| { let gen = Generator::new(stack, move |_yielder, ()| {
panic!("foo") panic!("foo")
}); });
let mut wrapper = Wrapper { gen: gen }; let mut wrapper = Wrapper { gen: gen };
wrapper.gen.next(); wrapper.gen.resume(());
} }