aboutsummaryrefslogtreecommitdiff
path: root/embassy-executor
diff options
context:
space:
mode:
Diffstat (limited to 'embassy-executor')
-rw-r--r--embassy-executor/Cargo.toml95
-rw-r--r--embassy-executor/README.md27
-rw-r--r--embassy-executor/build.rs92
-rw-r--r--embassy-executor/src/lib.rs178
4 files changed, 104 insertions, 288 deletions
diff --git a/embassy-executor/Cargo.toml b/embassy-executor/Cargo.toml
index d6f24ce84..79d899c61 100644
--- a/embassy-executor/Cargo.toml
+++ b/embassy-executor/Cargo.toml
@@ -108,98 +108,3 @@ timer-item-payload-size-2 = ["_timer-item-payload"]
108timer-item-payload-size-4 = ["_timer-item-payload"] 108timer-item-payload-size-4 = ["_timer-item-payload"]
109## 8 bytes 109## 8 bytes
110timer-item-payload-size-8 = ["_timer-item-payload"] 110timer-item-payload-size-8 = ["_timer-item-payload"]
111
112#! ### Task Arena Size
113#! Sets the [task arena](#task-arena) size. Necessary if you’re not using `nightly`.
114#!
115#! <details>
116#! <summary>Preconfigured Task Arena Sizes:</summary>
117#! <!-- rustdoc requires the following blank line for the feature list to render correctly! -->
118#!
119
120# BEGIN AUTOGENERATED CONFIG FEATURES
121# Generated by gen_config.py. DO NOT EDIT.
122## 64
123task-arena-size-64 = []
124## 128
125task-arena-size-128 = []
126## 192
127task-arena-size-192 = []
128## 256
129task-arena-size-256 = []
130## 320
131task-arena-size-320 = []
132## 384
133task-arena-size-384 = []
134## 512
135task-arena-size-512 = []
136## 640
137task-arena-size-640 = []
138## 768
139task-arena-size-768 = []
140## 1024
141task-arena-size-1024 = []
142## 1280
143task-arena-size-1280 = []
144## 1536
145task-arena-size-1536 = []
146## 2048
147task-arena-size-2048 = []
148## 2560
149task-arena-size-2560 = []
150## 3072
151task-arena-size-3072 = []
152## 4096 (default)
153task-arena-size-4096 = [] # Default
154## 5120
155task-arena-size-5120 = []
156## 6144
157task-arena-size-6144 = []
158## 8192
159task-arena-size-8192 = []
160## 10240
161task-arena-size-10240 = []
162## 12288
163task-arena-size-12288 = []
164## 16384
165task-arena-size-16384 = []
166## 20480
167task-arena-size-20480 = []
168## 24576
169task-arena-size-24576 = []
170## 32768
171task-arena-size-32768 = []
172## 40960
173task-arena-size-40960 = []
174## 49152
175task-arena-size-49152 = []
176## 65536
177task-arena-size-65536 = []
178## 81920
179task-arena-size-81920 = []
180## 98304
181task-arena-size-98304 = []
182## 131072
183task-arena-size-131072 = []
184## 163840
185task-arena-size-163840 = []
186## 196608
187task-arena-size-196608 = []
188## 262144
189task-arena-size-262144 = []
190## 327680
191task-arena-size-327680 = []
192## 393216
193task-arena-size-393216 = []
194## 524288
195task-arena-size-524288 = []
196## 655360
197task-arena-size-655360 = []
198## 786432
199task-arena-size-786432 = []
200## 1048576
201task-arena-size-1048576 = []
202
203# END AUTOGENERATED CONFIG FEATURES
204
205#! </details>
diff --git a/embassy-executor/README.md b/embassy-executor/README.md
index 074c73555..85f15edbb 100644
--- a/embassy-executor/README.md
+++ b/embassy-executor/README.md
@@ -3,35 +3,10 @@
3An async/await executor designed for embedded usage. 3An async/await executor designed for embedded usage.
4 4
5- No `alloc`, no heap needed. 5- No `alloc`, no heap needed.
6- With nightly Rust, task futures can be fully statically allocated. 6- Tasks are statically allocated. Each task gets its own `static`, with the exact size to hold the task (or multiple instances of it, if using `pool_size`) calculated automatically at compile time. If tasks don't fit in RAM, this is detected at compile time by the linker. Runtime panics due to running out of memory are not possible.
7- No "fixed capacity" data structures, executor works with 1 or 1000 tasks without needing config/tuning. 7- No "fixed capacity" data structures, executor works with 1 or 1000 tasks without needing config/tuning.
8- Integrated timer queue: sleeping is easy, just do `Timer::after_secs(1).await;`. 8- Integrated timer queue: sleeping is easy, just do `Timer::after_secs(1).await;`.
9- No busy-loop polling: CPU sleeps when there's no work to do, using interrupts or `WFE/SEV`. 9- No busy-loop polling: CPU sleeps when there's no work to do, using interrupts or `WFE/SEV`.
10- Efficient polling: a wake will only poll the woken task, not all of them. 10- Efficient polling: a wake will only poll the woken task, not all of them.
11- Fair: a task can't monopolize CPU time even if it's constantly being woken. All other tasks get a chance to run before a given task gets polled for the second time. 11- Fair: a task can't monopolize CPU time even if it's constantly being woken. All other tasks get a chance to run before a given task gets polled for the second time.
12- Creating multiple executor instances is supported, to run tasks with multiple priority levels. This allows higher-priority tasks to preempt lower-priority tasks. 12- Creating multiple executor instances is supported, to run tasks with multiple priority levels. This allows higher-priority tasks to preempt lower-priority tasks.
13
14## Task arena
15
16When the `nightly` Cargo feature is not enabled, `embassy-executor` allocates tasks out of an arena (a very simple bump allocator).
17
18If the task arena gets full, the program will panic at runtime. To guarantee this doesn't happen, you must set the size to the sum of sizes of all tasks.
19
20Tasks are allocated from the arena when spawned for the first time. If the task exits, the allocation is not released to the arena, but can be reused to spawn the task again. For multiple-instance tasks (like `#[embassy_executor::task(pool_size = 4)]`), the first spawn will allocate memory for all instances. This is done for performance and to increase predictability (for example, spawning at least 1 instance of every task at boot guarantees an immediate panic if the arena is too small, while allocating instances on-demand could delay the panic to only when the program is under load).
21
22The arena size can be configured in two ways:
23
24- Via Cargo features: enable a Cargo feature like `task-arena-size-8192`. Only a selection of values
25 is available, see [Task Area Sizes](#task-arena-size) for reference.
26- Via environment variables at build time: set the variable named `EMBASSY_EXECUTOR_TASK_ARENA_SIZE`. For example
27 `EMBASSY_EXECUTOR_TASK_ARENA_SIZE=4321 cargo build`. You can also set them in the `[env]` section of `.cargo/config.toml`.
28 Any value can be set, unlike with Cargo features.
29
30Environment variables take precedence over Cargo features. If two Cargo features are enabled for the same setting
31with different values, compilation fails.
32
33## Statically allocating tasks
34
35When using nightly Rust, enable the `nightly` Cargo feature. This will make `embassy-executor` use the `type_alias_impl_trait` feature to allocate all tasks in `static`s. Each task gets its own `static`, with the exact size to hold the task (or multiple instances of it, if using `pool_size`) calculated automatically at compile time. If tasks don't fit in RAM, this is detected at compile time by the linker. Runtime panics due to running out of memory are not possible.
36
37The configured arena size is ignored, no arena is used at all.
diff --git a/embassy-executor/build.rs b/embassy-executor/build.rs
index 8a41d7503..37becde3e 100644
--- a/embassy-executor/build.rs
+++ b/embassy-executor/build.rs
@@ -1,99 +1,7 @@
1use std::collections::HashMap;
2use std::fmt::Write;
3use std::path::PathBuf;
4use std::{env, fs};
5
6#[path = "./build_common.rs"] 1#[path = "./build_common.rs"]
7mod common; 2mod common;
8 3
9static CONFIGS: &[(&str, usize)] = &[
10 // BEGIN AUTOGENERATED CONFIG FEATURES
11 // Generated by gen_config.py. DO NOT EDIT.
12 ("TASK_ARENA_SIZE", 4096),
13 // END AUTOGENERATED CONFIG FEATURES
14];
15
16struct ConfigState {
17 value: usize,
18 seen_feature: bool,
19 seen_env: bool,
20}
21
22fn main() { 4fn main() {
23 let crate_name = env::var("CARGO_PKG_NAME")
24 .unwrap()
25 .to_ascii_uppercase()
26 .replace('-', "_");
27
28 // only rebuild if build.rs changed. Otherwise Cargo will rebuild if any
29 // other file changed.
30 println!("cargo:rerun-if-changed=build.rs");
31
32 // Rebuild if config envvar changed.
33 for (name, _) in CONFIGS {
34 println!("cargo:rerun-if-env-changed={crate_name}_{name}");
35 }
36
37 let mut configs = HashMap::new();
38 for (name, default) in CONFIGS {
39 configs.insert(
40 *name,
41 ConfigState {
42 value: *default,
43 seen_env: false,
44 seen_feature: false,
45 },
46 );
47 }
48
49 let prefix = format!("{crate_name}_");
50 for (var, value) in env::vars() {
51 if let Some(name) = var.strip_prefix(&prefix) {
52 let Some(cfg) = configs.get_mut(name) else {
53 panic!("Unknown env var {name}")
54 };
55
56 let Ok(value) = value.parse::<usize>() else {
57 panic!("Invalid value for env var {name}: {value}")
58 };
59
60 cfg.value = value;
61 cfg.seen_env = true;
62 }
63
64 if let Some(feature) = var.strip_prefix("CARGO_FEATURE_") {
65 if let Some(i) = feature.rfind('_') {
66 let name = &feature[..i];
67 let value = &feature[i + 1..];
68 if let Some(cfg) = configs.get_mut(name) {
69 let Ok(value) = value.parse::<usize>() else {
70 panic!("Invalid value for feature {name}: {value}")
71 };
72
73 // envvars take priority.
74 if !cfg.seen_env {
75 if cfg.seen_feature {
76 panic!("multiple values set for feature {}: {} and {}", name, cfg.value, value);
77 }
78
79 cfg.value = value;
80 cfg.seen_feature = true;
81 }
82 }
83 }
84 }
85 }
86
87 let mut data = String::new();
88
89 for (name, cfg) in &configs {
90 writeln!(&mut data, "pub const {}: usize = {};", name, cfg.value).unwrap();
91 }
92
93 let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap());
94 let out_file = out_dir.join("config.rs").to_string_lossy().to_string();
95 fs::write(out_file, data).unwrap();
96
97 let mut rustc_cfgs = common::CfgSet::new(); 5 let mut rustc_cfgs = common::CfgSet::new();
98 common::set_target_cfgs(&mut rustc_cfgs); 6 common::set_target_cfgs(&mut rustc_cfgs);
99} 7}
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;
50mod spawner; 50mod spawner;
51pub use spawner::*; 51pub use spawner::*;
52 52
53mod config {
54 #![allow(unused)]
55 include!(concat!(env!("OUT_DIR"), "/config.rs"));
56}
57
58/// Implementation details for embassy macros. 53/// Implementation details for embassy macros.
59/// Do not use. Used for macros and HALs only. Not covered by semver guarantees. 54/// Do not use. Used for macros and HALs only. Not covered by semver guarantees.
60#[doc(hidden)] 55#[doc(hidden)]
61#[cfg(not(feature = "nightly"))] 56#[cfg(not(feature = "nightly"))]
62pub mod _export { 57pub mod _export {
63 use core::alloc::Layout; 58 use core::cell::UnsafeCell;
64 use core::cell::{Cell, UnsafeCell};
65 use core::future::Future; 59 use core::future::Future;
66 use core::mem::MaybeUninit; 60 use core::mem::MaybeUninit;
67 use core::ptr::null_mut;
68
69 use critical_section::{CriticalSection, Mutex};
70 61
71 use crate::raw::TaskPool; 62 pub trait TaskFn<Args>: Copy {
72 63 type Fut: Future + 'static;
73 struct Arena<const N: usize> {
74 buf: UnsafeCell<MaybeUninit<[u8; N]>>,
75 ptr: Mutex<Cell<*mut u8>>,
76 } 64 }
77 65
78 unsafe impl<const N: usize> Sync for Arena<N> {} 66 macro_rules! task_fn_impl {
79 unsafe impl<const N: usize> Send for Arena<N> {} 67 ($($Tn:ident),*) => {
80 68 impl<F, Fut, $($Tn,)*> TaskFn<($($Tn,)*)> for F
81 impl<const N: usize> Arena<N> { 69 where
82 const fn new() -> Self { 70 F: Copy + FnOnce($($Tn,)*) -> Fut,
83 Self { 71 Fut: Future + 'static,
84 buf: UnsafeCell::new(MaybeUninit::uninit()), 72 {
85 ptr: Mutex::new(Cell::new(null_mut())), 73 type Fut = Fut;
86 }
87 }
88
89 fn alloc<T>(&'static self, cs: CriticalSection) -> &'static mut MaybeUninit<T> {
90 let layout = Layout::new::<T>();
91
92 let start = self.buf.get().cast::<u8>();
93 let end = unsafe { start.add(N) };
94
95 let mut ptr = self.ptr.borrow(cs).get();
96 if ptr.is_null() {
97 ptr = self.buf.get().cast::<u8>();
98 }
99
100 let bytes_left = (end as usize) - (ptr as usize);
101 let align_offset = (ptr as usize).next_multiple_of(layout.align()) - (ptr as usize);
102
103 if align_offset + layout.size() > bytes_left {
104 panic!("embassy-executor: task arena is full. You must increase the arena size, see the documentation for details: https://docs.embassy.dev/embassy-executor/");
105 } 74 }
75 };
76 }
106 77
107 let res = unsafe { ptr.add(align_offset) }; 78 task_fn_impl!();
108 let ptr = unsafe { ptr.add(align_offset + layout.size()) }; 79 task_fn_impl!(T0);
80 task_fn_impl!(T0, T1);
81 task_fn_impl!(T0, T1, T2);
82 task_fn_impl!(T0, T1, T2, T3);
83 task_fn_impl!(T0, T1, T2, T3, T4);
84 task_fn_impl!(T0, T1, T2, T3, T4, T5);
85 task_fn_impl!(T0, T1, T2, T3, T4, T5, T6);
86 task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7);
87 task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8);
88 task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9);
89 task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10);
90 task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11);
91 task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12);
92 task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13);
93 task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14);
94 task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15);
95
96 #[allow(private_bounds)]
97 #[repr(C)]
98 pub struct TaskPoolHolder<const SIZE: usize, const ALIGN: usize>
99 where
100 Align<ALIGN>: Alignment,
101 {
102 data: UnsafeCell<[MaybeUninit<u8>; SIZE]>,
103 align: Align<ALIGN>,
104 }
109 105
110 self.ptr.borrow(cs).set(ptr); 106 unsafe impl<const SIZE: usize, const ALIGN: usize> Send for TaskPoolHolder<SIZE, ALIGN> where Align<ALIGN>: Alignment {}
107 unsafe impl<const SIZE: usize, const ALIGN: usize> Sync for TaskPoolHolder<SIZE, ALIGN> where Align<ALIGN>: Alignment {}
111 108
112 unsafe { &mut *(res as *mut MaybeUninit<T>) } 109 #[allow(private_bounds)]
110 impl<const SIZE: usize, const ALIGN: usize> TaskPoolHolder<SIZE, ALIGN>
111 where
112 Align<ALIGN>: Alignment,
113 {
114 pub const fn get(&self) -> *const u8 {
115 self.data.get().cast()
113 } 116 }
114 } 117 }
115 118
116 static ARENA: Arena<{ crate::config::TASK_ARENA_SIZE }> = Arena::new(); 119 #[allow(private_bounds)]
120 #[repr(transparent)]
121 pub struct Align<const N: usize>([<Self as Alignment>::Archetype; 0])
122 where
123 Self: Alignment;
117 124
118 pub struct TaskPoolRef { 125 trait Alignment {
119 // type-erased `&'static mut TaskPool<F, N>` 126 /// A zero-sized type of particular alignment.
120 // Needed because statics can't have generics. 127 type Archetype: Copy + Eq + PartialEq + Send + Sync + Unpin;
121 ptr: Mutex<Cell<*mut ()>>,
122 } 128 }
123 unsafe impl Sync for TaskPoolRef {}
124 unsafe impl Send for TaskPoolRef {}
125
126 impl TaskPoolRef {
127 pub const fn new() -> Self {
128 Self {
129 ptr: Mutex::new(Cell::new(null_mut())),
130 }
131 }
132 129
133 /// Get the pool for this ref, allocating it from the arena the first time. 130 macro_rules! aligns {
134 /// 131 ($($AlignX:ident: $n:literal,)*) => {
135 /// safety: for a given TaskPoolRef instance, must always call with the exact 132 $(
136 /// same generic params. 133 #[derive(Copy, Clone, Eq, PartialEq)]
137 pub unsafe fn get<F: Future, const N: usize>(&'static self) -> &'static TaskPool<F, N> { 134 #[repr(align($n))]
138 critical_section::with(|cs| { 135 struct $AlignX {}
139 let ptr = self.ptr.borrow(cs); 136 impl Alignment for Align<$n> {
140 if ptr.get().is_null() { 137 type Archetype = $AlignX;
141 let pool = ARENA.alloc::<TaskPool<F, N>>(cs);
142 pool.write(TaskPool::new());
143 ptr.set(pool as *mut _ as _);
144 } 138 }
145 139 )*
146 unsafe { &*(ptr.get() as *const _) } 140 };
147 })
148 }
149 } 141 }
142
143 aligns!(
144 Align1: 1,
145 Align2: 2,
146 Align4: 4,
147 Align8: 8,
148 Align16: 16,
149 Align32: 32,
150 Align64: 64,
151 Align128: 128,
152 Align256: 256,
153 Align512: 512,
154 Align1024: 1024,
155 Align2048: 2048,
156 Align4096: 4096,
157 Align8192: 8192,
158 Align16384: 16384,
159 );
160 #[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))]
161 aligns!(
162 Align32768: 32768,
163 Align65536: 65536,
164 Align131072: 131072,
165 Align262144: 262144,
166 Align524288: 524288,
167 Align1048576: 1048576,
168 Align2097152: 2097152,
169 Align4194304: 4194304,
170 Align8388608: 8388608,
171 Align16777216: 16777216,
172 Align33554432: 33554432,
173 Align67108864: 67108864,
174 Align134217728: 134217728,
175 Align268435456: 268435456,
176 Align536870912: 536870912,
177 );
150} 178}