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.rs196
-rw-r--r--embassy-executor/tests/ui.rs1
-rw-r--r--embassy-executor/tests/ui/type_error.rs8
-rw-r--r--embassy-executor/tests/ui/type_error.stderr7
7 files changed, 142 insertions, 284 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..d6bd63665 100644
--- a/embassy-executor/src/lib.rs
+++ b/embassy-executor/src/lib.rs
@@ -50,101 +50,155 @@ 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 use crate::raw::TaskPool;
72 63
73 struct Arena<const N: usize> { 64 pub trait TaskFn<Args>: Copy {
74 buf: UnsafeCell<MaybeUninit<[u8; N]>>, 65 type Fut: Future + 'static;
75 ptr: Mutex<Cell<*mut u8>>,
76 } 66 }
77 67
78 unsafe impl<const N: usize> Sync for Arena<N> {} 68 macro_rules! task_fn_impl {
79 unsafe impl<const N: usize> Send for Arena<N> {} 69 ($($Tn:ident),*) => {
80 70 impl<F, Fut, $($Tn,)*> TaskFn<($($Tn,)*)> for F
81 impl<const N: usize> Arena<N> { 71 where
82 const fn new() -> Self { 72 F: Copy + FnOnce($($Tn,)*) -> Fut,
83 Self { 73 Fut: Future + 'static,
84 buf: UnsafeCell::new(MaybeUninit::uninit()), 74 {
85 ptr: Mutex::new(Cell::new(null_mut())), 75 type Fut = Fut;
86 } 76 }
87 } 77 };
88 78 }
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 79
95 let mut ptr = self.ptr.borrow(cs).get(); 80 task_fn_impl!();
96 if ptr.is_null() { 81 task_fn_impl!(T0);
97 ptr = self.buf.get().cast::<u8>(); 82 task_fn_impl!(T0, T1);
98 } 83 task_fn_impl!(T0, T1, T2);
84 task_fn_impl!(T0, T1, T2, T3);
85 task_fn_impl!(T0, T1, T2, T3, T4);
86 task_fn_impl!(T0, T1, T2, T3, T4, T5);
87 task_fn_impl!(T0, T1, T2, T3, T4, T5, T6);
88 task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7);
89 task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8);
90 task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9);
91 task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10);
92 task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11);
93 task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12);
94 task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13);
95 task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14);
96 task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15);
97
98 #[allow(private_bounds)]
99 #[repr(C)]
100 pub struct TaskPoolHolder<const SIZE: usize, const ALIGN: usize>
101 where
102 Align<ALIGN>: Alignment,
103 {
104 data: UnsafeCell<[MaybeUninit<u8>; SIZE]>,
105 align: Align<ALIGN>,
106 }
99 107
100 let bytes_left = (end as usize) - (ptr as usize); 108 unsafe impl<const SIZE: usize, const ALIGN: usize> Send for TaskPoolHolder<SIZE, ALIGN> where Align<ALIGN>: Alignment {}
101 let align_offset = (ptr as usize).next_multiple_of(layout.align()) - (ptr as usize); 109 unsafe impl<const SIZE: usize, const ALIGN: usize> Sync for TaskPoolHolder<SIZE, ALIGN> where Align<ALIGN>: Alignment {}
102 110
103 if align_offset + layout.size() > bytes_left { 111 #[allow(private_bounds)]
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/"); 112 impl<const SIZE: usize, const ALIGN: usize> TaskPoolHolder<SIZE, ALIGN>
105 } 113 where
114 Align<ALIGN>: Alignment,
115 {
116 pub const fn get(&self) -> *const u8 {
117 self.data.get().cast()
118 }
119 }
106 120
107 let res = unsafe { ptr.add(align_offset) }; 121 pub const fn task_pool_size<F, Args, Fut, const POOL_SIZE: usize>(_: F) -> usize
108 let ptr = unsafe { ptr.add(align_offset + layout.size()) }; 122 where
123 F: TaskFn<Args, Fut = Fut>,
124 Fut: Future + 'static,
125 {
126 size_of::<TaskPool<Fut, POOL_SIZE>>()
127 }
109 128
110 self.ptr.borrow(cs).set(ptr); 129 pub const fn task_pool_align<F, Args, Fut, const POOL_SIZE: usize>(_: F) -> usize
130 where
131 F: TaskFn<Args, Fut = Fut>,
132 Fut: Future + 'static,
133 {
134 align_of::<TaskPool<Fut, POOL_SIZE>>()
135 }
111 136
112 unsafe { &mut *(res as *mut MaybeUninit<T>) } 137 pub const fn task_pool_new<F, Args, Fut, const POOL_SIZE: usize>(_: F) -> TaskPool<Fut, POOL_SIZE>
113 } 138 where
139 F: TaskFn<Args, Fut = Fut>,
140 Fut: Future + 'static,
141 {
142 TaskPool::new()
114 } 143 }
115 144
116 static ARENA: Arena<{ crate::config::TASK_ARENA_SIZE }> = Arena::new(); 145 #[allow(private_bounds)]
146 #[repr(transparent)]
147 pub struct Align<const N: usize>([<Self as Alignment>::Archetype; 0])
148 where
149 Self: Alignment;
117 150
118 pub struct TaskPoolRef { 151 trait Alignment {
119 // type-erased `&'static mut TaskPool<F, N>` 152 /// A zero-sized type of particular alignment.
120 // Needed because statics can't have generics. 153 type Archetype: Copy + Eq + PartialEq + Send + Sync + Unpin;
121 ptr: Mutex<Cell<*mut ()>>,
122 } 154 }
123 unsafe impl Sync for TaskPoolRef {}
124 unsafe impl Send for TaskPoolRef {}
125 155
126 impl TaskPoolRef { 156 macro_rules! aligns {
127 pub const fn new() -> Self { 157 ($($AlignX:ident: $n:literal,)*) => {
128 Self { 158 $(
129 ptr: Mutex::new(Cell::new(null_mut())), 159 #[derive(Copy, Clone, Eq, PartialEq)]
130 } 160 #[repr(align($n))]
131 } 161 struct $AlignX {}
132 162 impl Alignment for Align<$n> {
133 /// Get the pool for this ref, allocating it from the arena the first time. 163 type Archetype = $AlignX;
134 ///
135 /// safety: for a given TaskPoolRef instance, must always call with the exact
136 /// same generic params.
137 pub unsafe fn get<F: Future, const N: usize>(&'static self) -> &'static TaskPool<F, N> {
138 critical_section::with(|cs| {
139 let ptr = self.ptr.borrow(cs);
140 if ptr.get().is_null() {
141 let pool = ARENA.alloc::<TaskPool<F, N>>(cs);
142 pool.write(TaskPool::new());
143 ptr.set(pool as *mut _ as _);
144 } 164 }
145 165 )*
146 unsafe { &*(ptr.get() as *const _) } 166 };
147 })
148 }
149 } 167 }
168
169 aligns!(
170 Align1: 1,
171 Align2: 2,
172 Align4: 4,
173 Align8: 8,
174 Align16: 16,
175 Align32: 32,
176 Align64: 64,
177 Align128: 128,
178 Align256: 256,
179 Align512: 512,
180 Align1024: 1024,
181 Align2048: 2048,
182 Align4096: 4096,
183 Align8192: 8192,
184 Align16384: 16384,
185 );
186 #[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))]
187 aligns!(
188 Align32768: 32768,
189 Align65536: 65536,
190 Align131072: 131072,
191 Align262144: 262144,
192 Align524288: 524288,
193 Align1048576: 1048576,
194 Align2097152: 2097152,
195 Align4194304: 4194304,
196 Align8388608: 8388608,
197 Align16777216: 16777216,
198 Align33554432: 33554432,
199 Align67108864: 67108864,
200 Align134217728: 134217728,
201 Align268435456: 268435456,
202 Align536870912: 536870912,
203 );
150} 204}
diff --git a/embassy-executor/tests/ui.rs b/embassy-executor/tests/ui.rs
index be4679485..278a4b903 100644
--- a/embassy-executor/tests/ui.rs
+++ b/embassy-executor/tests/ui.rs
@@ -19,5 +19,6 @@ fn ui() {
19 t.compile_fail("tests/ui/not_async.rs"); 19 t.compile_fail("tests/ui/not_async.rs");
20 t.compile_fail("tests/ui/self_ref.rs"); 20 t.compile_fail("tests/ui/self_ref.rs");
21 t.compile_fail("tests/ui/self.rs"); 21 t.compile_fail("tests/ui/self.rs");
22 t.compile_fail("tests/ui/type_error.rs");
22 t.compile_fail("tests/ui/where_clause.rs"); 23 t.compile_fail("tests/ui/where_clause.rs");
23} 24}
diff --git a/embassy-executor/tests/ui/type_error.rs b/embassy-executor/tests/ui/type_error.rs
new file mode 100644
index 000000000..1734bc6c4
--- /dev/null
+++ b/embassy-executor/tests/ui/type_error.rs
@@ -0,0 +1,8 @@
1#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
2
3#[embassy_executor::task]
4async fn task() {
5 5
6}
7
8fn main() {}
diff --git a/embassy-executor/tests/ui/type_error.stderr b/embassy-executor/tests/ui/type_error.stderr
new file mode 100644
index 000000000..bce315811
--- /dev/null
+++ b/embassy-executor/tests/ui/type_error.stderr
@@ -0,0 +1,7 @@
1error[E0308]: mismatched types
2 --> tests/ui/type_error.rs:5:5
3 |
44 | async fn task() {
5 | - help: try adding a return type: `-> i32`
65 | 5
7 | ^ expected `()`, found integer