diff options
| author | jrmoulton <[email protected]> | 2025-06-10 15:47:54 -0600 |
|---|---|---|
| committer | jrmoulton <[email protected]> | 2025-06-10 15:48:36 -0600 |
| commit | cfad9798ff99d4de0571a512d156b5fe1ef1d427 (patch) | |
| tree | fc3bf670f82d139de19466cddad1e909db7f3d2e /embassy-executor | |
| parent | fc342915e6155dec7bafa3e135da7f37a9a07f5c (diff) | |
| parent | 6186d111a5c150946ee5b7e9e68d987a38c1a463 (diff) | |
merge new embassy changes
Diffstat (limited to 'embassy-executor')
57 files changed, 1623 insertions, 692 deletions
diff --git a/embassy-executor/CHANGELOG.md b/embassy-executor/CHANGELOG.md index 5582b56ec..608c67724 100644 --- a/embassy-executor/CHANGELOG.md +++ b/embassy-executor/CHANGELOG.md | |||
| @@ -5,13 +5,46 @@ All notable changes to this project will be documented in this file. | |||
| 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), |
| 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). | 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). |
| 7 | 7 | ||
| 8 | ## Unreleased | 8 | ## unreleased |
| 9 | |||
| 10 | - Added support for Cortex-A and Cortex-R | ||
| 11 | |||
| 12 | ## 0.7.0 - 2025-01-02 | ||
| 13 | |||
| 14 | - Performance optimizations. | ||
| 15 | - Remove feature `integrated-timers`. Starting with `embassy-time-driver` v0.2, `embassy-time` v0.4 the timer queue is now part of the time driver, so it's no longer the executor's responsibility. Therefore, `embassy-executor` no longer provides an `embassy-time-queue-driver` implementation. | ||
| 16 | - Added the possibility for timer driver implementations to store arbitrary data in task headers. This can be used to make a timer queue intrusive list, similar to the previous `integrated-timers` feature. Payload size is controlled by the `timer-item-payload-size-X` features. | ||
| 17 | - Added `TaskRef::executor` to obtain a reference to a task's executor | ||
| 18 | |||
| 19 | ## 0.6.3 - 2024-11-12 | ||
| 20 | |||
| 21 | - Building with the `nightly` feature now works with the Xtensa Rust compiler 1.82. | ||
| 22 | - Compare vtable address instead of contents. Saves 44 bytes of flash on cortex-m. | ||
| 23 | |||
| 24 | ## 0.6.2 - 2024-11-06 | ||
| 25 | |||
| 26 | - The `nightly` feature no longer requires `nightly-2024-09-06` or newer. | ||
| 27 | |||
| 28 | ## 0.6.1 - 2024-10-21 | ||
| 29 | |||
| 30 | - Soundness fix: Deny using `impl Trait` in task arguments. This was previously accidentally allowed when not using the `nightly` feature, | ||
| 31 | and could cause out of bounds memory accesses if spawning the same task mulitple times with different underlying types | ||
| 32 | for the `impl Trait`. Affected versions are 0.4.x, 0.5.0 and 0.6.0, which have been yanked. | ||
| 33 | - Add an architecture-agnostic executor that spins waiting for tasks to run, enabled with the `arch-spin` feature. | ||
| 34 | - Update for breaking change in the nightly waker_getters API. The `nightly` feature now requires `nightly-2024-09-06` or newer. | ||
| 35 | - Improve macro error messages. | ||
| 9 | 36 | ||
| 10 | ## 0.6.0 - 2024-08-05 | 37 | ## 0.6.0 - 2024-08-05 |
| 11 | 38 | ||
| 12 | - Add collapse_debuginfo to fmt.rs macros. | 39 | - Add collapse_debuginfo to fmt.rs macros. |
| 13 | - initial support for avr | 40 | - initial support for AVR |
| 14 | - use nightly waker_getters APIs | 41 | - use nightly waker_getters APIs |
| 42 | |||
| 43 | ## 0.5.1 - 2024-10-21 | ||
| 44 | |||
| 45 | - Soundness fix: Deny using `impl Trait` in task arguments. This was previously accidentally allowed when not using the `nightly` feature, | ||
| 46 | and could cause out of bounds memory accesses if spawning the same task mulitple times with different underlying types | ||
| 47 | for the `impl Trait`. Affected versions are 0.4.x, 0.5.0 and 0.6.0, which have been yanked. | ||
| 15 | 48 | ||
| 16 | ## 0.5.0 - 2024-01-11 | 49 | ## 0.5.0 - 2024-01-11 |
| 17 | 50 | ||
diff --git a/embassy-executor/Cargo.toml b/embassy-executor/Cargo.toml index 5984cc49c..f014ccf30 100644 --- a/embassy-executor/Cargo.toml +++ b/embassy-executor/Cargo.toml | |||
| @@ -1,6 +1,6 @@ | |||
| 1 | [package] | 1 | [package] |
| 2 | name = "embassy-executor" | 2 | name = "embassy-executor" |
| 3 | version = "0.6.0" | 3 | version = "0.7.0" |
| 4 | edition = "2021" | 4 | edition = "2021" |
| 5 | license = "MIT OR Apache-2.0" | 5 | license = "MIT OR Apache-2.0" |
| 6 | description = "async/await executor designed for embedded usage" | 6 | description = "async/await executor designed for embedded usage" |
| @@ -29,13 +29,12 @@ targets = ["thumbv7em-none-eabi"] | |||
| 29 | features = ["defmt", "arch-cortex-m", "executor-thread", "executor-interrupt"] | 29 | features = ["defmt", "arch-cortex-m", "executor-thread", "executor-interrupt"] |
| 30 | 30 | ||
| 31 | [dependencies] | 31 | [dependencies] |
| 32 | defmt = { version = "0.3", optional = true } | 32 | defmt = { version = "1.0.1", optional = true } |
| 33 | log = { version = "0.4.14", optional = true } | 33 | log = { version = "0.4.14", optional = true } |
| 34 | rtos-trace = { version = "0.1.2", optional = true } | 34 | rtos-trace = { version = "0.1.3", optional = true } |
| 35 | 35 | ||
| 36 | embassy-executor-macros = { version = "0.5.0", path = "../embassy-executor-macros" } | 36 | embassy-executor-macros = { version = "0.6.2", path = "../embassy-executor-macros" } |
| 37 | embassy-time-driver = { version = "0.1.0", path = "../embassy-time-driver", optional = true } | 37 | embassy-time-driver = { version = "0.2", path = "../embassy-time-driver", optional = true } |
| 38 | embassy-time-queue-driver = { version = "0.1.0", path = "../embassy-time-queue-driver", optional = true } | ||
| 39 | critical-section = "1.1" | 38 | critical-section = "1.1" |
| 40 | 39 | ||
| 41 | document-features = "0.2.7" | 40 | document-features = "0.2.7" |
| @@ -46,16 +45,20 @@ portable-atomic = { version = "1.5", optional = true } | |||
| 46 | # arch-cortex-m dependencies | 45 | # arch-cortex-m dependencies |
| 47 | cortex-m = { version = "0.7.6", optional = true } | 46 | cortex-m = { version = "0.7.6", optional = true } |
| 48 | 47 | ||
| 48 | # arch-cortex-ar dependencies | ||
| 49 | cortex-ar = { version = "0.2", optional = true } | ||
| 50 | |||
| 49 | # arch-wasm dependencies | 51 | # arch-wasm dependencies |
| 50 | wasm-bindgen = { version = "0.2.82", optional = true } | 52 | wasm-bindgen = { version = "0.2.82", optional = true } |
| 51 | js-sys = { version = "0.3", optional = true } | 53 | js-sys = { version = "0.3", optional = true } |
| 52 | 54 | ||
| 53 | # arch-avr dependencies | 55 | # arch-avr dependencies |
| 54 | avr-device = { version = "0.5.3", optional = true } | 56 | avr-device = { version = "0.7.0", optional = true } |
| 55 | 57 | ||
| 56 | [dev-dependencies] | 58 | [dev-dependencies] |
| 57 | critical-section = { version = "1.1", features = ["std"] } | 59 | critical-section = { version = "1.1", features = ["std"] } |
| 58 | 60 | trybuild = "1.0" | |
| 61 | embassy-sync = { path = "../embassy-sync" } | ||
| 59 | 62 | ||
| 60 | [features] | 63 | [features] |
| 61 | 64 | ||
| @@ -67,21 +70,22 @@ nightly = ["embassy-executor-macros/nightly"] | |||
| 67 | # See: https://github.com/embassy-rs/embassy/pull/1263 | 70 | # See: https://github.com/embassy-rs/embassy/pull/1263 |
| 68 | turbowakers = [] | 71 | turbowakers = [] |
| 69 | 72 | ||
| 70 | ## Use the executor-integrated `embassy-time` timer queue. | ||
| 71 | integrated-timers = ["dep:embassy-time-driver", "dep:embassy-time-queue-driver"] | ||
| 72 | |||
| 73 | #! ### Architecture | 73 | #! ### Architecture |
| 74 | _arch = [] # some arch was picked | 74 | _arch = [] # some arch was picked |
| 75 | ## std | 75 | ## std |
| 76 | arch-std = ["_arch", "critical-section/std"] | 76 | arch-std = ["_arch"] |
| 77 | ## Cortex-M | 77 | ## Cortex-M |
| 78 | arch-cortex-m = ["_arch", "dep:cortex-m"] | 78 | arch-cortex-m = ["_arch", "dep:cortex-m"] |
| 79 | ## Cortex-A/R | ||
| 80 | arch-cortex-ar = ["_arch", "dep:cortex-ar"] | ||
| 79 | ## RISC-V 32 | 81 | ## RISC-V 32 |
| 80 | arch-riscv32 = ["_arch"] | 82 | arch-riscv32 = ["_arch"] |
| 81 | ## WASM | 83 | ## WASM |
| 82 | arch-wasm = ["_arch", "dep:wasm-bindgen", "dep:js-sys"] | 84 | arch-wasm = ["_arch", "dep:wasm-bindgen", "dep:js-sys"] |
| 83 | ## AVR | 85 | ## AVR |
| 84 | arch-avr = ["_arch", "dep:portable-atomic", "dep:avr-device"] | 86 | arch-avr = ["_arch", "dep:portable-atomic", "dep:avr-device"] |
| 87 | ## spin (architecture agnostic; never sleeps) | ||
| 88 | arch-spin = ["_arch"] | ||
| 85 | 89 | ||
| 86 | #! ### Executor | 90 | #! ### Executor |
| 87 | 91 | ||
| @@ -89,98 +93,23 @@ arch-avr = ["_arch", "dep:portable-atomic", "dep:avr-device"] | |||
| 89 | executor-thread = [] | 93 | executor-thread = [] |
| 90 | ## Enable the interrupt-mode executor (available in Cortex-M only) | 94 | ## Enable the interrupt-mode executor (available in Cortex-M only) |
| 91 | executor-interrupt = [] | 95 | executor-interrupt = [] |
| 92 | 96 | ## Enable tracing support (adds some overhead) | |
| 93 | #! ### Task Arena Size | 97 | trace = [] |
| 94 | #! Sets the [task arena](#task-arena) size. Necessary if you’re not using `nightly`. | 98 | ## Enable support for rtos-trace framework |
| 95 | #! | 99 | rtos-trace = ["dep:rtos-trace", "trace", "dep:embassy-time-driver"] |
| 96 | #! <details> | 100 | |
| 97 | #! <summary>Preconfigured Task Arena Sizes:</summary> | 101 | #! ### Timer Item Payload Size |
| 98 | #! <!-- rustdoc requires the following blank line for the feature list to render correctly! --> | 102 | #! Sets the size of the payload for timer items, allowing integrated timer implementors to store |
| 99 | #! | 103 | #! additional data in the timer item. The payload field will be aligned to this value as well. |
| 100 | 104 | #! If these features are not defined, the timer item will contain no payload field. | |
| 101 | # BEGIN AUTOGENERATED CONFIG FEATURES | 105 | |
| 102 | # Generated by gen_config.py. DO NOT EDIT. | 106 | _timer-item-payload = [] # A size was picked |
| 103 | ## 64 | 107 | |
| 104 | task-arena-size-64 = [] | 108 | ## 1 bytes |
| 105 | ## 128 | 109 | timer-item-payload-size-1 = ["_timer-item-payload"] |
| 106 | task-arena-size-128 = [] | 110 | ## 2 bytes |
| 107 | ## 192 | 111 | timer-item-payload-size-2 = ["_timer-item-payload"] |
| 108 | task-arena-size-192 = [] | 112 | ## 4 bytes |
| 109 | ## 256 | 113 | timer-item-payload-size-4 = ["_timer-item-payload"] |
| 110 | task-arena-size-256 = [] | 114 | ## 8 bytes |
| 111 | ## 320 | 115 | timer-item-payload-size-8 = ["_timer-item-payload"] |
| 112 | task-arena-size-320 = [] | ||
| 113 | ## 384 | ||
| 114 | task-arena-size-384 = [] | ||
| 115 | ## 512 | ||
| 116 | task-arena-size-512 = [] | ||
| 117 | ## 640 | ||
| 118 | task-arena-size-640 = [] | ||
| 119 | ## 768 | ||
| 120 | task-arena-size-768 = [] | ||
| 121 | ## 1024 | ||
| 122 | task-arena-size-1024 = [] | ||
| 123 | ## 1280 | ||
| 124 | task-arena-size-1280 = [] | ||
| 125 | ## 1536 | ||
| 126 | task-arena-size-1536 = [] | ||
| 127 | ## 2048 | ||
| 128 | task-arena-size-2048 = [] | ||
| 129 | ## 2560 | ||
| 130 | task-arena-size-2560 = [] | ||
| 131 | ## 3072 | ||
| 132 | task-arena-size-3072 = [] | ||
| 133 | ## 4096 (default) | ||
| 134 | task-arena-size-4096 = [] # Default | ||
| 135 | ## 5120 | ||
| 136 | task-arena-size-5120 = [] | ||
| 137 | ## 6144 | ||
| 138 | task-arena-size-6144 = [] | ||
| 139 | ## 8192 | ||
| 140 | task-arena-size-8192 = [] | ||
| 141 | ## 10240 | ||
| 142 | task-arena-size-10240 = [] | ||
| 143 | ## 12288 | ||
| 144 | task-arena-size-12288 = [] | ||
| 145 | ## 16384 | ||
| 146 | task-arena-size-16384 = [] | ||
| 147 | ## 20480 | ||
| 148 | task-arena-size-20480 = [] | ||
| 149 | ## 24576 | ||
| 150 | task-arena-size-24576 = [] | ||
| 151 | ## 32768 | ||
| 152 | task-arena-size-32768 = [] | ||
| 153 | ## 40960 | ||
| 154 | task-arena-size-40960 = [] | ||
| 155 | ## 49152 | ||
| 156 | task-arena-size-49152 = [] | ||
| 157 | ## 65536 | ||
| 158 | task-arena-size-65536 = [] | ||
| 159 | ## 81920 | ||
| 160 | task-arena-size-81920 = [] | ||
| 161 | ## 98304 | ||
| 162 | task-arena-size-98304 = [] | ||
| 163 | ## 131072 | ||
| 164 | task-arena-size-131072 = [] | ||
| 165 | ## 163840 | ||
| 166 | task-arena-size-163840 = [] | ||
| 167 | ## 196608 | ||
| 168 | task-arena-size-196608 = [] | ||
| 169 | ## 262144 | ||
| 170 | task-arena-size-262144 = [] | ||
| 171 | ## 327680 | ||
| 172 | task-arena-size-327680 = [] | ||
| 173 | ## 393216 | ||
| 174 | task-arena-size-393216 = [] | ||
| 175 | ## 524288 | ||
| 176 | task-arena-size-524288 = [] | ||
| 177 | ## 655360 | ||
| 178 | task-arena-size-655360 = [] | ||
| 179 | ## 786432 | ||
| 180 | task-arena-size-786432 = [] | ||
| 181 | ## 1048576 | ||
| 182 | task-arena-size-1048576 = [] | ||
| 183 | |||
| 184 | # END AUTOGENERATED CONFIG FEATURES | ||
| 185 | |||
| 186 | #! </details> | ||
diff --git a/embassy-executor/README.md b/embassy-executor/README.md index aa9d59907..85f15edbb 100644 --- a/embassy-executor/README.md +++ b/embassy-executor/README.md | |||
| @@ -3,35 +3,10 @@ | |||
| 3 | An async/await executor designed for embedded usage. | 3 | An 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 | |||
| 16 | When the `nightly` Cargo feature is not enabled, `embassy-executor` allocates tasks out of an arena (a very simple bump allocator). | ||
| 17 | |||
| 18 | If 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 | |||
| 20 | Tasks 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 | |||
| 22 | The 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 | |||
| 30 | Environment variables take precedence over Cargo features. If two Cargo features are enabled for the same setting | ||
| 31 | with different values, compilation fails. | ||
| 32 | |||
| 33 | ## Statically allocating tasks | ||
| 34 | |||
| 35 | When 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 | |||
| 37 | The 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 @@ | |||
| 1 | use std::collections::HashMap; | ||
| 2 | use std::fmt::Write; | ||
| 3 | use std::path::PathBuf; | ||
| 4 | use std::{env, fs}; | ||
| 5 | |||
| 6 | #[path = "./build_common.rs"] | 1 | #[path = "./build_common.rs"] |
| 7 | mod common; | 2 | mod common; |
| 8 | 3 | ||
| 9 | static 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 | |||
| 16 | struct ConfigState { | ||
| 17 | value: usize, | ||
| 18 | seen_feature: bool, | ||
| 19 | seen_env: bool, | ||
| 20 | } | ||
| 21 | |||
| 22 | fn main() { | 4 | fn 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/build_common.rs b/embassy-executor/build_common.rs index 4f24e6d37..b15a8369f 100644 --- a/embassy-executor/build_common.rs +++ b/embassy-executor/build_common.rs | |||
| @@ -92,3 +92,35 @@ pub fn set_target_cfgs(cfgs: &mut CfgSet) { | |||
| 92 | 92 | ||
| 93 | cfgs.set("has_fpu", target.ends_with("-eabihf")); | 93 | cfgs.set("has_fpu", target.ends_with("-eabihf")); |
| 94 | } | 94 | } |
| 95 | |||
| 96 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] | ||
| 97 | pub struct CompilerDate { | ||
| 98 | year: u16, | ||
| 99 | month: u8, | ||
| 100 | day: u8, | ||
| 101 | } | ||
| 102 | |||
| 103 | impl CompilerDate { | ||
| 104 | fn parse(date: &str) -> Option<Self> { | ||
| 105 | let mut parts = date.split('-'); | ||
| 106 | let year = parts.next()?.parse().ok()?; | ||
| 107 | let month = parts.next()?.parse().ok()?; | ||
| 108 | let day = parts.next()?.parse().ok()?; | ||
| 109 | Some(Self { year, month, day }) | ||
| 110 | } | ||
| 111 | } | ||
| 112 | |||
| 113 | impl PartialEq<&str> for CompilerDate { | ||
| 114 | fn eq(&self, other: &&str) -> bool { | ||
| 115 | let Some(other) = Self::parse(other) else { | ||
| 116 | return false; | ||
| 117 | }; | ||
| 118 | self.eq(&other) | ||
| 119 | } | ||
| 120 | } | ||
| 121 | |||
| 122 | impl PartialOrd<&str> for CompilerDate { | ||
| 123 | fn partial_cmp(&self, other: &&str) -> Option<std::cmp::Ordering> { | ||
| 124 | Self::parse(other).map(|other| self.cmp(&other)) | ||
| 125 | } | ||
| 126 | } | ||
diff --git a/embassy-executor/src/arch/cortex_ar.rs b/embassy-executor/src/arch/cortex_ar.rs new file mode 100644 index 000000000..f9e2f3f7c --- /dev/null +++ b/embassy-executor/src/arch/cortex_ar.rs | |||
| @@ -0,0 +1,84 @@ | |||
| 1 | #[cfg(feature = "executor-interrupt")] | ||
| 2 | compile_error!("`executor-interrupt` is not supported with `arch-cortex-ar`."); | ||
| 3 | |||
| 4 | #[export_name = "__pender"] | ||
| 5 | #[cfg(any(feature = "executor-thread", feature = "executor-interrupt"))] | ||
| 6 | fn __pender(context: *mut ()) { | ||
| 7 | // `context` is always `usize::MAX` created by `Executor::run`. | ||
| 8 | let context = context as usize; | ||
| 9 | |||
| 10 | #[cfg(feature = "executor-thread")] | ||
| 11 | // Try to make Rust optimize the branching away if we only use thread mode. | ||
| 12 | if !cfg!(feature = "executor-interrupt") || context == THREAD_PENDER { | ||
| 13 | cortex_ar::asm::sev(); | ||
| 14 | return; | ||
| 15 | } | ||
| 16 | } | ||
| 17 | |||
| 18 | #[cfg(feature = "executor-thread")] | ||
| 19 | pub use thread::*; | ||
| 20 | #[cfg(feature = "executor-thread")] | ||
| 21 | mod thread { | ||
| 22 | pub(super) const THREAD_PENDER: usize = usize::MAX; | ||
| 23 | |||
| 24 | use core::marker::PhantomData; | ||
| 25 | |||
| 26 | use cortex_ar::asm::wfe; | ||
| 27 | pub use embassy_executor_macros::main_cortex_ar as main; | ||
| 28 | |||
| 29 | use crate::{raw, Spawner}; | ||
| 30 | |||
| 31 | /// Thread mode executor, using WFE/SEV. | ||
| 32 | /// | ||
| 33 | /// This is the simplest and most common kind of executor. It runs on | ||
| 34 | /// thread mode (at the lowest priority level), and uses the `WFE` ARM instruction | ||
| 35 | /// to sleep when it has no more work to do. When a task is woken, a `SEV` instruction | ||
| 36 | /// is executed, to make the `WFE` exit from sleep and poll the task. | ||
| 37 | /// | ||
| 38 | /// This executor allows for ultra low power consumption for chips where `WFE` | ||
| 39 | /// triggers low-power sleep without extra steps. If your chip requires extra steps, | ||
| 40 | /// you may use [`raw::Executor`] directly to program custom behavior. | ||
| 41 | pub struct Executor { | ||
| 42 | inner: raw::Executor, | ||
| 43 | not_send: PhantomData<*mut ()>, | ||
| 44 | } | ||
| 45 | |||
| 46 | impl Executor { | ||
| 47 | /// Create a new Executor. | ||
| 48 | pub fn new() -> Self { | ||
| 49 | Self { | ||
| 50 | inner: raw::Executor::new(THREAD_PENDER as *mut ()), | ||
| 51 | not_send: PhantomData, | ||
| 52 | } | ||
| 53 | } | ||
| 54 | |||
| 55 | /// Run the executor. | ||
| 56 | /// | ||
| 57 | /// The `init` closure is called with a [`Spawner`] that spawns tasks on | ||
| 58 | /// this executor. Use it to spawn the initial task(s). After `init` returns, | ||
| 59 | /// the executor starts running the tasks. | ||
| 60 | /// | ||
| 61 | /// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`), | ||
| 62 | /// for example by passing it as an argument to the initial tasks. | ||
| 63 | /// | ||
| 64 | /// This function requires `&'static mut self`. This means you have to store the | ||
| 65 | /// Executor instance in a place where it'll live forever and grants you mutable | ||
| 66 | /// access. There's a few ways to do this: | ||
| 67 | /// | ||
| 68 | /// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe) | ||
| 69 | /// - a `static mut` (unsafe) | ||
| 70 | /// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe) | ||
| 71 | /// | ||
| 72 | /// This function never returns. | ||
| 73 | pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! { | ||
| 74 | init(self.inner.spawner()); | ||
| 75 | |||
| 76 | loop { | ||
| 77 | unsafe { | ||
| 78 | self.inner.poll(); | ||
| 79 | } | ||
| 80 | wfe(); | ||
| 81 | } | ||
| 82 | } | ||
| 83 | } | ||
| 84 | } | ||
diff --git a/embassy-executor/src/arch/cortex_m.rs b/embassy-executor/src/arch/cortex_m.rs index 5c517e0a2..1c9ddd8a0 100644 --- a/embassy-executor/src/arch/cortex_m.rs +++ b/embassy-executor/src/arch/cortex_m.rs | |||
| @@ -143,7 +143,7 @@ mod interrupt { | |||
| 143 | /// If this is not the case, you may use an interrupt from any unused peripheral. | 143 | /// If this is not the case, you may use an interrupt from any unused peripheral. |
| 144 | /// | 144 | /// |
| 145 | /// It is somewhat more complex to use, it's recommended to use the thread-mode | 145 | /// It is somewhat more complex to use, it's recommended to use the thread-mode |
| 146 | /// [`Executor`] instead, if it works for your use case. | 146 | /// [`Executor`](crate::Executor) instead, if it works for your use case. |
| 147 | pub struct InterruptExecutor { | 147 | pub struct InterruptExecutor { |
| 148 | started: Mutex<Cell<bool>>, | 148 | started: Mutex<Cell<bool>>, |
| 149 | executor: UnsafeCell<MaybeUninit<raw::Executor>>, | 149 | executor: UnsafeCell<MaybeUninit<raw::Executor>>, |
| @@ -179,11 +179,11 @@ mod interrupt { | |||
| 179 | /// The executor keeps running in the background through the interrupt. | 179 | /// The executor keeps running in the background through the interrupt. |
| 180 | /// | 180 | /// |
| 181 | /// This returns a [`SendSpawner`] you can use to spawn tasks on it. A [`SendSpawner`] | 181 | /// This returns a [`SendSpawner`] you can use to spawn tasks on it. A [`SendSpawner`] |
| 182 | /// is returned instead of a [`Spawner`](embassy_executor::Spawner) because the executor effectively runs in a | 182 | /// is returned instead of a [`Spawner`](crate::Spawner) because the executor effectively runs in a |
| 183 | /// different "thread" (the interrupt), so spawning tasks on it is effectively | 183 | /// different "thread" (the interrupt), so spawning tasks on it is effectively |
| 184 | /// sending them. | 184 | /// sending them. |
| 185 | /// | 185 | /// |
| 186 | /// To obtain a [`Spawner`](embassy_executor::Spawner) for this executor, use [`Spawner::for_current_executor()`](embassy_executor::Spawner::for_current_executor()) from | 186 | /// To obtain a [`Spawner`](crate::Spawner) for this executor, use [`Spawner::for_current_executor()`](crate::Spawner::for_current_executor()) from |
| 187 | /// a task running in it. | 187 | /// a task running in it. |
| 188 | /// | 188 | /// |
| 189 | /// # Interrupt requirements | 189 | /// # Interrupt requirements |
| @@ -195,6 +195,7 @@ mod interrupt { | |||
| 195 | /// You must set the interrupt priority before calling this method. You MUST NOT | 195 | /// You must set the interrupt priority before calling this method. You MUST NOT |
| 196 | /// do it after. | 196 | /// do it after. |
| 197 | /// | 197 | /// |
| 198 | /// [`SendSpawner`]: crate::SendSpawner | ||
| 198 | pub fn start(&'static self, irq: impl InterruptNumber) -> crate::SendSpawner { | 199 | pub fn start(&'static self, irq: impl InterruptNumber) -> crate::SendSpawner { |
| 199 | if critical_section::with(|cs| self.started.borrow(cs).replace(true)) { | 200 | if critical_section::with(|cs| self.started.borrow(cs).replace(true)) { |
| 200 | panic!("InterruptExecutor::start() called multiple times on the same executor."); | 201 | panic!("InterruptExecutor::start() called multiple times on the same executor."); |
| @@ -215,7 +216,7 @@ mod interrupt { | |||
| 215 | 216 | ||
| 216 | /// Get a SendSpawner for this executor | 217 | /// Get a SendSpawner for this executor |
| 217 | /// | 218 | /// |
| 218 | /// This returns a [`SendSpawner`] you can use to spawn tasks on this | 219 | /// This returns a [`SendSpawner`](crate::SendSpawner) you can use to spawn tasks on this |
| 219 | /// executor. | 220 | /// executor. |
| 220 | /// | 221 | /// |
| 221 | /// This MUST only be called on an executor that has already been started. | 222 | /// This MUST only be called on an executor that has already been started. |
diff --git a/embassy-executor/src/arch/spin.rs b/embassy-executor/src/arch/spin.rs new file mode 100644 index 000000000..340023620 --- /dev/null +++ b/embassy-executor/src/arch/spin.rs | |||
| @@ -0,0 +1,58 @@ | |||
| 1 | #[cfg(feature = "executor-interrupt")] | ||
| 2 | compile_error!("`executor-interrupt` is not supported with `arch-spin`."); | ||
| 3 | |||
| 4 | #[cfg(feature = "executor-thread")] | ||
| 5 | pub use thread::*; | ||
| 6 | #[cfg(feature = "executor-thread")] | ||
| 7 | mod thread { | ||
| 8 | use core::marker::PhantomData; | ||
| 9 | |||
| 10 | pub use embassy_executor_macros::main_spin as main; | ||
| 11 | |||
| 12 | use crate::{raw, Spawner}; | ||
| 13 | |||
| 14 | #[export_name = "__pender"] | ||
| 15 | fn __pender(_context: *mut ()) {} | ||
| 16 | |||
| 17 | /// Spin Executor | ||
| 18 | pub struct Executor { | ||
| 19 | inner: raw::Executor, | ||
| 20 | not_send: PhantomData<*mut ()>, | ||
| 21 | } | ||
| 22 | |||
| 23 | impl Executor { | ||
| 24 | /// Create a new Executor. | ||
| 25 | pub fn new() -> Self { | ||
| 26 | Self { | ||
| 27 | inner: raw::Executor::new(core::ptr::null_mut()), | ||
| 28 | not_send: PhantomData, | ||
| 29 | } | ||
| 30 | } | ||
| 31 | |||
| 32 | /// Run the executor. | ||
| 33 | /// | ||
| 34 | /// The `init` closure is called with a [`Spawner`] that spawns tasks on | ||
| 35 | /// this executor. Use it to spawn the initial task(s). After `init` returns, | ||
| 36 | /// the executor starts running the tasks. | ||
| 37 | /// | ||
| 38 | /// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`), | ||
| 39 | /// for example by passing it as an argument to the initial tasks. | ||
| 40 | /// | ||
| 41 | /// This function requires `&'static mut self`. This means you have to store the | ||
| 42 | /// Executor instance in a place where it'll live forever and grants you mutable | ||
| 43 | /// access. There's a few ways to do this: | ||
| 44 | /// | ||
| 45 | /// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe) | ||
| 46 | /// - a `static mut` (unsafe) | ||
| 47 | /// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe) | ||
| 48 | /// | ||
| 49 | /// This function never returns. | ||
| 50 | pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! { | ||
| 51 | init(self.inner.spawner()); | ||
| 52 | |||
| 53 | loop { | ||
| 54 | unsafe { self.inner.poll() }; | ||
| 55 | } | ||
| 56 | } | ||
| 57 | } | ||
| 58 | } | ||
diff --git a/embassy-executor/src/lib.rs b/embassy-executor/src/lib.rs index 553ed76d3..dfe420bab 100644 --- a/embassy-executor/src/lib.rs +++ b/embassy-executor/src/lib.rs | |||
| @@ -1,5 +1,4 @@ | |||
| 1 | #![cfg_attr(not(any(feature = "arch-std", feature = "arch-wasm")), no_std)] | 1 | #![cfg_attr(not(any(feature = "arch-std", feature = "arch-wasm")), no_std)] |
| 2 | #![cfg_attr(feature = "nightly", feature(waker_getters))] | ||
| 3 | #![allow(clippy::new_without_default)] | 2 | #![allow(clippy::new_without_default)] |
| 4 | #![doc = include_str!("../README.md")] | 3 | #![doc = include_str!("../README.md")] |
| 5 | #![warn(missing_docs)] | 4 | #![warn(missing_docs)] |
| @@ -24,120 +23,186 @@ macro_rules! check_at_most_one { | |||
| 24 | check_at_most_one!(@amo [$($f)*] [$($f)*] []); | 23 | check_at_most_one!(@amo [$($f)*] [$($f)*] []); |
| 25 | }; | 24 | }; |
| 26 | } | 25 | } |
| 27 | check_at_most_one!("arch-avr", "arch-cortex-m", "arch-riscv32", "arch-std", "arch-wasm",); | 26 | check_at_most_one!( |
| 27 | "arch-avr", | ||
| 28 | "arch-cortex-m", | ||
| 29 | "arch-cortex-ar", | ||
| 30 | "arch-riscv32", | ||
| 31 | "arch-std", | ||
| 32 | "arch-wasm", | ||
| 33 | "arch-spin", | ||
| 34 | ); | ||
| 28 | 35 | ||
| 29 | #[cfg(feature = "_arch")] | 36 | #[cfg(feature = "_arch")] |
| 30 | #[cfg_attr(feature = "arch-avr", path = "arch/avr.rs")] | 37 | #[cfg_attr(feature = "arch-avr", path = "arch/avr.rs")] |
| 31 | #[cfg_attr(feature = "arch-cortex-m", path = "arch/cortex_m.rs")] | 38 | #[cfg_attr(feature = "arch-cortex-m", path = "arch/cortex_m.rs")] |
| 39 | #[cfg_attr(feature = "arch-cortex-ar", path = "arch/cortex_ar.rs")] | ||
| 32 | #[cfg_attr(feature = "arch-riscv32", path = "arch/riscv32.rs")] | 40 | #[cfg_attr(feature = "arch-riscv32", path = "arch/riscv32.rs")] |
| 33 | #[cfg_attr(feature = "arch-std", path = "arch/std.rs")] | 41 | #[cfg_attr(feature = "arch-std", path = "arch/std.rs")] |
| 34 | #[cfg_attr(feature = "arch-wasm", path = "arch/wasm.rs")] | 42 | #[cfg_attr(feature = "arch-wasm", path = "arch/wasm.rs")] |
| 43 | #[cfg_attr(feature = "arch-spin", path = "arch/spin.rs")] | ||
| 35 | mod arch; | 44 | mod arch; |
| 36 | 45 | ||
| 37 | #[cfg(feature = "_arch")] | 46 | #[cfg(feature = "_arch")] |
| 38 | #[allow(unused_imports)] // don't warn if the module is empty. | 47 | #[allow(unused_imports)] // don't warn if the module is empty. |
| 39 | pub use arch::*; | 48 | pub use arch::*; |
| 49 | #[cfg(not(feature = "_arch"))] | ||
| 50 | pub use embassy_executor_macros::main_unspecified as main; | ||
| 40 | 51 | ||
| 41 | pub mod raw; | 52 | pub mod raw; |
| 42 | 53 | ||
| 43 | mod spawner; | 54 | mod spawner; |
| 44 | pub use spawner::*; | 55 | pub use spawner::*; |
| 45 | 56 | ||
| 46 | mod config { | ||
| 47 | #![allow(unused)] | ||
| 48 | include!(concat!(env!("OUT_DIR"), "/config.rs")); | ||
| 49 | } | ||
| 50 | |||
| 51 | /// Implementation details for embassy macros. | 57 | /// Implementation details for embassy macros. |
| 52 | /// Do not use. Used for macros and HALs only. Not covered by semver guarantees. | 58 | /// Do not use. Used for macros and HALs only. Not covered by semver guarantees. |
| 53 | #[doc(hidden)] | 59 | #[doc(hidden)] |
| 54 | #[cfg(not(feature = "nightly"))] | 60 | #[cfg(not(feature = "nightly"))] |
| 55 | pub mod _export { | 61 | pub mod _export { |
| 56 | use core::alloc::Layout; | 62 | use core::cell::UnsafeCell; |
| 57 | use core::cell::{Cell, UnsafeCell}; | ||
| 58 | use core::future::Future; | 63 | use core::future::Future; |
| 59 | use core::mem::MaybeUninit; | 64 | use core::mem::MaybeUninit; |
| 60 | use core::ptr::null_mut; | ||
| 61 | |||
| 62 | use critical_section::{CriticalSection, Mutex}; | ||
| 63 | 65 | ||
| 64 | use crate::raw::TaskPool; | 66 | use crate::raw::TaskPool; |
| 65 | 67 | ||
| 66 | struct Arena<const N: usize> { | 68 | pub trait TaskFn<Args>: Copy { |
| 67 | buf: UnsafeCell<MaybeUninit<[u8; N]>>, | 69 | type Fut: Future + 'static; |
| 68 | ptr: Mutex<Cell<*mut u8>>, | ||
| 69 | } | 70 | } |
| 70 | 71 | ||
| 71 | unsafe impl<const N: usize> Sync for Arena<N> {} | 72 | macro_rules! task_fn_impl { |
| 72 | unsafe impl<const N: usize> Send for Arena<N> {} | 73 | ($($Tn:ident),*) => { |
| 73 | 74 | impl<F, Fut, $($Tn,)*> TaskFn<($($Tn,)*)> for F | |
| 74 | impl<const N: usize> Arena<N> { | 75 | where |
| 75 | const fn new() -> Self { | 76 | F: Copy + FnOnce($($Tn,)*) -> Fut, |
| 76 | Self { | 77 | Fut: Future + 'static, |
| 77 | buf: UnsafeCell::new(MaybeUninit::uninit()), | 78 | { |
| 78 | ptr: Mutex::new(Cell::new(null_mut())), | 79 | type Fut = Fut; |
| 79 | } | 80 | } |
| 80 | } | 81 | }; |
| 81 | 82 | } | |
| 82 | fn alloc<T>(&'static self, cs: CriticalSection) -> &'static mut MaybeUninit<T> { | ||
| 83 | let layout = Layout::new::<T>(); | ||
| 84 | |||
| 85 | let start = self.buf.get().cast::<u8>(); | ||
| 86 | let end = unsafe { start.add(N) }; | ||
| 87 | 83 | ||
| 88 | let mut ptr = self.ptr.borrow(cs).get(); | 84 | task_fn_impl!(); |
| 89 | if ptr.is_null() { | 85 | task_fn_impl!(T0); |
| 90 | ptr = self.buf.get().cast::<u8>(); | 86 | task_fn_impl!(T0, T1); |
| 91 | } | 87 | task_fn_impl!(T0, T1, T2); |
| 88 | task_fn_impl!(T0, T1, T2, T3); | ||
| 89 | task_fn_impl!(T0, T1, T2, T3, T4); | ||
| 90 | task_fn_impl!(T0, T1, T2, T3, T4, T5); | ||
| 91 | task_fn_impl!(T0, T1, T2, T3, T4, T5, T6); | ||
| 92 | task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7); | ||
| 93 | task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8); | ||
| 94 | task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9); | ||
| 95 | task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10); | ||
| 96 | task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11); | ||
| 97 | task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12); | ||
| 98 | task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13); | ||
| 99 | task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14); | ||
| 100 | task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15); | ||
| 101 | |||
| 102 | #[allow(private_bounds)] | ||
| 103 | #[repr(C)] | ||
| 104 | pub struct TaskPoolHolder<const SIZE: usize, const ALIGN: usize> | ||
| 105 | where | ||
| 106 | Align<ALIGN>: Alignment, | ||
| 107 | { | ||
| 108 | data: UnsafeCell<[MaybeUninit<u8>; SIZE]>, | ||
| 109 | align: Align<ALIGN>, | ||
| 110 | } | ||
| 92 | 111 | ||
| 93 | let bytes_left = (end as usize) - (ptr as usize); | 112 | unsafe impl<const SIZE: usize, const ALIGN: usize> Send for TaskPoolHolder<SIZE, ALIGN> where Align<ALIGN>: Alignment {} |
| 94 | let align_offset = (ptr as usize).next_multiple_of(layout.align()) - (ptr as usize); | 113 | unsafe impl<const SIZE: usize, const ALIGN: usize> Sync for TaskPoolHolder<SIZE, ALIGN> where Align<ALIGN>: Alignment {} |
| 95 | 114 | ||
| 96 | if align_offset + layout.size() > bytes_left { | 115 | #[allow(private_bounds)] |
| 97 | panic!("embassy-executor: task arena is full. You must increase the arena size, see the documentation for details: https://docs.embassy.dev/embassy-executor/"); | 116 | impl<const SIZE: usize, const ALIGN: usize> TaskPoolHolder<SIZE, ALIGN> |
| 98 | } | 117 | where |
| 118 | Align<ALIGN>: Alignment, | ||
| 119 | { | ||
| 120 | pub const fn get(&self) -> *const u8 { | ||
| 121 | self.data.get().cast() | ||
| 122 | } | ||
| 123 | } | ||
| 99 | 124 | ||
| 100 | let res = unsafe { ptr.add(align_offset) }; | 125 | pub const fn task_pool_size<F, Args, Fut, const POOL_SIZE: usize>(_: F) -> usize |
| 101 | let ptr = unsafe { ptr.add(align_offset + layout.size()) }; | 126 | where |
| 127 | F: TaskFn<Args, Fut = Fut>, | ||
| 128 | Fut: Future + 'static, | ||
| 129 | { | ||
| 130 | size_of::<TaskPool<Fut, POOL_SIZE>>() | ||
| 131 | } | ||
| 102 | 132 | ||
| 103 | self.ptr.borrow(cs).set(ptr); | 133 | pub const fn task_pool_align<F, Args, Fut, const POOL_SIZE: usize>(_: F) -> usize |
| 134 | where | ||
| 135 | F: TaskFn<Args, Fut = Fut>, | ||
| 136 | Fut: Future + 'static, | ||
| 137 | { | ||
| 138 | align_of::<TaskPool<Fut, POOL_SIZE>>() | ||
| 139 | } | ||
| 104 | 140 | ||
| 105 | unsafe { &mut *(res as *mut MaybeUninit<T>) } | 141 | pub const fn task_pool_new<F, Args, Fut, const POOL_SIZE: usize>(_: F) -> TaskPool<Fut, POOL_SIZE> |
| 106 | } | 142 | where |
| 143 | F: TaskFn<Args, Fut = Fut>, | ||
| 144 | Fut: Future + 'static, | ||
| 145 | { | ||
| 146 | TaskPool::new() | ||
| 107 | } | 147 | } |
| 108 | 148 | ||
| 109 | static ARENA: Arena<{ crate::config::TASK_ARENA_SIZE }> = Arena::new(); | 149 | #[allow(private_bounds)] |
| 150 | #[repr(transparent)] | ||
| 151 | pub struct Align<const N: usize>([<Self as Alignment>::Archetype; 0]) | ||
| 152 | where | ||
| 153 | Self: Alignment; | ||
| 110 | 154 | ||
| 111 | pub struct TaskPoolRef { | 155 | trait Alignment { |
| 112 | // type-erased `&'static mut TaskPool<F, N>` | 156 | /// A zero-sized type of particular alignment. |
| 113 | // Needed because statics can't have generics. | 157 | type Archetype: Copy + Eq + PartialEq + Send + Sync + Unpin; |
| 114 | ptr: Mutex<Cell<*mut ()>>, | ||
| 115 | } | 158 | } |
| 116 | unsafe impl Sync for TaskPoolRef {} | ||
| 117 | unsafe impl Send for TaskPoolRef {} | ||
| 118 | 159 | ||
| 119 | impl TaskPoolRef { | 160 | macro_rules! aligns { |
| 120 | pub const fn new() -> Self { | 161 | ($($AlignX:ident: $n:literal,)*) => { |
| 121 | Self { | 162 | $( |
| 122 | ptr: Mutex::new(Cell::new(null_mut())), | 163 | #[derive(Copy, Clone, Eq, PartialEq)] |
| 123 | } | 164 | #[repr(align($n))] |
| 124 | } | 165 | struct $AlignX {} |
| 125 | 166 | impl Alignment for Align<$n> { | |
| 126 | /// Get the pool for this ref, allocating it from the arena the first time. | 167 | type Archetype = $AlignX; |
| 127 | /// | ||
| 128 | /// safety: for a given TaskPoolRef instance, must always call with the exact | ||
| 129 | /// same generic params. | ||
| 130 | pub unsafe fn get<F: Future, const N: usize>(&'static self) -> &'static TaskPool<F, N> { | ||
| 131 | critical_section::with(|cs| { | ||
| 132 | let ptr = self.ptr.borrow(cs); | ||
| 133 | if ptr.get().is_null() { | ||
| 134 | let pool = ARENA.alloc::<TaskPool<F, N>>(cs); | ||
| 135 | pool.write(TaskPool::new()); | ||
| 136 | ptr.set(pool as *mut _ as _); | ||
| 137 | } | 168 | } |
| 138 | 169 | )* | |
| 139 | unsafe { &*(ptr.get() as *const _) } | 170 | }; |
| 140 | }) | ||
| 141 | } | ||
| 142 | } | 171 | } |
| 172 | |||
| 173 | aligns!( | ||
| 174 | Align1: 1, | ||
| 175 | Align2: 2, | ||
| 176 | Align4: 4, | ||
| 177 | Align8: 8, | ||
| 178 | Align16: 16, | ||
| 179 | Align32: 32, | ||
| 180 | Align64: 64, | ||
| 181 | Align128: 128, | ||
| 182 | Align256: 256, | ||
| 183 | Align512: 512, | ||
| 184 | Align1024: 1024, | ||
| 185 | Align2048: 2048, | ||
| 186 | Align4096: 4096, | ||
| 187 | Align8192: 8192, | ||
| 188 | Align16384: 16384, | ||
| 189 | ); | ||
| 190 | #[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))] | ||
| 191 | aligns!( | ||
| 192 | Align32768: 32768, | ||
| 193 | Align65536: 65536, | ||
| 194 | Align131072: 131072, | ||
| 195 | Align262144: 262144, | ||
| 196 | Align524288: 524288, | ||
| 197 | Align1048576: 1048576, | ||
| 198 | Align2097152: 2097152, | ||
| 199 | Align4194304: 4194304, | ||
| 200 | Align8388608: 8388608, | ||
| 201 | Align16777216: 16777216, | ||
| 202 | Align33554432: 33554432, | ||
| 203 | Align67108864: 67108864, | ||
| 204 | Align134217728: 134217728, | ||
| 205 | Align268435456: 268435456, | ||
| 206 | Align536870912: 536870912, | ||
| 207 | ); | ||
| 143 | } | 208 | } |
diff --git a/embassy-executor/src/raw/mod.rs b/embassy-executor/src/raw/mod.rs index d9ea5c005..913da2e25 100644 --- a/embassy-executor/src/raw/mod.rs +++ b/embassy-executor/src/raw/mod.rs | |||
| @@ -11,13 +11,14 @@ | |||
| 11 | #[cfg_attr(not(target_has_atomic = "ptr"), path = "run_queue_critical_section.rs")] | 11 | #[cfg_attr(not(target_has_atomic = "ptr"), path = "run_queue_critical_section.rs")] |
| 12 | mod run_queue; | 12 | mod run_queue; |
| 13 | 13 | ||
| 14 | #[cfg_attr(all(cortex_m, target_has_atomic = "8"), path = "state_atomics_arm.rs")] | 14 | #[cfg_attr(all(cortex_m, target_has_atomic = "32"), path = "state_atomics_arm.rs")] |
| 15 | #[cfg_attr(all(not(cortex_m), target_has_atomic = "8"), path = "state_atomics.rs")] | 15 | #[cfg_attr(all(not(cortex_m), target_has_atomic = "8"), path = "state_atomics.rs")] |
| 16 | #[cfg_attr(not(target_has_atomic = "8"), path = "state_critical_section.rs")] | 16 | #[cfg_attr(not(target_has_atomic = "8"), path = "state_critical_section.rs")] |
| 17 | mod state; | 17 | mod state; |
| 18 | 18 | ||
| 19 | #[cfg(feature = "integrated-timers")] | 19 | pub mod timer_queue; |
| 20 | mod timer_queue; | 20 | #[cfg(feature = "trace")] |
| 21 | pub mod trace; | ||
| 21 | pub(crate) mod util; | 22 | pub(crate) mod util; |
| 22 | #[cfg_attr(feature = "turbowakers", path = "waker_turbo.rs")] | 23 | #[cfg_attr(feature = "turbowakers", path = "waker_turbo.rs")] |
| 23 | mod waker; | 24 | mod waker; |
| @@ -27,12 +28,13 @@ use core::marker::PhantomData; | |||
| 27 | use core::mem; | 28 | use core::mem; |
| 28 | use core::pin::Pin; | 29 | use core::pin::Pin; |
| 29 | use core::ptr::NonNull; | 30 | use core::ptr::NonNull; |
| 31 | #[cfg(not(feature = "arch-avr"))] | ||
| 32 | use core::sync::atomic::AtomicPtr; | ||
| 33 | use core::sync::atomic::Ordering; | ||
| 30 | use core::task::{Context, Poll}; | 34 | use core::task::{Context, Poll}; |
| 31 | 35 | ||
| 32 | #[cfg(feature = "integrated-timers")] | 36 | #[cfg(feature = "arch-avr")] |
| 33 | use embassy_time_driver::AlarmHandle; | 37 | use portable_atomic::AtomicPtr; |
| 34 | #[cfg(feature = "rtos-trace")] | ||
| 35 | use rtos_trace::trace; | ||
| 36 | 38 | ||
| 37 | use self::run_queue::{RunQueue, RunQueueItem}; | 39 | use self::run_queue::{RunQueue, RunQueueItem}; |
| 38 | use self::state::State; | 40 | use self::state::State; |
| @@ -41,20 +43,62 @@ pub use self::waker::task_from_waker; | |||
| 41 | use super::SpawnToken; | 43 | use super::SpawnToken; |
| 42 | 44 | ||
| 43 | /// Raw task header for use in task pointers. | 45 | /// Raw task header for use in task pointers. |
| 46 | /// | ||
| 47 | /// A task can be in one of the following states: | ||
| 48 | /// | ||
| 49 | /// - Not spawned: the task is ready to spawn. | ||
| 50 | /// - `SPAWNED`: the task is currently spawned and may be running. | ||
| 51 | /// - `RUN_ENQUEUED`: the task is enqueued to be polled. Note that the task may be `!SPAWNED`. | ||
| 52 | /// In this case, the `RUN_ENQUEUED` state will be cleared when the task is next polled, without | ||
| 53 | /// polling the task's future. | ||
| 54 | /// | ||
| 55 | /// A task's complete life cycle is as follows: | ||
| 56 | /// | ||
| 57 | /// ```text | ||
| 58 | /// ┌────────────┐ ┌────────────────────────┐ | ||
| 59 | /// │Not spawned │◄─5┤Not spawned|Run enqueued│ | ||
| 60 | /// │ ├6─►│ │ | ||
| 61 | /// └─────┬──────┘ └──────▲─────────────────┘ | ||
| 62 | /// 1 │ | ||
| 63 | /// │ ┌────────────┘ | ||
| 64 | /// │ 4 | ||
| 65 | /// ┌─────▼────┴─────────┐ | ||
| 66 | /// │Spawned|Run enqueued│ | ||
| 67 | /// │ │ | ||
| 68 | /// └─────┬▲─────────────┘ | ||
| 69 | /// 2│ | ||
| 70 | /// │3 | ||
| 71 | /// ┌─────▼┴─────┐ | ||
| 72 | /// │ Spawned │ | ||
| 73 | /// │ │ | ||
| 74 | /// └────────────┘ | ||
| 75 | /// ``` | ||
| 76 | /// | ||
| 77 | /// Transitions: | ||
| 78 | /// - 1: Task is spawned - `AvailableTask::claim -> Executor::spawn` | ||
| 79 | /// - 2: During poll - `RunQueue::dequeue_all -> State::run_dequeue` | ||
| 80 | /// - 3: Task wakes itself, waker wakes task, or task exits - `Waker::wake -> wake_task -> State::run_enqueue` | ||
| 81 | /// - 4: A run-queued task exits - `TaskStorage::poll -> Poll::Ready` | ||
| 82 | /// - 5: Task is dequeued. The task's future is not polled, because exiting the task replaces its `poll_fn`. | ||
| 83 | /// - 6: A task is waken when it is not spawned - `wake_task -> State::run_enqueue` | ||
| 44 | pub(crate) struct TaskHeader { | 84 | pub(crate) struct TaskHeader { |
| 45 | pub(crate) state: State, | 85 | pub(crate) state: State, |
| 46 | pub(crate) run_queue_item: RunQueueItem, | 86 | pub(crate) run_queue_item: RunQueueItem, |
| 47 | pub(crate) executor: SyncUnsafeCell<Option<&'static SyncExecutor>>, | 87 | pub(crate) executor: AtomicPtr<SyncExecutor>, |
| 48 | poll_fn: SyncUnsafeCell<Option<unsafe fn(TaskRef)>>, | 88 | poll_fn: SyncUnsafeCell<Option<unsafe fn(TaskRef)>>, |
| 49 | 89 | ||
| 50 | #[cfg(feature = "integrated-timers")] | 90 | /// Integrated timer queue storage. This field should not be accessed outside of the timer queue. |
| 51 | pub(crate) expires_at: SyncUnsafeCell<u64>, | ||
| 52 | #[cfg(feature = "integrated-timers")] | ||
| 53 | pub(crate) timer_queue_item: timer_queue::TimerQueueItem, | 91 | pub(crate) timer_queue_item: timer_queue::TimerQueueItem, |
| 92 | #[cfg(feature = "trace")] | ||
| 93 | pub(crate) name: Option<&'static str>, | ||
| 94 | #[cfg(feature = "trace")] | ||
| 95 | pub(crate) id: u32, | ||
| 96 | #[cfg(feature = "trace")] | ||
| 97 | all_tasks_next: AtomicPtr<TaskHeader>, | ||
| 54 | } | 98 | } |
| 55 | 99 | ||
| 56 | /// This is essentially a `&'static TaskStorage<F>` where the type of the future has been erased. | 100 | /// This is essentially a `&'static TaskStorage<F>` where the type of the future has been erased. |
| 57 | #[derive(Clone, Copy)] | 101 | #[derive(Clone, Copy, PartialEq)] |
| 58 | pub struct TaskRef { | 102 | pub struct TaskRef { |
| 59 | ptr: NonNull<TaskHeader>, | 103 | ptr: NonNull<TaskHeader>, |
| 60 | } | 104 | } |
| @@ -76,10 +120,31 @@ impl TaskRef { | |||
| 76 | } | 120 | } |
| 77 | } | 121 | } |
| 78 | 122 | ||
| 123 | /// # Safety | ||
| 124 | /// | ||
| 125 | /// The result of this function must only be compared | ||
| 126 | /// for equality, or stored, but not used. | ||
| 127 | pub const unsafe fn dangling() -> Self { | ||
| 128 | Self { | ||
| 129 | ptr: NonNull::dangling(), | ||
| 130 | } | ||
| 131 | } | ||
| 132 | |||
| 79 | pub(crate) fn header(self) -> &'static TaskHeader { | 133 | pub(crate) fn header(self) -> &'static TaskHeader { |
| 80 | unsafe { self.ptr.as_ref() } | 134 | unsafe { self.ptr.as_ref() } |
| 81 | } | 135 | } |
| 82 | 136 | ||
| 137 | /// Returns a reference to the executor that the task is currently running on. | ||
| 138 | pub unsafe fn executor(self) -> Option<&'static Executor> { | ||
| 139 | let executor = self.header().executor.load(Ordering::Relaxed); | ||
| 140 | executor.as_ref().map(|e| Executor::wrap(e)) | ||
| 141 | } | ||
| 142 | |||
| 143 | /// Returns a reference to the timer queue item. | ||
| 144 | pub fn timer_queue_item(&self) -> &'static timer_queue::TimerQueueItem { | ||
| 145 | &self.header().timer_queue_item | ||
| 146 | } | ||
| 147 | |||
| 83 | /// The returned pointer is valid for the entire TaskStorage. | 148 | /// The returned pointer is valid for the entire TaskStorage. |
| 84 | pub(crate) fn as_ptr(self) -> *const TaskHeader { | 149 | pub(crate) fn as_ptr(self) -> *const TaskHeader { |
| 85 | self.ptr.as_ptr() | 150 | self.ptr.as_ptr() |
| @@ -107,6 +172,10 @@ pub struct TaskStorage<F: Future + 'static> { | |||
| 107 | future: UninitCell<F>, // Valid if STATE_SPAWNED | 172 | future: UninitCell<F>, // Valid if STATE_SPAWNED |
| 108 | } | 173 | } |
| 109 | 174 | ||
| 175 | unsafe fn poll_exited(_p: TaskRef) { | ||
| 176 | // Nothing to do, the task is already !SPAWNED and dequeued. | ||
| 177 | } | ||
| 178 | |||
| 110 | impl<F: Future + 'static> TaskStorage<F> { | 179 | impl<F: Future + 'static> TaskStorage<F> { |
| 111 | const NEW: Self = Self::new(); | 180 | const NEW: Self = Self::new(); |
| 112 | 181 | ||
| @@ -116,14 +185,17 @@ impl<F: Future + 'static> TaskStorage<F> { | |||
| 116 | raw: TaskHeader { | 185 | raw: TaskHeader { |
| 117 | state: State::new(), | 186 | state: State::new(), |
| 118 | run_queue_item: RunQueueItem::new(), | 187 | run_queue_item: RunQueueItem::new(), |
| 119 | executor: SyncUnsafeCell::new(None), | 188 | executor: AtomicPtr::new(core::ptr::null_mut()), |
| 120 | // Note: this is lazily initialized so that a static `TaskStorage` will go in `.bss` | 189 | // Note: this is lazily initialized so that a static `TaskStorage` will go in `.bss` |
| 121 | poll_fn: SyncUnsafeCell::new(None), | 190 | poll_fn: SyncUnsafeCell::new(None), |
| 122 | 191 | ||
| 123 | #[cfg(feature = "integrated-timers")] | ||
| 124 | expires_at: SyncUnsafeCell::new(0), | ||
| 125 | #[cfg(feature = "integrated-timers")] | ||
| 126 | timer_queue_item: timer_queue::TimerQueueItem::new(), | 192 | timer_queue_item: timer_queue::TimerQueueItem::new(), |
| 193 | #[cfg(feature = "trace")] | ||
| 194 | name: None, | ||
| 195 | #[cfg(feature = "trace")] | ||
| 196 | id: 0, | ||
| 197 | #[cfg(feature = "trace")] | ||
| 198 | all_tasks_next: AtomicPtr::new(core::ptr::null_mut()), | ||
| 127 | }, | 199 | }, |
| 128 | future: UninitCell::uninit(), | 200 | future: UninitCell::uninit(), |
| 129 | } | 201 | } |
| @@ -151,18 +223,30 @@ impl<F: Future + 'static> TaskStorage<F> { | |||
| 151 | } | 223 | } |
| 152 | 224 | ||
| 153 | unsafe fn poll(p: TaskRef) { | 225 | unsafe fn poll(p: TaskRef) { |
| 154 | let this = &*(p.as_ptr() as *const TaskStorage<F>); | 226 | let this = &*p.as_ptr().cast::<TaskStorage<F>>(); |
| 155 | 227 | ||
| 156 | let future = Pin::new_unchecked(this.future.as_mut()); | 228 | let future = Pin::new_unchecked(this.future.as_mut()); |
| 157 | let waker = waker::from_task(p); | 229 | let waker = waker::from_task(p); |
| 158 | let mut cx = Context::from_waker(&waker); | 230 | let mut cx = Context::from_waker(&waker); |
| 159 | match future.poll(&mut cx) { | 231 | match future.poll(&mut cx) { |
| 160 | Poll::Ready(_) => { | 232 | Poll::Ready(_) => { |
| 233 | #[cfg(feature = "trace")] | ||
| 234 | let exec_ptr: *const SyncExecutor = this.raw.executor.load(Ordering::Relaxed); | ||
| 235 | |||
| 236 | // As the future has finished and this function will not be called | ||
| 237 | // again, we can safely drop the future here. | ||
| 161 | this.future.drop_in_place(); | 238 | this.future.drop_in_place(); |
| 239 | |||
| 240 | // We replace the poll_fn with a despawn function, so that the task is cleaned up | ||
| 241 | // when the executor polls it next. | ||
| 242 | this.raw.poll_fn.set(Some(poll_exited)); | ||
| 243 | |||
| 244 | // Make sure we despawn last, so that other threads can only spawn the task | ||
| 245 | // after we're done with it. | ||
| 162 | this.raw.state.despawn(); | 246 | this.raw.state.despawn(); |
| 163 | 247 | ||
| 164 | #[cfg(feature = "integrated-timers")] | 248 | #[cfg(feature = "trace")] |
| 165 | this.raw.expires_at.set(u64::MAX); | 249 | trace::task_end(exec_ptr, &p); |
| 166 | } | 250 | } |
| 167 | Poll::Pending => {} | 251 | Poll::Pending => {} |
| 168 | } | 252 | } |
| @@ -316,26 +400,13 @@ impl Pender { | |||
| 316 | pub(crate) struct SyncExecutor { | 400 | pub(crate) struct SyncExecutor { |
| 317 | run_queue: RunQueue, | 401 | run_queue: RunQueue, |
| 318 | pender: Pender, | 402 | pender: Pender, |
| 319 | |||
| 320 | #[cfg(feature = "integrated-timers")] | ||
| 321 | pub(crate) timer_queue: timer_queue::TimerQueue, | ||
| 322 | #[cfg(feature = "integrated-timers")] | ||
| 323 | alarm: AlarmHandle, | ||
| 324 | } | 403 | } |
| 325 | 404 | ||
| 326 | impl SyncExecutor { | 405 | impl SyncExecutor { |
| 327 | pub(crate) fn new(pender: Pender) -> Self { | 406 | pub(crate) fn new(pender: Pender) -> Self { |
| 328 | #[cfg(feature = "integrated-timers")] | ||
| 329 | let alarm = unsafe { unwrap!(embassy_time_driver::allocate_alarm()) }; | ||
| 330 | |||
| 331 | Self { | 407 | Self { |
| 332 | run_queue: RunQueue::new(), | 408 | run_queue: RunQueue::new(), |
| 333 | pender, | 409 | pender, |
| 334 | |||
| 335 | #[cfg(feature = "integrated-timers")] | ||
| 336 | timer_queue: timer_queue::TimerQueue::new(), | ||
| 337 | #[cfg(feature = "integrated-timers")] | ||
| 338 | alarm, | ||
| 339 | } | 410 | } |
| 340 | } | 411 | } |
| 341 | 412 | ||
| @@ -346,90 +417,50 @@ impl SyncExecutor { | |||
| 346 | /// - `task` must be set up to run in this executor. | 417 | /// - `task` must be set up to run in this executor. |
| 347 | /// - `task` must NOT be already enqueued (in this executor or another one). | 418 | /// - `task` must NOT be already enqueued (in this executor or another one). |
| 348 | #[inline(always)] | 419 | #[inline(always)] |
| 349 | unsafe fn enqueue(&self, task: TaskRef) { | 420 | unsafe fn enqueue(&self, task: TaskRef, l: state::Token) { |
| 350 | #[cfg(feature = "rtos-trace")] | 421 | #[cfg(feature = "trace")] |
| 351 | trace::task_ready_begin(task.as_ptr() as u32); | 422 | trace::task_ready_begin(self, &task); |
| 352 | 423 | ||
| 353 | if self.run_queue.enqueue(task) { | 424 | if self.run_queue.enqueue(task, l) { |
| 354 | self.pender.pend(); | 425 | self.pender.pend(); |
| 355 | } | 426 | } |
| 356 | } | 427 | } |
| 357 | 428 | ||
| 358 | #[cfg(feature = "integrated-timers")] | ||
| 359 | fn alarm_callback(ctx: *mut ()) { | ||
| 360 | let this: &Self = unsafe { &*(ctx as *const Self) }; | ||
| 361 | this.pender.pend(); | ||
| 362 | } | ||
| 363 | |||
| 364 | pub(super) unsafe fn spawn(&'static self, task: TaskRef) { | 429 | pub(super) unsafe fn spawn(&'static self, task: TaskRef) { |
| 365 | task.header().executor.set(Some(self)); | 430 | task.header() |
| 431 | .executor | ||
| 432 | .store((self as *const Self).cast_mut(), Ordering::Relaxed); | ||
| 366 | 433 | ||
| 367 | #[cfg(feature = "rtos-trace")] | 434 | #[cfg(feature = "trace")] |
| 368 | trace::task_new(task.as_ptr() as u32); | 435 | trace::task_new(self, &task); |
| 369 | 436 | ||
| 370 | self.enqueue(task); | 437 | state::locked(|l| { |
| 438 | self.enqueue(task, l); | ||
| 439 | }) | ||
| 371 | } | 440 | } |
| 372 | 441 | ||
| 373 | /// # Safety | 442 | /// # Safety |
| 374 | /// | 443 | /// |
| 375 | /// Same as [`Executor::poll`], plus you must only call this on the thread this executor was created. | 444 | /// Same as [`Executor::poll`], plus you must only call this on the thread this executor was created. |
| 376 | pub(crate) unsafe fn poll(&'static self) { | 445 | pub(crate) unsafe fn poll(&'static self) { |
| 377 | #[cfg(feature = "integrated-timers")] | 446 | #[cfg(feature = "trace")] |
| 378 | embassy_time_driver::set_alarm_callback(self.alarm, Self::alarm_callback, self as *const _ as *mut ()); | 447 | trace::poll_start(self); |
| 379 | |||
| 380 | #[allow(clippy::never_loop)] | ||
| 381 | loop { | ||
| 382 | #[cfg(feature = "integrated-timers")] | ||
| 383 | self.timer_queue | ||
| 384 | .dequeue_expired(embassy_time_driver::now(), wake_task_no_pend); | ||
| 385 | |||
| 386 | self.run_queue.dequeue_all(|p| { | ||
| 387 | let task = p.header(); | ||
| 388 | |||
| 389 | #[cfg(feature = "integrated-timers")] | ||
| 390 | task.expires_at.set(u64::MAX); | ||
| 391 | |||
| 392 | if !task.state.run_dequeue() { | ||
| 393 | // If task is not running, ignore it. This can happen in the following scenario: | ||
| 394 | // - Task gets dequeued, poll starts | ||
| 395 | // - While task is being polled, it gets woken. It gets placed in the queue. | ||
| 396 | // - Task poll finishes, returning done=true | ||
| 397 | // - RUNNING bit is cleared, but the task is already in the queue. | ||
| 398 | return; | ||
| 399 | } | ||
| 400 | |||
| 401 | #[cfg(feature = "rtos-trace")] | ||
| 402 | trace::task_exec_begin(p.as_ptr() as u32); | ||
| 403 | |||
| 404 | // Run the task | ||
| 405 | task.poll_fn.get().unwrap_unchecked()(p); | ||
| 406 | |||
| 407 | #[cfg(feature = "rtos-trace")] | ||
| 408 | trace::task_exec_end(); | ||
| 409 | |||
| 410 | // Enqueue or update into timer_queue | ||
| 411 | #[cfg(feature = "integrated-timers")] | ||
| 412 | self.timer_queue.update(p); | ||
| 413 | }); | ||
| 414 | |||
| 415 | #[cfg(feature = "integrated-timers")] | ||
| 416 | { | ||
| 417 | // If this is already in the past, set_alarm might return false | ||
| 418 | // In that case do another poll loop iteration. | ||
| 419 | let next_expiration = self.timer_queue.next_expiration(); | ||
| 420 | if embassy_time_driver::set_alarm(self.alarm, next_expiration) { | ||
| 421 | break; | ||
| 422 | } | ||
| 423 | } | ||
| 424 | 448 | ||
| 425 | #[cfg(not(feature = "integrated-timers"))] | 449 | self.run_queue.dequeue_all(|p| { |
| 426 | { | 450 | let task = p.header(); |
| 427 | break; | 451 | |
| 428 | } | 452 | #[cfg(feature = "trace")] |
| 429 | } | 453 | trace::task_exec_begin(self, &p); |
| 430 | 454 | ||
| 431 | #[cfg(feature = "rtos-trace")] | 455 | // Run the task |
| 432 | trace::system_idle(); | 456 | task.poll_fn.get().unwrap_unchecked()(p); |
| 457 | |||
| 458 | #[cfg(feature = "trace")] | ||
| 459 | trace::task_exec_end(self, &p); | ||
| 460 | }); | ||
| 461 | |||
| 462 | #[cfg(feature = "trace")] | ||
| 463 | trace::executor_idle(self) | ||
| 433 | } | 464 | } |
| 434 | } | 465 | } |
| 435 | 466 | ||
| @@ -516,6 +547,8 @@ impl Executor { | |||
| 516 | /// | 547 | /// |
| 517 | /// # Safety | 548 | /// # Safety |
| 518 | /// | 549 | /// |
| 550 | /// You must call `initialize` before calling this method. | ||
| 551 | /// | ||
| 519 | /// You must NOT call `poll` reentrantly on the same executor. | 552 | /// You must NOT call `poll` reentrantly on the same executor. |
| 520 | /// | 553 | /// |
| 521 | /// In particular, note that `poll` may call the pender synchronously. Therefore, you | 554 | /// In particular, note that `poll` may call the pender synchronously. Therefore, you |
| @@ -533,6 +566,11 @@ impl Executor { | |||
| 533 | pub fn spawner(&'static self) -> super::Spawner { | 566 | pub fn spawner(&'static self) -> super::Spawner { |
| 534 | super::Spawner::new(self) | 567 | super::Spawner::new(self) |
| 535 | } | 568 | } |
| 569 | |||
| 570 | /// Get a unique ID for this Executor. | ||
| 571 | pub fn id(&'static self) -> usize { | ||
| 572 | &self.inner as *const SyncExecutor as usize | ||
| 573 | } | ||
| 536 | } | 574 | } |
| 537 | 575 | ||
| 538 | /// Wake a task by `TaskRef`. | 576 | /// Wake a task by `TaskRef`. |
| @@ -540,13 +578,13 @@ impl Executor { | |||
| 540 | /// You can obtain a `TaskRef` from a `Waker` using [`task_from_waker`]. | 578 | /// You can obtain a `TaskRef` from a `Waker` using [`task_from_waker`]. |
| 541 | pub fn wake_task(task: TaskRef) { | 579 | pub fn wake_task(task: TaskRef) { |
| 542 | let header = task.header(); | 580 | let header = task.header(); |
| 543 | if header.state.run_enqueue() { | 581 | header.state.run_enqueue(|l| { |
| 544 | // We have just marked the task as scheduled, so enqueue it. | 582 | // We have just marked the task as scheduled, so enqueue it. |
| 545 | unsafe { | 583 | unsafe { |
| 546 | let executor = header.executor.get().unwrap_unchecked(); | 584 | let executor = header.executor.load(Ordering::Relaxed).as_ref().unwrap_unchecked(); |
| 547 | executor.enqueue(task); | 585 | executor.enqueue(task, l); |
| 548 | } | 586 | } |
| 549 | } | 587 | }); |
| 550 | } | 588 | } |
| 551 | 589 | ||
| 552 | /// Wake a task by `TaskRef` without calling pend. | 590 | /// Wake a task by `TaskRef` without calling pend. |
| @@ -554,57 +592,11 @@ pub fn wake_task(task: TaskRef) { | |||
| 554 | /// You can obtain a `TaskRef` from a `Waker` using [`task_from_waker`]. | 592 | /// You can obtain a `TaskRef` from a `Waker` using [`task_from_waker`]. |
| 555 | pub fn wake_task_no_pend(task: TaskRef) { | 593 | pub fn wake_task_no_pend(task: TaskRef) { |
| 556 | let header = task.header(); | 594 | let header = task.header(); |
| 557 | if header.state.run_enqueue() { | 595 | header.state.run_enqueue(|l| { |
| 558 | // We have just marked the task as scheduled, so enqueue it. | 596 | // We have just marked the task as scheduled, so enqueue it. |
| 559 | unsafe { | 597 | unsafe { |
| 560 | let executor = header.executor.get().unwrap_unchecked(); | 598 | let executor = header.executor.load(Ordering::Relaxed).as_ref().unwrap_unchecked(); |
| 561 | executor.run_queue.enqueue(task); | 599 | executor.run_queue.enqueue(task, l); |
| 562 | } | 600 | } |
| 563 | } | 601 | }); |
| 564 | } | 602 | } |
| 565 | |||
| 566 | #[cfg(feature = "integrated-timers")] | ||
| 567 | struct TimerQueue; | ||
| 568 | |||
| 569 | #[cfg(feature = "integrated-timers")] | ||
| 570 | impl embassy_time_queue_driver::TimerQueue for TimerQueue { | ||
| 571 | fn schedule_wake(&'static self, at: u64, waker: &core::task::Waker) { | ||
| 572 | let task = waker::task_from_waker(waker); | ||
| 573 | let task = task.header(); | ||
| 574 | unsafe { | ||
| 575 | let expires_at = task.expires_at.get(); | ||
| 576 | task.expires_at.set(expires_at.min(at)); | ||
| 577 | } | ||
| 578 | } | ||
| 579 | } | ||
| 580 | |||
| 581 | #[cfg(feature = "integrated-timers")] | ||
| 582 | embassy_time_queue_driver::timer_queue_impl!(static TIMER_QUEUE: TimerQueue = TimerQueue); | ||
| 583 | |||
| 584 | #[cfg(all(feature = "rtos-trace", feature = "integrated-timers"))] | ||
| 585 | const fn gcd(a: u64, b: u64) -> u64 { | ||
| 586 | if b == 0 { | ||
| 587 | a | ||
| 588 | } else { | ||
| 589 | gcd(b, a % b) | ||
| 590 | } | ||
| 591 | } | ||
| 592 | |||
| 593 | #[cfg(feature = "rtos-trace")] | ||
| 594 | impl rtos_trace::RtosTraceOSCallbacks for Executor { | ||
| 595 | fn task_list() { | ||
| 596 | // We don't know what tasks exist, so we can't send them. | ||
| 597 | } | ||
| 598 | #[cfg(feature = "integrated-timers")] | ||
| 599 | fn time() -> u64 { | ||
| 600 | const GCD_1M: u64 = gcd(embassy_time_driver::TICK_HZ, 1_000_000); | ||
| 601 | embassy_time_driver::now() * (1_000_000 / GCD_1M) / (embassy_time_driver::TICK_HZ / GCD_1M) | ||
| 602 | } | ||
| 603 | #[cfg(not(feature = "integrated-timers"))] | ||
| 604 | fn time() -> u64 { | ||
| 605 | 0 | ||
| 606 | } | ||
| 607 | } | ||
| 608 | |||
| 609 | #[cfg(feature = "rtos-trace")] | ||
| 610 | rtos_trace::global_os_callbacks! {Executor} | ||
diff --git a/embassy-executor/src/raw/run_queue_atomics.rs b/embassy-executor/src/raw/run_queue_atomics.rs index 90907cfda..ce511d79a 100644 --- a/embassy-executor/src/raw/run_queue_atomics.rs +++ b/embassy-executor/src/raw/run_queue_atomics.rs | |||
| @@ -45,7 +45,7 @@ impl RunQueue { | |||
| 45 | /// | 45 | /// |
| 46 | /// `item` must NOT be already enqueued in any queue. | 46 | /// `item` must NOT be already enqueued in any queue. |
| 47 | #[inline(always)] | 47 | #[inline(always)] |
| 48 | pub(crate) unsafe fn enqueue(&self, task: TaskRef) -> bool { | 48 | pub(crate) unsafe fn enqueue(&self, task: TaskRef, _: super::state::Token) -> bool { |
| 49 | let mut was_empty = false; | 49 | let mut was_empty = false; |
| 50 | 50 | ||
| 51 | self.head | 51 | self.head |
| @@ -81,6 +81,7 @@ impl RunQueue { | |||
| 81 | // safety: there are no concurrent accesses to `next` | 81 | // safety: there are no concurrent accesses to `next` |
| 82 | next = unsafe { task.header().run_queue_item.next.get() }; | 82 | next = unsafe { task.header().run_queue_item.next.get() }; |
| 83 | 83 | ||
| 84 | task.header().state.run_dequeue(); | ||
| 84 | on_task(task); | 85 | on_task(task); |
| 85 | } | 86 | } |
| 86 | } | 87 | } |
diff --git a/embassy-executor/src/raw/run_queue_critical_section.rs b/embassy-executor/src/raw/run_queue_critical_section.rs index ba59c8f29..86c4085ed 100644 --- a/embassy-executor/src/raw/run_queue_critical_section.rs +++ b/embassy-executor/src/raw/run_queue_critical_section.rs | |||
| @@ -44,13 +44,11 @@ impl RunQueue { | |||
| 44 | /// | 44 | /// |
| 45 | /// `item` must NOT be already enqueued in any queue. | 45 | /// `item` must NOT be already enqueued in any queue. |
| 46 | #[inline(always)] | 46 | #[inline(always)] |
| 47 | pub(crate) unsafe fn enqueue(&self, task: TaskRef) -> bool { | 47 | pub(crate) unsafe fn enqueue(&self, task: TaskRef, cs: CriticalSection<'_>) -> bool { |
| 48 | critical_section::with(|cs| { | 48 | let prev = self.head.borrow(cs).replace(Some(task)); |
| 49 | let prev = self.head.borrow(cs).replace(Some(task)); | 49 | task.header().run_queue_item.next.borrow(cs).set(prev); |
| 50 | task.header().run_queue_item.next.borrow(cs).set(prev); | ||
| 51 | 50 | ||
| 52 | prev.is_none() | 51 | prev.is_none() |
| 53 | }) | ||
| 54 | } | 52 | } |
| 55 | 53 | ||
| 56 | /// Empty the queue, then call `on_task` for each task that was in the queue. | 54 | /// Empty the queue, then call `on_task` for each task that was in the queue. |
| @@ -65,9 +63,10 @@ impl RunQueue { | |||
| 65 | // If the task re-enqueues itself, the `next` pointer will get overwritten. | 63 | // If the task re-enqueues itself, the `next` pointer will get overwritten. |
| 66 | // Therefore, first read the next pointer, and only then process the task. | 64 | // Therefore, first read the next pointer, and only then process the task. |
| 67 | 65 | ||
| 68 | // safety: we know if the task is enqueued, no one else will touch the `next` pointer. | 66 | critical_section::with(|cs| { |
| 69 | let cs = unsafe { CriticalSection::new() }; | 67 | next = task.header().run_queue_item.next.borrow(cs).get(); |
| 70 | next = task.header().run_queue_item.next.borrow(cs).get(); | 68 | task.header().state.run_dequeue(cs); |
| 69 | }); | ||
| 71 | 70 | ||
| 72 | on_task(task); | 71 | on_task(task); |
| 73 | } | 72 | } |
diff --git a/embassy-executor/src/raw/state_atomics.rs b/embassy-executor/src/raw/state_atomics.rs index e1279ac0b..e813548ae 100644 --- a/embassy-executor/src/raw/state_atomics.rs +++ b/embassy-executor/src/raw/state_atomics.rs | |||
| @@ -1,21 +1,28 @@ | |||
| 1 | use core::sync::atomic::{AtomicU32, Ordering}; | 1 | use core::sync::atomic::{AtomicU8, Ordering}; |
| 2 | |||
| 3 | #[derive(Clone, Copy)] | ||
| 4 | pub(crate) struct Token(()); | ||
| 5 | |||
| 6 | /// Creates a token and passes it to the closure. | ||
| 7 | /// | ||
| 8 | /// This is a no-op replacement for `CriticalSection::with` because we don't need any locking. | ||
| 9 | pub(crate) fn locked<R>(f: impl FnOnce(Token) -> R) -> R { | ||
| 10 | f(Token(())) | ||
| 11 | } | ||
| 2 | 12 | ||
| 3 | /// Task is spawned (has a future) | 13 | /// Task is spawned (has a future) |
| 4 | pub(crate) const STATE_SPAWNED: u32 = 1 << 0; | 14 | pub(crate) const STATE_SPAWNED: u8 = 1 << 0; |
| 5 | /// Task is in the executor run queue | 15 | /// Task is in the executor run queue |
| 6 | pub(crate) const STATE_RUN_QUEUED: u32 = 1 << 1; | 16 | pub(crate) const STATE_RUN_QUEUED: u8 = 1 << 1; |
| 7 | /// Task is in the executor timer queue | ||
| 8 | #[cfg(feature = "integrated-timers")] | ||
| 9 | pub(crate) const STATE_TIMER_QUEUED: u32 = 1 << 2; | ||
| 10 | 17 | ||
| 11 | pub(crate) struct State { | 18 | pub(crate) struct State { |
| 12 | state: AtomicU32, | 19 | state: AtomicU8, |
| 13 | } | 20 | } |
| 14 | 21 | ||
| 15 | impl State { | 22 | impl State { |
| 16 | pub const fn new() -> State { | 23 | pub const fn new() -> State { |
| 17 | Self { | 24 | Self { |
| 18 | state: AtomicU32::new(0), | 25 | state: AtomicU8::new(0), |
| 19 | } | 26 | } |
| 20 | } | 27 | } |
| 21 | 28 | ||
| @@ -33,41 +40,19 @@ impl State { | |||
| 33 | self.state.fetch_and(!STATE_SPAWNED, Ordering::AcqRel); | 40 | self.state.fetch_and(!STATE_SPAWNED, Ordering::AcqRel); |
| 34 | } | 41 | } |
| 35 | 42 | ||
| 36 | /// Mark the task as run-queued if it's spawned and isn't already run-queued. Return true on success. | 43 | /// Mark the task as run-queued if it's spawned and isn't already run-queued. Run the given |
| 44 | /// function if the task was successfully marked. | ||
| 37 | #[inline(always)] | 45 | #[inline(always)] |
| 38 | pub fn run_enqueue(&self) -> bool { | 46 | pub fn run_enqueue(&self, f: impl FnOnce(Token)) { |
| 39 | self.state | 47 | let prev = self.state.fetch_or(STATE_RUN_QUEUED, Ordering::AcqRel); |
| 40 | .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |state| { | 48 | if prev & STATE_RUN_QUEUED == 0 { |
| 41 | // If already scheduled, or if not started, | 49 | locked(f); |
| 42 | if (state & STATE_RUN_QUEUED != 0) || (state & STATE_SPAWNED == 0) { | 50 | } |
| 43 | None | ||
| 44 | } else { | ||
| 45 | // Mark it as scheduled | ||
| 46 | Some(state | STATE_RUN_QUEUED) | ||
| 47 | } | ||
| 48 | }) | ||
| 49 | .is_ok() | ||
| 50 | } | 51 | } |
| 51 | 52 | ||
| 52 | /// Unmark the task as run-queued. Return whether the task is spawned. | 53 | /// Unmark the task as run-queued. Return whether the task is spawned. |
| 53 | #[inline(always)] | 54 | #[inline(always)] |
| 54 | pub fn run_dequeue(&self) -> bool { | 55 | pub fn run_dequeue(&self) { |
| 55 | let state = self.state.fetch_and(!STATE_RUN_QUEUED, Ordering::AcqRel); | 56 | self.state.fetch_and(!STATE_RUN_QUEUED, Ordering::AcqRel); |
| 56 | state & STATE_SPAWNED != 0 | ||
| 57 | } | ||
| 58 | |||
| 59 | /// Mark the task as timer-queued. Return whether it was newly queued (i.e. not queued before) | ||
| 60 | #[cfg(feature = "integrated-timers")] | ||
| 61 | #[inline(always)] | ||
| 62 | pub fn timer_enqueue(&self) -> bool { | ||
| 63 | let old_state = self.state.fetch_or(STATE_TIMER_QUEUED, Ordering::AcqRel); | ||
| 64 | old_state & STATE_TIMER_QUEUED == 0 | ||
| 65 | } | ||
| 66 | |||
| 67 | /// Unmark the task as timer-queued. | ||
| 68 | #[cfg(feature = "integrated-timers")] | ||
| 69 | #[inline(always)] | ||
| 70 | pub fn timer_dequeue(&self) { | ||
| 71 | self.state.fetch_and(!STATE_TIMER_QUEUED, Ordering::AcqRel); | ||
| 72 | } | 57 | } |
| 73 | } | 58 | } |
diff --git a/embassy-executor/src/raw/state_atomics_arm.rs b/embassy-executor/src/raw/state_atomics_arm.rs index e4dfe5093..b743dcc2c 100644 --- a/embassy-executor/src/raw/state_atomics_arm.rs +++ b/embassy-executor/src/raw/state_atomics_arm.rs | |||
| @@ -1,6 +1,15 @@ | |||
| 1 | use core::arch::asm; | ||
| 2 | use core::sync::atomic::{compiler_fence, AtomicBool, AtomicU32, Ordering}; | 1 | use core::sync::atomic::{compiler_fence, AtomicBool, AtomicU32, Ordering}; |
| 3 | 2 | ||
| 3 | #[derive(Clone, Copy)] | ||
| 4 | pub(crate) struct Token(()); | ||
| 5 | |||
| 6 | /// Creates a token and passes it to the closure. | ||
| 7 | /// | ||
| 8 | /// This is a no-op replacement for `CriticalSection::with` because we don't need any locking. | ||
| 9 | pub(crate) fn locked<R>(f: impl FnOnce(Token) -> R) -> R { | ||
| 10 | f(Token(())) | ||
| 11 | } | ||
| 12 | |||
| 4 | // Must be kept in sync with the layout of `State`! | 13 | // Must be kept in sync with the layout of `State`! |
| 5 | pub(crate) const STATE_SPAWNED: u32 = 1 << 0; | 14 | pub(crate) const STATE_SPAWNED: u32 = 1 << 0; |
| 6 | pub(crate) const STATE_RUN_QUEUED: u32 = 1 << 8; | 15 | pub(crate) const STATE_RUN_QUEUED: u32 = 1 << 8; |
| @@ -11,9 +20,8 @@ pub(crate) struct State { | |||
| 11 | spawned: AtomicBool, | 20 | spawned: AtomicBool, |
| 12 | /// Task is in the executor run queue | 21 | /// Task is in the executor run queue |
| 13 | run_queued: AtomicBool, | 22 | run_queued: AtomicBool, |
| 14 | /// Task is in the executor timer queue | ||
| 15 | timer_queued: AtomicBool, | ||
| 16 | pad: AtomicBool, | 23 | pad: AtomicBool, |
| 24 | pad2: AtomicBool, | ||
| 17 | } | 25 | } |
| 18 | 26 | ||
| 19 | impl State { | 27 | impl State { |
| @@ -21,8 +29,8 @@ impl State { | |||
| 21 | Self { | 29 | Self { |
| 22 | spawned: AtomicBool::new(false), | 30 | spawned: AtomicBool::new(false), |
| 23 | run_queued: AtomicBool::new(false), | 31 | run_queued: AtomicBool::new(false), |
| 24 | timer_queued: AtomicBool::new(false), | ||
| 25 | pad: AtomicBool::new(false), | 32 | pad: AtomicBool::new(false), |
| 33 | pad2: AtomicBool::new(false), | ||
| 26 | } | 34 | } |
| 27 | } | 35 | } |
| 28 | 36 | ||
| @@ -54,50 +62,22 @@ impl State { | |||
| 54 | self.spawned.store(false, Ordering::Relaxed); | 62 | self.spawned.store(false, Ordering::Relaxed); |
| 55 | } | 63 | } |
| 56 | 64 | ||
| 57 | /// Mark the task as run-queued if it's spawned and isn't already run-queued. Return true on success. | 65 | /// Mark the task as run-queued if it's spawned and isn't already run-queued. Run the given |
| 66 | /// function if the task was successfully marked. | ||
| 58 | #[inline(always)] | 67 | #[inline(always)] |
| 59 | pub fn run_enqueue(&self) -> bool { | 68 | pub fn run_enqueue(&self, f: impl FnOnce(Token)) { |
| 60 | unsafe { | 69 | let old = self.run_queued.swap(true, Ordering::AcqRel); |
| 61 | loop { | ||
| 62 | let state: u32; | ||
| 63 | asm!("ldrex {}, [{}]", out(reg) state, in(reg) self, options(nostack)); | ||
| 64 | 70 | ||
| 65 | if (state & STATE_RUN_QUEUED != 0) || (state & STATE_SPAWNED == 0) { | 71 | if !old { |
| 66 | asm!("clrex", options(nomem, nostack)); | 72 | locked(f); |
| 67 | return false; | ||
| 68 | } | ||
| 69 | |||
| 70 | let outcome: usize; | ||
| 71 | let new_state = state | STATE_RUN_QUEUED; | ||
| 72 | asm!("strex {}, {}, [{}]", out(reg) outcome, in(reg) new_state, in(reg) self, options(nostack)); | ||
| 73 | if outcome == 0 { | ||
| 74 | return true; | ||
| 75 | } | ||
| 76 | } | ||
| 77 | } | 73 | } |
| 78 | } | 74 | } |
| 79 | 75 | ||
| 80 | /// Unmark the task as run-queued. Return whether the task is spawned. | 76 | /// Unmark the task as run-queued. Return whether the task is spawned. |
| 81 | #[inline(always)] | 77 | #[inline(always)] |
| 82 | pub fn run_dequeue(&self) -> bool { | 78 | pub fn run_dequeue(&self) { |
| 83 | compiler_fence(Ordering::Release); | 79 | compiler_fence(Ordering::Release); |
| 84 | 80 | ||
| 85 | let r = self.spawned.load(Ordering::Relaxed); | ||
| 86 | self.run_queued.store(false, Ordering::Relaxed); | 81 | self.run_queued.store(false, Ordering::Relaxed); |
| 87 | r | ||
| 88 | } | ||
| 89 | |||
| 90 | /// Mark the task as timer-queued. Return whether it was newly queued (i.e. not queued before) | ||
| 91 | #[cfg(feature = "integrated-timers")] | ||
| 92 | #[inline(always)] | ||
| 93 | pub fn timer_enqueue(&self) -> bool { | ||
| 94 | !self.timer_queued.swap(true, Ordering::Relaxed) | ||
| 95 | } | ||
| 96 | |||
| 97 | /// Unmark the task as timer-queued. | ||
| 98 | #[cfg(feature = "integrated-timers")] | ||
| 99 | #[inline(always)] | ||
| 100 | pub fn timer_dequeue(&self) { | ||
| 101 | self.timer_queued.store(false, Ordering::Relaxed); | ||
| 102 | } | 82 | } |
| 103 | } | 83 | } |
diff --git a/embassy-executor/src/raw/state_critical_section.rs b/embassy-executor/src/raw/state_critical_section.rs index c3cc1b0b7..ec08f2f58 100644 --- a/embassy-executor/src/raw/state_critical_section.rs +++ b/embassy-executor/src/raw/state_critical_section.rs | |||
| @@ -1,17 +1,15 @@ | |||
| 1 | use core::cell::Cell; | 1 | use core::cell::Cell; |
| 2 | 2 | ||
| 3 | use critical_section::Mutex; | 3 | pub(crate) use critical_section::{with as locked, CriticalSection as Token}; |
| 4 | use critical_section::{CriticalSection, Mutex}; | ||
| 4 | 5 | ||
| 5 | /// Task is spawned (has a future) | 6 | /// Task is spawned (has a future) |
| 6 | pub(crate) const STATE_SPAWNED: u32 = 1 << 0; | 7 | pub(crate) const STATE_SPAWNED: u8 = 1 << 0; |
| 7 | /// Task is in the executor run queue | 8 | /// Task is in the executor run queue |
| 8 | pub(crate) const STATE_RUN_QUEUED: u32 = 1 << 1; | 9 | pub(crate) const STATE_RUN_QUEUED: u8 = 1 << 1; |
| 9 | /// Task is in the executor timer queue | ||
| 10 | #[cfg(feature = "integrated-timers")] | ||
| 11 | pub(crate) const STATE_TIMER_QUEUED: u32 = 1 << 2; | ||
| 12 | 10 | ||
| 13 | pub(crate) struct State { | 11 | pub(crate) struct State { |
| 14 | state: Mutex<Cell<u32>>, | 12 | state: Mutex<Cell<u8>>, |
| 15 | } | 13 | } |
| 16 | 14 | ||
| 17 | impl State { | 15 | impl State { |
| @@ -21,14 +19,16 @@ impl State { | |||
| 21 | } | 19 | } |
| 22 | } | 20 | } |
| 23 | 21 | ||
| 24 | fn update<R>(&self, f: impl FnOnce(&mut u32) -> R) -> R { | 22 | fn update<R>(&self, f: impl FnOnce(&mut u8) -> R) -> R { |
| 25 | critical_section::with(|cs| { | 23 | critical_section::with(|cs| self.update_with_cs(cs, f)) |
| 26 | let s = self.state.borrow(cs); | 24 | } |
| 27 | let mut val = s.get(); | 25 | |
| 28 | let r = f(&mut val); | 26 | fn update_with_cs<R>(&self, cs: CriticalSection<'_>, f: impl FnOnce(&mut u8) -> R) -> R { |
| 29 | s.set(val); | 27 | let s = self.state.borrow(cs); |
| 30 | r | 28 | let mut val = s.get(); |
| 31 | }) | 29 | let r = f(&mut val); |
| 30 | s.set(val); | ||
| 31 | r | ||
| 32 | } | 32 | } |
| 33 | 33 | ||
| 34 | /// If task is idle, mark it as spawned + run_queued and return true. | 34 | /// If task is idle, mark it as spawned + run_queued and return true. |
| @@ -50,44 +50,24 @@ impl State { | |||
| 50 | self.update(|s| *s &= !STATE_SPAWNED); | 50 | self.update(|s| *s &= !STATE_SPAWNED); |
| 51 | } | 51 | } |
| 52 | 52 | ||
| 53 | /// Mark the task as run-queued if it's spawned and isn't already run-queued. Return true on success. | 53 | /// Mark the task as run-queued if it's spawned and isn't already run-queued. Run the given |
| 54 | /// function if the task was successfully marked. | ||
| 54 | #[inline(always)] | 55 | #[inline(always)] |
| 55 | pub fn run_enqueue(&self) -> bool { | 56 | pub fn run_enqueue(&self, f: impl FnOnce(Token)) { |
| 56 | self.update(|s| { | 57 | critical_section::with(|cs| { |
| 57 | if (*s & STATE_RUN_QUEUED != 0) || (*s & STATE_SPAWNED == 0) { | 58 | if self.update_with_cs(cs, |s| { |
| 58 | false | 59 | let ok = *s & STATE_RUN_QUEUED == 0; |
| 59 | } else { | ||
| 60 | *s |= STATE_RUN_QUEUED; | 60 | *s |= STATE_RUN_QUEUED; |
| 61 | true | 61 | ok |
| 62 | }) { | ||
| 63 | f(cs); | ||
| 62 | } | 64 | } |
| 63 | }) | 65 | }); |
| 64 | } | 66 | } |
| 65 | 67 | ||
| 66 | /// Unmark the task as run-queued. Return whether the task is spawned. | 68 | /// Unmark the task as run-queued. Return whether the task is spawned. |
| 67 | #[inline(always)] | 69 | #[inline(always)] |
| 68 | pub fn run_dequeue(&self) -> bool { | 70 | pub fn run_dequeue(&self, cs: CriticalSection<'_>) { |
| 69 | self.update(|s| { | 71 | self.update_with_cs(cs, |s| *s &= !STATE_RUN_QUEUED) |
| 70 | let ok = *s & STATE_SPAWNED != 0; | ||
| 71 | *s &= !STATE_RUN_QUEUED; | ||
| 72 | ok | ||
| 73 | }) | ||
| 74 | } | ||
| 75 | |||
| 76 | /// Mark the task as timer-queued. Return whether it was newly queued (i.e. not queued before) | ||
| 77 | #[cfg(feature = "integrated-timers")] | ||
| 78 | #[inline(always)] | ||
| 79 | pub fn timer_enqueue(&self) -> bool { | ||
| 80 | self.update(|s| { | ||
| 81 | let ok = *s & STATE_TIMER_QUEUED == 0; | ||
| 82 | *s |= STATE_TIMER_QUEUED; | ||
| 83 | ok | ||
| 84 | }) | ||
| 85 | } | ||
| 86 | |||
| 87 | /// Unmark the task as timer-queued. | ||
| 88 | #[cfg(feature = "integrated-timers")] | ||
| 89 | #[inline(always)] | ||
| 90 | pub fn timer_dequeue(&self) { | ||
| 91 | self.update(|s| *s &= !STATE_TIMER_QUEUED); | ||
| 92 | } | 72 | } |
| 93 | } | 73 | } |
diff --git a/embassy-executor/src/raw/timer_queue.rs b/embassy-executor/src/raw/timer_queue.rs index 94a5f340b..e52453be4 100644 --- a/embassy-executor/src/raw/timer_queue.rs +++ b/embassy-executor/src/raw/timer_queue.rs | |||
| @@ -1,76 +1,73 @@ | |||
| 1 | use core::cmp::min; | 1 | //! Timer queue operations. |
| 2 | |||
| 3 | use core::cell::Cell; | ||
| 2 | 4 | ||
| 3 | use super::TaskRef; | 5 | use super::TaskRef; |
| 4 | use crate::raw::util::SyncUnsafeCell; | ||
| 5 | 6 | ||
| 6 | pub(crate) struct TimerQueueItem { | 7 | #[cfg(feature = "_timer-item-payload")] |
| 7 | next: SyncUnsafeCell<Option<TaskRef>>, | 8 | macro_rules! define_opaque { |
| 8 | } | 9 | ($size:tt) => { |
| 10 | /// An opaque data type. | ||
| 11 | #[repr(align($size))] | ||
| 12 | pub struct OpaqueData { | ||
| 13 | data: [u8; $size], | ||
| 14 | } | ||
| 9 | 15 | ||
| 10 | impl TimerQueueItem { | 16 | impl OpaqueData { |
| 11 | pub const fn new() -> Self { | 17 | const fn new() -> Self { |
| 12 | Self { | 18 | Self { data: [0; $size] } |
| 13 | next: SyncUnsafeCell::new(None), | 19 | } |
| 20 | |||
| 21 | /// Access the data as a reference to a type `T`. | ||
| 22 | /// | ||
| 23 | /// Safety: | ||
| 24 | /// | ||
| 25 | /// The caller must ensure that the size of the type `T` is less than, or equal to | ||
| 26 | /// the size of the payload, and must ensure that the alignment of the type `T` is | ||
| 27 | /// less than, or equal to the alignment of the payload. | ||
| 28 | /// | ||
| 29 | /// The type must be valid when zero-initialized. | ||
| 30 | pub unsafe fn as_ref<T>(&self) -> &T { | ||
| 31 | &*(self.data.as_ptr() as *const T) | ||
| 32 | } | ||
| 14 | } | 33 | } |
| 15 | } | 34 | }; |
| 16 | } | 35 | } |
| 17 | 36 | ||
| 18 | pub(crate) struct TimerQueue { | 37 | #[cfg(feature = "timer-item-payload-size-1")] |
| 19 | head: SyncUnsafeCell<Option<TaskRef>>, | 38 | define_opaque!(1); |
| 20 | } | 39 | #[cfg(feature = "timer-item-payload-size-2")] |
| 40 | define_opaque!(2); | ||
| 41 | #[cfg(feature = "timer-item-payload-size-4")] | ||
| 42 | define_opaque!(4); | ||
| 43 | #[cfg(feature = "timer-item-payload-size-8")] | ||
| 44 | define_opaque!(8); | ||
| 21 | 45 | ||
| 22 | impl TimerQueue { | 46 | /// An item in the timer queue. |
| 23 | pub const fn new() -> Self { | 47 | pub struct TimerQueueItem { |
| 24 | Self { | 48 | /// The next item in the queue. |
| 25 | head: SyncUnsafeCell::new(None), | 49 | /// |
| 26 | } | 50 | /// If this field contains `Some`, the item is in the queue. The last item in the queue has a |
| 27 | } | 51 | /// value of `Some(dangling_pointer)` |
| 52 | pub next: Cell<Option<TaskRef>>, | ||
| 28 | 53 | ||
| 29 | pub(crate) unsafe fn update(&self, p: TaskRef) { | 54 | /// The time at which this item expires. |
| 30 | let task = p.header(); | 55 | pub expires_at: Cell<u64>, |
| 31 | if task.expires_at.get() != u64::MAX { | ||
| 32 | if task.state.timer_enqueue() { | ||
| 33 | task.timer_queue_item.next.set(self.head.get()); | ||
| 34 | self.head.set(Some(p)); | ||
| 35 | } | ||
| 36 | } | ||
| 37 | } | ||
| 38 | 56 | ||
| 39 | pub(crate) unsafe fn next_expiration(&self) -> u64 { | 57 | /// Some implementation-defined, zero-initialized piece of data. |
| 40 | let mut res = u64::MAX; | 58 | #[cfg(feature = "_timer-item-payload")] |
| 41 | self.retain(|p| { | 59 | pub payload: OpaqueData, |
| 42 | let task = p.header(); | 60 | } |
| 43 | let expires = task.expires_at.get(); | ||
| 44 | res = min(res, expires); | ||
| 45 | expires != u64::MAX | ||
| 46 | }); | ||
| 47 | res | ||
| 48 | } | ||
| 49 | 61 | ||
| 50 | pub(crate) unsafe fn dequeue_expired(&self, now: u64, on_task: impl Fn(TaskRef)) { | 62 | unsafe impl Sync for TimerQueueItem {} |
| 51 | self.retain(|p| { | ||
| 52 | let task = p.header(); | ||
| 53 | if task.expires_at.get() <= now { | ||
| 54 | on_task(p); | ||
| 55 | false | ||
| 56 | } else { | ||
| 57 | true | ||
| 58 | } | ||
| 59 | }); | ||
| 60 | } | ||
| 61 | 63 | ||
| 62 | pub(crate) unsafe fn retain(&self, mut f: impl FnMut(TaskRef) -> bool) { | 64 | impl TimerQueueItem { |
| 63 | let mut prev = &self.head; | 65 | pub(crate) const fn new() -> Self { |
| 64 | while let Some(p) = prev.get() { | 66 | Self { |
| 65 | let task = p.header(); | 67 | next: Cell::new(None), |
| 66 | if f(p) { | 68 | expires_at: Cell::new(0), |
| 67 | // Skip to next | 69 | #[cfg(feature = "_timer-item-payload")] |
| 68 | prev = &task.timer_queue_item.next; | 70 | payload: OpaqueData::new(), |
| 69 | } else { | ||
| 70 | // Remove it | ||
| 71 | prev.set(task.timer_queue_item.next.get()); | ||
| 72 | task.state.timer_dequeue(); | ||
| 73 | } | ||
| 74 | } | 71 | } |
| 75 | } | 72 | } |
| 76 | } | 73 | } |
diff --git a/embassy-executor/src/raw/trace.rs b/embassy-executor/src/raw/trace.rs new file mode 100644 index 000000000..6c9cfda25 --- /dev/null +++ b/embassy-executor/src/raw/trace.rs | |||
| @@ -0,0 +1,412 @@ | |||
| 1 | //! # Tracing | ||
| 2 | //! | ||
| 3 | //! The `trace` feature enables a number of callbacks that can be used to track the | ||
| 4 | //! lifecycle of tasks and/or executors. | ||
| 5 | //! | ||
| 6 | //! Callbacks will have one or both of the following IDs passed to them: | ||
| 7 | //! | ||
| 8 | //! 1. A `task_id`, a `u32` value unique to a task for the duration of the time it is valid | ||
| 9 | //! 2. An `executor_id`, a `u32` value unique to an executor for the duration of the time it is | ||
| 10 | //! valid | ||
| 11 | //! | ||
| 12 | //! Today, both `task_id` and `executor_id` are u32s containing the least significant 32 bits of | ||
| 13 | //! the address of the task or executor, however this is NOT a stable guarantee, and MAY change | ||
| 14 | //! at any time. | ||
| 15 | //! | ||
| 16 | //! IDs are only guaranteed to be unique for the duration of time the item is valid. If a task | ||
| 17 | //! ends, and is re-spawned, it MAY or MAY NOT have the same ID. For tasks, this valid time is defined | ||
| 18 | //! as the time between `_embassy_trace_task_new` and `_embassy_trace_task_end` for a given task. | ||
| 19 | //! For executors, this time is not defined, but is often "forever" for practical embedded | ||
| 20 | //! programs. | ||
| 21 | //! | ||
| 22 | //! Callbacks can be used by enabling the `trace` feature, and providing implementations of the | ||
| 23 | //! `extern "Rust"` functions below. All callbacks must be implemented. | ||
| 24 | //! | ||
| 25 | //! ## Task Tracing lifecycle | ||
| 26 | //! | ||
| 27 | //! ```text | ||
| 28 | //! ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ | ||
| 29 | //! │(1) │ | ||
| 30 | //! │ │ | ||
| 31 | //! ╔════▼════╗ (2) ┌─────────┐ (3) ┌─────────┐ │ | ||
| 32 | //! │ ║ SPAWNED ║────▶│ WAITING │────▶│ RUNNING │ | ||
| 33 | //! ╚═════════╝ └─────────┘ └─────────┘ │ | ||
| 34 | //! │ ▲ ▲ │ │ │ | ||
| 35 | //! │ (4) │ │(6) │ | ||
| 36 | //! │ │(7) └ ─ ─ ┘ │ │ | ||
| 37 | //! │ │ │ │ | ||
| 38 | //! │ ┌──────┐ (5) │ │ ┌─────┐ | ||
| 39 | //! │ IDLE │◀────────────────┘ └─▶│ END │ │ | ||
| 40 | //! │ └──────┘ └─────┘ | ||
| 41 | //! ┌──────────────────────┐ │ | ||
| 42 | //! └ ┤ Task Trace Lifecycle │─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ | ||
| 43 | //! └──────────────────────┘ | ||
| 44 | //! ``` | ||
| 45 | //! | ||
| 46 | //! 1. A task is spawned, `_embassy_trace_task_new` is called | ||
| 47 | //! 2. A task is enqueued for the first time, `_embassy_trace_task_ready_begin` is called | ||
| 48 | //! 3. A task is polled, `_embassy_trace_task_exec_begin` is called | ||
| 49 | //! 4. WHILE a task is polled, the task is re-awoken, and `_embassy_trace_task_ready_begin` is | ||
| 50 | //! called. The task does not IMMEDIATELY move state, until polling is complete and the | ||
| 51 | //! RUNNING state is existed. `_embassy_trace_task_exec_end` is called when polling is | ||
| 52 | //! complete, marking the transition to WAITING | ||
| 53 | //! 5. Polling is complete, `_embassy_trace_task_exec_end` is called | ||
| 54 | //! 6. The task has completed, and `_embassy_trace_task_end` is called | ||
| 55 | //! 7. A task is awoken, `_embassy_trace_task_ready_begin` is called | ||
| 56 | //! | ||
| 57 | //! ## Executor Tracing lifecycle | ||
| 58 | //! | ||
| 59 | //! ```text | ||
| 60 | //! ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ | ||
| 61 | //! │(1) │ | ||
| 62 | //! │ │ | ||
| 63 | //! ╔═══▼══╗ (2) ┌────────────┐ (3) ┌─────────┐ │ | ||
| 64 | //! │ ║ IDLE ║──────────▶│ SCHEDULING │──────▶│ POLLING │ | ||
| 65 | //! ╚══════╝ └────────────┘ └─────────┘ │ | ||
| 66 | //! │ ▲ │ ▲ │ | ||
| 67 | //! │ (5) │ │ (4) │ │ | ||
| 68 | //! │ └──────────────┘ └────────────┘ | ||
| 69 | //! ┌──────────────────────────┐ │ | ||
| 70 | //! └ ┤ Executor Trace Lifecycle │─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ | ||
| 71 | //! └──────────────────────────┘ | ||
| 72 | //! ``` | ||
| 73 | //! | ||
| 74 | //! 1. The executor is started (no associated trace) | ||
| 75 | //! 2. A task on this executor is awoken. `_embassy_trace_task_ready_begin` is called | ||
| 76 | //! when this occurs, and `_embassy_trace_poll_start` is called when the executor | ||
| 77 | //! actually begins running | ||
| 78 | //! 3. The executor has decided a task to poll. `_embassy_trace_task_exec_begin` is called | ||
| 79 | //! 4. The executor finishes polling the task. `_embassy_trace_task_exec_end` is called | ||
| 80 | //! 5. The executor has finished polling tasks. `_embassy_trace_executor_idle` is called | ||
| 81 | |||
| 82 | #![allow(unused)] | ||
| 83 | |||
| 84 | use core::cell::UnsafeCell; | ||
| 85 | use core::sync::atomic::{AtomicPtr, AtomicUsize, Ordering}; | ||
| 86 | |||
| 87 | use rtos_trace::TaskInfo; | ||
| 88 | |||
| 89 | use crate::raw::{SyncExecutor, TaskHeader, TaskRef}; | ||
| 90 | use crate::spawner::{SpawnError, SpawnToken, Spawner}; | ||
| 91 | |||
| 92 | /// Global task tracker instance | ||
| 93 | /// | ||
| 94 | /// This static provides access to the global task tracker which maintains | ||
| 95 | /// a list of all tasks in the system. It's automatically updated by the | ||
| 96 | /// task lifecycle hooks in the trace module. | ||
| 97 | pub static TASK_TRACKER: TaskTracker = TaskTracker::new(); | ||
| 98 | |||
| 99 | /// A thread-safe tracker for all tasks in the system | ||
| 100 | /// | ||
| 101 | /// This struct uses an intrusive linked list approach to track all tasks | ||
| 102 | /// without additional memory allocations. It maintains a global list of | ||
| 103 | /// tasks that can be traversed to find all currently existing tasks. | ||
| 104 | pub struct TaskTracker { | ||
| 105 | head: AtomicPtr<TaskHeader>, | ||
| 106 | } | ||
| 107 | |||
| 108 | impl TaskTracker { | ||
| 109 | /// Creates a new empty task tracker | ||
| 110 | /// | ||
| 111 | /// Initializes a tracker with no tasks in its list. | ||
| 112 | pub const fn new() -> Self { | ||
| 113 | Self { | ||
| 114 | head: AtomicPtr::new(core::ptr::null_mut()), | ||
| 115 | } | ||
| 116 | } | ||
| 117 | |||
| 118 | /// Adds a task to the tracker | ||
| 119 | /// | ||
| 120 | /// This method inserts a task at the head of the intrusive linked list. | ||
| 121 | /// The operation is thread-safe and lock-free, using atomic operations | ||
| 122 | /// to ensure consistency even when called from different contexts. | ||
| 123 | /// | ||
| 124 | /// # Arguments | ||
| 125 | /// * `task` - The task reference to add to the tracker | ||
| 126 | pub fn add(&self, task: TaskRef) { | ||
| 127 | let task_ptr = task.as_ptr() as *mut TaskHeader; | ||
| 128 | |||
| 129 | loop { | ||
| 130 | let current_head = self.head.load(Ordering::Acquire); | ||
| 131 | unsafe { | ||
| 132 | (*task_ptr).all_tasks_next.store(current_head, Ordering::Relaxed); | ||
| 133 | } | ||
| 134 | |||
| 135 | if self | ||
| 136 | .head | ||
| 137 | .compare_exchange(current_head, task_ptr, Ordering::Release, Ordering::Relaxed) | ||
| 138 | .is_ok() | ||
| 139 | { | ||
| 140 | break; | ||
| 141 | } | ||
| 142 | } | ||
| 143 | } | ||
| 144 | |||
| 145 | /// Performs an operation on each task in the tracker | ||
| 146 | /// | ||
| 147 | /// This method traverses the entire list of tasks and calls the provided | ||
| 148 | /// function for each task. This allows inspecting or processing all tasks | ||
| 149 | /// in the system without modifying the tracker's structure. | ||
| 150 | /// | ||
| 151 | /// # Arguments | ||
| 152 | /// * `f` - A function to call for each task in the tracker | ||
| 153 | pub fn for_each<F>(&self, mut f: F) | ||
| 154 | where | ||
| 155 | F: FnMut(TaskRef), | ||
| 156 | { | ||
| 157 | let mut current = self.head.load(Ordering::Acquire); | ||
| 158 | while !current.is_null() { | ||
| 159 | let task = unsafe { TaskRef::from_ptr(current) }; | ||
| 160 | f(task); | ||
| 161 | |||
| 162 | current = unsafe { (*current).all_tasks_next.load(Ordering::Acquire) }; | ||
| 163 | } | ||
| 164 | } | ||
| 165 | } | ||
| 166 | |||
| 167 | /// Extension trait for `TaskRef` that provides tracing functionality. | ||
| 168 | /// | ||
| 169 | /// This trait is only available when the `trace` feature is enabled. | ||
| 170 | /// It extends `TaskRef` with methods for accessing and modifying task identifiers | ||
| 171 | /// and names, which are useful for debugging, logging, and performance analysis. | ||
| 172 | pub trait TaskRefTrace { | ||
| 173 | /// Get the name for a task | ||
| 174 | fn name(&self) -> Option<&'static str>; | ||
| 175 | |||
| 176 | /// Set the name for a task | ||
| 177 | fn set_name(&self, name: Option<&'static str>); | ||
| 178 | |||
| 179 | /// Get the ID for a task | ||
| 180 | fn id(&self) -> u32; | ||
| 181 | |||
| 182 | /// Set the ID for a task | ||
| 183 | fn set_id(&self, id: u32); | ||
| 184 | } | ||
| 185 | |||
| 186 | impl TaskRefTrace for TaskRef { | ||
| 187 | fn name(&self) -> Option<&'static str> { | ||
| 188 | self.header().name | ||
| 189 | } | ||
| 190 | |||
| 191 | fn set_name(&self, name: Option<&'static str>) { | ||
| 192 | unsafe { | ||
| 193 | let header_ptr = self.ptr.as_ptr() as *mut TaskHeader; | ||
| 194 | (*header_ptr).name = name; | ||
| 195 | } | ||
| 196 | } | ||
| 197 | |||
| 198 | fn id(&self) -> u32 { | ||
| 199 | self.header().id | ||
| 200 | } | ||
| 201 | |||
| 202 | fn set_id(&self, id: u32) { | ||
| 203 | unsafe { | ||
| 204 | let header_ptr = self.ptr.as_ptr() as *mut TaskHeader; | ||
| 205 | (*header_ptr).id = id; | ||
| 206 | } | ||
| 207 | } | ||
| 208 | } | ||
| 209 | |||
| 210 | #[cfg(not(feature = "rtos-trace"))] | ||
| 211 | extern "Rust" { | ||
| 212 | /// This callback is called when the executor begins polling. This will always | ||
| 213 | /// be paired with a later call to `_embassy_trace_executor_idle`. | ||
| 214 | /// | ||
| 215 | /// This marks the EXECUTOR state transition from IDLE -> SCHEDULING. | ||
| 216 | fn _embassy_trace_poll_start(executor_id: u32); | ||
| 217 | |||
| 218 | /// This callback is called AFTER a task is initialized/allocated, and BEFORE | ||
| 219 | /// it is enqueued to run for the first time. If the task ends (and does not | ||
| 220 | /// loop "forever"), there will be a matching call to `_embassy_trace_task_end`. | ||
| 221 | /// | ||
| 222 | /// Tasks start life in the SPAWNED state. | ||
| 223 | fn _embassy_trace_task_new(executor_id: u32, task_id: u32); | ||
| 224 | |||
| 225 | /// This callback is called AFTER a task is destructed/freed. This will always | ||
| 226 | /// have a prior matching call to `_embassy_trace_task_new`. | ||
| 227 | fn _embassy_trace_task_end(executor_id: u32, task_id: u32); | ||
| 228 | |||
| 229 | /// This callback is called AFTER a task has been dequeued from the runqueue, | ||
| 230 | /// and BEFORE the task is polled. There will always be a matching call to | ||
| 231 | /// `_embassy_trace_task_exec_end`. | ||
| 232 | /// | ||
| 233 | /// This marks the TASK state transition from WAITING -> RUNNING | ||
| 234 | /// This marks the EXECUTOR state transition from SCHEDULING -> POLLING | ||
| 235 | fn _embassy_trace_task_exec_begin(executor_id: u32, task_id: u32); | ||
| 236 | |||
| 237 | /// This callback is called AFTER a task has completed polling. There will | ||
| 238 | /// always be a matching call to `_embassy_trace_task_exec_begin`. | ||
| 239 | /// | ||
| 240 | /// This marks the TASK state transition from either: | ||
| 241 | /// * RUNNING -> IDLE - if there were no `_embassy_trace_task_ready_begin` events | ||
| 242 | /// for this task since the last `_embassy_trace_task_exec_begin` for THIS task | ||
| 243 | /// * RUNNING -> WAITING - if there WAS a `_embassy_trace_task_ready_begin` event | ||
| 244 | /// for this task since the last `_embassy_trace_task_exec_begin` for THIS task | ||
| 245 | /// | ||
| 246 | /// This marks the EXECUTOR state transition from POLLING -> SCHEDULING | ||
| 247 | fn _embassy_trace_task_exec_end(excutor_id: u32, task_id: u32); | ||
| 248 | |||
| 249 | /// This callback is called AFTER the waker for a task is awoken, and BEFORE it | ||
| 250 | /// is added to the run queue. | ||
| 251 | /// | ||
| 252 | /// If the given task is currently RUNNING, this marks no state change, BUT the | ||
| 253 | /// RUNNING task will then move to the WAITING stage when polling is complete. | ||
| 254 | /// | ||
| 255 | /// If the given task is currently IDLE, this marks the TASK state transition | ||
| 256 | /// from IDLE -> WAITING. | ||
| 257 | /// | ||
| 258 | /// NOTE: This may be called from an interrupt, outside the context of the current | ||
| 259 | /// task or executor. | ||
| 260 | fn _embassy_trace_task_ready_begin(executor_id: u32, task_id: u32); | ||
| 261 | |||
| 262 | /// This callback is called AFTER all dequeued tasks in a single call to poll | ||
| 263 | /// have been processed. This will always be paired with a call to | ||
| 264 | /// `_embassy_trace_executor_idle`. | ||
| 265 | /// | ||
| 266 | /// This marks the EXECUTOR state transition from SCHEDULING -> IDLE | ||
| 267 | fn _embassy_trace_executor_idle(executor_id: u32); | ||
| 268 | } | ||
| 269 | |||
| 270 | #[inline] | ||
| 271 | pub(crate) fn poll_start(executor: &SyncExecutor) { | ||
| 272 | #[cfg(not(feature = "rtos-trace"))] | ||
| 273 | unsafe { | ||
| 274 | _embassy_trace_poll_start(executor as *const _ as u32) | ||
| 275 | } | ||
| 276 | } | ||
| 277 | |||
| 278 | #[inline] | ||
| 279 | pub(crate) fn task_new(executor: &SyncExecutor, task: &TaskRef) { | ||
| 280 | #[cfg(not(feature = "rtos-trace"))] | ||
| 281 | unsafe { | ||
| 282 | _embassy_trace_task_new(executor as *const _ as u32, task.as_ptr() as u32) | ||
| 283 | } | ||
| 284 | |||
| 285 | #[cfg(feature = "rtos-trace")] | ||
| 286 | rtos_trace::trace::task_new(task.as_ptr() as u32); | ||
| 287 | |||
| 288 | #[cfg(feature = "rtos-trace")] | ||
| 289 | TASK_TRACKER.add(*task); | ||
| 290 | } | ||
| 291 | |||
| 292 | #[inline] | ||
| 293 | pub(crate) fn task_end(executor: *const SyncExecutor, task: &TaskRef) { | ||
| 294 | #[cfg(not(feature = "rtos-trace"))] | ||
| 295 | unsafe { | ||
| 296 | _embassy_trace_task_end(executor as u32, task.as_ptr() as u32) | ||
| 297 | } | ||
| 298 | } | ||
| 299 | |||
| 300 | #[inline] | ||
| 301 | pub(crate) fn task_ready_begin(executor: &SyncExecutor, task: &TaskRef) { | ||
| 302 | #[cfg(not(feature = "rtos-trace"))] | ||
| 303 | unsafe { | ||
| 304 | _embassy_trace_task_ready_begin(executor as *const _ as u32, task.as_ptr() as u32) | ||
| 305 | } | ||
| 306 | #[cfg(feature = "rtos-trace")] | ||
| 307 | rtos_trace::trace::task_ready_begin(task.as_ptr() as u32); | ||
| 308 | } | ||
| 309 | |||
| 310 | #[inline] | ||
| 311 | pub(crate) fn task_exec_begin(executor: &SyncExecutor, task: &TaskRef) { | ||
| 312 | #[cfg(not(feature = "rtos-trace"))] | ||
| 313 | unsafe { | ||
| 314 | _embassy_trace_task_exec_begin(executor as *const _ as u32, task.as_ptr() as u32) | ||
| 315 | } | ||
| 316 | #[cfg(feature = "rtos-trace")] | ||
| 317 | rtos_trace::trace::task_exec_begin(task.as_ptr() as u32); | ||
| 318 | } | ||
| 319 | |||
| 320 | #[inline] | ||
| 321 | pub(crate) fn task_exec_end(executor: &SyncExecutor, task: &TaskRef) { | ||
| 322 | #[cfg(not(feature = "rtos-trace"))] | ||
| 323 | unsafe { | ||
| 324 | _embassy_trace_task_exec_end(executor as *const _ as u32, task.as_ptr() as u32) | ||
| 325 | } | ||
| 326 | #[cfg(feature = "rtos-trace")] | ||
| 327 | rtos_trace::trace::task_exec_end(); | ||
| 328 | } | ||
| 329 | |||
| 330 | #[inline] | ||
| 331 | pub(crate) fn executor_idle(executor: &SyncExecutor) { | ||
| 332 | #[cfg(not(feature = "rtos-trace"))] | ||
| 333 | unsafe { | ||
| 334 | _embassy_trace_executor_idle(executor as *const _ as u32) | ||
| 335 | } | ||
| 336 | #[cfg(feature = "rtos-trace")] | ||
| 337 | rtos_trace::trace::system_idle(); | ||
| 338 | } | ||
| 339 | |||
| 340 | /// Returns an iterator over all active tasks in the system | ||
| 341 | /// | ||
| 342 | /// This function provides a convenient way to iterate over all tasks | ||
| 343 | /// that are currently tracked in the system. The returned iterator | ||
| 344 | /// yields each task in the global task tracker. | ||
| 345 | /// | ||
| 346 | /// # Returns | ||
| 347 | /// An iterator that yields `TaskRef` items for each task | ||
| 348 | fn get_all_active_tasks() -> impl Iterator<Item = TaskRef> + 'static { | ||
| 349 | struct TaskIterator<'a> { | ||
| 350 | tracker: &'a TaskTracker, | ||
| 351 | current: *mut TaskHeader, | ||
| 352 | } | ||
| 353 | |||
| 354 | impl<'a> Iterator for TaskIterator<'a> { | ||
| 355 | type Item = TaskRef; | ||
| 356 | |||
| 357 | fn next(&mut self) -> Option<Self::Item> { | ||
| 358 | if self.current.is_null() { | ||
| 359 | return None; | ||
| 360 | } | ||
| 361 | |||
| 362 | let task = unsafe { TaskRef::from_ptr(self.current) }; | ||
| 363 | self.current = unsafe { (*self.current).all_tasks_next.load(Ordering::Acquire) }; | ||
| 364 | |||
| 365 | Some(task) | ||
| 366 | } | ||
| 367 | } | ||
| 368 | |||
| 369 | TaskIterator { | ||
| 370 | tracker: &TASK_TRACKER, | ||
| 371 | current: TASK_TRACKER.head.load(Ordering::Acquire), | ||
| 372 | } | ||
| 373 | } | ||
| 374 | |||
| 375 | /// Perform an action on each active task | ||
| 376 | fn with_all_active_tasks<F>(f: F) | ||
| 377 | where | ||
| 378 | F: FnMut(TaskRef), | ||
| 379 | { | ||
| 380 | TASK_TRACKER.for_each(f); | ||
| 381 | } | ||
| 382 | |||
| 383 | #[cfg(feature = "rtos-trace")] | ||
| 384 | impl rtos_trace::RtosTraceOSCallbacks for crate::raw::SyncExecutor { | ||
| 385 | fn task_list() { | ||
| 386 | with_all_active_tasks(|task| { | ||
| 387 | let name = task.name().unwrap_or("unnamed task\0"); | ||
| 388 | let info = rtos_trace::TaskInfo { | ||
| 389 | name, | ||
| 390 | priority: 0, | ||
| 391 | stack_base: 0, | ||
| 392 | stack_size: 0, | ||
| 393 | }; | ||
| 394 | rtos_trace::trace::task_send_info(task.id(), info); | ||
| 395 | }); | ||
| 396 | } | ||
| 397 | fn time() -> u64 { | ||
| 398 | const fn gcd(a: u64, b: u64) -> u64 { | ||
| 399 | if b == 0 { | ||
| 400 | a | ||
| 401 | } else { | ||
| 402 | gcd(b, a % b) | ||
| 403 | } | ||
| 404 | } | ||
| 405 | |||
| 406 | const GCD_1M: u64 = gcd(embassy_time_driver::TICK_HZ, 1_000_000); | ||
| 407 | embassy_time_driver::now() * (1_000_000 / GCD_1M) / (embassy_time_driver::TICK_HZ / GCD_1M) | ||
| 408 | } | ||
| 409 | } | ||
| 410 | |||
| 411 | #[cfg(feature = "rtos-trace")] | ||
| 412 | rtos_trace::global_os_callbacks! {SyncExecutor} | ||
diff --git a/embassy-executor/src/raw/waker.rs b/embassy-executor/src/raw/waker.rs index 8d3910a25..d0d7b003d 100644 --- a/embassy-executor/src/raw/waker.rs +++ b/embassy-executor/src/raw/waker.rs | |||
| @@ -26,38 +26,17 @@ pub(crate) unsafe fn from_task(p: TaskRef) -> Waker { | |||
| 26 | /// (1 word) instead of full Wakers (2 words). This saves a bit of RAM and helps | 26 | /// (1 word) instead of full Wakers (2 words). This saves a bit of RAM and helps |
| 27 | /// avoid dynamic dispatch. | 27 | /// avoid dynamic dispatch. |
| 28 | /// | 28 | /// |
| 29 | /// You can use the returned task pointer to wake the task with [`wake_task`](super::wake_task). | 29 | /// You can use the returned task pointer to wake the task with [`wake_task`]. |
| 30 | /// | 30 | /// |
| 31 | /// # Panics | 31 | /// # Panics |
| 32 | /// | 32 | /// |
| 33 | /// Panics if the waker is not created by the Embassy executor. | 33 | /// Panics if the waker is not created by the Embassy executor. |
| 34 | pub fn task_from_waker(waker: &Waker) -> TaskRef { | 34 | pub fn task_from_waker(waker: &Waker) -> TaskRef { |
| 35 | let (vtable, data) = { | 35 | // make sure to compare vtable addresses. Doing `==` on the references |
| 36 | #[cfg(not(feature = "nightly"))] | 36 | // will compare the contents, which is slower. |
| 37 | { | 37 | if waker.vtable() as *const _ != &VTABLE as *const _ { |
| 38 | struct WakerHack { | ||
| 39 | data: *const (), | ||
| 40 | vtable: &'static RawWakerVTable, | ||
| 41 | } | ||
| 42 | |||
| 43 | // safety: OK because WakerHack has the same layout as Waker. | ||
| 44 | // This is not really guaranteed because the structs are `repr(Rust)`, it is | ||
| 45 | // indeed the case in the current implementation. | ||
| 46 | // TODO use waker_getters when stable. https://github.com/rust-lang/rust/issues/96992 | ||
| 47 | let hack: &WakerHack = unsafe { core::mem::transmute(waker) }; | ||
| 48 | (hack.vtable, hack.data) | ||
| 49 | } | ||
| 50 | |||
| 51 | #[cfg(feature = "nightly")] | ||
| 52 | { | ||
| 53 | let raw_waker = waker.as_raw(); | ||
| 54 | (raw_waker.vtable(), raw_waker.data()) | ||
| 55 | } | ||
| 56 | }; | ||
| 57 | |||
| 58 | if vtable != &VTABLE { | ||
| 59 | panic!("Found waker not created by the Embassy executor. `embassy_time::Timer` only works with the Embassy executor.") | 38 | panic!("Found waker not created by the Embassy executor. `embassy_time::Timer` only works with the Embassy executor.") |
| 60 | } | 39 | } |
| 61 | // safety: our wakers are always created with `TaskRef::as_ptr` | 40 | // safety: our wakers are always created with `TaskRef::as_ptr` |
| 62 | unsafe { TaskRef::from_ptr(data as *const TaskHeader) } | 41 | unsafe { TaskRef::from_ptr(waker.data() as *const TaskHeader) } |
| 63 | } | 42 | } |
diff --git a/embassy-executor/src/spawner.rs b/embassy-executor/src/spawner.rs index 271606244..522d97db3 100644 --- a/embassy-executor/src/spawner.rs +++ b/embassy-executor/src/spawner.rs | |||
| @@ -1,9 +1,12 @@ | |||
| 1 | use core::future::poll_fn; | 1 | use core::future::{poll_fn, Future}; |
| 2 | use core::marker::PhantomData; | 2 | use core::marker::PhantomData; |
| 3 | use core::mem; | 3 | use core::mem; |
| 4 | use core::sync::atomic::Ordering; | ||
| 4 | use core::task::Poll; | 5 | use core::task::Poll; |
| 5 | 6 | ||
| 6 | use super::raw; | 7 | use super::raw; |
| 8 | #[cfg(feature = "trace")] | ||
| 9 | use crate::raw::trace::TaskRefTrace; | ||
| 7 | 10 | ||
| 8 | /// Token to spawn a newly-created task in an executor. | 11 | /// Token to spawn a newly-created task in an executor. |
| 9 | /// | 12 | /// |
| @@ -21,7 +24,7 @@ use super::raw; | |||
| 21 | /// Once you've invoked a task function and obtained a SpawnToken, you *must* spawn it. | 24 | /// Once you've invoked a task function and obtained a SpawnToken, you *must* spawn it. |
| 22 | #[must_use = "Calling a task function does nothing on its own. You must spawn the returned SpawnToken, typically with Spawner::spawn()"] | 25 | #[must_use = "Calling a task function does nothing on its own. You must spawn the returned SpawnToken, typically with Spawner::spawn()"] |
| 23 | pub struct SpawnToken<S> { | 26 | pub struct SpawnToken<S> { |
| 24 | raw_task: Option<raw::TaskRef>, | 27 | pub(crate) raw_task: Option<raw::TaskRef>, |
| 25 | phantom: PhantomData<*mut S>, | 28 | phantom: PhantomData<*mut S>, |
| 26 | } | 29 | } |
| 27 | 30 | ||
| @@ -33,6 +36,15 @@ impl<S> SpawnToken<S> { | |||
| 33 | } | 36 | } |
| 34 | } | 37 | } |
| 35 | 38 | ||
| 39 | /// Returns the task id if available, otherwise 0 | ||
| 40 | /// This can be used in combination with rtos-trace to match task names with id's | ||
| 41 | pub fn id(&self) -> u32 { | ||
| 42 | match self.raw_task { | ||
| 43 | None => 0, | ||
| 44 | Some(t) => t.as_ptr() as u32, | ||
| 45 | } | ||
| 46 | } | ||
| 47 | |||
| 36 | /// Return a SpawnToken that represents a failed spawn. | 48 | /// Return a SpawnToken that represents a failed spawn. |
| 37 | pub fn new_failed() -> Self { | 49 | pub fn new_failed() -> Self { |
| 38 | Self { | 50 | Self { |
| @@ -50,8 +62,7 @@ impl<S> Drop for SpawnToken<S> { | |||
| 50 | } | 62 | } |
| 51 | 63 | ||
| 52 | /// Error returned when spawning a task. | 64 | /// Error returned when spawning a task. |
| 53 | #[derive(Copy, Clone, Debug)] | 65 | #[derive(Copy, Clone)] |
| 54 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 55 | pub enum SpawnError { | 66 | pub enum SpawnError { |
| 56 | /// Too many instances of this task are already running. | 67 | /// Too many instances of this task are already running. |
| 57 | /// | 68 | /// |
| @@ -61,6 +72,31 @@ pub enum SpawnError { | |||
| 61 | Busy, | 72 | Busy, |
| 62 | } | 73 | } |
| 63 | 74 | ||
| 75 | impl core::fmt::Debug for SpawnError { | ||
| 76 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||
| 77 | core::fmt::Display::fmt(self, f) | ||
| 78 | } | ||
| 79 | } | ||
| 80 | |||
| 81 | impl core::fmt::Display for SpawnError { | ||
| 82 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||
| 83 | match self { | ||
| 84 | SpawnError::Busy => write!(f, "Busy - Too many instances of this task are already running. Check the `pool_size` attribute of the task."), | ||
| 85 | } | ||
| 86 | } | ||
| 87 | } | ||
| 88 | |||
| 89 | #[cfg(feature = "defmt")] | ||
| 90 | impl defmt::Format for SpawnError { | ||
| 91 | fn format(&self, f: defmt::Formatter) { | ||
| 92 | match self { | ||
| 93 | SpawnError::Busy => defmt::write!(f, "Busy - Too many instances of this task are already running. Check the `pool_size` attribute of the task."), | ||
| 94 | } | ||
| 95 | } | ||
| 96 | } | ||
| 97 | |||
| 98 | impl core::error::Error for SpawnError {} | ||
| 99 | |||
| 64 | /// Handle to spawn tasks into an executor. | 100 | /// Handle to spawn tasks into an executor. |
| 65 | /// | 101 | /// |
| 66 | /// This Spawner can spawn any task (Send and non-Send ones), but it can | 102 | /// This Spawner can spawn any task (Send and non-Send ones), but it can |
| @@ -69,7 +105,7 @@ pub enum SpawnError { | |||
| 69 | /// If you want to spawn tasks from another thread, use [SendSpawner]. | 105 | /// If you want to spawn tasks from another thread, use [SendSpawner]. |
| 70 | #[derive(Copy, Clone)] | 106 | #[derive(Copy, Clone)] |
| 71 | pub struct Spawner { | 107 | pub struct Spawner { |
| 72 | executor: &'static raw::Executor, | 108 | pub(crate) executor: &'static raw::Executor, |
| 73 | not_send: PhantomData<*mut ()>, | 109 | not_send: PhantomData<*mut ()>, |
| 74 | } | 110 | } |
| 75 | 111 | ||
| @@ -89,14 +125,19 @@ impl Spawner { | |||
| 89 | /// # Panics | 125 | /// # Panics |
| 90 | /// | 126 | /// |
| 91 | /// Panics if the current executor is not an Embassy executor. | 127 | /// Panics if the current executor is not an Embassy executor. |
| 92 | pub async fn for_current_executor() -> Self { | 128 | pub fn for_current_executor() -> impl Future<Output = Self> { |
| 93 | poll_fn(|cx| { | 129 | poll_fn(|cx| { |
| 94 | let task = raw::task_from_waker(cx.waker()); | 130 | let task = raw::task_from_waker(cx.waker()); |
| 95 | let executor = unsafe { task.header().executor.get().unwrap_unchecked() }; | 131 | let executor = unsafe { |
| 132 | task.header() | ||
| 133 | .executor | ||
| 134 | .load(Ordering::Relaxed) | ||
| 135 | .as_ref() | ||
| 136 | .unwrap_unchecked() | ||
| 137 | }; | ||
| 96 | let executor = unsafe { raw::Executor::wrap(executor) }; | 138 | let executor = unsafe { raw::Executor::wrap(executor) }; |
| 97 | Poll::Ready(Self::new(executor)) | 139 | Poll::Ready(Self::new(executor)) |
| 98 | }) | 140 | }) |
| 99 | .await | ||
| 100 | } | 141 | } |
| 101 | 142 | ||
| 102 | /// Spawn a task into an executor. | 143 | /// Spawn a task into an executor. |
| @@ -134,6 +175,58 @@ impl Spawner { | |||
| 134 | pub fn make_send(&self) -> SendSpawner { | 175 | pub fn make_send(&self) -> SendSpawner { |
| 135 | SendSpawner::new(&self.executor.inner) | 176 | SendSpawner::new(&self.executor.inner) |
| 136 | } | 177 | } |
| 178 | |||
| 179 | /// Return the unique ID of this Spawner's Executor. | ||
| 180 | pub fn executor_id(&self) -> usize { | ||
| 181 | self.executor.id() | ||
| 182 | } | ||
| 183 | } | ||
| 184 | |||
| 185 | /// Extension trait adding tracing capabilities to the Spawner | ||
| 186 | /// | ||
| 187 | /// This trait provides an additional method to spawn tasks with an associated name, | ||
| 188 | /// which can be useful for debugging and tracing purposes. | ||
| 189 | pub trait SpawnerTraceExt { | ||
| 190 | /// Spawns a new task with a specified name. | ||
| 191 | /// | ||
| 192 | /// # Arguments | ||
| 193 | /// * `name` - Static string name to associate with the task | ||
| 194 | /// * `token` - Token representing the task to spawn | ||
| 195 | /// | ||
| 196 | /// # Returns | ||
| 197 | /// Result indicating whether the spawn was successful | ||
| 198 | fn spawn_named<S>(&self, name: &'static str, token: SpawnToken<S>) -> Result<(), SpawnError>; | ||
| 199 | } | ||
| 200 | |||
| 201 | /// Implementation of the SpawnerTraceExt trait for Spawner when trace is enabled | ||
| 202 | #[cfg(feature = "trace")] | ||
| 203 | impl SpawnerTraceExt for Spawner { | ||
| 204 | fn spawn_named<S>(&self, name: &'static str, token: SpawnToken<S>) -> Result<(), SpawnError> { | ||
| 205 | let task = token.raw_task; | ||
| 206 | core::mem::forget(token); | ||
| 207 | |||
| 208 | match task { | ||
| 209 | Some(task) => { | ||
| 210 | // Set the name and ID when trace is enabled | ||
| 211 | task.set_name(Some(name)); | ||
| 212 | let task_id = task.as_ptr() as u32; | ||
| 213 | task.set_id(task_id); | ||
| 214 | |||
| 215 | unsafe { self.executor.spawn(task) }; | ||
| 216 | Ok(()) | ||
| 217 | } | ||
| 218 | None => Err(SpawnError::Busy), | ||
| 219 | } | ||
| 220 | } | ||
| 221 | } | ||
| 222 | |||
| 223 | /// Implementation of the SpawnerTraceExt trait for Spawner when trace is disabled | ||
| 224 | #[cfg(not(feature = "trace"))] | ||
| 225 | impl SpawnerTraceExt for Spawner { | ||
| 226 | fn spawn_named<S>(&self, _name: &'static str, token: SpawnToken<S>) -> Result<(), SpawnError> { | ||
| 227 | // When trace is disabled, just forward to regular spawn and ignore the name | ||
| 228 | self.spawn(token) | ||
| 229 | } | ||
| 137 | } | 230 | } |
| 138 | 231 | ||
| 139 | /// Handle to spawn tasks into an executor from any thread. | 232 | /// Handle to spawn tasks into an executor from any thread. |
| @@ -161,13 +254,18 @@ impl SendSpawner { | |||
| 161 | /// # Panics | 254 | /// # Panics |
| 162 | /// | 255 | /// |
| 163 | /// Panics if the current executor is not an Embassy executor. | 256 | /// Panics if the current executor is not an Embassy executor. |
| 164 | pub async fn for_current_executor() -> Self { | 257 | pub fn for_current_executor() -> impl Future<Output = Self> { |
| 165 | poll_fn(|cx| { | 258 | poll_fn(|cx| { |
| 166 | let task = raw::task_from_waker(cx.waker()); | 259 | let task = raw::task_from_waker(cx.waker()); |
| 167 | let executor = unsafe { task.header().executor.get().unwrap_unchecked() }; | 260 | let executor = unsafe { |
| 261 | task.header() | ||
| 262 | .executor | ||
| 263 | .load(Ordering::Relaxed) | ||
| 264 | .as_ref() | ||
| 265 | .unwrap_unchecked() | ||
| 266 | }; | ||
| 168 | Poll::Ready(Self::new(executor)) | 267 | Poll::Ready(Self::new(executor)) |
| 169 | }) | 268 | }) |
| 170 | .await | ||
| 171 | } | 269 | } |
| 172 | 270 | ||
| 173 | /// Spawn a task into an executor. | 271 | /// Spawn a task into an executor. |
diff --git a/embassy-executor/tests/test.rs b/embassy-executor/tests/test.rs index 348cc7dc4..78c49c071 100644 --- a/embassy-executor/tests/test.rs +++ b/embassy-executor/tests/test.rs | |||
| @@ -40,6 +40,7 @@ fn setup() -> (&'static Executor, Trace) { | |||
| 40 | let trace = Trace::new(); | 40 | let trace = Trace::new(); |
| 41 | let context = Box::leak(Box::new(trace.clone())) as *mut _ as *mut (); | 41 | let context = Box::leak(Box::new(trace.clone())) as *mut _ as *mut (); |
| 42 | let executor = &*Box::leak(Box::new(Executor::new(context))); | 42 | let executor = &*Box::leak(Box::new(Executor::new(context))); |
| 43 | |||
| 43 | (executor, trace) | 44 | (executor, trace) |
| 44 | } | 45 | } |
| 45 | 46 | ||
| @@ -137,6 +138,139 @@ fn executor_task_self_wake_twice() { | |||
| 137 | } | 138 | } |
| 138 | 139 | ||
| 139 | #[test] | 140 | #[test] |
| 141 | fn waking_after_completion_does_not_poll() { | ||
| 142 | use embassy_sync::waitqueue::AtomicWaker; | ||
| 143 | |||
| 144 | #[task] | ||
| 145 | async fn task1(trace: Trace, waker: &'static AtomicWaker) { | ||
| 146 | poll_fn(|cx| { | ||
| 147 | trace.push("poll task1"); | ||
| 148 | waker.register(cx.waker()); | ||
| 149 | Poll::Ready(()) | ||
| 150 | }) | ||
| 151 | .await | ||
| 152 | } | ||
| 153 | |||
| 154 | let waker = Box::leak(Box::new(AtomicWaker::new())); | ||
| 155 | |||
| 156 | let (executor, trace) = setup(); | ||
| 157 | executor.spawner().spawn(task1(trace.clone(), waker)).unwrap(); | ||
| 158 | |||
| 159 | unsafe { executor.poll() }; | ||
| 160 | waker.wake(); | ||
| 161 | unsafe { executor.poll() }; | ||
| 162 | |||
| 163 | // Exited task may be waken but is not polled | ||
| 164 | waker.wake(); | ||
| 165 | waker.wake(); | ||
| 166 | unsafe { executor.poll() }; // Clears running status | ||
| 167 | |||
| 168 | // Can respawn waken-but-dead task | ||
| 169 | executor.spawner().spawn(task1(trace.clone(), waker)).unwrap(); | ||
| 170 | |||
| 171 | unsafe { executor.poll() }; | ||
| 172 | |||
| 173 | assert_eq!( | ||
| 174 | trace.get(), | ||
| 175 | &[ | ||
| 176 | "pend", // spawning a task pends the executor | ||
| 177 | "poll task1", // | ||
| 178 | "pend", // manual wake, gets cleared by poll | ||
| 179 | "pend", // manual wake, single pend for two wakes | ||
| 180 | "pend", // respawning a task pends the executor | ||
| 181 | "poll task1", // | ||
| 182 | ] | ||
| 183 | ) | ||
| 184 | } | ||
| 185 | |||
| 186 | #[test] | ||
| 187 | fn waking_with_old_waker_after_respawn() { | ||
| 188 | use embassy_sync::waitqueue::AtomicWaker; | ||
| 189 | |||
| 190 | async fn yield_now(trace: Trace) { | ||
| 191 | let mut yielded = false; | ||
| 192 | poll_fn(|cx| { | ||
| 193 | if yielded { | ||
| 194 | Poll::Ready(()) | ||
| 195 | } else { | ||
| 196 | trace.push("yield_now"); | ||
| 197 | yielded = true; | ||
| 198 | cx.waker().wake_by_ref(); | ||
| 199 | Poll::Pending | ||
| 200 | } | ||
| 201 | }) | ||
| 202 | .await | ||
| 203 | } | ||
| 204 | |||
| 205 | #[task] | ||
| 206 | async fn task1(trace: Trace, waker: &'static AtomicWaker) { | ||
| 207 | yield_now(trace.clone()).await; | ||
| 208 | poll_fn(|cx| { | ||
| 209 | trace.push("poll task1"); | ||
| 210 | waker.register(cx.waker()); | ||
| 211 | Poll::Ready(()) | ||
| 212 | }) | ||
| 213 | .await; | ||
| 214 | } | ||
| 215 | |||
| 216 | let waker = Box::leak(Box::new(AtomicWaker::new())); | ||
| 217 | |||
| 218 | let (executor, trace) = setup(); | ||
| 219 | executor.spawner().spawn(task1(trace.clone(), waker)).unwrap(); | ||
| 220 | |||
| 221 | unsafe { executor.poll() }; | ||
| 222 | unsafe { executor.poll() }; // progress to registering the waker | ||
| 223 | waker.wake(); | ||
| 224 | unsafe { executor.poll() }; | ||
| 225 | // Task has exited | ||
| 226 | |||
| 227 | assert_eq!( | ||
| 228 | trace.get(), | ||
| 229 | &[ | ||
| 230 | "pend", // spawning a task pends the executor | ||
| 231 | "yield_now", // | ||
| 232 | "pend", // yield_now wakes the task | ||
| 233 | "poll task1", // | ||
| 234 | "pend", // task self-wakes | ||
| 235 | ] | ||
| 236 | ); | ||
| 237 | |||
| 238 | // Can respawn task on another executor | ||
| 239 | let (other_executor, other_trace) = setup(); | ||
| 240 | other_executor | ||
| 241 | .spawner() | ||
| 242 | .spawn(task1(other_trace.clone(), waker)) | ||
| 243 | .unwrap(); | ||
| 244 | |||
| 245 | unsafe { other_executor.poll() }; // just run to the yield_now | ||
| 246 | waker.wake(); // trigger old waker registration | ||
| 247 | unsafe { executor.poll() }; | ||
| 248 | unsafe { other_executor.poll() }; | ||
| 249 | |||
| 250 | // First executor's trace has not changed | ||
| 251 | assert_eq!( | ||
| 252 | trace.get(), | ||
| 253 | &[ | ||
| 254 | "pend", // spawning a task pends the executor | ||
| 255 | "yield_now", // | ||
| 256 | "pend", // yield_now wakes the task | ||
| 257 | "poll task1", // | ||
| 258 | "pend", // task self-wakes | ||
| 259 | ] | ||
| 260 | ); | ||
| 261 | |||
| 262 | assert_eq!( | ||
| 263 | other_trace.get(), | ||
| 264 | &[ | ||
| 265 | "pend", // spawning a task pends the executor | ||
| 266 | "yield_now", // | ||
| 267 | "pend", // manual wake, gets cleared by poll | ||
| 268 | "poll task1", // | ||
| 269 | ] | ||
| 270 | ); | ||
| 271 | } | ||
| 272 | |||
| 273 | #[test] | ||
| 140 | fn executor_task_cfg_args() { | 274 | fn executor_task_cfg_args() { |
| 141 | // simulate cfg'ing away argument c | 275 | // simulate cfg'ing away argument c |
| 142 | #[task] | 276 | #[task] |
diff --git a/embassy-executor/tests/ui.rs b/embassy-executor/tests/ui.rs new file mode 100644 index 000000000..278a4b903 --- /dev/null +++ b/embassy-executor/tests/ui.rs | |||
| @@ -0,0 +1,24 @@ | |||
| 1 | #[cfg(not(miri))] | ||
| 2 | #[test] | ||
| 3 | fn ui() { | ||
| 4 | let t = trybuild::TestCases::new(); | ||
| 5 | t.compile_fail("tests/ui/abi.rs"); | ||
| 6 | t.compile_fail("tests/ui/bad_return.rs"); | ||
| 7 | t.compile_fail("tests/ui/generics.rs"); | ||
| 8 | t.compile_fail("tests/ui/impl_trait_nested.rs"); | ||
| 9 | t.compile_fail("tests/ui/impl_trait.rs"); | ||
| 10 | t.compile_fail("tests/ui/impl_trait_static.rs"); | ||
| 11 | t.compile_fail("tests/ui/nonstatic_ref_anon_nested.rs"); | ||
| 12 | t.compile_fail("tests/ui/nonstatic_ref_anon.rs"); | ||
| 13 | t.compile_fail("tests/ui/nonstatic_ref_elided.rs"); | ||
| 14 | t.compile_fail("tests/ui/nonstatic_ref_generic.rs"); | ||
| 15 | t.compile_fail("tests/ui/nonstatic_struct_anon.rs"); | ||
| 16 | #[cfg(not(feature = "nightly"))] // we can't catch this case with the macro, so the output changes on nightly. | ||
| 17 | t.compile_fail("tests/ui/nonstatic_struct_elided.rs"); | ||
| 18 | t.compile_fail("tests/ui/nonstatic_struct_generic.rs"); | ||
| 19 | t.compile_fail("tests/ui/not_async.rs"); | ||
| 20 | t.compile_fail("tests/ui/self_ref.rs"); | ||
| 21 | t.compile_fail("tests/ui/self.rs"); | ||
| 22 | t.compile_fail("tests/ui/type_error.rs"); | ||
| 23 | t.compile_fail("tests/ui/where_clause.rs"); | ||
| 24 | } | ||
diff --git a/embassy-executor/tests/ui/abi.rs b/embassy-executor/tests/ui/abi.rs new file mode 100644 index 000000000..fd52f7e41 --- /dev/null +++ b/embassy-executor/tests/ui/abi.rs | |||
| @@ -0,0 +1,8 @@ | |||
| 1 | #![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] | ||
| 2 | |||
| 3 | struct Foo<'a>(&'a ()); | ||
| 4 | |||
| 5 | #[embassy_executor::task] | ||
| 6 | async extern "C" fn task() {} | ||
| 7 | |||
| 8 | fn main() {} | ||
diff --git a/embassy-executor/tests/ui/abi.stderr b/embassy-executor/tests/ui/abi.stderr new file mode 100644 index 000000000..e264e371a --- /dev/null +++ b/embassy-executor/tests/ui/abi.stderr | |||
| @@ -0,0 +1,5 @@ | |||
| 1 | error: task functions must not have an ABI qualifier | ||
| 2 | --> tests/ui/abi.rs:6:1 | ||
| 3 | | | ||
| 4 | 6 | async extern "C" fn task() {} | ||
| 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
diff --git a/embassy-executor/tests/ui/bad_return.rs b/embassy-executor/tests/ui/bad_return.rs new file mode 100644 index 000000000..f09a5205b --- /dev/null +++ b/embassy-executor/tests/ui/bad_return.rs | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | #![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] | ||
| 2 | |||
| 3 | struct Foo<'a>(&'a ()); | ||
| 4 | |||
| 5 | #[embassy_executor::task] | ||
| 6 | async fn task() -> u32 { | ||
| 7 | 5 | ||
| 8 | } | ||
| 9 | |||
| 10 | fn main() {} | ||
diff --git a/embassy-executor/tests/ui/bad_return.stderr b/embassy-executor/tests/ui/bad_return.stderr new file mode 100644 index 000000000..e9d94dff8 --- /dev/null +++ b/embassy-executor/tests/ui/bad_return.stderr | |||
| @@ -0,0 +1,5 @@ | |||
| 1 | error: task functions must either not return a value, return `()` or return `!` | ||
| 2 | --> tests/ui/bad_return.rs:6:1 | ||
| 3 | | | ||
| 4 | 6 | async fn task() -> u32 { | ||
| 5 | | ^^^^^^^^^^^^^^^^^^^^^^ | ||
diff --git a/embassy-executor/tests/ui/generics.rs b/embassy-executor/tests/ui/generics.rs new file mode 100644 index 000000000..b83123bb1 --- /dev/null +++ b/embassy-executor/tests/ui/generics.rs | |||
| @@ -0,0 +1,8 @@ | |||
| 1 | #![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] | ||
| 2 | |||
| 3 | struct Foo<'a>(&'a ()); | ||
| 4 | |||
| 5 | #[embassy_executor::task] | ||
| 6 | async fn task<T: Sized>(_t: T) {} | ||
| 7 | |||
| 8 | fn main() {} | ||
diff --git a/embassy-executor/tests/ui/generics.stderr b/embassy-executor/tests/ui/generics.stderr new file mode 100644 index 000000000..197719a7b --- /dev/null +++ b/embassy-executor/tests/ui/generics.stderr | |||
| @@ -0,0 +1,5 @@ | |||
| 1 | error: task functions must not be generic | ||
| 2 | --> tests/ui/generics.rs:6:1 | ||
| 3 | | | ||
| 4 | 6 | async fn task<T: Sized>(_t: T) {} | ||
| 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
diff --git a/embassy-executor/tests/ui/impl_trait.rs b/embassy-executor/tests/ui/impl_trait.rs new file mode 100644 index 000000000..a21402aa0 --- /dev/null +++ b/embassy-executor/tests/ui/impl_trait.rs | |||
| @@ -0,0 +1,6 @@ | |||
| 1 | #![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] | ||
| 2 | |||
| 3 | #[embassy_executor::task] | ||
| 4 | async fn foo(_x: impl Sized) {} | ||
| 5 | |||
| 6 | fn main() {} | ||
diff --git a/embassy-executor/tests/ui/impl_trait.stderr b/embassy-executor/tests/ui/impl_trait.stderr new file mode 100644 index 000000000..099b1828f --- /dev/null +++ b/embassy-executor/tests/ui/impl_trait.stderr | |||
| @@ -0,0 +1,5 @@ | |||
| 1 | error: `impl Trait` is not allowed in task arguments. It is syntax sugar for generics, and tasks can't be generic. | ||
| 2 | --> tests/ui/impl_trait.rs:4:18 | ||
| 3 | | | ||
| 4 | 4 | async fn foo(_x: impl Sized) {} | ||
| 5 | | ^^^^^^^^^^ | ||
diff --git a/embassy-executor/tests/ui/impl_trait_nested.rs b/embassy-executor/tests/ui/impl_trait_nested.rs new file mode 100644 index 000000000..07442b8fa --- /dev/null +++ b/embassy-executor/tests/ui/impl_trait_nested.rs | |||
| @@ -0,0 +1,8 @@ | |||
| 1 | #![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] | ||
| 2 | |||
| 3 | struct Foo<T>(T); | ||
| 4 | |||
| 5 | #[embassy_executor::task] | ||
| 6 | async fn foo(_x: Foo<impl Sized + 'static>) {} | ||
| 7 | |||
| 8 | fn main() {} | ||
diff --git a/embassy-executor/tests/ui/impl_trait_nested.stderr b/embassy-executor/tests/ui/impl_trait_nested.stderr new file mode 100644 index 000000000..39592f958 --- /dev/null +++ b/embassy-executor/tests/ui/impl_trait_nested.stderr | |||
| @@ -0,0 +1,5 @@ | |||
| 1 | error: `impl Trait` is not allowed in task arguments. It is syntax sugar for generics, and tasks can't be generic. | ||
| 2 | --> tests/ui/impl_trait_nested.rs:6:22 | ||
| 3 | | | ||
| 4 | 6 | async fn foo(_x: Foo<impl Sized + 'static>) {} | ||
| 5 | | ^^^^^^^^^^^^^^^^^^^^ | ||
diff --git a/embassy-executor/tests/ui/impl_trait_static.rs b/embassy-executor/tests/ui/impl_trait_static.rs new file mode 100644 index 000000000..272470f98 --- /dev/null +++ b/embassy-executor/tests/ui/impl_trait_static.rs | |||
| @@ -0,0 +1,6 @@ | |||
| 1 | #![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] | ||
| 2 | |||
| 3 | #[embassy_executor::task] | ||
| 4 | async fn foo(_x: impl Sized + 'static) {} | ||
| 5 | |||
| 6 | fn main() {} | ||
diff --git a/embassy-executor/tests/ui/impl_trait_static.stderr b/embassy-executor/tests/ui/impl_trait_static.stderr new file mode 100644 index 000000000..0032a20c9 --- /dev/null +++ b/embassy-executor/tests/ui/impl_trait_static.stderr | |||
| @@ -0,0 +1,5 @@ | |||
| 1 | error: `impl Trait` is not allowed in task arguments. It is syntax sugar for generics, and tasks can't be generic. | ||
| 2 | --> tests/ui/impl_trait_static.rs:4:18 | ||
| 3 | | | ||
| 4 | 4 | async fn foo(_x: impl Sized + 'static) {} | ||
| 5 | | ^^^^^^^^^^^^^^^^^^^^ | ||
diff --git a/embassy-executor/tests/ui/nonstatic_ref_anon.rs b/embassy-executor/tests/ui/nonstatic_ref_anon.rs new file mode 100644 index 000000000..417c360a1 --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_ref_anon.rs | |||
| @@ -0,0 +1,6 @@ | |||
| 1 | #![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] | ||
| 2 | |||
| 3 | #[embassy_executor::task] | ||
| 4 | async fn foo(_x: &'_ u32) {} | ||
| 5 | |||
| 6 | fn main() {} | ||
diff --git a/embassy-executor/tests/ui/nonstatic_ref_anon.stderr b/embassy-executor/tests/ui/nonstatic_ref_anon.stderr new file mode 100644 index 000000000..0544de843 --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_ref_anon.stderr | |||
| @@ -0,0 +1,5 @@ | |||
| 1 | error: Arguments for tasks must live forever. Try using the `'static` lifetime. | ||
| 2 | --> tests/ui/nonstatic_ref_anon.rs:4:19 | ||
| 3 | | | ||
| 4 | 4 | async fn foo(_x: &'_ u32) {} | ||
| 5 | | ^^ | ||
diff --git a/embassy-executor/tests/ui/nonstatic_ref_anon_nested.rs b/embassy-executor/tests/ui/nonstatic_ref_anon_nested.rs new file mode 100644 index 000000000..175ebccc1 --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_ref_anon_nested.rs | |||
| @@ -0,0 +1,6 @@ | |||
| 1 | #![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] | ||
| 2 | |||
| 3 | #[embassy_executor::task] | ||
| 4 | async fn foo(_x: &'static &'_ u32) {} | ||
| 5 | |||
| 6 | fn main() {} | ||
diff --git a/embassy-executor/tests/ui/nonstatic_ref_anon_nested.stderr b/embassy-executor/tests/ui/nonstatic_ref_anon_nested.stderr new file mode 100644 index 000000000..79f262e6b --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_ref_anon_nested.stderr | |||
| @@ -0,0 +1,5 @@ | |||
| 1 | error: Arguments for tasks must live forever. Try using the `'static` lifetime. | ||
| 2 | --> tests/ui/nonstatic_ref_anon_nested.rs:4:28 | ||
| 3 | | | ||
| 4 | 4 | async fn foo(_x: &'static &'_ u32) {} | ||
| 5 | | ^^ | ||
diff --git a/embassy-executor/tests/ui/nonstatic_ref_elided.rs b/embassy-executor/tests/ui/nonstatic_ref_elided.rs new file mode 100644 index 000000000..cf49ad709 --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_ref_elided.rs | |||
| @@ -0,0 +1,6 @@ | |||
| 1 | #![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] | ||
| 2 | |||
| 3 | #[embassy_executor::task] | ||
| 4 | async fn foo(_x: &u32) {} | ||
| 5 | |||
| 6 | fn main() {} | ||
diff --git a/embassy-executor/tests/ui/nonstatic_ref_elided.stderr b/embassy-executor/tests/ui/nonstatic_ref_elided.stderr new file mode 100644 index 000000000..7e2b9eb7c --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_ref_elided.stderr | |||
| @@ -0,0 +1,5 @@ | |||
| 1 | error: Arguments for tasks must live forever. Try using the `'static` lifetime. | ||
| 2 | --> tests/ui/nonstatic_ref_elided.rs:4:18 | ||
| 3 | | | ||
| 4 | 4 | async fn foo(_x: &u32) {} | ||
| 5 | | ^ | ||
diff --git a/embassy-executor/tests/ui/nonstatic_ref_generic.rs b/embassy-executor/tests/ui/nonstatic_ref_generic.rs new file mode 100644 index 000000000..3f8a26cf8 --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_ref_generic.rs | |||
| @@ -0,0 +1,6 @@ | |||
| 1 | #![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] | ||
| 2 | |||
| 3 | #[embassy_executor::task] | ||
| 4 | async fn foo<'a>(_x: &'a u32) {} | ||
| 5 | |||
| 6 | fn main() {} | ||
diff --git a/embassy-executor/tests/ui/nonstatic_ref_generic.stderr b/embassy-executor/tests/ui/nonstatic_ref_generic.stderr new file mode 100644 index 000000000..af8491ad7 --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_ref_generic.stderr | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | error: task functions must not be generic | ||
| 2 | --> tests/ui/nonstatic_ref_generic.rs:4:1 | ||
| 3 | | | ||
| 4 | 4 | async fn foo<'a>(_x: &'a u32) {} | ||
| 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
| 6 | |||
| 7 | error: Arguments for tasks must live forever. Try using the `'static` lifetime. | ||
| 8 | --> tests/ui/nonstatic_ref_generic.rs:4:23 | ||
| 9 | | | ||
| 10 | 4 | async fn foo<'a>(_x: &'a u32) {} | ||
| 11 | | ^^ | ||
diff --git a/embassy-executor/tests/ui/nonstatic_struct_anon.rs b/embassy-executor/tests/ui/nonstatic_struct_anon.rs new file mode 100644 index 000000000..ba95d1459 --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_struct_anon.rs | |||
| @@ -0,0 +1,8 @@ | |||
| 1 | #![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] | ||
| 2 | |||
| 3 | struct Foo<'a>(&'a ()); | ||
| 4 | |||
| 5 | #[embassy_executor::task] | ||
| 6 | async fn task(_x: Foo<'_>) {} | ||
| 7 | |||
| 8 | fn main() {} | ||
diff --git a/embassy-executor/tests/ui/nonstatic_struct_anon.stderr b/embassy-executor/tests/ui/nonstatic_struct_anon.stderr new file mode 100644 index 000000000..5df2a6e06 --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_struct_anon.stderr | |||
| @@ -0,0 +1,5 @@ | |||
| 1 | error: Arguments for tasks must live forever. Try using the `'static` lifetime. | ||
| 2 | --> tests/ui/nonstatic_struct_anon.rs:6:23 | ||
| 3 | | | ||
| 4 | 6 | async fn task(_x: Foo<'_>) {} | ||
| 5 | | ^^ | ||
diff --git a/embassy-executor/tests/ui/nonstatic_struct_elided.rs b/embassy-executor/tests/ui/nonstatic_struct_elided.rs new file mode 100644 index 000000000..4cfe2966a --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_struct_elided.rs | |||
| @@ -0,0 +1,8 @@ | |||
| 1 | #![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] | ||
| 2 | |||
| 3 | struct Foo<'a>(&'a ()); | ||
| 4 | |||
| 5 | #[embassy_executor::task] | ||
| 6 | async fn task(_x: Foo) {} | ||
| 7 | |||
| 8 | fn main() {} | ||
diff --git a/embassy-executor/tests/ui/nonstatic_struct_elided.stderr b/embassy-executor/tests/ui/nonstatic_struct_elided.stderr new file mode 100644 index 000000000..099ef8b4e --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_struct_elided.stderr | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | error[E0726]: implicit elided lifetime not allowed here | ||
| 2 | --> tests/ui/nonstatic_struct_elided.rs:6:19 | ||
| 3 | | | ||
| 4 | 6 | async fn task(_x: Foo) {} | ||
| 5 | | ^^^ expected lifetime parameter | ||
| 6 | | | ||
| 7 | help: indicate the anonymous lifetime | ||
| 8 | | | ||
| 9 | 6 | async fn task(_x: Foo<'_>) {} | ||
| 10 | | ++++ | ||
diff --git a/embassy-executor/tests/ui/nonstatic_struct_generic.rs b/embassy-executor/tests/ui/nonstatic_struct_generic.rs new file mode 100644 index 000000000..ec3d908f6 --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_struct_generic.rs | |||
| @@ -0,0 +1,8 @@ | |||
| 1 | #![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] | ||
| 2 | |||
| 3 | struct Foo<'a>(&'a ()); | ||
| 4 | |||
| 5 | #[embassy_executor::task] | ||
| 6 | async fn task<'a>(_x: Foo<'a>) {} | ||
| 7 | |||
| 8 | fn main() {} | ||
diff --git a/embassy-executor/tests/ui/nonstatic_struct_generic.stderr b/embassy-executor/tests/ui/nonstatic_struct_generic.stderr new file mode 100644 index 000000000..61d5231bc --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_struct_generic.stderr | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | error: task functions must not be generic | ||
| 2 | --> tests/ui/nonstatic_struct_generic.rs:6:1 | ||
| 3 | | | ||
| 4 | 6 | async fn task<'a>(_x: Foo<'a>) {} | ||
| 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
| 6 | |||
| 7 | error: Arguments for tasks must live forever. Try using the `'static` lifetime. | ||
| 8 | --> tests/ui/nonstatic_struct_generic.rs:6:27 | ||
| 9 | | | ||
| 10 | 6 | async fn task<'a>(_x: Foo<'a>) {} | ||
| 11 | | ^^ | ||
diff --git a/embassy-executor/tests/ui/not_async.rs b/embassy-executor/tests/ui/not_async.rs new file mode 100644 index 000000000..f3f7e9bd2 --- /dev/null +++ b/embassy-executor/tests/ui/not_async.rs | |||
| @@ -0,0 +1,8 @@ | |||
| 1 | #![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] | ||
| 2 | |||
| 3 | struct Foo<'a>(&'a ()); | ||
| 4 | |||
| 5 | #[embassy_executor::task] | ||
| 6 | fn task() {} | ||
| 7 | |||
| 8 | fn main() {} | ||
diff --git a/embassy-executor/tests/ui/not_async.stderr b/embassy-executor/tests/ui/not_async.stderr new file mode 100644 index 000000000..27f040d9c --- /dev/null +++ b/embassy-executor/tests/ui/not_async.stderr | |||
| @@ -0,0 +1,5 @@ | |||
| 1 | error: task functions must be async | ||
| 2 | --> tests/ui/not_async.rs:6:1 | ||
| 3 | | | ||
| 4 | 6 | fn task() {} | ||
| 5 | | ^^^^^^^^^ | ||
diff --git a/embassy-executor/tests/ui/self.rs b/embassy-executor/tests/ui/self.rs new file mode 100644 index 000000000..f83a962d1 --- /dev/null +++ b/embassy-executor/tests/ui/self.rs | |||
| @@ -0,0 +1,8 @@ | |||
| 1 | #![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] | ||
| 2 | |||
| 3 | struct Foo<'a>(&'a ()); | ||
| 4 | |||
| 5 | #[embassy_executor::task] | ||
| 6 | async fn task(self) {} | ||
| 7 | |||
| 8 | fn main() {} | ||
diff --git a/embassy-executor/tests/ui/self.stderr b/embassy-executor/tests/ui/self.stderr new file mode 100644 index 000000000..aaf031573 --- /dev/null +++ b/embassy-executor/tests/ui/self.stderr | |||
| @@ -0,0 +1,13 @@ | |||
| 1 | error: task functions must not have `self` arguments | ||
| 2 | --> tests/ui/self.rs:6:15 | ||
| 3 | | | ||
| 4 | 6 | async fn task(self) {} | ||
| 5 | | ^^^^ | ||
| 6 | |||
| 7 | error: `self` parameter is only allowed in associated functions | ||
| 8 | --> tests/ui/self.rs:6:15 | ||
| 9 | | | ||
| 10 | 6 | async fn task(self) {} | ||
| 11 | | ^^^^ not semantically valid as function parameter | ||
| 12 | | | ||
| 13 | = note: associated functions are those in `impl` or `trait` definitions | ||
diff --git a/embassy-executor/tests/ui/self_ref.rs b/embassy-executor/tests/ui/self_ref.rs new file mode 100644 index 000000000..5e49bba5e --- /dev/null +++ b/embassy-executor/tests/ui/self_ref.rs | |||
| @@ -0,0 +1,8 @@ | |||
| 1 | #![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] | ||
| 2 | |||
| 3 | struct Foo<'a>(&'a ()); | ||
| 4 | |||
| 5 | #[embassy_executor::task] | ||
| 6 | async fn task(&mut self) {} | ||
| 7 | |||
| 8 | fn main() {} | ||
diff --git a/embassy-executor/tests/ui/self_ref.stderr b/embassy-executor/tests/ui/self_ref.stderr new file mode 100644 index 000000000..dd2052977 --- /dev/null +++ b/embassy-executor/tests/ui/self_ref.stderr | |||
| @@ -0,0 +1,13 @@ | |||
| 1 | error: task functions must not have `self` arguments | ||
| 2 | --> tests/ui/self_ref.rs:6:15 | ||
| 3 | | | ||
| 4 | 6 | async fn task(&mut self) {} | ||
| 5 | | ^^^^^^^^^ | ||
| 6 | |||
| 7 | error: `self` parameter is only allowed in associated functions | ||
| 8 | --> tests/ui/self_ref.rs:6:15 | ||
| 9 | | | ||
| 10 | 6 | async fn task(&mut self) {} | ||
| 11 | | ^^^^^^^^^ not semantically valid as function parameter | ||
| 12 | | | ||
| 13 | = note: associated functions are those in `impl` or `trait` definitions | ||
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] | ||
| 4 | async fn task() { | ||
| 5 | 5 | ||
| 6 | } | ||
| 7 | |||
| 8 | fn 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 @@ | |||
| 1 | error[E0308]: mismatched types | ||
| 2 | --> tests/ui/type_error.rs:5:5 | ||
| 3 | | | ||
| 4 | 4 | async fn task() { | ||
| 5 | | - help: try adding a return type: `-> i32` | ||
| 6 | 5 | 5 | ||
| 7 | | ^ expected `()`, found integer | ||
diff --git a/embassy-executor/tests/ui/where_clause.rs b/embassy-executor/tests/ui/where_clause.rs new file mode 100644 index 000000000..848d78149 --- /dev/null +++ b/embassy-executor/tests/ui/where_clause.rs | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | #![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] | ||
| 2 | |||
| 3 | struct Foo<'a>(&'a ()); | ||
| 4 | |||
| 5 | #[embassy_executor::task] | ||
| 6 | async fn task() | ||
| 7 | where | ||
| 8 | (): Sized, | ||
| 9 | { | ||
| 10 | } | ||
| 11 | |||
| 12 | fn main() {} | ||
diff --git a/embassy-executor/tests/ui/where_clause.stderr b/embassy-executor/tests/ui/where_clause.stderr new file mode 100644 index 000000000..eba45af40 --- /dev/null +++ b/embassy-executor/tests/ui/where_clause.stderr | |||
| @@ -0,0 +1,7 @@ | |||
| 1 | error: task functions must not have `where` clauses | ||
| 2 | --> tests/ui/where_clause.rs:6:1 | ||
| 3 | | | ||
| 4 | 6 | / async fn task() | ||
| 5 | 7 | | where | ||
| 6 | 8 | | (): Sized, | ||
| 7 | | |______________^ | ||
