use core::cell::UnsafeCell;
use core::future::Future;
use core::mem::MaybeUninit;
use core::pin::Pin;
use core::task::{Context, Poll, Waker};
pub const MAX_TASKS: usize = crate::config::MAX_TASKS;
const _: () = assert!(MAX_TASKS <= 32);
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
pub struct TaskId(u16);
impl TaskId {
pub const fn new(priority: u8, index: u8) -> Self {
Self(((priority as u16) << 8) | (index as u16))
}
pub fn priority(self) -> u8 {
(self.0 >> 8) as u8
}
pub fn index(self) -> u8 {
(self.0 & 0xFF) as u8
}
pub const fn as_u16(self) -> u16 {
self.0
}
pub const fn from_u16(v: u16) -> Self {
Self(v)
}
}
pub const MAX_PRIORITY: u8 = (crate::config::PRIORITY_LEVELS - 1) as u8;
pub const DEFAULT_TASK_SIZE: usize = 512;
pub const TASK_CELL_ALIGN: usize = 16;
#[repr(C, align(16))]
pub struct TaskCell<const SIZE: usize> {
buf: UnsafeCell<MaybeUninit<[u8; SIZE]>>,
initialized: core::sync::atomic::AtomicBool,
completed: core::sync::atomic::AtomicBool,
}
impl<const SIZE: usize> Default for TaskCell<SIZE> {
fn default() -> Self {
Self::new()
}
}
unsafe impl<const SIZE: usize> Sync for TaskCell<SIZE> {}
impl<const SIZE: usize> TaskCell<SIZE> {
pub const fn new() -> Self {
Self {
buf: UnsafeCell::new(MaybeUninit::uninit()),
initialized: core::sync::atomic::AtomicBool::new(false),
completed: core::sync::atomic::AtomicBool::new(false),
}
}
pub fn is_completed(&self) -> bool {
self.completed.load(core::sync::atomic::Ordering::Acquire)
}
pub unsafe fn poll<F: Future<Output = ()> + 'static>(
&self,
init: fn() -> F,
waker: &Waker,
) -> Poll<()> {
assert!(
core::mem::size_of::<F>() <= SIZE,
"rivet: task future ({} bytes) exceeds reserved stack size ({} bytes); \
increase #[rivet::task(stack = N)]",
core::mem::size_of::<F>(),
SIZE
);
assert!(
core::mem::align_of::<F>() <= TASK_CELL_ALIGN,
"rivet: task future requires stricter alignment than supported"
);
let ptr = self.buf.get() as *mut u8;
if !self.initialized.load(core::sync::atomic::Ordering::Acquire) {
let future = init();
core::ptr::write(ptr as *mut F, future);
self.initialized
.store(true, core::sync::atomic::Ordering::Release);
}
if self.completed.load(core::sync::atomic::Ordering::Acquire) {
return Poll::Ready(());
}
let fut: &mut F = &mut *(ptr as *mut F);
let pinned = Pin::new_unchecked(fut);
let mut cx = Context::from_waker(waker);
let result = pinned.poll(&mut cx);
if result.is_ready() {
unsafe {
core::ptr::drop_in_place(ptr as *mut F);
}
self.completed
.store(true, core::sync::atomic::Ordering::Release);
}
result
}
}
#[repr(C)]
pub struct TaskReg {
pub priority: u8,
pub index_in_priority: u8,
pub _reserved: [u8; 2],
pub poll_fn: unsafe fn(user_data: *mut (), waker: &Waker) -> Poll<()>,
pub completed_fn: unsafe fn(user_data: *mut ()) -> bool,
pub user_data: *mut (),
}
unsafe impl Sync for TaskReg {}
#[macro_export]
macro_rules! register_task {
($name:ident, priority = $prio:expr, poll_fn = $poll:expr, completed = $completed:expr, buf = $buf:expr) => {
#[link_section = ".rivet_tasks"]
#[used]
static $name: $crate::task::TaskReg = $crate::task::TaskReg {
priority: $prio,
index_in_priority: 0,
_reserved: [0; 2],
poll_fn: $poll as unsafe fn(*mut (), &::core::task::Waker) -> ::core::task::Poll<()>,
completed_fn: $completed as unsafe fn(*mut ()) -> bool,
user_data: unsafe { &raw const $buf as *mut () },
};
};
}
pub(crate) struct TaskRegistry {
pub tasks: [[Option<*const TaskReg>; MAX_TASKS]; (MAX_PRIORITY as usize) + 1],
pub count_per_priority: [u8; (MAX_PRIORITY as usize) + 1],
pub total: u8,
}
impl TaskRegistry {
pub const fn new() -> Self {
Self {
tasks: [[None; MAX_TASKS]; (MAX_PRIORITY as usize) + 1],
count_per_priority: [0; (MAX_PRIORITY as usize) + 1],
total: 0,
}
}
}
extern "C" {
static __rivet_tasks_start: u8;
static __rivet_tasks_end: u8;
}
pub(crate) fn iter_task_regs() -> impl Iterator<Item = &'static TaskReg> {
let start = core::ptr::addr_of!(__rivet_tasks_start) as *const TaskReg;
let end = core::ptr::addr_of!(__rivet_tasks_end) as *const TaskReg;
let count = unsafe {
end.offset_from(start)
};
let count = if count < 0 { 0 } else { count as usize };
(0..count).map(move |i| unsafe {
&*start.add(i)
})
}
#[cfg(test)]
mod tests {
use super::*;
use core::sync::atomic::{AtomicU32, Ordering};
static POLL_COUNT: AtomicU32 = AtomicU32::new(0);
async fn counting_task() {
loop {
POLL_COUNT.fetch_add(1, Ordering::Relaxed);
TestYield { yielded: false }.await;
}
}
struct TestYield {
yielded: bool,
}
impl Future for TestYield {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
if self.yielded {
Poll::Ready(())
} else {
self.yielded = true;
cx.waker().wake_by_ref();
Poll::Pending
}
}
}
#[test]
fn task_cell_polls_real_async_fn() {
crate::kernel_test! {
POLL_COUNT.store(0, Ordering::Relaxed);
static CELL: TaskCell<256> = TaskCell::new();
let waker = crate::waker::task_waker(crate::task::TaskId::new(0, 0));
unsafe {
let _ = CELL.poll(counting_task, &waker);
let _ = CELL.poll(counting_task, &waker);
}
assert!(POLL_COUNT.load(Ordering::Relaxed) >= 1);
}
}
#[test]
#[should_panic(expected = "exceeds reserved stack size")]
fn task_cell_panics_when_future_too_large() {
crate::kernel_test! {
async fn big_task() {
let mut buf = [0u8; 1024];
buf[0] = 1;
TestYield { yielded: false }.await;
core::hint::black_box(&buf);
}
static CELL: TaskCell<8> = TaskCell::new();
let waker = crate::waker::task_waker(crate::task::TaskId::new(0, 0));
unsafe {
let _ = CELL.poll(big_task, &waker);
}
}
}
}