use alloc::{
alloc::{Layout, dealloc},
sync::Arc,
};
use core::{
cell::{Cell, UnsafeCell},
future::Future,
marker::PhantomPinned,
pin::Pin,
ptr,
sync::atomic::{AtomicPtr, AtomicUsize, Ordering},
task::{Context, Poll},
};
use super::SharedExecutor;
use crate::{
runtime::{delivery::inbox::InboxNode, task_runtime},
thread::{ThreadId, WakeIntent},
};
pub(super) const RUN_QUEUED: usize = 1 << 0;
pub(super) const POLLING: usize = 1 << 1;
pub(super) const COMPLETE: usize = 1 << 2;
const FUTURE_EMPTY: usize = 1 << 3;
const REFCOUNT_OVERFLOW_INVARIANT: u32 = 0x4558_0001;
const EARLY_RECLAIM_INVARIANT: u32 = 0x4558_0002;
type PollFuture = unsafe fn(*mut CoroutineHeader, &mut Context<'_>) -> Poll<()>;
type DropFuture = unsafe fn(*mut CoroutineHeader);
type Deallocate = unsafe fn(*mut CoroutineHeader);
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct CoroutineId {
owner_thread: ThreadId,
generation: u64,
}
impl CoroutineId {
pub(super) const fn new(owner_thread: ThreadId, generation: u64) -> Self {
Self {
owner_thread,
generation,
}
}
pub const fn owner_thread(self) -> ThreadId {
self.owner_thread
}
pub const fn generation(self) -> u64 {
self.generation
}
}
#[repr(C)]
pub struct CoroutineHeader {
reclaim: InboxNode,
id: CoroutineId,
pub(super) state: AtomicUsize,
references: AtomicUsize,
executor: Arc<SharedExecutor>,
ready_next: AtomicPtr<Self>,
owner_next: Cell<*mut Self>,
poll_future: PollFuture,
drop_future: DropFuture,
deallocate: Deallocate,
_pin: PhantomPinned,
}
impl CoroutineHeader {
pub const fn id(&self) -> CoroutineId {
self.id
}
pub const fn owner_thread(&self) -> ThreadId {
self.id.owner_thread()
}
pub(super) unsafe fn poll_raw(header: *mut Self, context: &mut Context<'_>) -> Poll<()> {
let poll_future = unsafe {
core::ptr::addr_of!((*header).poll_future).read()
};
unsafe {
poll_future(header, context)
}
}
pub(super) unsafe fn drop_future_raw(header: *mut Self) {
let drop_future = unsafe {
core::ptr::addr_of!((*header).drop_future).read()
};
unsafe {
drop_future(header);
}
}
pub(crate) unsafe fn deallocate_raw(header: *mut Self) {
let state = unsafe { (*header).state.load(Ordering::Acquire) };
if state & (COMPLETE | FUTURE_EMPTY) != (COMPLETE | FUTURE_EMPTY) {
task_runtime::fatal_invariant(EARLY_RECLAIM_INVARIANT, unsafe {
(*header).id.generation() as usize
});
}
let deallocate = unsafe {
(*header).deallocate
};
unsafe {
deallocate(header);
}
}
pub(crate) fn reclaim_node(self: Pin<&'static Self>) -> Pin<&'static InboxNode> {
unsafe {
self.map_unchecked(|header| &header.reclaim)
}
}
pub(crate) fn address(self: Pin<&'static Self>) -> usize {
(self.get_ref() as *const Self).addr()
}
pub(super) fn next(&self, kind: super::inbox::InboxKind) -> &AtomicPtr<Self> {
match kind {
super::inbox::InboxKind::Ready => &self.ready_next,
}
}
pub(super) fn owner_next(&self) -> *mut Self {
self.owner_next.get()
}
pub(super) fn set_owner_next(&self, next: *mut Self) {
self.owner_next.set(next);
}
}
unsafe impl Send for CoroutineHeader {}
unsafe impl Sync for CoroutineHeader {}
#[repr(C)]
pub(super) struct Coroutine<F> {
header: CoroutineHeader,
future: UnsafeCell<Option<F>>,
}
impl<F> Coroutine<F>
where
F: Future<Output = ()>,
{
pub(super) fn new(id: CoroutineId, executor: Arc<SharedExecutor>, future: F) -> Self {
Self {
header: CoroutineHeader {
reclaim: InboxNode::new(crate::runtime::delivery::inbox::InboxKind::Reclaim),
id,
state: AtomicUsize::new(0),
references: AtomicUsize::new(1),
executor,
ready_next: AtomicPtr::new(ptr::null_mut()),
owner_next: Cell::new(ptr::null_mut()),
poll_future: poll_future::<F>,
drop_future: drop_future::<F>,
deallocate: deallocate::<F>,
_pin: PhantomPinned,
},
future: UnsafeCell::new(Some(future)),
}
}
}
pub(super) unsafe fn schedule(header: *mut CoroutineHeader) {
unsafe {
schedule_with_intent(header, WakeIntent::Normal);
}
}
pub(super) unsafe fn schedule_sync(header: *mut CoroutineHeader) {
unsafe {
schedule_with_intent(header, WakeIntent::Sync);
}
}
unsafe fn schedule_with_intent(header: *mut CoroutineHeader, intent: WakeIntent) {
let header_ref = unsafe {
&*header
};
let mut observed = header_ref.state.load(Ordering::Acquire);
loop {
if observed & (COMPLETE | RUN_QUEUED) != 0 {
return;
}
match header_ref.state.compare_exchange_weak(
observed,
observed | RUN_QUEUED,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => break,
Err(updated) => observed = updated,
}
}
retain_reference(header_ref);
if !header_ref.executor.publish_ready(header, intent) {
header_ref.state.fetch_and(!RUN_QUEUED, Ordering::AcqRel);
unsafe {
release_reference(header);
}
}
}
pub(super) fn retain_reference(header: &CoroutineHeader) {
let mut references = header.references.load(Ordering::Relaxed);
loop {
let Some(next) = references.checked_add(1) else {
task_runtime::fatal_invariant(
REFCOUNT_OVERFLOW_INVARIANT,
header.id.generation() as usize,
);
};
if references == 0 {
task_runtime::fatal_invariant(
REFCOUNT_OVERFLOW_INVARIANT,
header.id.generation() as usize,
);
}
match header.references.compare_exchange_weak(
references,
next,
Ordering::Relaxed,
Ordering::Relaxed,
) {
Ok(_) => return,
Err(updated) => references = updated,
}
}
}
pub(super) unsafe fn release_reference(header: *mut CoroutineHeader) {
let header_ref = unsafe {
&*header
};
let previous = header_ref.references.fetch_sub(1, Ordering::Release);
if previous == 0 {
task_runtime::fatal_invariant(
REFCOUNT_OVERFLOW_INVARIANT,
header_ref.id.generation() as usize,
);
}
if previous != 1 {
return;
}
core::sync::atomic::fence(Ordering::Acquire);
if !task_runtime::in_hard_irq() {
unsafe {
CoroutineHeader::deallocate_raw(header);
}
return;
}
let header = unsafe {
Pin::new_unchecked(header_ref)
};
crate::runtime::service::reclaim::publish_deferred_coroutine_reclaim(header);
}
unsafe fn poll_future<F>(header: *mut CoroutineHeader, context: &mut Context<'_>) -> Poll<()>
where
F: Future<Output = ()>,
{
let coroutine = header.cast::<Coroutine<F>>();
let future = unsafe {
&mut *(*coroutine).future.get()
};
match future.as_mut() {
Some(future) => unsafe {
Pin::new_unchecked(future).poll(context)
},
None => Poll::Ready(()),
}
}
unsafe fn drop_future<F>(header: *mut CoroutineHeader)
where
F: Future<Output = ()>,
{
let coroutine = header.cast::<Coroutine<F>>();
let future = unsafe {
&mut *(*coroutine).future.get()
};
let future = future.take();
unsafe {
(*header).state.fetch_or(FUTURE_EMPTY, Ordering::Release);
}
drop(future);
}
unsafe fn deallocate<F>(header: *mut CoroutineHeader)
where
F: Future<Output = ()>,
{
unsafe {
core::ptr::drop_in_place(core::ptr::addr_of_mut!((*header).executor));
dealloc(header.cast::<u8>(), Layout::new::<Coroutine<F>>());
}
}