Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 5 additions & 7 deletions benches/coroutine.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use core::pin::pin;

use corosensei::stack::DefaultStack;
use corosensei::{on_stack, Coroutine, ScopedCoroutine};
use criterion::measurement::Measurement;
Expand All @@ -25,12 +23,12 @@ fn coroutine_call<M: Measurement + 'static>(name: &str, c: &mut Criterion<M>) {

c.bench_function(name, move |b| {
b.iter(|| {
let identity = pin!(ScopedCoroutine::<usize, (), usize, _>::with_stack(
let coroutine = ScopedCoroutine::<usize, (), usize, _>::with_stack(
&mut stack,
|_yielder, input| input
));
identity.resume(black_box(0usize));
})
|_yielder, input| input,
);
coroutine.scope(|mut identity| identity.resume(black_box(0usize)));
});
});
}

Expand Down
13 changes: 7 additions & 6 deletions src/coroutine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,15 +159,15 @@ impl<Input, Yield, Return> Coroutine<Input, Yield, Return, DefaultStack> {
/// This function returns a `Coroutine` which, when resumed, will execute
/// `func` to completion. When desired the `func` can suspend itself via
/// `Yielder::suspend`.
pub fn new<F>(f: F) -> Self
pub fn new<F>(func: F) -> Self
where
F: FnOnce(&Yielder<Input, Yield>, Input) -> Return,
F: 'static,
Input: 'static,
Yield: 'static,
Return: 'static,
{
Self::with_stack(Default::default(), f)
Self::with_stack(Default::default(), func)
}
}

Expand All @@ -177,7 +177,7 @@ impl<Input, Yield, Return, Stack: stack::Stack> Coroutine<Input, Yield, Return,
/// This function returns a coroutine which, when resumed, will execute
/// `func` to completion. When desired the `func` can suspend itself via
/// [`Yielder::suspend`].
pub fn with_stack<F>(stack: Stack, f: F) -> Self
pub fn with_stack<F>(stack: Stack, func: F) -> Self
where
F: FnOnce(&Yielder<Input, Yield>, Input) -> Return,
F: 'static,
Expand All @@ -186,12 +186,12 @@ impl<Input, Yield, Return, Stack: stack::Stack> Coroutine<Input, Yield, Return,
Return: 'static,
Stack: 'static,
{
unsafe { Self::with_stack_unchecked(stack, f) }
unsafe { Self::with_stack_unchecked(stack, func) }
}

/// Unsafe version of [`Coroutine::with_stack`] without `'static` bounds.
/// This is used by `ScopedCoroutine`.
pub(crate) unsafe fn with_stack_unchecked<F>(stack: Stack, f: F) -> Self
pub(crate) unsafe fn with_stack_unchecked<F>(stack: Stack, func: F) -> Self
where
F: FnOnce(&Yielder<Input, Yield>, Input) -> Return,
{
Expand Down Expand Up @@ -255,7 +255,8 @@ impl<Input, Yield, Return, Stack: stack::Stack> Coroutine<Input, Yield, Return,
// Set up the stack so that the coroutine starts executing
// coroutine_func. Write the given function object to the stack so
// its address is passed to coroutine_func on the first resume.
let stack_ptr = arch::init_stack(&stack, coroutine_func::<Input, Yield, Return, F>, f);
let stack_ptr =
arch::init_stack(&stack, coroutine_func::<Input, Yield, Return, F>, func);
let sanitizer_fiber = stack.sanitizer_fiber();
Self {
stack,
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ pub mod trap;
mod util;

pub use coroutine::{on_stack, Coroutine, CoroutineResult, Yielder};
pub use scoped::ScopedCoroutine;
pub use scoped::{ScopedCoroutine, ScopedCoroutineRef};

#[cfg(test)]
mod tests;
185 changes: 143 additions & 42 deletions src/scoped.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use core::marker::PhantomData;

use crate::stack;
#[cfg(feature = "default-stack")]
use crate::stack::DefaultStack;
use crate::Yielder;
use crate::{stack, trap::CoroutineTrapHandler};
use crate::{Coroutine, CoroutineResult};
use core::pin::Pin;
use crate::trap::CoroutineTrapHandler;
use crate::{Coroutine, CoroutineResult, Yielder};

/// Variant of [`Coroutine`] which allows the use of non-`'static` lifetimes.
///
Expand All @@ -12,14 +13,44 @@ use core::pin::Pin;
/// - Lifetimes in `Input`, `Yield` and `Return`,
/// - Borrowed values in the coroutine body closure.
///
/// However this requires that the coroutine itself be pinned to ensure that it
/// is properly dropped before any of these lifetimes end. The easiest way to
/// do so it to use the [`pin!`] macro when constructing the coroutine
/// To ensure safety, this requires that the coroutine only be accessed within
/// a scope, at the end of which it is dropped. Within the scope, a
/// [`ScopedCoroutineRef`] is provided, which mirrors the API of
/// `Coroutine`.
///
/// # Example
///
/// ```
/// use corosensei::{ScopedCoroutine, Yielder};
///
/// [`pin!`]: core::pin::pin
/// let mut counter = 0;
/// let coroutine = ScopedCoroutine::new(|yielder: &Yielder<i32, ()>, input| {
/// if input == 0 {
/// return;
/// }
/// counter += input;
/// loop {
/// let input = yielder.suspend(());
/// if input == 0 {
/// break;
/// }
/// counter += input;
/// }
/// });
/// coroutine.scope(|mut coroutine| {
/// coroutine.as_mut().resume(1);
/// coroutine.as_mut().resume(2);
/// coroutine.as_mut().resume(3);
/// coroutine.as_mut().resume(4);
/// coroutine.as_mut().resume(5);
/// });
/// assert_eq!(counter, 15);
/// ```
#[cfg(not(feature = "default-stack"))]
pub struct ScopedCoroutine<Input, Yield, Return, Stack: stack::Stack> {
pub struct ScopedCoroutine<'a, Input, Yield, Return, Stack: stack::Stack> {
inner: Coroutine<Input, Yield, Return, Stack>,
// Ensure any borrows in the coroutine body outlive the ScopedCoroutine.
marker: PhantomData<&'a mut ()>,
}

/// Variant of [`Coroutine`] which allows the use of non-`'static` lifetimes.
Expand All @@ -29,48 +60,125 @@ pub struct ScopedCoroutine<Input, Yield, Return, Stack: stack::Stack> {
/// - Lifetimes in `Input`, `Yield` and `Return`,
/// - Borrowed values in the coroutine body closure.
///
/// However this requires that the coroutine itself be pinned to ensure that it
/// is properly dropped before any of these lifetimes end. The easiest way to
/// do so it to use the [`pin!`] macro when constructing the coroutine
/// To ensure safety, this requires that the coroutine only be accessed within
/// a scope, at the end of which it is dropped. Within the scope, a
/// [`ScopedCoroutineRef`] is provided, which mirrors the API of
/// `Coroutine`.
///
/// # Example
///
/// ```
/// use corosensei::ScopedCoroutine;
///
/// [`pin!`]: core::pin::pin
/// let mut counter = 0;
/// let coroutine = ScopedCoroutine::new(|yielder, input| {
/// if input == 0 {
/// return;
/// }
/// counter += input;
/// loop {
/// let input = yielder.suspend(());
/// if input == 0 {
/// break;
/// }
/// counter += input;
/// }
/// });
/// coroutine.scope(|mut coroutine| {
/// coroutine.as_mut().resume(1);
/// coroutine.as_mut().resume(2);
/// coroutine.as_mut().resume(3);
/// coroutine.as_mut().resume(4);
/// coroutine.as_mut().resume(5);
/// });
/// assert_eq!(counter, 15);
/// ```
#[cfg(feature = "default-stack")]
pub struct ScopedCoroutine<Input, Yield, Return, Stack: stack::Stack = DefaultStack> {
pub struct ScopedCoroutine<'a, Input, Yield, Return, Stack: stack::Stack = DefaultStack> {
inner: Coroutine<Input, Yield, Return, Stack>,
// Ensure any borrows in the coroutine body outlive the ScopedCoroutine.
marker: PhantomData<&'a mut ()>,
}

#[cfg(feature = "default-stack")]
impl<Input, Yield, Return> ScopedCoroutine<Input, Yield, Return, DefaultStack> {
/// Creates a new coroutine which will execute `func` on a new stack.
impl<'a, Input, Yield, Return> ScopedCoroutine<'a, Input, Yield, Return, DefaultStack> {
/// Creates a new coroutine which will execute `func` on the given stack.
///
/// This function returns a `ScopedCoroutine` which, when resumed, will
/// execute `func` to completion. When desired the `func` can suspend
/// itself via `Yielder::suspend`.
pub fn new<F>(f: F) -> Self
/// This function creates a coroutine which, when resumed, will execute
/// `body` to completion. When desired the `func` can suspend itself via
/// [`Yielder::suspend`].
pub fn new<F: 'a>(func: F) -> Self
where
F: FnOnce(&Yielder<Input, Yield>, Input) -> Return,
{
Self::with_stack(Default::default(), f)
Self::with_stack(Default::default(), func)
}
}

impl<Input, Yield, Return, Stack: stack::Stack> ScopedCoroutine<Input, Yield, Return, Stack> {
impl<'a, Input, Yield, Return, Stack: stack::Stack>
ScopedCoroutine<'a, Input, Yield, Return, Stack>
{
/// Creates a new coroutine which will execute `func` on the given stack.
///
/// This function returns a coroutine which, when resumed, will execute
/// `func` to completion. When desired the `func` can suspend itself via
/// This function creates a coroutine which, when resumed, will execute
/// `body` to completion. When desired the `func` can suspend itself via
/// [`Yielder::suspend`].
pub fn with_stack<F>(stack: Stack, f: F) -> Self
pub fn with_stack<F: 'a>(stack: Stack, func: F) -> Self
where
F: FnOnce(&Yielder<Input, Yield>, Input) -> Return,
{
unsafe {
Self {
inner: Coroutine::with_stack_unchecked(stack, f),
}
Self {
inner: unsafe { Coroutine::with_stack_unchecked(stack, func) },
marker: PhantomData,
}
}

/// Extracts the stack from a coroutine that has finished executing.
///
/// This allows the stack to be re-used for another coroutine.
pub fn into_stack(self) -> Stack {
self.inner.into_stack()
}

/// Creates a scope in which the coroutine can be executed.
///
/// This ensure that any lifetimes used by the coroutine do not escape the
/// scope and that the coroutine is dropped at the end of the scope.
pub fn scope<F, T>(mut self, f: F) -> T
where
F: FnOnce(ScopedCoroutineRef<'_, Input, Yield, Return, Stack>) -> T,
{
let coroutine = ScopedCoroutineRef {
inner: &mut self.inner,
};
f(coroutine)
}
}

/// Reference to a coroutine within a scope created by
/// [`ScopedCoroutine::scope`].
///
/// Note that, unlike normal mutable references, Rust will not automatically
/// re-borrow this type so you may need to use the
/// [`ScopedCoroutineRef::as_mut`] method when invoking methods that take a
/// mutable reference.
pub struct ScopedCoroutineRef<'a, Input, Yield, Return, Stack: stack::Stack> {
inner: &'a mut Coroutine<Input, Yield, Return, Stack>,
}

impl<Input, Yield, Return, Stack: stack::Stack>
ScopedCoroutineRef<'_, Input, Yield, Return, Stack>
{
/// Reborrows the `ScopedCoroutineRef`.
///
/// This is useful when the reference needs to be used multiple times.
///
/// This is necessary before Rust only supports automatic reborrowing for
/// plain mutable references.
pub fn as_mut(&mut self) -> ScopedCoroutineRef<'_, Input, Yield, Return, Stack> {
ScopedCoroutineRef { inner: self.inner }
}

/// Resumes execution of this coroutine.
///
/// This function will transfer execution to the coroutine and resume from
Expand All @@ -88,8 +196,8 @@ impl<Input, Yield, Return, Stack: stack::Stack> ScopedCoroutine<Input, Yield, Re
///
/// If the coroutine itself panics during execution then the panic will be
/// propagated to this caller.
pub fn resume(self: Pin<&mut Self>, val: Input) -> CoroutineResult<Yield, Return> {
unsafe { self.get_unchecked_mut().inner.resume(val) }
pub fn resume(&mut self, val: Input) -> CoroutineResult<Yield, Return> {
self.inner.resume(val)
}

/// Returns whether this coroutine has been resumed at least once.
Expand All @@ -115,8 +223,8 @@ impl<Input, Yield, Return, Stack: stack::Stack> ScopedCoroutine<Input, Yield, Re
///
/// This can only be done safely if there are no objects currently on the
/// coroutine's stack that need to execute `Drop` code.
pub unsafe fn force_reset(self: Pin<&mut Self>) {
unsafe { self.get_unchecked_mut().inner.force_reset() }
pub unsafe fn force_reset(&mut self) {
self.inner.force_reset()
}

/// Unwinds the coroutine stack, dropping any live objects that are
Expand All @@ -137,15 +245,8 @@ impl<Input, Yield, Return, Stack: stack::Stack> ScopedCoroutine<Input, Yield, Re
/// rethrown.
/// - This crate was compiled without the `unwind` feature and the
/// coroutine is currently suspended in the yielder (`started && !done`).
pub fn force_unwind(self: Pin<&mut Self>) {
unsafe { self.get_unchecked_mut().inner.force_unwind() }
}

/// Extracts the stack from a coroutine that has finished executing.
///
/// This allows the stack to be re-used for another coroutine.
pub fn into_stack(self) -> Stack {
self.inner.into_stack()
pub fn force_unwind(&mut self) {
self.inner.force_unwind()
}

/// Returns a [`CoroutineTrapHandler`] which can be used to handle traps that
Expand Down
Loading