use core::cell::Cell;
use core::hint::unreachable_unchecked;
use core::marker::PhantomData;
use core::mem::{self, ManuallyDrop};
use core::ptr;
use crate::coroutine::arch::{self, STACK_ALIGNMENT};
#[cfg(windows)]
use crate::coroutine::stack::StackTebFields;
use crate::coroutine::stack::{self, DefaultStack, StackPointer};
use crate::coroutine::trap::CoroutineTrapHandler;
use crate::coroutine::unwind::{self, initial_func_abi, CaughtPanic, ForcedUnwindErr};
use crate::coroutine::util::{self, EncodedValue};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum CoroutineResult<Yield, Return> {
Yield(Yield),
Return(Return),
}
impl<Yield, Return> CoroutineResult<Yield, Return> {
pub fn as_yield(self) -> Option<Yield> {
match self {
CoroutineResult::Yield(val) => Some(val),
CoroutineResult::Return(_) => None,
}
}
pub fn as_return(self) -> Option<Return> {
match self {
CoroutineResult::Yield(_) => None,
CoroutineResult::Return(val) => Some(val),
}
}
}
pub type Coroutine<Input, Yield, Return, Stack = DefaultStack> =
ScopedCoroutine<'static, Input, Yield, Return, Stack>;
pub struct ScopedCoroutine<'a, Input, Yield, Return, Stack: stack::Stack> {
stack: Stack,
stack_ptr: Option<StackPointer>,
initial_stack_ptr: StackPointer,
drop_fn: unsafe fn(ptr: *mut u8),
marker: PhantomData<&'a fn(Input) -> CoroutineResult<Yield, Return>>,
marker2: PhantomData<*mut ()>,
}
unsafe impl<Input, Yield, Return, Stack: stack::Stack + Sync> Sync
for ScopedCoroutine<'_, Input, Yield, Return, Stack>
{
}
#[cfg(feature = "default-stack")]
impl<'a, Input, Yield, Return> ScopedCoroutine<'a, Input, Yield, Return, DefaultStack> {
pub fn new<F>(f: F) -> Self
where
F: FnOnce(&Yielder<Input, Yield>, Input) -> Return,
F: 'a,
{
Self::with_stack(Default::default(), f)
}
}
impl<'a, Input, Yield, Return, Stack: stack::Stack>
ScopedCoroutine<'a, Input, Yield, Return, Stack>
{
pub fn with_stack<F>(stack: Stack, f: F) -> Self
where
F: FnOnce(&Yielder<Input, Yield>, Input) -> Return,
F: 'a,
{
initial_func_abi! {
unsafe fn coroutine_func<Input, Yield, Return, F>(
input: EncodedValue,
parent_link: &mut StackPointer,
func: *mut F,
) -> !
where
F: FnOnce(&Yielder<Input, Yield>, Input) -> Return,
{
let yielder = &*(parent_link as *mut StackPointer as *const Yielder<Input, Yield>);
debug_assert_eq!(func as usize % mem::align_of::<F>(), 0);
let f = func.read();
let input : Result<Input, ForcedUnwindErr> = util::decode_val(input);
let input = match input {
Ok(input) => input,
Err(_) => unreachable_unchecked(),
};
let result = unwind::catch_unwind_at_root(|| f(yielder, input));
let mut result = ManuallyDrop::new(result);
arch::switch_and_reset(util::encode_val(&mut result), yielder.stack_ptr.as_ptr());
}
}
unsafe fn drop_fn<T>(ptr: *mut u8) {
ptr::drop_in_place(ptr as *mut T);
}
unsafe {
let stack_ptr = arch::init_stack(&stack, coroutine_func::<Input, Yield, Return, F>, f);
Self {
stack,
stack_ptr: Some(stack_ptr),
initial_stack_ptr: stack_ptr,
drop_fn: drop_fn::<F>,
marker: PhantomData,
marker2: PhantomData,
}
}
}
pub fn resume(&mut self, val: Input) -> CoroutineResult<Yield, Return> {
unsafe {
let stack_ptr = self
.stack_ptr
.expect("attempt to resume a completed coroutine");
match self.resume_inner(stack_ptr, Ok(val)) {
CoroutineResult::Yield(val) => CoroutineResult::Yield(val),
CoroutineResult::Return(result) => {
CoroutineResult::Return(unwind::maybe_resume_unwind(result))
}
}
}
}
unsafe fn resume_inner(
&mut self,
stack_ptr: StackPointer,
input: Result<Input, ForcedUnwindErr>,
) -> CoroutineResult<Yield, Result<Return, CaughtPanic>> {
self.stack_ptr = None;
let mut input = ManuallyDrop::new(input);
let (result, stack_ptr) =
arch::switch_and_link(util::encode_val(&mut input), stack_ptr, self.stack.base());
self.stack_ptr = stack_ptr;
if stack_ptr.is_some() {
CoroutineResult::Yield(util::decode_val(result))
} else {
CoroutineResult::Return(util::decode_val(result))
}
}
pub fn started(&self) -> bool {
self.stack_ptr != Some(self.initial_stack_ptr)
}
pub fn done(&self) -> bool {
self.stack_ptr.is_none()
}
pub unsafe fn force_reset(&mut self) {
self.stack_ptr = None;
}
pub fn force_unwind(&mut self) {
if let Some(stack_ptr) = self.stack_ptr {
self.force_unwind_slow(stack_ptr);
}
}
#[cold]
fn force_unwind_slow(&mut self, stack_ptr: StackPointer) {
if !self.started() {
unsafe {
arch::drop_initial_obj(self.stack.base(), stack_ptr, self.drop_fn);
}
self.stack_ptr = None;
return;
}
#[cfg(feature = "unwind")]
{
extern crate std;
let forced_unwind = unwind::ForcedUnwind(stack_ptr);
let result = unwind::catch_forced_unwind(|| {
#[cfg(not(feature = "asm-unwind"))]
let result = unsafe { self.resume_inner(stack_ptr, Err(forced_unwind)) };
#[cfg(feature = "asm-unwind")]
let result = unsafe { self.resume_with_exception(stack_ptr, forced_unwind) };
match result {
CoroutineResult::Yield(_) | CoroutineResult::Return(Ok(_)) => Ok(()),
CoroutineResult::Return(Err(e)) => Err(e),
}
});
match result {
Ok(_) => panic!("the ForcedUnwind panic was caught and not rethrown"),
Err(e) => {
if let Some(forced_unwind) = e.downcast_ref::<unwind::ForcedUnwind>() {
if forced_unwind.0 == stack_ptr {
return;
}
}
std::panic::resume_unwind(e);
}
}
}
#[cfg(not(feature = "unwind"))]
panic!("can't unwind a suspended coroutine without the \"unwind\" feature");
}
#[cfg(feature = "asm-unwind")]
unsafe fn resume_with_exception(
&mut self,
stack_ptr: StackPointer,
forced_unwind: unwind::ForcedUnwind,
) -> CoroutineResult<Yield, Result<Return, CaughtPanic>> {
self.stack_ptr = None;
let (result, stack_ptr) = arch::switch_and_throw(forced_unwind, stack_ptr, self.stack.base());
self.stack_ptr = stack_ptr;
if stack_ptr.is_some() {
CoroutineResult::Yield(util::decode_val(result))
} else {
CoroutineResult::Return(util::decode_val(result))
}
}
#[allow(unused_mut)]
pub fn into_stack(mut self) -> Stack {
assert!(
self.done(),
"cannot extract stack from an incomplete coroutine"
);
#[cfg(windows)]
unsafe {
arch::update_stack_teb_fields(&mut self.stack);
}
unsafe {
let stack = ptr::read(&self.stack);
mem::forget(self);
stack
}
}
pub fn trap_handler(&self) -> CoroutineTrapHandler<Return> {
CoroutineTrapHandler {
stack_base: self.stack.base(),
stack_limit: self.stack.limit(),
marker: PhantomData,
}
}
}
impl<'a, Input, Yield, Return, Stack: stack::Stack> Drop
for ScopedCoroutine<'a, Input, Yield, Return, Stack>
{
fn drop(&mut self) {
let guard = scopeguard::guard((), |()| {
panic!("cannot propagte coroutine panic with #![no_std]");
});
self.force_unwind();
mem::forget(guard);
#[cfg(windows)]
unsafe {
arch::update_stack_teb_fields(&mut self.stack);
}
}
}
#[repr(transparent)]
pub struct Yielder<Input, Yield> {
stack_ptr: Cell<StackPointer>,
marker: PhantomData<fn(Yield) -> Input>,
}
impl<Input, Yield> Yielder<Input, Yield> {
pub fn suspend(&self, val: Yield) -> Input {
unsafe {
let mut val = ManuallyDrop::new(val);
let result = arch::switch_yield(util::encode_val(&mut val), self.stack_ptr.as_ptr());
unwind::maybe_force_unwind(util::decode_val(result))
}
}
pub fn on_parent_stack<F, R>(&self, f: F) -> R
where
F: FnOnce() -> R,
F: Send,
{
let stack_ptr =
unsafe { StackPointer::new_unchecked(self.stack_ptr.get().get() - arch::PARENT_LINK_OFFSET) };
let stack = unsafe { ParentStack::new(stack_ptr) };
on_stack(stack, f)
}
}
pub fn on_stack<F, R>(stack: impl stack::Stack, f: F) -> R
where
F: FnOnce() -> R,
{
union FuncOrResult<F, R> {
func: ManuallyDrop<F>,
result: ManuallyDrop<Result<R, CaughtPanic>>,
}
initial_func_abi! {
unsafe fn wrapper<F, R>(ptr: *mut u8)
where
F: FnOnce() -> R,
{
let data = &mut *(ptr as *mut FuncOrResult<F, R>);
let func = ManuallyDrop::take(&mut data.func);
let result = unwind::catch_unwind_at_root(func);
data.result = ManuallyDrop::new(result);
}
}
unsafe {
let mut data = FuncOrResult {
func: ManuallyDrop::new(f),
};
arch::on_stack(&mut data as *mut _ as *mut u8, stack, wrapper::<F, R>);
unwind::maybe_resume_unwind(ManuallyDrop::take(&mut data.result))
}
}
struct ParentStack {
stack_base: StackPointer,
#[cfg(windows)]
stack_ptr: StackPointer,
}
impl ParentStack {
#[inline]
unsafe fn new(stack_ptr: StackPointer) -> Self {
let stack_base = StackPointer::new_unchecked(stack_ptr.get() & !(STACK_ALIGNMENT - 1));
Self {
stack_base,
#[cfg(windows)]
stack_ptr,
}
}
}
unsafe impl stack::Stack for ParentStack {
#[inline]
fn base(&self) -> StackPointer {
self.stack_base
}
#[inline]
fn limit(&self) -> StackPointer {
self.stack_base
}
#[inline]
#[cfg(windows)]
fn teb_fields(&self) -> StackTebFields {
unsafe { arch::read_parent_stack_teb_fields(self.stack_ptr) }
}
#[inline]
#[cfg(windows)]
fn update_teb_fields(&mut self, stack_limit: usize, guaranteed_stack_bytes: usize) {
unsafe {
arch::update_parent_stack_teb_fields(self.stack_ptr, stack_limit, guaranteed_stack_bytes);
}
}
}