From 695a6da322aa2d75c8f702b2ed8b67f9ad12c3a0 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Fri, 28 Mar 2025 18:59:02 +0100 Subject: Statically allocate task pools on stable Rust. Thanks @0e4ef622 for the awesome idea of how to do it and the first implementation. Co-Authored-By: Matthew Tran <0e4ef622@gmail.com> --- embassy-executor/src/lib.rs | 178 +++++++++++++++++++++++++------------------- 1 file changed, 103 insertions(+), 75 deletions(-) (limited to 'embassy-executor/src/lib.rs') diff --git a/embassy-executor/src/lib.rs b/embassy-executor/src/lib.rs index d816539ac..5485f6a6a 100644 --- a/embassy-executor/src/lib.rs +++ b/embassy-executor/src/lib.rs @@ -50,101 +50,129 @@ pub mod raw; mod spawner; pub use spawner::*; -mod config { - #![allow(unused)] - include!(concat!(env!("OUT_DIR"), "/config.rs")); -} - /// Implementation details for embassy macros. /// Do not use. Used for macros and HALs only. Not covered by semver guarantees. #[doc(hidden)] #[cfg(not(feature = "nightly"))] pub mod _export { - use core::alloc::Layout; - use core::cell::{Cell, UnsafeCell}; + use core::cell::UnsafeCell; use core::future::Future; use core::mem::MaybeUninit; - use core::ptr::null_mut; - - use critical_section::{CriticalSection, Mutex}; - use crate::raw::TaskPool; - - struct Arena { - buf: UnsafeCell>, - ptr: Mutex>, + pub trait TaskFn: Copy { + type Fut: Future + 'static; } - unsafe impl Sync for Arena {} - unsafe impl Send for Arena {} - - impl Arena { - const fn new() -> Self { - Self { - buf: UnsafeCell::new(MaybeUninit::uninit()), - ptr: Mutex::new(Cell::new(null_mut())), - } - } - - fn alloc(&'static self, cs: CriticalSection) -> &'static mut MaybeUninit { - let layout = Layout::new::(); - - let start = self.buf.get().cast::(); - let end = unsafe { start.add(N) }; - - let mut ptr = self.ptr.borrow(cs).get(); - if ptr.is_null() { - ptr = self.buf.get().cast::(); - } - - let bytes_left = (end as usize) - (ptr as usize); - let align_offset = (ptr as usize).next_multiple_of(layout.align()) - (ptr as usize); - - if align_offset + layout.size() > bytes_left { - panic!("embassy-executor: task arena is full. You must increase the arena size, see the documentation for details: https://docs.embassy.dev/embassy-executor/"); + macro_rules! task_fn_impl { + ($($Tn:ident),*) => { + impl TaskFn<($($Tn,)*)> for F + where + F: Copy + FnOnce($($Tn,)*) -> Fut, + Fut: Future + 'static, + { + type Fut = Fut; } + }; + } - let res = unsafe { ptr.add(align_offset) }; - let ptr = unsafe { ptr.add(align_offset + layout.size()) }; + task_fn_impl!(); + task_fn_impl!(T0); + task_fn_impl!(T0, T1); + task_fn_impl!(T0, T1, T2); + task_fn_impl!(T0, T1, T2, T3); + task_fn_impl!(T0, T1, T2, T3, T4); + task_fn_impl!(T0, T1, T2, T3, T4, T5); + task_fn_impl!(T0, T1, T2, T3, T4, T5, T6); + task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7); + task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8); + task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9); + task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10); + task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11); + task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12); + task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13); + task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14); + task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15); + + #[allow(private_bounds)] + #[repr(C)] + pub struct TaskPoolHolder + where + Align: Alignment, + { + data: UnsafeCell<[MaybeUninit; SIZE]>, + align: Align, + } - self.ptr.borrow(cs).set(ptr); + unsafe impl Send for TaskPoolHolder where Align: Alignment {} + unsafe impl Sync for TaskPoolHolder where Align: Alignment {} - unsafe { &mut *(res as *mut MaybeUninit) } + #[allow(private_bounds)] + impl TaskPoolHolder + where + Align: Alignment, + { + pub const fn get(&self) -> *const u8 { + self.data.get().cast() } } - static ARENA: Arena<{ crate::config::TASK_ARENA_SIZE }> = Arena::new(); + #[allow(private_bounds)] + #[repr(transparent)] + pub struct Align([::Archetype; 0]) + where + Self: Alignment; - pub struct TaskPoolRef { - // type-erased `&'static mut TaskPool` - // Needed because statics can't have generics. - ptr: Mutex>, + trait Alignment { + /// A zero-sized type of particular alignment. + type Archetype: Copy + Eq + PartialEq + Send + Sync + Unpin; } - unsafe impl Sync for TaskPoolRef {} - unsafe impl Send for TaskPoolRef {} - - impl TaskPoolRef { - pub const fn new() -> Self { - Self { - ptr: Mutex::new(Cell::new(null_mut())), - } - } - /// Get the pool for this ref, allocating it from the arena the first time. - /// - /// safety: for a given TaskPoolRef instance, must always call with the exact - /// same generic params. - pub unsafe fn get(&'static self) -> &'static TaskPool { - critical_section::with(|cs| { - let ptr = self.ptr.borrow(cs); - if ptr.get().is_null() { - let pool = ARENA.alloc::>(cs); - pool.write(TaskPool::new()); - ptr.set(pool as *mut _ as _); + macro_rules! aligns { + ($($AlignX:ident: $n:literal,)*) => { + $( + #[derive(Copy, Clone, Eq, PartialEq)] + #[repr(align($n))] + struct $AlignX {} + impl Alignment for Align<$n> { + type Archetype = $AlignX; } - - unsafe { &*(ptr.get() as *const _) } - }) - } + )* + }; } + + aligns!( + Align1: 1, + Align2: 2, + Align4: 4, + Align8: 8, + Align16: 16, + Align32: 32, + Align64: 64, + Align128: 128, + Align256: 256, + Align512: 512, + Align1024: 1024, + Align2048: 2048, + Align4096: 4096, + Align8192: 8192, + Align16384: 16384, + ); + #[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))] + aligns!( + Align32768: 32768, + Align65536: 65536, + Align131072: 131072, + Align262144: 262144, + Align524288: 524288, + Align1048576: 1048576, + Align2097152: 2097152, + Align4194304: 4194304, + Align8388608: 8388608, + Align16777216: 16777216, + Align33554432: 33554432, + Align67108864: 67108864, + Align134217728: 134217728, + Align268435456: 268435456, + Align536870912: 536870912, + ); } -- cgit