aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDario Nieuwenhuis <[email protected]>2023-11-24 22:39:08 +0100
committerDario Nieuwenhuis <[email protected]>2023-11-24 23:52:09 +0100
commit996c3c1f7e389b1e7d26ca6f02524fff3d63212e (patch)
tree8e09c42e79abb4e2d14d03a5466afef3707a730a
parent171cdb94c7906670723b0965ca66d72a2352ac73 (diff)
executor: make task arena size configurable.
-rw-r--r--embassy-executor/Cargo.toml88
-rw-r--r--embassy-executor/README.md28
-rw-r--r--embassy-executor/build.rs93
-rw-r--r--embassy-executor/gen_config.py82
-rw-r--r--embassy-executor/src/lib.rs10
5 files changed, 275 insertions, 26 deletions
diff --git a/embassy-executor/Cargo.toml b/embassy-executor/Cargo.toml
index 3ff8440ca..ae46b17c6 100644
--- a/embassy-executor/Cargo.toml
+++ b/embassy-executor/Cargo.toml
@@ -27,6 +27,30 @@ default-target = "thumbv7em-none-eabi"
27targets = ["thumbv7em-none-eabi"] 27targets = ["thumbv7em-none-eabi"]
28features = ["nightly", "defmt", "arch-cortex-m", "executor-thread", "executor-interrupt"] 28features = ["nightly", "defmt", "arch-cortex-m", "executor-thread", "executor-interrupt"]
29 29
30[dependencies]
31defmt = { version = "0.3", optional = true }
32log = { version = "0.4.14", optional = true }
33rtos-trace = { version = "0.1.2", optional = true }
34
35embassy-macros = { version = "0.2.1", path = "../embassy-macros" }
36embassy-time = { version = "0.1.5", path = "../embassy-time", optional = true}
37critical-section = "1.1"
38
39# needed for riscv
40# remove when https://github.com/rust-lang/rust/pull/114499 is merged
41portable-atomic = { version = "1.5", optional = true }
42
43# arch-cortex-m dependencies
44cortex-m = { version = "0.7.6", optional = true }
45
46# arch-wasm dependencies
47wasm-bindgen = { version = "0.2.82", optional = true }
48js-sys = { version = "0.3", optional = true }
49
50[dev-dependencies]
51critical-section = { version = "1.1", features = ["std"] }
52
53
30[features] 54[features]
31 55
32# Architecture 56# Architecture
@@ -49,25 +73,47 @@ turbowakers = []
49 73
50integrated-timers = ["dep:embassy-time"] 74integrated-timers = ["dep:embassy-time"]
51 75
52[dependencies] 76# BEGIN AUTOGENERATED CONFIG FEATURES
53defmt = { version = "0.3", optional = true } 77# Generated by gen_config.py. DO NOT EDIT.
54log = { version = "0.4.14", optional = true } 78task-arena-size-64 = []
55rtos-trace = { version = "0.1.2", optional = true } 79task-arena-size-128 = []
56 80task-arena-size-192 = []
57embassy-macros = { version = "0.2.1", path = "../embassy-macros" } 81task-arena-size-256 = []
58embassy-time = { version = "0.1.5", path = "../embassy-time", optional = true} 82task-arena-size-320 = []
59critical-section = "1.1" 83task-arena-size-384 = []
60 84task-arena-size-512 = []
61# needed for riscv 85task-arena-size-640 = []
62# remove when https://github.com/rust-lang/rust/pull/114499 is merged 86task-arena-size-768 = []
63portable-atomic = { version = "1.5", optional = true } 87task-arena-size-1024 = []
64 88task-arena-size-1280 = []
65# arch-cortex-m dependencies 89task-arena-size-1536 = []
66cortex-m = { version = "0.7.6", optional = true } 90task-arena-size-2048 = []
91task-arena-size-2560 = []
92task-arena-size-3072 = []
93task-arena-size-4096 = [] # Default
94task-arena-size-5120 = []
95task-arena-size-6144 = []
96task-arena-size-8192 = []
97task-arena-size-10240 = []
98task-arena-size-12288 = []
99task-arena-size-16384 = []
100task-arena-size-20480 = []
101task-arena-size-24576 = []
102task-arena-size-32768 = []
103task-arena-size-40960 = []
104task-arena-size-49152 = []
105task-arena-size-65536 = []
106task-arena-size-81920 = []
107task-arena-size-98304 = []
108task-arena-size-131072 = []
109task-arena-size-163840 = []
110task-arena-size-196608 = []
111task-arena-size-262144 = []
112task-arena-size-327680 = []
113task-arena-size-393216 = []
114task-arena-size-524288 = []
115task-arena-size-655360 = []
116task-arena-size-786432 = []
117task-arena-size-1048576 = []
67 118
68# arch-wasm dependencies 119# END AUTOGENERATED CONFIG FEATURES
69wasm-bindgen = { version = "0.2.82", optional = true }
70js-sys = { version = "0.3", optional = true }
71
72[dev-dependencies]
73critical-section = { version = "1.1", features = ["std"] }
diff --git a/embassy-executor/README.md b/embassy-executor/README.md
index 3c1448a18..80ecfc71a 100644
--- a/embassy-executor/README.md
+++ b/embassy-executor/README.md
@@ -2,10 +2,36 @@
2 2
3An async/await executor designed for embedded usage. 3An async/await executor designed for embedded usage.
4 4
5- No `alloc`, no heap needed. Task futures are statically allocated. 5- No `alloc`, no heap needed.
6- With nightly Rust, task futures can be fully statically allocated.
6- 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.
7- 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;`.
8- 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`.
9- 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.
10- 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.
11- 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 exists, 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, check `Cargo.toml` for the list.
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 6fe82b44f..07f31e3fb 100644
--- a/embassy-executor/build.rs
+++ b/embassy-executor/build.rs
@@ -1,6 +1,97 @@
1use std::env; 1use std::collections::HashMap;
2use std::fmt::Write;
3use std::path::PathBuf;
4use std::{env, fs};
5
6static CONFIGS: &[(&str, usize)] = &[
7 // BEGIN AUTOGENERATED CONFIG FEATURES
8 // Generated by gen_config.py. DO NOT EDIT.
9 ("TASK_ARENA_SIZE", 4096),
10 // END AUTOGENERATED CONFIG FEATURES
11];
12
13struct ConfigState {
14 value: usize,
15 seen_feature: bool,
16 seen_env: bool,
17}
2 18
3fn main() { 19fn main() {
20 let crate_name = env::var("CARGO_PKG_NAME")
21 .unwrap()
22 .to_ascii_uppercase()
23 .replace('-', "_");
24
25 // only rebuild if build.rs changed. Otherwise Cargo will rebuild if any
26 // other file changed.
27 println!("cargo:rerun-if-changed=build.rs");
28
29 // Rebuild if config envvar changed.
30 for (name, _) in CONFIGS {
31 println!("cargo:rerun-if-env-changed={crate_name}_{name}");
32 }
33
34 let mut configs = HashMap::new();
35 for (name, default) in CONFIGS {
36 configs.insert(
37 *name,
38 ConfigState {
39 value: *default,
40 seen_env: false,
41 seen_feature: false,
42 },
43 );
44 }
45
46 let prefix = format!("{crate_name}_");
47 for (var, value) in env::vars() {
48 if let Some(name) = var.strip_prefix(&prefix) {
49 let Some(cfg) = configs.get_mut(name) else {
50 panic!("Unknown env var {name}")
51 };
52
53 let Ok(value) = value.parse::<usize>() else {
54 panic!("Invalid value for env var {name}: {value}")
55 };
56
57 cfg.value = value;
58 cfg.seen_env = true;
59 }
60
61 if let Some(feature) = var.strip_prefix("CARGO_FEATURE_") {
62 if let Some(i) = feature.rfind('_') {
63 let name = &feature[..i];
64 let value = &feature[i + 1..];
65 if let Some(cfg) = configs.get_mut(name) {
66 let Ok(value) = value.parse::<usize>() else {
67 panic!("Invalid value for feature {name}: {value}")
68 };
69
70 // envvars take priority.
71 if !cfg.seen_env {
72 if cfg.seen_feature {
73 panic!("multiple values set for feature {}: {} and {}", name, cfg.value, value);
74 }
75
76 cfg.value = value;
77 cfg.seen_feature = true;
78 }
79 }
80 }
81 }
82 }
83
84 let mut data = String::new();
85
86 for (name, cfg) in &configs {
87 writeln!(&mut data, "pub const {}: usize = {};", name, cfg.value).unwrap();
88 }
89
90 let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap());
91 let out_file = out_dir.join("config.rs").to_string_lossy().to_string();
92 fs::write(out_file, data).unwrap();
93
94 // cortex-m targets
4 let target = env::var("TARGET").unwrap(); 95 let target = env::var("TARGET").unwrap();
5 96
6 if target.starts_with("thumbv6m-") { 97 if target.starts_with("thumbv6m-") {
diff --git a/embassy-executor/gen_config.py b/embassy-executor/gen_config.py
new file mode 100644
index 000000000..e427d29f4
--- /dev/null
+++ b/embassy-executor/gen_config.py
@@ -0,0 +1,82 @@
1import os
2
3abspath = os.path.abspath(__file__)
4dname = os.path.dirname(abspath)
5os.chdir(dname)
6
7features = []
8
9
10def feature(name, default, min=None, max=None, pow2=None, vals=None, factors=[]):
11 if vals is None:
12 assert min is not None
13 assert max is not None
14
15 vals = set()
16 val = min
17 while val <= max:
18 vals.add(val)
19 for f in factors:
20 if val * f <= max:
21 vals.add(val * f)
22 if (pow2 == True or (isinstance(pow2, int) and val >= pow2)) and val > 0:
23 val *= 2
24 else:
25 val += 1
26 vals.add(default)
27 vals = sorted(list(vals))
28
29 features.append(
30 {
31 "name": name,
32 "default": default,
33 "vals": vals,
34 }
35 )
36
37
38feature(
39 "task_arena_size", default=4096, min=64, max=1024 * 1024, pow2=True, factors=[3, 5]
40)
41
42# ========= Update Cargo.toml
43
44things = ""
45for f in features:
46 name = f["name"].replace("_", "-")
47 for val in f["vals"]:
48 things += f"{name}-{val} = []"
49 if val == f["default"]:
50 things += " # Default"
51 things += "\n"
52 things += "\n"
53
54SEPARATOR_START = "# BEGIN AUTOGENERATED CONFIG FEATURES\n"
55SEPARATOR_END = "# END AUTOGENERATED CONFIG FEATURES\n"
56HELP = "# Generated by gen_config.py. DO NOT EDIT.\n"
57with open("Cargo.toml", "r") as f:
58 data = f.read()
59before, data = data.split(SEPARATOR_START, maxsplit=1)
60_, after = data.split(SEPARATOR_END, maxsplit=1)
61data = before + SEPARATOR_START + HELP + things + SEPARATOR_END + after
62with open("Cargo.toml", "w") as f:
63 f.write(data)
64
65
66# ========= Update build.rs
67
68things = ""
69for f in features:
70 name = f["name"].upper()
71 things += f' ("{name}", {f["default"]}),\n'
72
73SEPARATOR_START = "// BEGIN AUTOGENERATED CONFIG FEATURES\n"
74SEPARATOR_END = "// END AUTOGENERATED CONFIG FEATURES\n"
75HELP = " // Generated by gen_config.py. DO NOT EDIT.\n"
76with open("build.rs", "r") as f:
77 data = f.read()
78before, data = data.split(SEPARATOR_START, maxsplit=1)
79_, after = data.split(SEPARATOR_END, maxsplit=1)
80data = before + SEPARATOR_START + HELP + things + " " + SEPARATOR_END + after
81with open("build.rs", "w") as f:
82 f.write(data)
diff --git a/embassy-executor/src/lib.rs b/embassy-executor/src/lib.rs
index ac7dbb035..d8ac4893b 100644
--- a/embassy-executor/src/lib.rs
+++ b/embassy-executor/src/lib.rs
@@ -40,6 +40,11 @@ pub mod raw;
40mod spawner; 40mod spawner;
41pub use spawner::*; 41pub use spawner::*;
42 42
43mod config {
44 #![allow(unused)]
45 include!(concat!(env!("OUT_DIR"), "/config.rs"));
46}
47
43/// Implementation details for embassy macros. 48/// Implementation details for embassy macros.
44/// Do not use. Used for macros and HALs only. Not covered by semver guarantees. 49/// Do not use. Used for macros and HALs only. Not covered by semver guarantees.
45#[doc(hidden)] 50#[doc(hidden)]
@@ -86,7 +91,7 @@ pub mod _export {
86 let align_offset = (ptr as usize).next_multiple_of(layout.align()) - (ptr as usize); 91 let align_offset = (ptr as usize).next_multiple_of(layout.align()) - (ptr as usize);
87 92
88 if align_offset + layout.size() > bytes_left { 93 if align_offset + layout.size() > bytes_left {
89 panic!("arena full"); 94 panic!("embassy-executor: task arena is full. You must increase the arena size, see the documentation for details: https://docs.embassy.dev/embassy-executor/");
90 } 95 }
91 96
92 let res = unsafe { ptr.add(align_offset) }; 97 let res = unsafe { ptr.add(align_offset) };
@@ -98,8 +103,7 @@ pub mod _export {
98 } 103 }
99 } 104 }
100 105
101 const ARENA_SIZE: usize = 16 * 1024; 106 static ARENA: Arena<{ crate::config::TASK_ARENA_SIZE }> = Arena::new();
102 static ARENA: Arena<ARENA_SIZE> = Arena::new();
103 107
104 pub struct TaskPoolRef { 108 pub struct TaskPoolRef {
105 // type-erased `&'static mut TaskPool<F, N>` 109 // type-erased `&'static mut TaskPool<F, N>`