diff --git a/embassy-executor/Cargo.toml b/embassy-executor/Cargo.toml index e2fedce3c8..fb566d4420 100644 --- a/embassy-executor/Cargo.toml +++ b/embassy-executor/Cargo.toml @@ -62,6 +62,8 @@ critical-section = { version = "1.1", features = ["std"] } ## Enable nightly-only features nightly = ["embassy-executor-macros/nightly"] +alloc = [] + # Enables turbo wakers, which requires patching core. Not surfaced in the docs by default due to # being an complicated advanced and undocumented feature. # See: https://github.com/embassy-rs/embassy/pull/1263 diff --git a/embassy-executor/src/raw/mod.rs b/embassy-executor/src/raw/mod.rs index d9ea5c0057..6744f74a73 100644 --- a/embassy-executor/src/raw/mod.rs +++ b/embassy-executor/src/raw/mod.rs @@ -45,7 +45,7 @@ pub(crate) struct TaskHeader { pub(crate) state: State, pub(crate) run_queue_item: RunQueueItem, pub(crate) executor: SyncUnsafeCell>, - poll_fn: SyncUnsafeCell>, + pub(crate) poll_fn: SyncUnsafeCell>, #[cfg(feature = "integrated-timers")] pub(crate) expires_at: SyncUnsafeCell, @@ -63,7 +63,7 @@ unsafe impl Send for TaskRef where &'static TaskHeader: Send {} unsafe impl Sync for TaskRef where &'static TaskHeader: Sync {} impl TaskRef { - fn new(task: &'static TaskStorage) -> Self { + pub(crate) fn new(task: &'static TaskStorage) -> Self { Self { ptr: NonNull::from(task).cast(), } @@ -103,8 +103,8 @@ impl TaskRef { // This makes it safe to cast between TaskHeader and TaskStorage pointers. #[repr(C)] pub struct TaskStorage { - raw: TaskHeader, - future: UninitCell, // Valid if STATE_SPAWNED + pub(crate) raw: TaskHeader, + pub(crate) future: UninitCell, // Valid if STATE_SPAWNED } impl TaskStorage { @@ -150,7 +150,7 @@ impl TaskStorage { } } - unsafe fn poll(p: TaskRef) { + pub(crate) unsafe fn poll(p: TaskRef) { let this = &*(p.as_ptr() as *const TaskStorage); let future = Pin::new_unchecked(this.future.as_mut()); diff --git a/embassy-executor/src/spawner.rs b/embassy-executor/src/spawner.rs index 2716062445..71f4a7784e 100644 --- a/embassy-executor/src/spawner.rs +++ b/embassy-executor/src/spawner.rs @@ -5,6 +5,9 @@ use core::task::Poll; use super::raw; +#[cfg(feature = "alloc")] +extern crate alloc; + /// Token to spawn a newly-created task in an executor. /// /// When calling a task function (like `#[embassy_executor::task] async fn my_task() { ... }`), the returned @@ -59,6 +62,10 @@ pub enum SpawnError { /// running at a time. You may allow multiple instances to run in parallel with /// `#[embassy_executor::task(pool_size = 4)]`, at the cost of higher RAM usage. Busy, + + #[cfg(feature = "alloc")] + /// Out of memory. + OOM, } /// Handle to spawn tasks into an executor. @@ -115,6 +122,36 @@ impl Spawner { } } + #[cfg(feature = "alloc")] + /// Spawn a dynamically allocated task into the executor. + /// LIMITATIONS AND WARNINGS: + /// 1. `future` must be a closure of the form `move || my_async_fn(args)`, where `my_async_fn` + /// is an `async fn`, NOT a hand-written `Future`. + /// 2. `my_async_fn` must never return (runs forever). + pub fn spawn_fut F>(&self, future: FutFn) -> Result<(), SpawnError> { + use alloc::alloc::{alloc, Layout}; + use crate::raw::{TaskRef, TaskStorage}; + + let task_storage = unsafe { + let layout = Layout::new::>(); + let task_storage = alloc(layout) as *mut TaskStorage; + if task_storage.is_null() { + return Err(SpawnError::OOM); + } + task_storage.write(TaskStorage::::new()); + let task_storage = &mut *task_storage; + task_storage.raw.state.spawn(); + task_storage.raw.poll_fn.set(Some(TaskStorage::::poll)); + task_storage.future.write_in_place(future); + task_storage + }; + + let task_ref = TaskRef::new(task_storage); + let token = unsafe { SpawnToken::::new(task_ref) }; + self.spawn(token).unwrap(); + Ok(()) + } + // Used by the `embassy_executor_macros::main!` macro to throw an error when spawn // fails. This is here to allow conditional use of `defmt::unwrap!` // without introducing a `defmt` feature in the `embassy_executor_macros` package, @@ -186,6 +223,36 @@ impl SendSpawner { } } + #[cfg(feature = "alloc")] + /// Spawn a dynamically allocated task into the executor. + /// LIMITATIONS AND WARNINGS: + /// 1. `future` must be a closure of the form `move || my_async_fn(args)`, where `my_async_fn` + /// is an `async fn`, NOT a hand-written `Future`. + /// 2. `my_async_fn` must never return (runs forever). + pub fn spawn_fut F) + Send>(&self, future: FutFn) -> Result<(), SpawnError> { + use alloc::alloc::{alloc, Layout}; + use crate::raw::{TaskRef, TaskStorage}; + + let task_storage = unsafe { + let layout = Layout::new::>(); + let task_storage = alloc(layout) as *mut TaskStorage; + if task_storage.is_null() { + return Err(SpawnError::OOM); + } + task_storage.write(TaskStorage::::new()); + let task_storage = &mut *task_storage; + task_storage.raw.state.spawn(); + task_storage.raw.poll_fn.set(Some(TaskStorage::::poll)); + task_storage.future.write_in_place(future); + task_storage + }; + + let task_ref = TaskRef::new(task_storage); + let token = unsafe { SpawnToken::::new(task_ref) }; + self.spawn(token).unwrap(); + Ok(()) + } + /// Spawn a task into an executor, panicking on failure. /// /// # Panics