init project
This commit is contained in:
97
embassy-executor/CHANGELOG.md
Normal file
97
embassy-executor/CHANGELOG.md
Normal file
@@ -0,0 +1,97 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## 0.7.0 - 2025-01-02
|
||||
|
||||
- Performance optimizations.
|
||||
- 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.
|
||||
- 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.
|
||||
- Added `TaskRef::executor` to obtain a reference to a task's executor
|
||||
|
||||
## 0.6.3 - 2024-11-12
|
||||
|
||||
- Building with the `nightly` feature now works with the Xtensa Rust compiler 1.82.
|
||||
- Compare vtable address instead of contents. Saves 44 bytes of flash on cortex-m.
|
||||
|
||||
## 0.6.2 - 2024-11-06
|
||||
|
||||
- The `nightly` feature no longer requires `nightly-2024-09-06` or newer.
|
||||
|
||||
## 0.6.1 - 2024-10-21
|
||||
|
||||
- Soundness fix: Deny using `impl Trait` in task arguments. This was previously accidentally allowed when not using the `nightly` feature,
|
||||
and could cause out of bounds memory accesses if spawning the same task mulitple times with different underlying types
|
||||
for the `impl Trait`. Affected versions are 0.4.x, 0.5.0 and 0.6.0, which have been yanked.
|
||||
- Add an architecture-agnostic executor that spins waiting for tasks to run, enabled with the `arch-spin` feature.
|
||||
- Update for breaking change in the nightly waker_getters API. The `nightly` feature now requires `nightly-2024-09-06` or newer.
|
||||
- Improve macro error messages.
|
||||
|
||||
## 0.6.0 - 2024-08-05
|
||||
|
||||
- Add collapse_debuginfo to fmt.rs macros.
|
||||
- initial support for AVR
|
||||
- use nightly waker_getters APIs
|
||||
|
||||
## 0.5.1 - 2024-10-21
|
||||
|
||||
- Soundness fix: Deny using `impl Trait` in task arguments. This was previously accidentally allowed when not using the `nightly` feature,
|
||||
and could cause out of bounds memory accesses if spawning the same task mulitple times with different underlying types
|
||||
for the `impl Trait`. Affected versions are 0.4.x, 0.5.0 and 0.6.0, which have been yanked.
|
||||
|
||||
## 0.5.0 - 2024-01-11
|
||||
|
||||
- Updated to `embassy-time-driver 0.1`, `embassy-time-queue-driver 0.1`, compatible with `embassy-time v0.3` and higher.
|
||||
|
||||
## 0.4.0 - 2023-12-05
|
||||
|
||||
- Removed `arch-xtensa`. Use the executor provided by the HAL crate (`esp-hal`, `esp32s3-hal`, etc...) instead.
|
||||
- Added an arena allocator for tasks, allowing using the `main` and `task` macros on Rust 1.75 stable. (it is only used if the `nightly` feature is not enabled. When `nightly` is enabled, `type_alias_impl_trait` is used to statically allocate tasks, as before).
|
||||
|
||||
## 0.3.3 - 2023-11-15
|
||||
|
||||
- Add `main` macro reexport for Xtensa arch.
|
||||
- Remove use of `atomic-polyfill`. The executor now has multiple implementations of its internal data structures for cases where the target supports atomics or doesn't.
|
||||
|
||||
## 0.3.2 - 2023-11-06
|
||||
|
||||
- Use `atomic-polyfill` for `riscv32`
|
||||
- Removed unused dependencies (static_cell, futures-util)
|
||||
|
||||
## 0.3.1 - 2023-11-01
|
||||
|
||||
- Fix spurious "Found waker not created by the Embassy executor" error in recent nightlies.
|
||||
|
||||
## 0.3.0 - 2023-08-25
|
||||
|
||||
- Replaced Pender. Implementations now must define an extern function called `__pender`.
|
||||
- Made `raw::AvailableTask` public
|
||||
- Made `SpawnToken::new_failed` public
|
||||
- You can now use arbitrary expressions to specify `#[task(pool_size = X)]`
|
||||
|
||||
## 0.2.1 - 2023-08-10
|
||||
|
||||
- Avoid calling `pend()` when waking expired timers
|
||||
- Properly reset finished task state with `integrated-timers` enabled
|
||||
- Introduce `InterruptExecutor::spawner()`
|
||||
- Fix incorrect critical section in Xtensa executor
|
||||
|
||||
## 0.2.0 - 2023-04-27
|
||||
|
||||
- Replace unnecessary atomics in runqueue
|
||||
- add Pender, rework Cargo features.
|
||||
- add support for turbo-wakers.
|
||||
- Allow TaskStorage to auto-implement `Sync`
|
||||
- Use AtomicPtr for signal_ctx, removes 1 unsafe.
|
||||
- Replace unsound critical sections with atomics
|
||||
|
||||
## 0.1.1 - 2022-11-23
|
||||
|
||||
- Fix features for documentation
|
||||
|
||||
## 0.1.0 - 2022-11-23
|
||||
|
||||
- First release
|
||||
205
embassy-executor/Cargo.toml
Normal file
205
embassy-executor/Cargo.toml
Normal file
@@ -0,0 +1,205 @@
|
||||
[package]
|
||||
name = "embassy-executor"
|
||||
version = "0.7.0"
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "async/await executor designed for embedded usage"
|
||||
repository = "https://github.com/embassy-rs/embassy"
|
||||
documentation = "https://docs.embassy.dev/embassy-executor"
|
||||
categories = [
|
||||
"embedded",
|
||||
"no-std",
|
||||
"asynchronous",
|
||||
]
|
||||
|
||||
[package.metadata.embassy_docs]
|
||||
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-executor-v$VERSION/embassy-executor/src/"
|
||||
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-executor/src/"
|
||||
features = ["defmt"]
|
||||
flavors = [
|
||||
{ name = "std", target = "x86_64-unknown-linux-gnu", features = ["arch-std", "executor-thread"] },
|
||||
{ name = "wasm", target = "wasm32-unknown-unknown", features = ["arch-wasm", "executor-thread"] },
|
||||
{ name = "cortex-m", target = "thumbv7em-none-eabi", features = ["arch-cortex-m", "executor-thread", "executor-interrupt"] },
|
||||
{ name = "riscv32", target = "riscv32imac-unknown-none-elf", features = ["arch-riscv32", "executor-thread"] },
|
||||
]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
default-target = "thumbv7em-none-eabi"
|
||||
targets = ["thumbv7em-none-eabi"]
|
||||
features = ["defmt", "arch-cortex-m", "executor-thread", "executor-interrupt"]
|
||||
|
||||
[dependencies]
|
||||
defmt = { version = "0.3", optional = true }
|
||||
log = { version = "0.4.14", optional = true }
|
||||
rtos-trace = { version = "0.1.3", optional = true }
|
||||
|
||||
embassy-executor-macros = { version = "0.6.2", path = "../embassy-executor-macros" }
|
||||
embassy-time-driver = { version = "0.2", path = "../embassy-time-driver", optional = true }
|
||||
critical-section = "1.1"
|
||||
|
||||
document-features = "0.2.7"
|
||||
|
||||
# needed for AVR
|
||||
portable-atomic = { version = "1.5", optional = true }
|
||||
|
||||
# arch-cortex-m dependencies
|
||||
cortex-m = { version = "0.7.6", optional = true }
|
||||
|
||||
# arch-wasm dependencies
|
||||
wasm-bindgen = { version = "0.2.82", optional = true }
|
||||
js-sys = { version = "0.3", optional = true }
|
||||
|
||||
# arch-avr dependencies
|
||||
avr-device = { version = "0.5.3", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
critical-section = { version = "1.1", features = ["std"] }
|
||||
trybuild = "1.0"
|
||||
embassy-sync = { path = "../embassy-sync" }
|
||||
|
||||
[features]
|
||||
|
||||
## Enable nightly-only features
|
||||
nightly = ["embassy-executor-macros/nightly"]
|
||||
|
||||
# Enables turbo wakers, which requires patching core. Not surfaced in the docs by default due to
|
||||
# being an complicated advanced and undocumented feature.
|
||||
# See: https://github.com/embassy-rs/embassy/pull/1263
|
||||
turbowakers = []
|
||||
|
||||
#! ### Architecture
|
||||
_arch = [] # some arch was picked
|
||||
## std
|
||||
arch-std = ["_arch", "critical-section/std"]
|
||||
## Cortex-M
|
||||
arch-cortex-m = ["_arch", "dep:cortex-m"]
|
||||
## RISC-V 32
|
||||
arch-riscv32 = ["_arch"]
|
||||
## WASM
|
||||
arch-wasm = ["_arch", "dep:wasm-bindgen", "dep:js-sys", "critical-section/std"]
|
||||
## AVR
|
||||
arch-avr = ["_arch", "dep:portable-atomic", "dep:avr-device"]
|
||||
## spin (architecture agnostic; never sleeps)
|
||||
arch-spin = ["_arch"]
|
||||
|
||||
#! ### Executor
|
||||
|
||||
## Enable the thread-mode executor (using WFE/SEV in Cortex-M, WFI in other embedded archs)
|
||||
executor-thread = []
|
||||
## Enable the interrupt-mode executor (available in Cortex-M only)
|
||||
executor-interrupt = []
|
||||
## Enable tracing support (adds some overhead)
|
||||
trace = []
|
||||
## Enable support for rtos-trace framework
|
||||
rtos-trace = ["dep:rtos-trace", "trace", "dep:embassy-time-driver"]
|
||||
|
||||
#! ### Timer Item Payload Size
|
||||
#! Sets the size of the payload for timer items, allowing integrated timer implementors to store
|
||||
#! additional data in the timer item. The payload field will be aligned to this value as well.
|
||||
#! If these features are not defined, the timer item will contain no payload field.
|
||||
|
||||
_timer-item-payload = [] # A size was picked
|
||||
|
||||
## 1 bytes
|
||||
timer-item-payload-size-1 = ["_timer-item-payload"]
|
||||
## 2 bytes
|
||||
timer-item-payload-size-2 = ["_timer-item-payload"]
|
||||
## 4 bytes
|
||||
timer-item-payload-size-4 = ["_timer-item-payload"]
|
||||
## 8 bytes
|
||||
timer-item-payload-size-8 = ["_timer-item-payload"]
|
||||
|
||||
#! ### Task Arena Size
|
||||
#! Sets the [task arena](#task-arena) size. Necessary if you’re not using `nightly`.
|
||||
#!
|
||||
#! <details>
|
||||
#! <summary>Preconfigured Task Arena Sizes:</summary>
|
||||
#! <!-- rustdoc requires the following blank line for the feature list to render correctly! -->
|
||||
#!
|
||||
|
||||
# BEGIN AUTOGENERATED CONFIG FEATURES
|
||||
# Generated by gen_config.py. DO NOT EDIT.
|
||||
## 64
|
||||
task-arena-size-64 = []
|
||||
## 128
|
||||
task-arena-size-128 = []
|
||||
## 192
|
||||
task-arena-size-192 = []
|
||||
## 256
|
||||
task-arena-size-256 = []
|
||||
## 320
|
||||
task-arena-size-320 = []
|
||||
## 384
|
||||
task-arena-size-384 = []
|
||||
## 512
|
||||
task-arena-size-512 = []
|
||||
## 640
|
||||
task-arena-size-640 = []
|
||||
## 768
|
||||
task-arena-size-768 = []
|
||||
## 1024
|
||||
task-arena-size-1024 = []
|
||||
## 1280
|
||||
task-arena-size-1280 = []
|
||||
## 1536
|
||||
task-arena-size-1536 = []
|
||||
## 2048
|
||||
task-arena-size-2048 = []
|
||||
## 2560
|
||||
task-arena-size-2560 = []
|
||||
## 3072
|
||||
task-arena-size-3072 = []
|
||||
## 4096 (default)
|
||||
task-arena-size-4096 = [] # Default
|
||||
## 5120
|
||||
task-arena-size-5120 = []
|
||||
## 6144
|
||||
task-arena-size-6144 = []
|
||||
## 8192
|
||||
task-arena-size-8192 = []
|
||||
## 10240
|
||||
task-arena-size-10240 = []
|
||||
## 12288
|
||||
task-arena-size-12288 = []
|
||||
## 16384
|
||||
task-arena-size-16384 = []
|
||||
## 20480
|
||||
task-arena-size-20480 = []
|
||||
## 24576
|
||||
task-arena-size-24576 = []
|
||||
## 32768
|
||||
task-arena-size-32768 = []
|
||||
## 40960
|
||||
task-arena-size-40960 = []
|
||||
## 49152
|
||||
task-arena-size-49152 = []
|
||||
## 65536
|
||||
task-arena-size-65536 = []
|
||||
## 81920
|
||||
task-arena-size-81920 = []
|
||||
## 98304
|
||||
task-arena-size-98304 = []
|
||||
## 131072
|
||||
task-arena-size-131072 = []
|
||||
## 163840
|
||||
task-arena-size-163840 = []
|
||||
## 196608
|
||||
task-arena-size-196608 = []
|
||||
## 262144
|
||||
task-arena-size-262144 = []
|
||||
## 327680
|
||||
task-arena-size-327680 = []
|
||||
## 393216
|
||||
task-arena-size-393216 = []
|
||||
## 524288
|
||||
task-arena-size-524288 = []
|
||||
## 655360
|
||||
task-arena-size-655360 = []
|
||||
## 786432
|
||||
task-arena-size-786432 = []
|
||||
## 1048576
|
||||
task-arena-size-1048576 = []
|
||||
|
||||
# END AUTOGENERATED CONFIG FEATURES
|
||||
|
||||
#! </details>
|
||||
37
embassy-executor/README.md
Normal file
37
embassy-executor/README.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# embassy-executor
|
||||
|
||||
An async/await executor designed for embedded usage.
|
||||
|
||||
- No `alloc`, no heap needed.
|
||||
- With nightly Rust, task futures can be fully statically allocated.
|
||||
- No "fixed capacity" data structures, executor works with 1 or 1000 tasks without needing config/tuning.
|
||||
- Integrated timer queue: sleeping is easy, just do `Timer::after_secs(1).await;`.
|
||||
- No busy-loop polling: CPU sleeps when there's no work to do, using interrupts or `WFE/SEV`.
|
||||
- Efficient polling: a wake will only poll the woken task, not all of them.
|
||||
- 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.
|
||||
- Creating multiple executor instances is supported, to run tasks with multiple priority levels. This allows higher-priority tasks to preempt lower-priority tasks.
|
||||
|
||||
## Task arena
|
||||
|
||||
When the `nightly` Cargo feature is not enabled, `embassy-executor` allocates tasks out of an arena (a very simple bump allocator).
|
||||
|
||||
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.
|
||||
|
||||
Tasks are allocated from the arena when spawned for the first time. If the task exits, the allocation is not released to the arena, but can be reused to spawn the task again. For multiple-instance tasks (like `#[embassy_executor::task(pool_size = 4)]`), the first spawn will allocate memory for all instances. This is done for performance and to increase predictability (for example, spawning at least 1 instance of every task at boot guarantees an immediate panic if the arena is too small, while allocating instances on-demand could delay the panic to only when the program is under load).
|
||||
|
||||
The arena size can be configured in two ways:
|
||||
|
||||
- Via Cargo features: enable a Cargo feature like `task-arena-size-8192`. Only a selection of values
|
||||
is available, see [Task Area Sizes](#task-arena-size) for reference.
|
||||
- Via environment variables at build time: set the variable named `EMBASSY_EXECUTOR_TASK_ARENA_SIZE`. For example
|
||||
`EMBASSY_EXECUTOR_TASK_ARENA_SIZE=4321 cargo build`. You can also set them in the `[env]` section of `.cargo/config.toml`.
|
||||
Any value can be set, unlike with Cargo features.
|
||||
|
||||
Environment variables take precedence over Cargo features. If two Cargo features are enabled for the same setting
|
||||
with different values, compilation fails.
|
||||
|
||||
## Statically allocating tasks
|
||||
|
||||
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.
|
||||
|
||||
The configured arena size is ignored, no arena is used at all.
|
||||
99
embassy-executor/build.rs
Normal file
99
embassy-executor/build.rs
Normal file
@@ -0,0 +1,99 @@
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::{env, fs};
|
||||
|
||||
#[path = "./build_common.rs"]
|
||||
mod common;
|
||||
|
||||
static CONFIGS: &[(&str, usize)] = &[
|
||||
// BEGIN AUTOGENERATED CONFIG FEATURES
|
||||
// Generated by gen_config.py. DO NOT EDIT.
|
||||
("TASK_ARENA_SIZE", 4096),
|
||||
// END AUTOGENERATED CONFIG FEATURES
|
||||
];
|
||||
|
||||
struct ConfigState {
|
||||
value: usize,
|
||||
seen_feature: bool,
|
||||
seen_env: bool,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let crate_name = env::var("CARGO_PKG_NAME")
|
||||
.unwrap()
|
||||
.to_ascii_uppercase()
|
||||
.replace('-', "_");
|
||||
|
||||
// only rebuild if build.rs changed. Otherwise Cargo will rebuild if any
|
||||
// other file changed.
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
|
||||
// Rebuild if config envvar changed.
|
||||
for (name, _) in CONFIGS {
|
||||
println!("cargo:rerun-if-env-changed={crate_name}_{name}");
|
||||
}
|
||||
|
||||
let mut configs = HashMap::new();
|
||||
for (name, default) in CONFIGS {
|
||||
configs.insert(
|
||||
*name,
|
||||
ConfigState {
|
||||
value: *default,
|
||||
seen_env: false,
|
||||
seen_feature: false,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let prefix = format!("{crate_name}_");
|
||||
for (var, value) in env::vars() {
|
||||
if let Some(name) = var.strip_prefix(&prefix) {
|
||||
let Some(cfg) = configs.get_mut(name) else {
|
||||
panic!("Unknown env var {name}")
|
||||
};
|
||||
|
||||
let Ok(value) = value.parse::<usize>() else {
|
||||
panic!("Invalid value for env var {name}: {value}")
|
||||
};
|
||||
|
||||
cfg.value = value;
|
||||
cfg.seen_env = true;
|
||||
}
|
||||
|
||||
if let Some(feature) = var.strip_prefix("CARGO_FEATURE_") {
|
||||
if let Some(i) = feature.rfind('_') {
|
||||
let name = &feature[..i];
|
||||
let value = &feature[i + 1..];
|
||||
if let Some(cfg) = configs.get_mut(name) {
|
||||
let Ok(value) = value.parse::<usize>() else {
|
||||
panic!("Invalid value for feature {name}: {value}")
|
||||
};
|
||||
|
||||
// envvars take priority.
|
||||
if !cfg.seen_env {
|
||||
if cfg.seen_feature {
|
||||
panic!("multiple values set for feature {}: {} and {}", name, cfg.value, value);
|
||||
}
|
||||
|
||||
cfg.value = value;
|
||||
cfg.seen_feature = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut data = String::new();
|
||||
|
||||
for (name, cfg) in &configs {
|
||||
writeln!(&mut data, "pub const {}: usize = {};", name, cfg.value).unwrap();
|
||||
}
|
||||
|
||||
let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap());
|
||||
let out_file = out_dir.join("config.rs").to_string_lossy().to_string();
|
||||
fs::write(out_file, data).unwrap();
|
||||
|
||||
let mut rustc_cfgs = common::CfgSet::new();
|
||||
common::set_target_cfgs(&mut rustc_cfgs);
|
||||
}
|
||||
126
embassy-executor/build_common.rs
Normal file
126
embassy-executor/build_common.rs
Normal file
@@ -0,0 +1,126 @@
|
||||
// NOTE: this file is copy-pasted between several Embassy crates, because there is no
|
||||
// straightforward way to share this code:
|
||||
// - it cannot be placed into the root of the repo and linked from each build.rs using `#[path =
|
||||
// "../build_common.rs"]`, because `cargo publish` requires that all files published with a crate
|
||||
// reside in the crate's directory,
|
||||
// - it cannot be symlinked from `embassy-xxx/build_common.rs` to `../build_common.rs`, because
|
||||
// symlinks don't work on Windows.
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::env;
|
||||
|
||||
/// Helper for emitting cargo instruction for enabling configs (`cargo:rustc-cfg=X`) and declaring
|
||||
/// them (`cargo:rust-check-cfg=cfg(X)`).
|
||||
#[derive(Debug)]
|
||||
pub struct CfgSet {
|
||||
enabled: HashSet<String>,
|
||||
declared: HashSet<String>,
|
||||
}
|
||||
|
||||
impl CfgSet {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
enabled: HashSet::new(),
|
||||
declared: HashSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Enable a config, which can then be used in `#[cfg(...)]` for conditional compilation.
|
||||
///
|
||||
/// All configs that can potentially be enabled should be unconditionally declared using
|
||||
/// [`Self::declare()`].
|
||||
pub fn enable(&mut self, cfg: impl AsRef<str>) {
|
||||
if self.enabled.insert(cfg.as_ref().to_owned()) {
|
||||
println!("cargo:rustc-cfg={}", cfg.as_ref());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enable_all(&mut self, cfgs: &[impl AsRef<str>]) {
|
||||
for cfg in cfgs.iter() {
|
||||
self.enable(cfg.as_ref());
|
||||
}
|
||||
}
|
||||
|
||||
/// Declare a valid config for conditional compilation, without enabling it.
|
||||
///
|
||||
/// This enables rustc to check that the configs in `#[cfg(...)]` attributes are valid.
|
||||
pub fn declare(&mut self, cfg: impl AsRef<str>) {
|
||||
if self.declared.insert(cfg.as_ref().to_owned()) {
|
||||
println!("cargo:rustc-check-cfg=cfg({})", cfg.as_ref());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn declare_all(&mut self, cfgs: &[impl AsRef<str>]) {
|
||||
for cfg in cfgs.iter() {
|
||||
self.declare(cfg.as_ref());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set(&mut self, cfg: impl Into<String>, enable: bool) {
|
||||
let cfg = cfg.into();
|
||||
if enable {
|
||||
self.enable(cfg.clone());
|
||||
}
|
||||
self.declare(cfg);
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets configs that describe the target platform.
|
||||
pub fn set_target_cfgs(cfgs: &mut CfgSet) {
|
||||
let target = env::var("TARGET").unwrap();
|
||||
|
||||
if target.starts_with("thumbv6m-") {
|
||||
cfgs.enable_all(&["cortex_m", "armv6m"]);
|
||||
} else if target.starts_with("thumbv7m-") {
|
||||
cfgs.enable_all(&["cortex_m", "armv7m"]);
|
||||
} else if target.starts_with("thumbv7em-") {
|
||||
cfgs.enable_all(&["cortex_m", "armv7m", "armv7em"]);
|
||||
} else if target.starts_with("thumbv8m.base") {
|
||||
cfgs.enable_all(&["cortex_m", "armv8m", "armv8m_base"]);
|
||||
} else if target.starts_with("thumbv8m.main") {
|
||||
cfgs.enable_all(&["cortex_m", "armv8m", "armv8m_main"]);
|
||||
}
|
||||
cfgs.declare_all(&[
|
||||
"cortex_m",
|
||||
"armv6m",
|
||||
"armv7m",
|
||||
"armv7em",
|
||||
"armv8m",
|
||||
"armv8m_base",
|
||||
"armv8m_main",
|
||||
]);
|
||||
|
||||
cfgs.set("has_fpu", target.ends_with("-eabihf"));
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct CompilerDate {
|
||||
year: u16,
|
||||
month: u8,
|
||||
day: u8,
|
||||
}
|
||||
|
||||
impl CompilerDate {
|
||||
fn parse(date: &str) -> Option<Self> {
|
||||
let mut parts = date.split('-');
|
||||
let year = parts.next()?.parse().ok()?;
|
||||
let month = parts.next()?.parse().ok()?;
|
||||
let day = parts.next()?.parse().ok()?;
|
||||
Some(Self { year, month, day })
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<&str> for CompilerDate {
|
||||
fn eq(&self, other: &&str) -> bool {
|
||||
let Some(other) = Self::parse(other) else {
|
||||
return false;
|
||||
};
|
||||
self.eq(&other)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd<&str> for CompilerDate {
|
||||
fn partial_cmp(&self, other: &&str) -> Option<std::cmp::Ordering> {
|
||||
Self::parse(other).map(|other| self.cmp(&other))
|
||||
}
|
||||
}
|
||||
88
embassy-executor/gen_config.py
Normal file
88
embassy-executor/gen_config.py
Normal file
@@ -0,0 +1,88 @@
|
||||
import os
|
||||
|
||||
abspath = os.path.abspath(__file__)
|
||||
dname = os.path.dirname(abspath)
|
||||
os.chdir(dname)
|
||||
|
||||
features = []
|
||||
|
||||
|
||||
def feature(name, default, min=None, max=None, pow2=None, vals=None, factors=[]):
|
||||
if vals is None:
|
||||
assert min is not None
|
||||
assert max is not None
|
||||
|
||||
vals = set()
|
||||
val = min
|
||||
while val <= max:
|
||||
vals.add(val)
|
||||
for f in factors:
|
||||
if val * f <= max:
|
||||
vals.add(val * f)
|
||||
if (pow2 == True or (isinstance(pow2, int) and val >= pow2)) and val > 0:
|
||||
val *= 2
|
||||
else:
|
||||
val += 1
|
||||
vals.add(default)
|
||||
vals = sorted(list(vals))
|
||||
|
||||
features.append(
|
||||
{
|
||||
"name": name,
|
||||
"default": default,
|
||||
"vals": vals,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
feature(
|
||||
"task_arena_size", default=4096, min=64, max=1024 * 1024, pow2=True, factors=[3, 5]
|
||||
)
|
||||
|
||||
# ========= Update Cargo.toml
|
||||
|
||||
things = ""
|
||||
for f in features:
|
||||
name = f["name"].replace("_", "-")
|
||||
for val in f["vals"]:
|
||||
things += f"## {val}"
|
||||
if val == f["default"]:
|
||||
things += " (default)\n"
|
||||
else:
|
||||
things += "\n"
|
||||
|
||||
things += f"{name}-{val} = []"
|
||||
if val == f["default"]:
|
||||
things += " # Default"
|
||||
things += "\n"
|
||||
things += "\n"
|
||||
|
||||
SEPARATOR_START = "# BEGIN AUTOGENERATED CONFIG FEATURES\n"
|
||||
SEPARATOR_END = "# END AUTOGENERATED CONFIG FEATURES\n"
|
||||
HELP = "# Generated by gen_config.py. DO NOT EDIT.\n"
|
||||
with open("Cargo.toml", "r") as f:
|
||||
data = f.read()
|
||||
before, data = data.split(SEPARATOR_START, maxsplit=1)
|
||||
_, after = data.split(SEPARATOR_END, maxsplit=1)
|
||||
data = before + SEPARATOR_START + HELP + things + SEPARATOR_END + after
|
||||
with open("Cargo.toml", "w") as f:
|
||||
f.write(data)
|
||||
|
||||
|
||||
# ========= Update build.rs
|
||||
|
||||
things = ""
|
||||
for f in features:
|
||||
name = f["name"].upper()
|
||||
things += f' ("{name}", {f["default"]}),\n'
|
||||
|
||||
SEPARATOR_START = "// BEGIN AUTOGENERATED CONFIG FEATURES\n"
|
||||
SEPARATOR_END = "// END AUTOGENERATED CONFIG FEATURES\n"
|
||||
HELP = " // Generated by gen_config.py. DO NOT EDIT.\n"
|
||||
with open("build.rs", "r") as f:
|
||||
data = f.read()
|
||||
before, data = data.split(SEPARATOR_START, maxsplit=1)
|
||||
_, after = data.split(SEPARATOR_END, maxsplit=1)
|
||||
data = before + SEPARATOR_START + HELP + things + " " + SEPARATOR_END + after
|
||||
with open("build.rs", "w") as f:
|
||||
f.write(data)
|
||||
72
embassy-executor/src/arch/avr.rs
Normal file
72
embassy-executor/src/arch/avr.rs
Normal file
@@ -0,0 +1,72 @@
|
||||
#[cfg(feature = "executor-interrupt")]
|
||||
compile_error!("`executor-interrupt` is not supported with `arch-avr`.");
|
||||
|
||||
#[cfg(feature = "executor-thread")]
|
||||
pub use thread::*;
|
||||
#[cfg(feature = "executor-thread")]
|
||||
mod thread {
|
||||
use core::marker::PhantomData;
|
||||
|
||||
pub use embassy_executor_macros::main_avr as main;
|
||||
use portable_atomic::{AtomicBool, Ordering};
|
||||
|
||||
use crate::{raw, Spawner};
|
||||
|
||||
static SIGNAL_WORK_THREAD_MODE: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
#[export_name = "__pender"]
|
||||
fn __pender(_context: *mut ()) {
|
||||
SIGNAL_WORK_THREAD_MODE.store(true, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
/// avr Executor
|
||||
pub struct Executor {
|
||||
inner: raw::Executor,
|
||||
not_send: PhantomData<*mut ()>,
|
||||
}
|
||||
|
||||
impl Executor {
|
||||
/// Create a new Executor.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: raw::Executor::new(core::ptr::null_mut()),
|
||||
not_send: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Run the executor.
|
||||
///
|
||||
/// The `init` closure is called with a [`Spawner`] that spawns tasks on
|
||||
/// this executor. Use it to spawn the initial task(s). After `init` returns,
|
||||
/// the executor starts running the tasks.
|
||||
///
|
||||
/// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`),
|
||||
/// for example by passing it as an argument to the initial tasks.
|
||||
///
|
||||
/// This function requires `&'static mut self`. This means you have to store the
|
||||
/// Executor instance in a place where it'll live forever and grants you mutable
|
||||
/// access. There's a few ways to do this:
|
||||
///
|
||||
/// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe)
|
||||
/// - a `static mut` (unsafe)
|
||||
/// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe)
|
||||
///
|
||||
/// This function never returns.
|
||||
pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! {
|
||||
init(self.inner.spawner());
|
||||
|
||||
loop {
|
||||
unsafe {
|
||||
avr_device::interrupt::disable();
|
||||
if !SIGNAL_WORK_THREAD_MODE.swap(false, Ordering::SeqCst) {
|
||||
avr_device::interrupt::enable();
|
||||
avr_device::asm::sleep();
|
||||
} else {
|
||||
avr_device::interrupt::enable();
|
||||
self.inner.poll();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
231
embassy-executor/src/arch/cortex_m.rs
Normal file
231
embassy-executor/src/arch/cortex_m.rs
Normal file
@@ -0,0 +1,231 @@
|
||||
#[export_name = "__pender"]
|
||||
#[cfg(any(feature = "executor-thread", feature = "executor-interrupt"))]
|
||||
fn __pender(context: *mut ()) {
|
||||
unsafe {
|
||||
// Safety: `context` is either `usize::MAX` created by `Executor::run`, or a valid interrupt
|
||||
// request number given to `InterruptExecutor::start`.
|
||||
|
||||
let context = context as usize;
|
||||
|
||||
#[cfg(feature = "executor-thread")]
|
||||
// Try to make Rust optimize the branching away if we only use thread mode.
|
||||
if !cfg!(feature = "executor-interrupt") || context == THREAD_PENDER {
|
||||
core::arch::asm!("sev");
|
||||
return;
|
||||
}
|
||||
|
||||
#[cfg(feature = "executor-interrupt")]
|
||||
{
|
||||
use cortex_m::interrupt::InterruptNumber;
|
||||
use cortex_m::peripheral::NVIC;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct Irq(u16);
|
||||
unsafe impl InterruptNumber for Irq {
|
||||
fn number(self) -> u16 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
let irq = Irq(context as u16);
|
||||
|
||||
// STIR is faster, but is only available in v7 and higher.
|
||||
#[cfg(not(armv6m))]
|
||||
{
|
||||
let mut nvic: NVIC = core::mem::transmute(());
|
||||
nvic.request(irq);
|
||||
}
|
||||
|
||||
#[cfg(armv6m)]
|
||||
NVIC::pend(irq);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "executor-thread")]
|
||||
pub use thread::*;
|
||||
#[cfg(feature = "executor-thread")]
|
||||
mod thread {
|
||||
pub(super) const THREAD_PENDER: usize = usize::MAX;
|
||||
|
||||
use core::arch::asm;
|
||||
use core::marker::PhantomData;
|
||||
|
||||
pub use embassy_executor_macros::main_cortex_m as main;
|
||||
|
||||
use crate::{raw, Spawner};
|
||||
|
||||
/// Thread mode executor, using WFE/SEV.
|
||||
///
|
||||
/// This is the simplest and most common kind of executor. It runs on
|
||||
/// thread mode (at the lowest priority level), and uses the `WFE` ARM instruction
|
||||
/// to sleep when it has no more work to do. When a task is woken, a `SEV` instruction
|
||||
/// is executed, to make the `WFE` exit from sleep and poll the task.
|
||||
///
|
||||
/// This executor allows for ultra low power consumption for chips where `WFE`
|
||||
/// triggers low-power sleep without extra steps. If your chip requires extra steps,
|
||||
/// you may use [`raw::Executor`] directly to program custom behavior.
|
||||
pub struct Executor {
|
||||
inner: raw::Executor,
|
||||
not_send: PhantomData<*mut ()>,
|
||||
}
|
||||
|
||||
impl Executor {
|
||||
/// Create a new Executor.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: raw::Executor::new(THREAD_PENDER as *mut ()),
|
||||
not_send: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Run the executor.
|
||||
///
|
||||
/// The `init` closure is called with a [`Spawner`] that spawns tasks on
|
||||
/// this executor. Use it to spawn the initial task(s). After `init` returns,
|
||||
/// the executor starts running the tasks.
|
||||
///
|
||||
/// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`),
|
||||
/// for example by passing it as an argument to the initial tasks.
|
||||
///
|
||||
/// This function requires `&'static mut self`. This means you have to store the
|
||||
/// Executor instance in a place where it'll live forever and grants you mutable
|
||||
/// access. There's a few ways to do this:
|
||||
///
|
||||
/// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe)
|
||||
/// - a `static mut` (unsafe)
|
||||
/// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe)
|
||||
///
|
||||
/// This function never returns.
|
||||
pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! {
|
||||
init(self.inner.spawner());
|
||||
|
||||
loop {
|
||||
unsafe {
|
||||
self.inner.poll();
|
||||
asm!("wfe");
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "executor-interrupt")]
|
||||
pub use interrupt::*;
|
||||
#[cfg(feature = "executor-interrupt")]
|
||||
mod interrupt {
|
||||
use core::cell::{Cell, UnsafeCell};
|
||||
use core::mem::MaybeUninit;
|
||||
|
||||
use cortex_m::interrupt::InterruptNumber;
|
||||
use cortex_m::peripheral::NVIC;
|
||||
use critical_section::Mutex;
|
||||
|
||||
use crate::raw;
|
||||
|
||||
/// Interrupt mode executor.
|
||||
///
|
||||
/// This executor runs tasks in interrupt mode. The interrupt handler is set up
|
||||
/// to poll tasks, and when a task is woken the interrupt is pended from software.
|
||||
///
|
||||
/// This allows running async tasks at a priority higher than thread mode. One
|
||||
/// use case is to leave thread mode free for non-async tasks. Another use case is
|
||||
/// to run multiple executors: one in thread mode for low priority tasks and another in
|
||||
/// interrupt mode for higher priority tasks. Higher priority tasks will preempt lower
|
||||
/// priority ones.
|
||||
///
|
||||
/// It is even possible to run multiple interrupt mode executors at different priorities,
|
||||
/// by assigning different priorities to the interrupts. For an example on how to do this,
|
||||
/// See the 'multiprio' example for 'embassy-nrf'.
|
||||
///
|
||||
/// To use it, you have to pick an interrupt that won't be used by the hardware.
|
||||
/// Some chips reserve some interrupts for this purpose, sometimes named "software interrupts" (SWI).
|
||||
/// If this is not the case, you may use an interrupt from any unused peripheral.
|
||||
///
|
||||
/// It is somewhat more complex to use, it's recommended to use the thread-mode
|
||||
/// [`Executor`] instead, if it works for your use case.
|
||||
pub struct InterruptExecutor {
|
||||
started: Mutex<Cell<bool>>,
|
||||
executor: UnsafeCell<MaybeUninit<raw::Executor>>,
|
||||
}
|
||||
|
||||
unsafe impl Send for InterruptExecutor {}
|
||||
unsafe impl Sync for InterruptExecutor {}
|
||||
|
||||
impl InterruptExecutor {
|
||||
/// Create a new, not started `InterruptExecutor`.
|
||||
#[inline]
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
started: Mutex::new(Cell::new(false)),
|
||||
executor: UnsafeCell::new(MaybeUninit::uninit()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Executor interrupt callback.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// - You MUST call this from the interrupt handler, and from nowhere else.
|
||||
/// - You must not call this before calling `start()`.
|
||||
pub unsafe fn on_interrupt(&'static self) {
|
||||
let executor = unsafe { (&*self.executor.get()).assume_init_ref() };
|
||||
executor.poll();
|
||||
}
|
||||
|
||||
/// Start the executor.
|
||||
///
|
||||
/// This initializes the executor, enables the interrupt, and returns.
|
||||
/// The executor keeps running in the background through the interrupt.
|
||||
///
|
||||
/// This returns a [`SendSpawner`] you can use to spawn tasks on it. A [`SendSpawner`]
|
||||
/// is returned instead of a [`Spawner`](embassy_executor::Spawner) because the executor effectively runs in a
|
||||
/// different "thread" (the interrupt), so spawning tasks on it is effectively
|
||||
/// sending them.
|
||||
///
|
||||
/// To obtain a [`Spawner`](embassy_executor::Spawner) for this executor, use [`Spawner::for_current_executor()`](embassy_executor::Spawner::for_current_executor()) from
|
||||
/// a task running in it.
|
||||
///
|
||||
/// # Interrupt requirements
|
||||
///
|
||||
/// You must write the interrupt handler yourself, and make it call [`on_interrupt()`](Self::on_interrupt).
|
||||
///
|
||||
/// This method already enables (unmasks) the interrupt, you must NOT do it yourself.
|
||||
///
|
||||
/// You must set the interrupt priority before calling this method. You MUST NOT
|
||||
/// do it after.
|
||||
///
|
||||
pub fn start(&'static self, irq: impl InterruptNumber) -> crate::SendSpawner {
|
||||
if critical_section::with(|cs| self.started.borrow(cs).replace(true)) {
|
||||
panic!("InterruptExecutor::start() called multiple times on the same executor.");
|
||||
}
|
||||
|
||||
unsafe {
|
||||
(&mut *self.executor.get())
|
||||
.as_mut_ptr()
|
||||
.write(raw::Executor::new(irq.number() as *mut ()))
|
||||
}
|
||||
|
||||
let executor = unsafe { (&*self.executor.get()).assume_init_ref() };
|
||||
|
||||
unsafe { NVIC::unmask(irq) }
|
||||
|
||||
executor.spawner().make_send()
|
||||
}
|
||||
|
||||
/// Get a SendSpawner for this executor
|
||||
///
|
||||
/// This returns a [`SendSpawner`] you can use to spawn tasks on this
|
||||
/// executor.
|
||||
///
|
||||
/// This MUST only be called on an executor that has already been started.
|
||||
/// The function will panic otherwise.
|
||||
pub fn spawner(&'static self) -> crate::SendSpawner {
|
||||
if !critical_section::with(|cs| self.started.borrow(cs).get()) {
|
||||
panic!("InterruptExecutor::spawner() called on uninitialized executor.");
|
||||
}
|
||||
let executor = unsafe { (&*self.executor.get()).assume_init_ref() };
|
||||
executor.spawner().make_send()
|
||||
}
|
||||
}
|
||||
}
|
||||
80
embassy-executor/src/arch/riscv32.rs
Normal file
80
embassy-executor/src/arch/riscv32.rs
Normal file
@@ -0,0 +1,80 @@
|
||||
#[cfg(feature = "executor-interrupt")]
|
||||
compile_error!("`executor-interrupt` is not supported with `arch-riscv32`.");
|
||||
|
||||
#[cfg(feature = "executor-thread")]
|
||||
pub use thread::*;
|
||||
#[cfg(feature = "executor-thread")]
|
||||
mod thread {
|
||||
use core::marker::PhantomData;
|
||||
use core::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
pub use embassy_executor_macros::main_riscv as main;
|
||||
|
||||
use crate::{raw, Spawner};
|
||||
|
||||
/// global atomic used to keep track of whether there is work to do since sev() is not available on RISCV
|
||||
static SIGNAL_WORK_THREAD_MODE: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
#[export_name = "__pender"]
|
||||
fn __pender(_context: *mut ()) {
|
||||
SIGNAL_WORK_THREAD_MODE.store(true, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
/// RISCV32 Executor
|
||||
pub struct Executor {
|
||||
inner: raw::Executor,
|
||||
not_send: PhantomData<*mut ()>,
|
||||
}
|
||||
|
||||
impl Executor {
|
||||
/// Create a new Executor.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: raw::Executor::new(core::ptr::null_mut()),
|
||||
not_send: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Run the executor.
|
||||
///
|
||||
/// The `init` closure is called with a [`Spawner`] that spawns tasks on
|
||||
/// this executor. Use it to spawn the initial task(s). After `init` returns,
|
||||
/// the executor starts running the tasks.
|
||||
///
|
||||
/// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`),
|
||||
/// for example by passing it as an argument to the initial tasks.
|
||||
///
|
||||
/// This function requires `&'static mut self`. This means you have to store the
|
||||
/// Executor instance in a place where it'll live forever and grants you mutable
|
||||
/// access. There's a few ways to do this:
|
||||
///
|
||||
/// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe)
|
||||
/// - a `static mut` (unsafe)
|
||||
/// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe)
|
||||
///
|
||||
/// This function never returns.
|
||||
pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! {
|
||||
init(self.inner.spawner());
|
||||
|
||||
loop {
|
||||
unsafe {
|
||||
self.inner.poll();
|
||||
// we do not care about race conditions between the load and store operations, interrupts
|
||||
//will only set this value to true.
|
||||
critical_section::with(|_| {
|
||||
// if there is work to do, loop back to polling
|
||||
// TODO can we relax this?
|
||||
if SIGNAL_WORK_THREAD_MODE.load(Ordering::SeqCst) {
|
||||
SIGNAL_WORK_THREAD_MODE.store(false, Ordering::SeqCst);
|
||||
}
|
||||
// if not, wait for interrupt
|
||||
else {
|
||||
core::arch::asm!("wfi");
|
||||
}
|
||||
});
|
||||
// if an interrupt occurred while waiting, it will be serviced here
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
58
embassy-executor/src/arch/spin.rs
Normal file
58
embassy-executor/src/arch/spin.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
#[cfg(feature = "executor-interrupt")]
|
||||
compile_error!("`executor-interrupt` is not supported with `arch-spin`.");
|
||||
|
||||
#[cfg(feature = "executor-thread")]
|
||||
pub use thread::*;
|
||||
#[cfg(feature = "executor-thread")]
|
||||
mod thread {
|
||||
use core::marker::PhantomData;
|
||||
|
||||
pub use embassy_executor_macros::main_spin as main;
|
||||
|
||||
use crate::{raw, Spawner};
|
||||
|
||||
#[export_name = "__pender"]
|
||||
fn __pender(_context: *mut ()) {}
|
||||
|
||||
/// Spin Executor
|
||||
pub struct Executor {
|
||||
inner: raw::Executor,
|
||||
not_send: PhantomData<*mut ()>,
|
||||
}
|
||||
|
||||
impl Executor {
|
||||
/// Create a new Executor.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: raw::Executor::new(core::ptr::null_mut()),
|
||||
not_send: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Run the executor.
|
||||
///
|
||||
/// The `init` closure is called with a [`Spawner`] that spawns tasks on
|
||||
/// this executor. Use it to spawn the initial task(s). After `init` returns,
|
||||
/// the executor starts running the tasks.
|
||||
///
|
||||
/// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`),
|
||||
/// for example by passing it as an argument to the initial tasks.
|
||||
///
|
||||
/// This function requires `&'static mut self`. This means you have to store the
|
||||
/// Executor instance in a place where it'll live forever and grants you mutable
|
||||
/// access. There's a few ways to do this:
|
||||
///
|
||||
/// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe)
|
||||
/// - a `static mut` (unsafe)
|
||||
/// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe)
|
||||
///
|
||||
/// This function never returns.
|
||||
pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! {
|
||||
init(self.inner.spawner());
|
||||
|
||||
loop {
|
||||
unsafe { self.inner.poll() };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
94
embassy-executor/src/arch/std.rs
Normal file
94
embassy-executor/src/arch/std.rs
Normal file
@@ -0,0 +1,94 @@
|
||||
#[cfg(feature = "executor-interrupt")]
|
||||
compile_error!("`executor-interrupt` is not supported with `arch-std`.");
|
||||
|
||||
#[cfg(feature = "executor-thread")]
|
||||
pub use thread::*;
|
||||
#[cfg(feature = "executor-thread")]
|
||||
mod thread {
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::{Condvar, Mutex};
|
||||
|
||||
pub use embassy_executor_macros::main_std as main;
|
||||
|
||||
use crate::{raw, Spawner};
|
||||
|
||||
#[export_name = "__pender"]
|
||||
fn __pender(context: *mut ()) {
|
||||
let signaler: &'static Signaler = unsafe { std::mem::transmute(context) };
|
||||
signaler.signal()
|
||||
}
|
||||
|
||||
/// Single-threaded std-based executor.
|
||||
pub struct Executor {
|
||||
inner: raw::Executor,
|
||||
not_send: PhantomData<*mut ()>,
|
||||
signaler: &'static Signaler,
|
||||
}
|
||||
|
||||
impl Executor {
|
||||
/// Create a new Executor.
|
||||
pub fn new() -> Self {
|
||||
let signaler = Box::leak(Box::new(Signaler::new()));
|
||||
Self {
|
||||
inner: raw::Executor::new(signaler as *mut Signaler as *mut ()),
|
||||
not_send: PhantomData,
|
||||
signaler,
|
||||
}
|
||||
}
|
||||
|
||||
/// Run the executor.
|
||||
///
|
||||
/// The `init` closure is called with a [`Spawner`] that spawns tasks on
|
||||
/// this executor. Use it to spawn the initial task(s). After `init` returns,
|
||||
/// the executor starts running the tasks.
|
||||
///
|
||||
/// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`),
|
||||
/// for example by passing it as an argument to the initial tasks.
|
||||
///
|
||||
/// This function requires `&'static mut self`. This means you have to store the
|
||||
/// Executor instance in a place where it'll live forever and grants you mutable
|
||||
/// access. There's a few ways to do this:
|
||||
///
|
||||
/// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe)
|
||||
/// - a `static mut` (unsafe)
|
||||
/// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe)
|
||||
///
|
||||
/// This function never returns.
|
||||
pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! {
|
||||
init(self.inner.spawner());
|
||||
|
||||
loop {
|
||||
unsafe { self.inner.poll() };
|
||||
self.signaler.wait()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Signaler {
|
||||
mutex: Mutex<bool>,
|
||||
condvar: Condvar,
|
||||
}
|
||||
|
||||
impl Signaler {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
mutex: Mutex::new(false),
|
||||
condvar: Condvar::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn wait(&self) {
|
||||
let mut signaled = self.mutex.lock().unwrap();
|
||||
while !*signaled {
|
||||
signaled = self.condvar.wait(signaled).unwrap();
|
||||
}
|
||||
*signaled = false;
|
||||
}
|
||||
|
||||
fn signal(&self) {
|
||||
let mut signaled = self.mutex.lock().unwrap();
|
||||
*signaled = true;
|
||||
self.condvar.notify_one();
|
||||
}
|
||||
}
|
||||
}
|
||||
83
embassy-executor/src/arch/wasm.rs
Normal file
83
embassy-executor/src/arch/wasm.rs
Normal file
@@ -0,0 +1,83 @@
|
||||
#[cfg(feature = "executor-interrupt")]
|
||||
compile_error!("`executor-interrupt` is not supported with `arch-wasm`.");
|
||||
|
||||
#[cfg(feature = "executor-thread")]
|
||||
pub use thread::*;
|
||||
#[cfg(feature = "executor-thread")]
|
||||
mod thread {
|
||||
|
||||
use core::marker::PhantomData;
|
||||
|
||||
pub use embassy_executor_macros::main_wasm as main;
|
||||
use js_sys::Promise;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use crate::raw::util::UninitCell;
|
||||
use crate::{raw, Spawner};
|
||||
|
||||
#[export_name = "__pender"]
|
||||
fn __pender(context: *mut ()) {
|
||||
let signaler: &'static WasmContext = unsafe { std::mem::transmute(context) };
|
||||
let _ = signaler.promise.then(unsafe { signaler.closure.as_mut() });
|
||||
}
|
||||
|
||||
pub(crate) struct WasmContext {
|
||||
promise: Promise,
|
||||
closure: UninitCell<Closure<dyn FnMut(JsValue)>>,
|
||||
}
|
||||
|
||||
impl WasmContext {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
promise: Promise::resolve(&JsValue::undefined()),
|
||||
closure: UninitCell::uninit(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// WASM executor, wasm_bindgen to schedule tasks on the JS event loop.
|
||||
pub struct Executor {
|
||||
inner: raw::Executor,
|
||||
ctx: &'static WasmContext,
|
||||
not_send: PhantomData<*mut ()>,
|
||||
}
|
||||
|
||||
impl Executor {
|
||||
/// Create a new Executor.
|
||||
pub fn new() -> Self {
|
||||
let ctx = Box::leak(Box::new(WasmContext::new()));
|
||||
Self {
|
||||
inner: raw::Executor::new(ctx as *mut WasmContext as *mut ()),
|
||||
ctx,
|
||||
not_send: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Run the executor.
|
||||
///
|
||||
/// The `init` closure is called with a [`Spawner`] that spawns tasks on
|
||||
/// this executor. Use it to spawn the initial task(s). After `init` returns,
|
||||
/// the executor starts running the tasks.
|
||||
///
|
||||
/// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`),
|
||||
/// for example by passing it as an argument to the initial tasks.
|
||||
///
|
||||
/// This function requires `&'static mut self`. This means you have to store the
|
||||
/// Executor instance in a place where it'll live forever and grants you mutable
|
||||
/// access. There's a few ways to do this:
|
||||
///
|
||||
/// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe)
|
||||
/// - a `static mut` (unsafe)
|
||||
/// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe)
|
||||
pub fn start(&'static mut self, init: impl FnOnce(Spawner)) {
|
||||
unsafe {
|
||||
let executor = &self.inner;
|
||||
let future = Closure::new(move |_| {
|
||||
executor.poll();
|
||||
});
|
||||
self.ctx.closure.write_in_place(|| future);
|
||||
init(self.inner.spawner());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
270
embassy-executor/src/fmt.rs
Normal file
270
embassy-executor/src/fmt.rs
Normal file
@@ -0,0 +1,270 @@
|
||||
#![macro_use]
|
||||
#![allow(unused)]
|
||||
|
||||
use core::fmt::{Debug, Display, LowerHex};
|
||||
|
||||
#[cfg(all(feature = "defmt", feature = "log"))]
|
||||
compile_error!("You may not enable both `defmt` and `log` features.");
|
||||
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! assert {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::assert!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::assert!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! assert_eq {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::assert_eq!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::assert_eq!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! assert_ne {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::assert_ne!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::assert_ne!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! debug_assert {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::debug_assert!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::debug_assert!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! debug_assert_eq {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::debug_assert_eq!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::debug_assert_eq!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! debug_assert_ne {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::debug_assert_ne!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::debug_assert_ne!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! todo {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::todo!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::todo!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! unreachable {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::unreachable!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::unreachable!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! panic {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::panic!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::panic!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! trace {
|
||||
($s:literal $(, $x:expr)* $(,)?) => {
|
||||
{
|
||||
#[cfg(feature = "log")]
|
||||
::log::trace!($s $(, $x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::trace!($s $(, $x)*);
|
||||
#[cfg(not(any(feature = "log", feature="defmt")))]
|
||||
let _ = ($( & $x ),*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! debug {
|
||||
($s:literal $(, $x:expr)* $(,)?) => {
|
||||
{
|
||||
#[cfg(feature = "log")]
|
||||
::log::debug!($s $(, $x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::debug!($s $(, $x)*);
|
||||
#[cfg(not(any(feature = "log", feature="defmt")))]
|
||||
let _ = ($( & $x ),*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! info {
|
||||
($s:literal $(, $x:expr)* $(,)?) => {
|
||||
{
|
||||
#[cfg(feature = "log")]
|
||||
::log::info!($s $(, $x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::info!($s $(, $x)*);
|
||||
#[cfg(not(any(feature = "log", feature="defmt")))]
|
||||
let _ = ($( & $x ),*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! warn {
|
||||
($s:literal $(, $x:expr)* $(,)?) => {
|
||||
{
|
||||
#[cfg(feature = "log")]
|
||||
::log::warn!($s $(, $x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::warn!($s $(, $x)*);
|
||||
#[cfg(not(any(feature = "log", feature="defmt")))]
|
||||
let _ = ($( & $x ),*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! error {
|
||||
($s:literal $(, $x:expr)* $(,)?) => {
|
||||
{
|
||||
#[cfg(feature = "log")]
|
||||
::log::error!($s $(, $x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::error!($s $(, $x)*);
|
||||
#[cfg(not(any(feature = "log", feature="defmt")))]
|
||||
let _ = ($( & $x ),*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! unwrap {
|
||||
($($x:tt)*) => {
|
||||
::defmt::unwrap!($($x)*)
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
#[collapse_debuginfo(yes)]
|
||||
macro_rules! unwrap {
|
||||
($arg:expr) => {
|
||||
match $crate::fmt::Try::into_result($arg) {
|
||||
::core::result::Result::Ok(t) => t,
|
||||
::core::result::Result::Err(e) => {
|
||||
::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e);
|
||||
}
|
||||
}
|
||||
};
|
||||
($arg:expr, $($msg:expr),+ $(,)? ) => {
|
||||
match $crate::fmt::Try::into_result($arg) {
|
||||
::core::result::Result::Ok(t) => t,
|
||||
::core::result::Result::Err(e) => {
|
||||
::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub struct NoneError;
|
||||
|
||||
pub trait Try {
|
||||
type Ok;
|
||||
type Error;
|
||||
fn into_result(self) -> Result<Self::Ok, Self::Error>;
|
||||
}
|
||||
|
||||
impl<T> Try for Option<T> {
|
||||
type Ok = T;
|
||||
type Error = NoneError;
|
||||
|
||||
#[inline]
|
||||
fn into_result(self) -> Result<T, NoneError> {
|
||||
self.ok_or(NoneError)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E> Try for Result<T, E> {
|
||||
type Ok = T;
|
||||
type Error = E;
|
||||
|
||||
#[inline]
|
||||
fn into_result(self) -> Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct Bytes<'a>(pub &'a [u8]);
|
||||
|
||||
impl<'a> Debug for Bytes<'a> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
write!(f, "{:#02x?}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Display for Bytes<'a> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
write!(f, "{:#02x?}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> LowerHex for Bytes<'a> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
write!(f, "{:#02x?}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
impl<'a> defmt::Format for Bytes<'a> {
|
||||
fn format(&self, fmt: defmt::Formatter) {
|
||||
defmt::write!(fmt, "{:02x}", self.0)
|
||||
}
|
||||
}
|
||||
150
embassy-executor/src/lib.rs
Normal file
150
embassy-executor/src/lib.rs
Normal file
@@ -0,0 +1,150 @@
|
||||
#![cfg_attr(not(any(feature = "arch-std", feature = "arch-wasm")), no_std)]
|
||||
#![allow(clippy::new_without_default)]
|
||||
#![doc = include_str!("../README.md")]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
//! ## Feature flags
|
||||
#![doc = document_features::document_features!(feature_label = r#"<span class="stab portability"><code>{feature}</code></span>"#)]
|
||||
|
||||
// This mod MUST go first, so that the others see its macros.
|
||||
pub(crate) mod fmt;
|
||||
|
||||
pub use embassy_executor_macros::task;
|
||||
|
||||
macro_rules! check_at_most_one {
|
||||
(@amo [$($feats:literal)*] [] [$($res:tt)*]) => {
|
||||
#[cfg(any($($res)*))]
|
||||
compile_error!(concat!("At most one of these features can be enabled at the same time:", $(" `", $feats, "`",)*));
|
||||
};
|
||||
(@amo $feats:tt [$curr:literal $($rest:literal)*] [$($res:tt)*]) => {
|
||||
check_at_most_one!(@amo $feats [$($rest)*] [$($res)* $(all(feature=$curr, feature=$rest),)*]);
|
||||
};
|
||||
($($f:literal),*$(,)?) => {
|
||||
check_at_most_one!(@amo [$($f)*] [$($f)*] []);
|
||||
};
|
||||
}
|
||||
check_at_most_one!(
|
||||
"arch-avr",
|
||||
"arch-cortex-m",
|
||||
"arch-riscv32",
|
||||
"arch-std",
|
||||
"arch-wasm",
|
||||
"arch-spin",
|
||||
);
|
||||
|
||||
#[cfg(feature = "_arch")]
|
||||
#[cfg_attr(feature = "arch-avr", path = "arch/avr.rs")]
|
||||
#[cfg_attr(feature = "arch-cortex-m", path = "arch/cortex_m.rs")]
|
||||
#[cfg_attr(feature = "arch-riscv32", path = "arch/riscv32.rs")]
|
||||
#[cfg_attr(feature = "arch-std", path = "arch/std.rs")]
|
||||
#[cfg_attr(feature = "arch-wasm", path = "arch/wasm.rs")]
|
||||
#[cfg_attr(feature = "arch-spin", path = "arch/spin.rs")]
|
||||
mod arch;
|
||||
|
||||
#[cfg(feature = "_arch")]
|
||||
#[allow(unused_imports)] // don't warn if the module is empty.
|
||||
pub use arch::*;
|
||||
|
||||
pub mod raw;
|
||||
|
||||
mod spawner;
|
||||
pub use spawner::*;
|
||||
|
||||
mod config {
|
||||
#![allow(unused)]
|
||||
include!(concat!(env!("OUT_DIR"), "/config.rs"));
|
||||
}
|
||||
|
||||
/// Implementation details for embassy macros.
|
||||
/// Do not use. Used for macros and HALs only. Not covered by semver guarantees.
|
||||
#[doc(hidden)]
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
pub mod _export {
|
||||
use core::alloc::Layout;
|
||||
use core::cell::{Cell, UnsafeCell};
|
||||
use core::future::Future;
|
||||
use core::mem::MaybeUninit;
|
||||
use core::ptr::null_mut;
|
||||
|
||||
use critical_section::{CriticalSection, Mutex};
|
||||
|
||||
use crate::raw::TaskPool;
|
||||
|
||||
struct Arena<const N: usize> {
|
||||
buf: UnsafeCell<MaybeUninit<[u8; N]>>,
|
||||
ptr: Mutex<Cell<*mut u8>>,
|
||||
}
|
||||
|
||||
unsafe impl<const N: usize> Sync for Arena<N> {}
|
||||
unsafe impl<const N: usize> Send for Arena<N> {}
|
||||
|
||||
impl<const N: usize> Arena<N> {
|
||||
const fn new() -> Self {
|
||||
Self {
|
||||
buf: UnsafeCell::new(MaybeUninit::uninit()),
|
||||
ptr: Mutex::new(Cell::new(null_mut())),
|
||||
}
|
||||
}
|
||||
|
||||
fn alloc<T>(&'static self, cs: CriticalSection) -> &'static mut MaybeUninit<T> {
|
||||
let layout = Layout::new::<T>();
|
||||
|
||||
let start = self.buf.get().cast::<u8>();
|
||||
let end = unsafe { start.add(N) };
|
||||
|
||||
let mut ptr = self.ptr.borrow(cs).get();
|
||||
if ptr.is_null() {
|
||||
ptr = self.buf.get().cast::<u8>();
|
||||
}
|
||||
|
||||
let bytes_left = (end as usize) - (ptr as usize);
|
||||
let align_offset = (ptr as usize).next_multiple_of(layout.align()) - (ptr as usize);
|
||||
|
||||
if align_offset + layout.size() > bytes_left {
|
||||
panic!("embassy-executor: task arena is full. You must increase the arena size, see the documentation for details: https://docs.embassy.dev/embassy-executor/");
|
||||
}
|
||||
|
||||
let res = unsafe { ptr.add(align_offset) };
|
||||
let ptr = unsafe { ptr.add(align_offset + layout.size()) };
|
||||
|
||||
self.ptr.borrow(cs).set(ptr);
|
||||
|
||||
unsafe { &mut *(res as *mut MaybeUninit<T>) }
|
||||
}
|
||||
}
|
||||
|
||||
static ARENA: Arena<{ crate::config::TASK_ARENA_SIZE }> = Arena::new();
|
||||
|
||||
pub struct TaskPoolRef {
|
||||
// type-erased `&'static mut TaskPool<F, N>`
|
||||
// Needed because statics can't have generics.
|
||||
ptr: Mutex<Cell<*mut ()>>,
|
||||
}
|
||||
unsafe impl Sync for TaskPoolRef {}
|
||||
unsafe impl Send for TaskPoolRef {}
|
||||
|
||||
impl TaskPoolRef {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
ptr: Mutex::new(Cell::new(null_mut())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the pool for this ref, allocating it from the arena the first time.
|
||||
///
|
||||
/// safety: for a given TaskPoolRef instance, must always call with the exact
|
||||
/// same generic params.
|
||||
pub unsafe fn get<F: Future, const N: usize>(&'static self) -> &'static TaskPool<F, N> {
|
||||
critical_section::with(|cs| {
|
||||
let ptr = self.ptr.borrow(cs);
|
||||
if ptr.get().is_null() {
|
||||
let pool = ARENA.alloc::<TaskPool<F, N>>(cs);
|
||||
pool.write(TaskPool::new());
|
||||
ptr.set(pool as *mut _ as _);
|
||||
}
|
||||
|
||||
unsafe { &*(ptr.get() as *const _) }
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
571
embassy-executor/src/raw/mod.rs
Normal file
571
embassy-executor/src/raw/mod.rs
Normal file
@@ -0,0 +1,571 @@
|
||||
//! Raw executor.
|
||||
//!
|
||||
//! This module exposes "raw" Executor and Task structs for more low level control.
|
||||
//!
|
||||
//! ## WARNING: here be dragons!
|
||||
//!
|
||||
//! Using this module requires respecting subtle safety contracts. If you can, prefer using the safe
|
||||
//! [executor wrappers](crate::Executor) and the [`embassy_executor::task`](embassy_executor_macros::task) macro, which are fully safe.
|
||||
|
||||
#[cfg_attr(target_has_atomic = "ptr", path = "run_queue_atomics.rs")]
|
||||
#[cfg_attr(not(target_has_atomic = "ptr"), path = "run_queue_critical_section.rs")]
|
||||
mod run_queue;
|
||||
|
||||
#[cfg_attr(all(cortex_m, target_has_atomic = "8"), path = "state_atomics_arm.rs")]
|
||||
#[cfg_attr(all(not(cortex_m), target_has_atomic = "8"), path = "state_atomics.rs")]
|
||||
#[cfg_attr(not(target_has_atomic = "8"), path = "state_critical_section.rs")]
|
||||
mod state;
|
||||
|
||||
pub mod timer_queue;
|
||||
#[cfg(feature = "trace")]
|
||||
mod trace;
|
||||
pub(crate) mod util;
|
||||
#[cfg_attr(feature = "turbowakers", path = "waker_turbo.rs")]
|
||||
mod waker;
|
||||
|
||||
use core::future::Future;
|
||||
use core::marker::PhantomData;
|
||||
use core::mem;
|
||||
use core::pin::Pin;
|
||||
use core::ptr::NonNull;
|
||||
use core::sync::atomic::{AtomicPtr, Ordering};
|
||||
use core::task::{Context, Poll};
|
||||
|
||||
use self::run_queue::{RunQueue, RunQueueItem};
|
||||
use self::state::State;
|
||||
use self::util::{SyncUnsafeCell, UninitCell};
|
||||
pub use self::waker::task_from_waker;
|
||||
use super::SpawnToken;
|
||||
|
||||
/// Raw task header for use in task pointers.
|
||||
///
|
||||
/// A task can be in one of the following states:
|
||||
///
|
||||
/// - Not spawned: the task is ready to spawn.
|
||||
/// - `SPAWNED`: the task is currently spawned and may be running.
|
||||
/// - `RUN_ENQUEUED`: the task is enqueued to be polled. Note that the task may be `!SPAWNED`.
|
||||
/// In this case, the `RUN_ENQUEUED` state will be cleared when the task is next polled, without
|
||||
/// polling the task's future.
|
||||
///
|
||||
/// A task's complete life cycle is as follows:
|
||||
///
|
||||
/// ```text
|
||||
/// ┌────────────┐ ┌────────────────────────┐
|
||||
/// │Not spawned │◄─5┤Not spawned|Run enqueued│
|
||||
/// │ ├6─►│ │
|
||||
/// └─────┬──────┘ └──────▲─────────────────┘
|
||||
/// 1 │
|
||||
/// │ ┌────────────┘
|
||||
/// │ 4
|
||||
/// ┌─────▼────┴─────────┐
|
||||
/// │Spawned|Run enqueued│
|
||||
/// │ │
|
||||
/// └─────┬▲─────────────┘
|
||||
/// 2│
|
||||
/// │3
|
||||
/// ┌─────▼┴─────┐
|
||||
/// │ Spawned │
|
||||
/// │ │
|
||||
/// └────────────┘
|
||||
/// ```
|
||||
///
|
||||
/// Transitions:
|
||||
/// - 1: Task is spawned - `AvailableTask::claim -> Executor::spawn`
|
||||
/// - 2: During poll - `RunQueue::dequeue_all -> State::run_dequeue`
|
||||
/// - 3: Task wakes itself, waker wakes task, or task exits - `Waker::wake -> wake_task -> State::run_enqueue`
|
||||
/// - 4: A run-queued task exits - `TaskStorage::poll -> Poll::Ready`
|
||||
/// - 5: Task is dequeued. The task's future is not polled, because exiting the task replaces its `poll_fn`.
|
||||
/// - 6: A task is waken when it is not spawned - `wake_task -> State::run_enqueue`
|
||||
pub(crate) struct TaskHeader {
|
||||
pub(crate) state: State,
|
||||
pub(crate) run_queue_item: RunQueueItem,
|
||||
pub(crate) executor: AtomicPtr<SyncExecutor>,
|
||||
poll_fn: SyncUnsafeCell<Option<unsafe fn(TaskRef)>>,
|
||||
|
||||
/// Integrated timer queue storage. This field should not be accessed outside of the timer queue.
|
||||
pub(crate) timer_queue_item: timer_queue::TimerQueueItem,
|
||||
}
|
||||
|
||||
/// This is essentially a `&'static TaskStorage<F>` where the type of the future has been erased.
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub struct TaskRef {
|
||||
ptr: NonNull<TaskHeader>,
|
||||
}
|
||||
|
||||
unsafe impl Send for TaskRef where &'static TaskHeader: Send {}
|
||||
unsafe impl Sync for TaskRef where &'static TaskHeader: Sync {}
|
||||
|
||||
impl TaskRef {
|
||||
fn new<F: Future + 'static>(task: &'static TaskStorage<F>) -> Self {
|
||||
Self {
|
||||
ptr: NonNull::from(task).cast(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Safety: The pointer must have been obtained with `Task::as_ptr`
|
||||
pub(crate) unsafe fn from_ptr(ptr: *const TaskHeader) -> Self {
|
||||
Self {
|
||||
ptr: NonNull::new_unchecked(ptr as *mut TaskHeader),
|
||||
}
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// The result of this function must only be compared
|
||||
/// for equality, or stored, but not used.
|
||||
pub const unsafe fn dangling() -> Self {
|
||||
Self {
|
||||
ptr: NonNull::dangling(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn header(self) -> &'static TaskHeader {
|
||||
unsafe { self.ptr.as_ref() }
|
||||
}
|
||||
|
||||
/// Returns a reference to the executor that the task is currently running on.
|
||||
pub unsafe fn executor(self) -> Option<&'static Executor> {
|
||||
let executor = self.header().executor.load(Ordering::Relaxed);
|
||||
executor.as_ref().map(|e| Executor::wrap(e))
|
||||
}
|
||||
|
||||
/// Returns a reference to the timer queue item.
|
||||
pub fn timer_queue_item(&self) -> &'static timer_queue::TimerQueueItem {
|
||||
&self.header().timer_queue_item
|
||||
}
|
||||
|
||||
/// The returned pointer is valid for the entire TaskStorage.
|
||||
pub(crate) fn as_ptr(self) -> *const TaskHeader {
|
||||
self.ptr.as_ptr()
|
||||
}
|
||||
}
|
||||
|
||||
/// Raw storage in which a task can be spawned.
|
||||
///
|
||||
/// This struct holds the necessary memory to spawn one task whose future is `F`.
|
||||
/// At a given time, the `TaskStorage` may be in spawned or not-spawned state. You
|
||||
/// may spawn it with [`TaskStorage::spawn()`], which will fail if it is already spawned.
|
||||
///
|
||||
/// A `TaskStorage` must live forever, it may not be deallocated even after the task has finished
|
||||
/// running. Hence the relevant methods require `&'static self`. It may be reused, however.
|
||||
///
|
||||
/// Internally, the [embassy_executor::task](embassy_executor_macros::task) macro allocates an array of `TaskStorage`s
|
||||
/// in a `static`. The most common reason to use the raw `Task` is to have control of where
|
||||
/// the memory for the task is allocated: on the stack, or on the heap with e.g. `Box::leak`, etc.
|
||||
|
||||
// repr(C) is needed to guarantee that the Task is located at offset 0
|
||||
// This makes it safe to cast between TaskHeader and TaskStorage pointers.
|
||||
#[repr(C)]
|
||||
pub struct TaskStorage<F: Future + 'static> {
|
||||
raw: TaskHeader,
|
||||
future: UninitCell<F>, // Valid if STATE_SPAWNED
|
||||
}
|
||||
|
||||
unsafe fn poll_exited(_p: TaskRef) {
|
||||
// Nothing to do, the task is already !SPAWNED and dequeued.
|
||||
}
|
||||
|
||||
impl<F: Future + 'static> TaskStorage<F> {
|
||||
const NEW: Self = Self::new();
|
||||
|
||||
/// Create a new TaskStorage, in not-spawned state.
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
raw: TaskHeader {
|
||||
state: State::new(),
|
||||
run_queue_item: RunQueueItem::new(),
|
||||
executor: AtomicPtr::new(core::ptr::null_mut()),
|
||||
// Note: this is lazily initialized so that a static `TaskStorage` will go in `.bss`
|
||||
poll_fn: SyncUnsafeCell::new(None),
|
||||
|
||||
timer_queue_item: timer_queue::TimerQueueItem::new(),
|
||||
},
|
||||
future: UninitCell::uninit(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to spawn the task.
|
||||
///
|
||||
/// The `future` closure constructs the future. It's only called if spawning is
|
||||
/// actually possible. It is a closure instead of a simple `future: F` param to ensure
|
||||
/// the future is constructed in-place, avoiding a temporary copy in the stack thanks to
|
||||
/// NRVO optimizations.
|
||||
///
|
||||
/// This function will fail if the task is already spawned and has not finished running.
|
||||
/// In this case, the error is delayed: a "poisoned" SpawnToken is returned, which will
|
||||
/// cause [`Spawner::spawn()`](super::Spawner::spawn) to return the error.
|
||||
///
|
||||
/// Once the task has finished running, you may spawn it again. It is allowed to spawn it
|
||||
/// on a different executor.
|
||||
pub fn spawn(&'static self, future: impl FnOnce() -> F) -> SpawnToken<impl Sized> {
|
||||
let task = AvailableTask::claim(self);
|
||||
match task {
|
||||
Some(task) => task.initialize(future),
|
||||
None => SpawnToken::new_failed(),
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn poll(p: TaskRef) {
|
||||
let this = &*p.as_ptr().cast::<TaskStorage<F>>();
|
||||
|
||||
let future = Pin::new_unchecked(this.future.as_mut());
|
||||
let waker = waker::from_task(p);
|
||||
let mut cx = Context::from_waker(&waker);
|
||||
match future.poll(&mut cx) {
|
||||
Poll::Ready(_) => {
|
||||
// As the future has finished and this function will not be called
|
||||
// again, we can safely drop the future here.
|
||||
this.future.drop_in_place();
|
||||
|
||||
// We replace the poll_fn with a despawn function, so that the task is cleaned up
|
||||
// when the executor polls it next.
|
||||
this.raw.poll_fn.set(Some(poll_exited));
|
||||
|
||||
// Make sure we despawn last, so that other threads can only spawn the task
|
||||
// after we're done with it.
|
||||
this.raw.state.despawn();
|
||||
}
|
||||
Poll::Pending => {}
|
||||
}
|
||||
|
||||
// the compiler is emitting a virtual call for waker drop, but we know
|
||||
// it's a noop for our waker.
|
||||
mem::forget(waker);
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[allow(dead_code)]
|
||||
fn _assert_sync(self) {
|
||||
fn assert_sync<T: Sync>(_: T) {}
|
||||
|
||||
assert_sync(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// An uninitialized [`TaskStorage`].
|
||||
pub struct AvailableTask<F: Future + 'static> {
|
||||
task: &'static TaskStorage<F>,
|
||||
}
|
||||
|
||||
impl<F: Future + 'static> AvailableTask<F> {
|
||||
/// Try to claim a [`TaskStorage`].
|
||||
///
|
||||
/// This function returns `None` if a task has already been spawned and has not finished running.
|
||||
pub fn claim(task: &'static TaskStorage<F>) -> Option<Self> {
|
||||
task.raw.state.spawn().then(|| Self { task })
|
||||
}
|
||||
|
||||
fn initialize_impl<S>(self, future: impl FnOnce() -> F) -> SpawnToken<S> {
|
||||
unsafe {
|
||||
self.task.raw.poll_fn.set(Some(TaskStorage::<F>::poll));
|
||||
self.task.future.write_in_place(future);
|
||||
|
||||
let task = TaskRef::new(self.task);
|
||||
|
||||
SpawnToken::new(task)
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize the [`TaskStorage`] to run the given future.
|
||||
pub fn initialize(self, future: impl FnOnce() -> F) -> SpawnToken<F> {
|
||||
self.initialize_impl::<F>(future)
|
||||
}
|
||||
|
||||
/// Initialize the [`TaskStorage`] to run the given future.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `future` must be a closure of the form `move || my_async_fn(args)`, where `my_async_fn`
|
||||
/// is an `async fn`, NOT a hand-written `Future`.
|
||||
#[doc(hidden)]
|
||||
pub unsafe fn __initialize_async_fn<FutFn>(self, future: impl FnOnce() -> F) -> SpawnToken<FutFn> {
|
||||
// When send-spawning a task, we construct the future in this thread, and effectively
|
||||
// "send" it to the executor thread by enqueuing it in its queue. Therefore, in theory,
|
||||
// send-spawning should require the future `F` to be `Send`.
|
||||
//
|
||||
// The problem is this is more restrictive than needed. Once the future is executing,
|
||||
// it is never sent to another thread. It is only sent when spawning. It should be
|
||||
// enough for the task's arguments to be Send. (and in practice it's super easy to
|
||||
// accidentally make your futures !Send, for example by holding an `Rc` or a `&RefCell` across an `.await`.)
|
||||
//
|
||||
// We can do it by sending the task args and constructing the future in the executor thread
|
||||
// on first poll. However, this cannot be done in-place, so it'll waste stack space for a copy
|
||||
// of the args.
|
||||
//
|
||||
// Luckily, an `async fn` future contains just the args when freshly constructed. So, if the
|
||||
// args are Send, it's OK to send a !Send future, as long as we do it before first polling it.
|
||||
//
|
||||
// (Note: this is how the generators are implemented today, it's not officially guaranteed yet,
|
||||
// but it's possible it'll be guaranteed in the future. See zulip thread:
|
||||
// https://rust-lang.zulipchat.com/#narrow/stream/187312-wg-async/topic/.22only.20before.20poll.22.20Send.20futures )
|
||||
//
|
||||
// The `FutFn` captures all the args, so if it's Send, the task can be send-spawned.
|
||||
// This is why we return `SpawnToken<FutFn>` below.
|
||||
//
|
||||
// This ONLY holds for `async fn` futures. The other `spawn` methods can be called directly
|
||||
// by the user, with arbitrary hand-implemented futures. This is why these return `SpawnToken<F>`.
|
||||
self.initialize_impl::<FutFn>(future)
|
||||
}
|
||||
}
|
||||
|
||||
/// Raw storage that can hold up to N tasks of the same type.
|
||||
///
|
||||
/// This is essentially a `[TaskStorage<F>; N]`.
|
||||
pub struct TaskPool<F: Future + 'static, const N: usize> {
|
||||
pool: [TaskStorage<F>; N],
|
||||
}
|
||||
|
||||
impl<F: Future + 'static, const N: usize> TaskPool<F, N> {
|
||||
/// Create a new TaskPool, with all tasks in non-spawned state.
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
pool: [TaskStorage::NEW; N],
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_impl<T>(&'static self, future: impl FnOnce() -> F) -> SpawnToken<T> {
|
||||
match self.pool.iter().find_map(AvailableTask::claim) {
|
||||
Some(task) => task.initialize_impl::<T>(future),
|
||||
None => SpawnToken::new_failed(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to spawn a task in the pool.
|
||||
///
|
||||
/// See [`TaskStorage::spawn()`] for details.
|
||||
///
|
||||
/// This will loop over the pool and spawn the task in the first storage that
|
||||
/// is currently free. If none is free, a "poisoned" SpawnToken is returned,
|
||||
/// which will cause [`Spawner::spawn()`](super::Spawner::spawn) to return the error.
|
||||
pub fn spawn(&'static self, future: impl FnOnce() -> F) -> SpawnToken<impl Sized> {
|
||||
self.spawn_impl::<F>(future)
|
||||
}
|
||||
|
||||
/// Like spawn(), but allows the task to be send-spawned if the args are Send even if
|
||||
/// the future is !Send.
|
||||
///
|
||||
/// Not covered by semver guarantees. DO NOT call this directly. Intended to be used
|
||||
/// by the Embassy macros ONLY.
|
||||
///
|
||||
/// SAFETY: `future` must be a closure of the form `move || my_async_fn(args)`, where `my_async_fn`
|
||||
/// is an `async fn`, NOT a hand-written `Future`.
|
||||
#[doc(hidden)]
|
||||
pub unsafe fn _spawn_async_fn<FutFn>(&'static self, future: FutFn) -> SpawnToken<impl Sized>
|
||||
where
|
||||
FutFn: FnOnce() -> F,
|
||||
{
|
||||
// See the comment in AvailableTask::__initialize_async_fn for explanation.
|
||||
self.spawn_impl::<FutFn>(future)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) struct Pender(*mut ());
|
||||
|
||||
unsafe impl Send for Pender {}
|
||||
unsafe impl Sync for Pender {}
|
||||
|
||||
impl Pender {
|
||||
pub(crate) fn pend(self) {
|
||||
extern "Rust" {
|
||||
fn __pender(context: *mut ());
|
||||
}
|
||||
unsafe { __pender(self.0) };
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct SyncExecutor {
|
||||
run_queue: RunQueue,
|
||||
pender: Pender,
|
||||
}
|
||||
|
||||
impl SyncExecutor {
|
||||
pub(crate) fn new(pender: Pender) -> Self {
|
||||
Self {
|
||||
run_queue: RunQueue::new(),
|
||||
pender,
|
||||
}
|
||||
}
|
||||
|
||||
/// Enqueue a task in the task queue
|
||||
///
|
||||
/// # Safety
|
||||
/// - `task` must be a valid pointer to a spawned task.
|
||||
/// - `task` must be set up to run in this executor.
|
||||
/// - `task` must NOT be already enqueued (in this executor or another one).
|
||||
#[inline(always)]
|
||||
unsafe fn enqueue(&self, task: TaskRef, l: state::Token) {
|
||||
#[cfg(feature = "trace")]
|
||||
trace::task_ready_begin(self, &task);
|
||||
|
||||
if self.run_queue.enqueue(task, l) {
|
||||
self.pender.pend();
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) unsafe fn spawn(&'static self, task: TaskRef) {
|
||||
task.header()
|
||||
.executor
|
||||
.store((self as *const Self).cast_mut(), Ordering::Relaxed);
|
||||
|
||||
#[cfg(feature = "trace")]
|
||||
trace::task_new(self, &task);
|
||||
|
||||
state::locked(|l| {
|
||||
self.enqueue(task, l);
|
||||
})
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// Same as [`Executor::poll`], plus you must only call this on the thread this executor was created.
|
||||
pub(crate) unsafe fn poll(&'static self) {
|
||||
self.run_queue.dequeue_all(|p| {
|
||||
let task = p.header();
|
||||
|
||||
#[cfg(feature = "trace")]
|
||||
trace::task_exec_begin(self, &p);
|
||||
|
||||
// Run the task
|
||||
task.poll_fn.get().unwrap_unchecked()(p);
|
||||
|
||||
#[cfg(feature = "trace")]
|
||||
trace::task_exec_end(self, &p);
|
||||
});
|
||||
|
||||
#[cfg(feature = "trace")]
|
||||
trace::executor_idle(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Raw executor.
|
||||
///
|
||||
/// This is the core of the Embassy executor. It is low-level, requiring manual
|
||||
/// handling of wakeups and task polling. If you can, prefer using one of the
|
||||
/// [higher level executors](crate::Executor).
|
||||
///
|
||||
/// The raw executor leaves it up to you to handle wakeups and scheduling:
|
||||
///
|
||||
/// - To get the executor to do work, call `poll()`. This will poll all queued tasks (all tasks
|
||||
/// that "want to run").
|
||||
/// - You must supply a pender function, as shown below. The executor will call it to notify you
|
||||
/// it has work to do. You must arrange for `poll()` to be called as soon as possible.
|
||||
/// - Enabling `arch-xx` features will define a pender function for you. This means that you
|
||||
/// are limited to using the executors provided to you by the architecture/platform
|
||||
/// implementation. If you need a different executor, you must not enable `arch-xx` features.
|
||||
///
|
||||
/// The pender can be called from *any* context: any thread, any interrupt priority
|
||||
/// level, etc. It may be called synchronously from any `Executor` method call as well.
|
||||
/// You must deal with this correctly.
|
||||
///
|
||||
/// In particular, you must NOT call `poll` directly from the pender callback, as this violates
|
||||
/// the requirement for `poll` to not be called reentrantly.
|
||||
///
|
||||
/// The pender function must be exported with the name `__pender` and have the following signature:
|
||||
///
|
||||
/// ```rust
|
||||
/// #[export_name = "__pender"]
|
||||
/// fn pender(context: *mut ()) {
|
||||
/// // schedule `poll()` to be called
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// The `context` argument is a piece of arbitrary data the executor will pass to the pender.
|
||||
/// You can set the `context` when calling [`Executor::new()`]. You can use it to, for example,
|
||||
/// differentiate between executors, or to pass a pointer to a callback that should be called.
|
||||
#[repr(transparent)]
|
||||
pub struct Executor {
|
||||
pub(crate) inner: SyncExecutor,
|
||||
|
||||
_not_sync: PhantomData<*mut ()>,
|
||||
}
|
||||
|
||||
impl Executor {
|
||||
pub(crate) unsafe fn wrap(inner: &SyncExecutor) -> &Self {
|
||||
mem::transmute(inner)
|
||||
}
|
||||
|
||||
/// Create a new executor.
|
||||
///
|
||||
/// When the executor has work to do, it will call the pender function and pass `context` to it.
|
||||
///
|
||||
/// See [`Executor`] docs for details on the pender.
|
||||
pub fn new(context: *mut ()) -> Self {
|
||||
Self {
|
||||
inner: SyncExecutor::new(Pender(context)),
|
||||
_not_sync: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawn a task in this executor.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `task` must be a valid pointer to an initialized but not-already-spawned task.
|
||||
///
|
||||
/// It is OK to use `unsafe` to call this from a thread that's not the executor thread.
|
||||
/// In this case, the task's Future must be Send. This is because this is effectively
|
||||
/// sending the task to the executor thread.
|
||||
pub(super) unsafe fn spawn(&'static self, task: TaskRef) {
|
||||
self.inner.spawn(task)
|
||||
}
|
||||
|
||||
/// Poll all queued tasks in this executor.
|
||||
///
|
||||
/// This loops over all tasks that are queued to be polled (i.e. they're
|
||||
/// freshly spawned or they've been woken). Other tasks are not polled.
|
||||
///
|
||||
/// You must call `poll` after receiving a call to the pender. It is OK
|
||||
/// to call `poll` even when not requested by the pender, but it wastes
|
||||
/// energy.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// You must call `initialize` before calling this method.
|
||||
///
|
||||
/// You must NOT call `poll` reentrantly on the same executor.
|
||||
///
|
||||
/// In particular, note that `poll` may call the pender synchronously. Therefore, you
|
||||
/// must NOT directly call `poll()` from the pender callback. Instead, the callback has to
|
||||
/// somehow schedule for `poll()` to be called later, at a time you know for sure there's
|
||||
/// no `poll()` already running.
|
||||
pub unsafe fn poll(&'static self) {
|
||||
self.inner.poll()
|
||||
}
|
||||
|
||||
/// Get a spawner that spawns tasks in this executor.
|
||||
///
|
||||
/// It is OK to call this method multiple times to obtain multiple
|
||||
/// `Spawner`s. You may also copy `Spawner`s.
|
||||
pub fn spawner(&'static self) -> super::Spawner {
|
||||
super::Spawner::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Wake a task by `TaskRef`.
|
||||
///
|
||||
/// You can obtain a `TaskRef` from a `Waker` using [`task_from_waker`].
|
||||
pub fn wake_task(task: TaskRef) {
|
||||
let header = task.header();
|
||||
header.state.run_enqueue(|l| {
|
||||
// We have just marked the task as scheduled, so enqueue it.
|
||||
unsafe {
|
||||
let executor = header.executor.load(Ordering::Relaxed).as_ref().unwrap_unchecked();
|
||||
executor.enqueue(task, l);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Wake a task by `TaskRef` without calling pend.
|
||||
///
|
||||
/// You can obtain a `TaskRef` from a `Waker` using [`task_from_waker`].
|
||||
pub fn wake_task_no_pend(task: TaskRef) {
|
||||
let header = task.header();
|
||||
header.state.run_enqueue(|l| {
|
||||
// We have just marked the task as scheduled, so enqueue it.
|
||||
unsafe {
|
||||
let executor = header.executor.load(Ordering::Relaxed).as_ref().unwrap_unchecked();
|
||||
executor.run_queue.enqueue(task, l);
|
||||
}
|
||||
});
|
||||
}
|
||||
88
embassy-executor/src/raw/run_queue_atomics.rs
Normal file
88
embassy-executor/src/raw/run_queue_atomics.rs
Normal file
@@ -0,0 +1,88 @@
|
||||
use core::ptr;
|
||||
use core::ptr::NonNull;
|
||||
use core::sync::atomic::{AtomicPtr, Ordering};
|
||||
|
||||
use super::{TaskHeader, TaskRef};
|
||||
use crate::raw::util::SyncUnsafeCell;
|
||||
|
||||
pub(crate) struct RunQueueItem {
|
||||
next: SyncUnsafeCell<Option<TaskRef>>,
|
||||
}
|
||||
|
||||
impl RunQueueItem {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
next: SyncUnsafeCell::new(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Atomic task queue using a very, very simple lock-free linked-list queue:
|
||||
///
|
||||
/// To enqueue a task, task.next is set to the old head, and head is atomically set to task.
|
||||
///
|
||||
/// Dequeuing is done in batches: the queue is emptied by atomically replacing head with
|
||||
/// null. Then the batch is iterated following the next pointers until null is reached.
|
||||
///
|
||||
/// Note that batches will be iterated in the reverse order as they were enqueued. This is OK
|
||||
/// for our purposes: it can't create fairness problems since the next batch won't run until the
|
||||
/// current batch is completely processed, so even if a task enqueues itself instantly (for example
|
||||
/// by waking its own waker) can't prevent other tasks from running.
|
||||
pub(crate) struct RunQueue {
|
||||
head: AtomicPtr<TaskHeader>,
|
||||
}
|
||||
|
||||
impl RunQueue {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
head: AtomicPtr::new(ptr::null_mut()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Enqueues an item. Returns true if the queue was empty.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `item` must NOT be already enqueued in any queue.
|
||||
#[inline(always)]
|
||||
pub(crate) unsafe fn enqueue(&self, task: TaskRef, _: super::state::Token) -> bool {
|
||||
let mut was_empty = false;
|
||||
|
||||
self.head
|
||||
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |prev| {
|
||||
was_empty = prev.is_null();
|
||||
unsafe {
|
||||
// safety: the pointer is either null or valid
|
||||
let prev = NonNull::new(prev).map(|ptr| TaskRef::from_ptr(ptr.as_ptr()));
|
||||
// safety: there are no concurrent accesses to `next`
|
||||
task.header().run_queue_item.next.set(prev);
|
||||
}
|
||||
Some(task.as_ptr() as *mut _)
|
||||
})
|
||||
.ok();
|
||||
|
||||
was_empty
|
||||
}
|
||||
|
||||
/// Empty the queue, then call `on_task` for each task that was in the queue.
|
||||
/// NOTE: It is OK for `on_task` to enqueue more tasks. In this case they're left in the queue
|
||||
/// and will be processed by the *next* call to `dequeue_all`, *not* the current one.
|
||||
pub(crate) fn dequeue_all(&self, on_task: impl Fn(TaskRef)) {
|
||||
// Atomically empty the queue.
|
||||
let ptr = self.head.swap(ptr::null_mut(), Ordering::AcqRel);
|
||||
|
||||
// safety: the pointer is either null or valid
|
||||
let mut next = unsafe { NonNull::new(ptr).map(|ptr| TaskRef::from_ptr(ptr.as_ptr())) };
|
||||
|
||||
// Iterate the linked list of tasks that were previously in the queue.
|
||||
while let Some(task) = next {
|
||||
// If the task re-enqueues itself, the `next` pointer will get overwritten.
|
||||
// Therefore, first read the next pointer, and only then process the task.
|
||||
// safety: there are no concurrent accesses to `next`
|
||||
next = unsafe { task.header().run_queue_item.next.get() };
|
||||
|
||||
task.header().state.run_dequeue();
|
||||
on_task(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
74
embassy-executor/src/raw/run_queue_critical_section.rs
Normal file
74
embassy-executor/src/raw/run_queue_critical_section.rs
Normal file
@@ -0,0 +1,74 @@
|
||||
use core::cell::Cell;
|
||||
|
||||
use critical_section::{CriticalSection, Mutex};
|
||||
|
||||
use super::TaskRef;
|
||||
|
||||
pub(crate) struct RunQueueItem {
|
||||
next: Mutex<Cell<Option<TaskRef>>>,
|
||||
}
|
||||
|
||||
impl RunQueueItem {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
next: Mutex::new(Cell::new(None)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Atomic task queue using a very, very simple lock-free linked-list queue:
|
||||
///
|
||||
/// To enqueue a task, task.next is set to the old head, and head is atomically set to task.
|
||||
///
|
||||
/// Dequeuing is done in batches: the queue is emptied by atomically replacing head with
|
||||
/// null. Then the batch is iterated following the next pointers until null is reached.
|
||||
///
|
||||
/// Note that batches will be iterated in the reverse order as they were enqueued. This is OK
|
||||
/// for our purposes: it can't create fairness problems since the next batch won't run until the
|
||||
/// current batch is completely processed, so even if a task enqueues itself instantly (for example
|
||||
/// by waking its own waker) can't prevent other tasks from running.
|
||||
pub(crate) struct RunQueue {
|
||||
head: Mutex<Cell<Option<TaskRef>>>,
|
||||
}
|
||||
|
||||
impl RunQueue {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
head: Mutex::new(Cell::new(None)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Enqueues an item. Returns true if the queue was empty.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `item` must NOT be already enqueued in any queue.
|
||||
#[inline(always)]
|
||||
pub(crate) unsafe fn enqueue(&self, task: TaskRef, cs: CriticalSection<'_>) -> bool {
|
||||
let prev = self.head.borrow(cs).replace(Some(task));
|
||||
task.header().run_queue_item.next.borrow(cs).set(prev);
|
||||
|
||||
prev.is_none()
|
||||
}
|
||||
|
||||
/// Empty the queue, then call `on_task` for each task that was in the queue.
|
||||
/// NOTE: It is OK for `on_task` to enqueue more tasks. In this case they're left in the queue
|
||||
/// and will be processed by the *next* call to `dequeue_all`, *not* the current one.
|
||||
pub(crate) fn dequeue_all(&self, on_task: impl Fn(TaskRef)) {
|
||||
// Atomically empty the queue.
|
||||
let mut next = critical_section::with(|cs| self.head.borrow(cs).take());
|
||||
|
||||
// Iterate the linked list of tasks that were previously in the queue.
|
||||
while let Some(task) = next {
|
||||
// If the task re-enqueues itself, the `next` pointer will get overwritten.
|
||||
// Therefore, first read the next pointer, and only then process the task.
|
||||
|
||||
critical_section::with(|cs| {
|
||||
next = task.header().run_queue_item.next.borrow(cs).get();
|
||||
task.header().state.run_dequeue(cs);
|
||||
});
|
||||
|
||||
on_task(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
58
embassy-executor/src/raw/state_atomics.rs
Normal file
58
embassy-executor/src/raw/state_atomics.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
use core::sync::atomic::{AtomicU32, Ordering};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) struct Token(());
|
||||
|
||||
/// Creates a token and passes it to the closure.
|
||||
///
|
||||
/// This is a no-op replacement for `CriticalSection::with` because we don't need any locking.
|
||||
pub(crate) fn locked<R>(f: impl FnOnce(Token) -> R) -> R {
|
||||
f(Token(()))
|
||||
}
|
||||
|
||||
/// Task is spawned (has a future)
|
||||
pub(crate) const STATE_SPAWNED: u32 = 1 << 0;
|
||||
/// Task is in the executor run queue
|
||||
pub(crate) const STATE_RUN_QUEUED: u32 = 1 << 1;
|
||||
|
||||
pub(crate) struct State {
|
||||
state: AtomicU32,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub const fn new() -> State {
|
||||
Self {
|
||||
state: AtomicU32::new(0),
|
||||
}
|
||||
}
|
||||
|
||||
/// If task is idle, mark it as spawned + run_queued and return true.
|
||||
#[inline(always)]
|
||||
pub fn spawn(&self) -> bool {
|
||||
self.state
|
||||
.compare_exchange(0, STATE_SPAWNED | STATE_RUN_QUEUED, Ordering::AcqRel, Ordering::Acquire)
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
/// Unmark the task as spawned.
|
||||
#[inline(always)]
|
||||
pub fn despawn(&self) {
|
||||
self.state.fetch_and(!STATE_SPAWNED, Ordering::AcqRel);
|
||||
}
|
||||
|
||||
/// Mark the task as run-queued if it's spawned and isn't already run-queued. Run the given
|
||||
/// function if the task was successfully marked.
|
||||
#[inline(always)]
|
||||
pub fn run_enqueue(&self, f: impl FnOnce(Token)) {
|
||||
let prev = self.state.fetch_or(STATE_RUN_QUEUED, Ordering::AcqRel);
|
||||
if prev & STATE_RUN_QUEUED == 0 {
|
||||
locked(f);
|
||||
}
|
||||
}
|
||||
|
||||
/// Unmark the task as run-queued. Return whether the task is spawned.
|
||||
#[inline(always)]
|
||||
pub fn run_dequeue(&self) {
|
||||
self.state.fetch_and(!STATE_RUN_QUEUED, Ordering::AcqRel);
|
||||
}
|
||||
}
|
||||
83
embassy-executor/src/raw/state_atomics_arm.rs
Normal file
83
embassy-executor/src/raw/state_atomics_arm.rs
Normal file
@@ -0,0 +1,83 @@
|
||||
use core::sync::atomic::{compiler_fence, AtomicBool, AtomicU32, Ordering};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) struct Token(());
|
||||
|
||||
/// Creates a token and passes it to the closure.
|
||||
///
|
||||
/// This is a no-op replacement for `CriticalSection::with` because we don't need any locking.
|
||||
pub(crate) fn locked<R>(f: impl FnOnce(Token) -> R) -> R {
|
||||
f(Token(()))
|
||||
}
|
||||
|
||||
// Must be kept in sync with the layout of `State`!
|
||||
pub(crate) const STATE_SPAWNED: u32 = 1 << 0;
|
||||
pub(crate) const STATE_RUN_QUEUED: u32 = 1 << 8;
|
||||
|
||||
#[repr(C, align(4))]
|
||||
pub(crate) struct State {
|
||||
/// Task is spawned (has a future)
|
||||
spawned: AtomicBool,
|
||||
/// Task is in the executor run queue
|
||||
run_queued: AtomicBool,
|
||||
pad: AtomicBool,
|
||||
pad2: AtomicBool,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub const fn new() -> State {
|
||||
Self {
|
||||
spawned: AtomicBool::new(false),
|
||||
run_queued: AtomicBool::new(false),
|
||||
pad: AtomicBool::new(false),
|
||||
pad2: AtomicBool::new(false),
|
||||
}
|
||||
}
|
||||
|
||||
fn as_u32(&self) -> &AtomicU32 {
|
||||
unsafe { &*(self as *const _ as *const AtomicU32) }
|
||||
}
|
||||
|
||||
/// If task is idle, mark it as spawned + run_queued and return true.
|
||||
#[inline(always)]
|
||||
pub fn spawn(&self) -> bool {
|
||||
compiler_fence(Ordering::Release);
|
||||
let r = self
|
||||
.as_u32()
|
||||
.compare_exchange(
|
||||
0,
|
||||
STATE_SPAWNED | STATE_RUN_QUEUED,
|
||||
Ordering::Relaxed,
|
||||
Ordering::Relaxed,
|
||||
)
|
||||
.is_ok();
|
||||
compiler_fence(Ordering::Acquire);
|
||||
r
|
||||
}
|
||||
|
||||
/// Unmark the task as spawned.
|
||||
#[inline(always)]
|
||||
pub fn despawn(&self) {
|
||||
compiler_fence(Ordering::Release);
|
||||
self.spawned.store(false, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
/// Mark the task as run-queued if it's spawned and isn't already run-queued. Run the given
|
||||
/// function if the task was successfully marked.
|
||||
#[inline(always)]
|
||||
pub fn run_enqueue(&self, f: impl FnOnce(Token)) {
|
||||
let old = self.run_queued.swap(true, Ordering::AcqRel);
|
||||
|
||||
if !old {
|
||||
locked(f);
|
||||
}
|
||||
}
|
||||
|
||||
/// Unmark the task as run-queued. Return whether the task is spawned.
|
||||
#[inline(always)]
|
||||
pub fn run_dequeue(&self) {
|
||||
compiler_fence(Ordering::Release);
|
||||
|
||||
self.run_queued.store(false, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
73
embassy-executor/src/raw/state_critical_section.rs
Normal file
73
embassy-executor/src/raw/state_critical_section.rs
Normal file
@@ -0,0 +1,73 @@
|
||||
use core::cell::Cell;
|
||||
|
||||
pub(crate) use critical_section::{with as locked, CriticalSection as Token};
|
||||
use critical_section::{CriticalSection, Mutex};
|
||||
|
||||
/// Task is spawned (has a future)
|
||||
pub(crate) const STATE_SPAWNED: u32 = 1 << 0;
|
||||
/// Task is in the executor run queue
|
||||
pub(crate) const STATE_RUN_QUEUED: u32 = 1 << 1;
|
||||
|
||||
pub(crate) struct State {
|
||||
state: Mutex<Cell<u32>>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub const fn new() -> State {
|
||||
Self {
|
||||
state: Mutex::new(Cell::new(0)),
|
||||
}
|
||||
}
|
||||
|
||||
fn update<R>(&self, f: impl FnOnce(&mut u32) -> R) -> R {
|
||||
critical_section::with(|cs| self.update_with_cs(cs, f))
|
||||
}
|
||||
|
||||
fn update_with_cs<R>(&self, cs: CriticalSection<'_>, f: impl FnOnce(&mut u32) -> R) -> R {
|
||||
let s = self.state.borrow(cs);
|
||||
let mut val = s.get();
|
||||
let r = f(&mut val);
|
||||
s.set(val);
|
||||
r
|
||||
}
|
||||
|
||||
/// If task is idle, mark it as spawned + run_queued and return true.
|
||||
#[inline(always)]
|
||||
pub fn spawn(&self) -> bool {
|
||||
self.update(|s| {
|
||||
if *s == 0 {
|
||||
*s = STATE_SPAWNED | STATE_RUN_QUEUED;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Unmark the task as spawned.
|
||||
#[inline(always)]
|
||||
pub fn despawn(&self) {
|
||||
self.update(|s| *s &= !STATE_SPAWNED);
|
||||
}
|
||||
|
||||
/// Mark the task as run-queued if it's spawned and isn't already run-queued. Run the given
|
||||
/// function if the task was successfully marked.
|
||||
#[inline(always)]
|
||||
pub fn run_enqueue(&self, f: impl FnOnce(Token)) {
|
||||
critical_section::with(|cs| {
|
||||
if self.update_with_cs(cs, |s| {
|
||||
let ok = *s & STATE_RUN_QUEUED == 0;
|
||||
*s |= STATE_RUN_QUEUED;
|
||||
ok
|
||||
}) {
|
||||
f(cs);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Unmark the task as run-queued. Return whether the task is spawned.
|
||||
#[inline(always)]
|
||||
pub fn run_dequeue(&self, cs: CriticalSection<'_>) {
|
||||
self.update_with_cs(cs, |s| *s &= !STATE_RUN_QUEUED)
|
||||
}
|
||||
}
|
||||
73
embassy-executor/src/raw/timer_queue.rs
Normal file
73
embassy-executor/src/raw/timer_queue.rs
Normal file
@@ -0,0 +1,73 @@
|
||||
//! Timer queue operations.
|
||||
|
||||
use core::cell::Cell;
|
||||
|
||||
use super::TaskRef;
|
||||
|
||||
#[cfg(feature = "_timer-item-payload")]
|
||||
macro_rules! define_opaque {
|
||||
($size:tt) => {
|
||||
/// An opaque data type.
|
||||
#[repr(align($size))]
|
||||
pub struct OpaqueData {
|
||||
data: [u8; $size],
|
||||
}
|
||||
|
||||
impl OpaqueData {
|
||||
const fn new() -> Self {
|
||||
Self { data: [0; $size] }
|
||||
}
|
||||
|
||||
/// Access the data as a reference to a type `T`.
|
||||
///
|
||||
/// Safety:
|
||||
///
|
||||
/// The caller must ensure that the size of the type `T` is less than, or equal to
|
||||
/// the size of the payload, and must ensure that the alignment of the type `T` is
|
||||
/// less than, or equal to the alignment of the payload.
|
||||
///
|
||||
/// The type must be valid when zero-initialized.
|
||||
pub unsafe fn as_ref<T>(&self) -> &T {
|
||||
&*(self.data.as_ptr() as *const T)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(feature = "timer-item-payload-size-1")]
|
||||
define_opaque!(1);
|
||||
#[cfg(feature = "timer-item-payload-size-2")]
|
||||
define_opaque!(2);
|
||||
#[cfg(feature = "timer-item-payload-size-4")]
|
||||
define_opaque!(4);
|
||||
#[cfg(feature = "timer-item-payload-size-8")]
|
||||
define_opaque!(8);
|
||||
|
||||
/// An item in the timer queue.
|
||||
pub struct TimerQueueItem {
|
||||
/// The next item in the queue.
|
||||
///
|
||||
/// If this field contains `Some`, the item is in the queue. The last item in the queue has a
|
||||
/// value of `Some(dangling_pointer)`
|
||||
pub next: Cell<Option<TaskRef>>,
|
||||
|
||||
/// The time at which this item expires.
|
||||
pub expires_at: Cell<u64>,
|
||||
|
||||
/// Some implementation-defined, zero-initialized piece of data.
|
||||
#[cfg(feature = "_timer-item-payload")]
|
||||
pub payload: OpaqueData,
|
||||
}
|
||||
|
||||
unsafe impl Sync for TimerQueueItem {}
|
||||
|
||||
impl TimerQueueItem {
|
||||
pub(crate) const fn new() -> Self {
|
||||
Self {
|
||||
next: Cell::new(None),
|
||||
expires_at: Cell::new(0),
|
||||
#[cfg(feature = "_timer-item-payload")]
|
||||
payload: OpaqueData::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
84
embassy-executor/src/raw/trace.rs
Normal file
84
embassy-executor/src/raw/trace.rs
Normal file
@@ -0,0 +1,84 @@
|
||||
#![allow(unused)]
|
||||
use crate::raw::{SyncExecutor, TaskRef};
|
||||
|
||||
#[cfg(not(feature = "rtos-trace"))]
|
||||
extern "Rust" {
|
||||
fn _embassy_trace_task_new(executor_id: u32, task_id: u32);
|
||||
fn _embassy_trace_task_exec_begin(executor_id: u32, task_id: u32);
|
||||
fn _embassy_trace_task_exec_end(excutor_id: u32, task_id: u32);
|
||||
fn _embassy_trace_task_ready_begin(executor_id: u32, task_id: u32);
|
||||
fn _embassy_trace_executor_idle(executor_id: u32);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn task_new(executor: &SyncExecutor, task: &TaskRef) {
|
||||
#[cfg(not(feature = "rtos-trace"))]
|
||||
unsafe {
|
||||
_embassy_trace_task_new(executor as *const _ as u32, task.as_ptr() as u32)
|
||||
}
|
||||
|
||||
#[cfg(feature = "rtos-trace")]
|
||||
rtos_trace::trace::task_new(task.as_ptr() as u32);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn task_ready_begin(executor: &SyncExecutor, task: &TaskRef) {
|
||||
#[cfg(not(feature = "rtos-trace"))]
|
||||
unsafe {
|
||||
_embassy_trace_task_ready_begin(executor as *const _ as u32, task.as_ptr() as u32)
|
||||
}
|
||||
#[cfg(feature = "rtos-trace")]
|
||||
rtos_trace::trace::task_ready_begin(task.as_ptr() as u32);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn task_exec_begin(executor: &SyncExecutor, task: &TaskRef) {
|
||||
#[cfg(not(feature = "rtos-trace"))]
|
||||
unsafe {
|
||||
_embassy_trace_task_exec_begin(executor as *const _ as u32, task.as_ptr() as u32)
|
||||
}
|
||||
#[cfg(feature = "rtos-trace")]
|
||||
rtos_trace::trace::task_exec_begin(task.as_ptr() as u32);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn task_exec_end(executor: &SyncExecutor, task: &TaskRef) {
|
||||
#[cfg(not(feature = "rtos-trace"))]
|
||||
unsafe {
|
||||
_embassy_trace_task_exec_end(executor as *const _ as u32, task.as_ptr() as u32)
|
||||
}
|
||||
#[cfg(feature = "rtos-trace")]
|
||||
rtos_trace::trace::task_exec_end();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn executor_idle(executor: &SyncExecutor) {
|
||||
#[cfg(not(feature = "rtos-trace"))]
|
||||
unsafe {
|
||||
_embassy_trace_executor_idle(executor as *const _ as u32)
|
||||
}
|
||||
#[cfg(feature = "rtos-trace")]
|
||||
rtos_trace::trace::system_idle();
|
||||
}
|
||||
|
||||
#[cfg(feature = "rtos-trace")]
|
||||
impl rtos_trace::RtosTraceOSCallbacks for crate::raw::SyncExecutor {
|
||||
fn task_list() {
|
||||
// We don't know what tasks exist, so we can't send them.
|
||||
}
|
||||
fn time() -> u64 {
|
||||
const fn gcd(a: u64, b: u64) -> u64 {
|
||||
if b == 0 {
|
||||
a
|
||||
} else {
|
||||
gcd(b, a % b)
|
||||
}
|
||||
}
|
||||
|
||||
const GCD_1M: u64 = gcd(embassy_time_driver::TICK_HZ, 1_000_000);
|
||||
embassy_time_driver::now() * (1_000_000 / GCD_1M) / (embassy_time_driver::TICK_HZ / GCD_1M)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rtos-trace")]
|
||||
rtos_trace::global_os_callbacks! {SyncExecutor}
|
||||
57
embassy-executor/src/raw/util.rs
Normal file
57
embassy-executor/src/raw/util.rs
Normal file
@@ -0,0 +1,57 @@
|
||||
use core::cell::UnsafeCell;
|
||||
use core::mem::MaybeUninit;
|
||||
use core::ptr;
|
||||
|
||||
pub(crate) struct UninitCell<T>(MaybeUninit<UnsafeCell<T>>);
|
||||
impl<T> UninitCell<T> {
|
||||
pub const fn uninit() -> Self {
|
||||
Self(MaybeUninit::uninit())
|
||||
}
|
||||
|
||||
pub unsafe fn as_mut_ptr(&self) -> *mut T {
|
||||
(*self.0.as_ptr()).get()
|
||||
}
|
||||
|
||||
#[allow(clippy::mut_from_ref)]
|
||||
pub unsafe fn as_mut(&self) -> &mut T {
|
||||
&mut *self.as_mut_ptr()
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
pub unsafe fn write_in_place(&self, func: impl FnOnce() -> T) {
|
||||
ptr::write(self.as_mut_ptr(), func())
|
||||
}
|
||||
|
||||
pub unsafe fn drop_in_place(&self) {
|
||||
ptr::drop_in_place(self.as_mut_ptr())
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T> Sync for UninitCell<T> {}
|
||||
|
||||
#[repr(transparent)]
|
||||
pub struct SyncUnsafeCell<T> {
|
||||
value: UnsafeCell<T>,
|
||||
}
|
||||
|
||||
unsafe impl<T: Sync> Sync for SyncUnsafeCell<T> {}
|
||||
|
||||
impl<T> SyncUnsafeCell<T> {
|
||||
#[inline]
|
||||
pub const fn new(value: T) -> Self {
|
||||
Self {
|
||||
value: UnsafeCell::new(value),
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn set(&self, value: T) {
|
||||
*self.value.get() = value;
|
||||
}
|
||||
|
||||
pub unsafe fn get(&self) -> T
|
||||
where
|
||||
T: Copy,
|
||||
{
|
||||
*self.value.get()
|
||||
}
|
||||
}
|
||||
42
embassy-executor/src/raw/waker.rs
Normal file
42
embassy-executor/src/raw/waker.rs
Normal file
@@ -0,0 +1,42 @@
|
||||
use core::task::{RawWaker, RawWakerVTable, Waker};
|
||||
|
||||
use super::{wake_task, TaskHeader, TaskRef};
|
||||
|
||||
static VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake, drop);
|
||||
|
||||
unsafe fn clone(p: *const ()) -> RawWaker {
|
||||
RawWaker::new(p, &VTABLE)
|
||||
}
|
||||
|
||||
unsafe fn wake(p: *const ()) {
|
||||
wake_task(TaskRef::from_ptr(p as *const TaskHeader))
|
||||
}
|
||||
|
||||
unsafe fn drop(_: *const ()) {
|
||||
// nop
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn from_task(p: TaskRef) -> Waker {
|
||||
Waker::from_raw(RawWaker::new(p.as_ptr() as _, &VTABLE))
|
||||
}
|
||||
|
||||
/// Get a task pointer from a waker.
|
||||
///
|
||||
/// This can be used as an optimization in wait queues to store task pointers
|
||||
/// (1 word) instead of full Wakers (2 words). This saves a bit of RAM and helps
|
||||
/// avoid dynamic dispatch.
|
||||
///
|
||||
/// You can use the returned task pointer to wake the task with [`wake_task`](super::wake_task).
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the waker is not created by the Embassy executor.
|
||||
pub fn task_from_waker(waker: &Waker) -> TaskRef {
|
||||
// make sure to compare vtable addresses. Doing `==` on the references
|
||||
// will compare the contents, which is slower.
|
||||
if waker.vtable() as *const _ != &VTABLE as *const _ {
|
||||
panic!("Found waker not created by the Embassy executor. `embassy_time::Timer` only works with the Embassy executor.")
|
||||
}
|
||||
// safety: our wakers are always created with `TaskRef::as_ptr`
|
||||
unsafe { TaskRef::from_ptr(waker.data() as *const TaskHeader) }
|
||||
}
|
||||
34
embassy-executor/src/raw/waker_turbo.rs
Normal file
34
embassy-executor/src/raw/waker_turbo.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
use core::ptr::NonNull;
|
||||
use core::task::Waker;
|
||||
|
||||
use super::{wake_task, TaskHeader, TaskRef};
|
||||
|
||||
pub(crate) unsafe fn from_task(p: TaskRef) -> Waker {
|
||||
Waker::from_turbo_ptr(NonNull::new_unchecked(p.as_ptr() as _))
|
||||
}
|
||||
|
||||
/// Get a task pointer from a waker.
|
||||
///
|
||||
/// This can be used as an optimization in wait queues to store task pointers
|
||||
/// (1 word) instead of full Wakers (2 words). This saves a bit of RAM and helps
|
||||
/// avoid dynamic dispatch.
|
||||
///
|
||||
/// You can use the returned task pointer to wake the task with [`wake_task`](super::wake_task).
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the waker is not created by the Embassy executor.
|
||||
pub fn task_from_waker(waker: &Waker) -> TaskRef {
|
||||
let ptr = waker.as_turbo_ptr().as_ptr();
|
||||
|
||||
// safety: our wakers are always created with `TaskRef::as_ptr`
|
||||
unsafe { TaskRef::from_ptr(ptr as *const TaskHeader) }
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
#[no_mangle]
|
||||
fn _turbo_wake(ptr: NonNull<()>) {
|
||||
// safety: our wakers are always created with `TaskRef::as_ptr`
|
||||
let task = unsafe { TaskRef::from_ptr(ptr.as_ptr() as *const TaskHeader) };
|
||||
wake_task(task)
|
||||
}
|
||||
232
embassy-executor/src/spawner.rs
Normal file
232
embassy-executor/src/spawner.rs
Normal file
@@ -0,0 +1,232 @@
|
||||
use core::future::{poll_fn, Future};
|
||||
use core::marker::PhantomData;
|
||||
use core::mem;
|
||||
use core::sync::atomic::Ordering;
|
||||
use core::task::Poll;
|
||||
|
||||
use super::raw;
|
||||
|
||||
/// Token to spawn a newly-created task in an executor.
|
||||
///
|
||||
/// When calling a task function (like `#[embassy_executor::task] async fn my_task() { ... }`), the returned
|
||||
/// value is a `SpawnToken` that represents an instance of the task, ready to spawn. You must
|
||||
/// then spawn it into an executor, typically with [`Spawner::spawn()`].
|
||||
///
|
||||
/// The generic parameter `S` determines whether the task can be spawned in executors
|
||||
/// in other threads or not. If `S: Send`, it can, which allows spawning it into a [`SendSpawner`].
|
||||
/// If not, it can't, so it can only be spawned into the current thread's executor, with [`Spawner`].
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Dropping a SpawnToken instance panics. You may not "abort" spawning a task in this way.
|
||||
/// Once you've invoked a task function and obtained a SpawnToken, you *must* spawn it.
|
||||
#[must_use = "Calling a task function does nothing on its own. You must spawn the returned SpawnToken, typically with Spawner::spawn()"]
|
||||
pub struct SpawnToken<S> {
|
||||
raw_task: Option<raw::TaskRef>,
|
||||
phantom: PhantomData<*mut S>,
|
||||
}
|
||||
|
||||
impl<S> SpawnToken<S> {
|
||||
pub(crate) unsafe fn new(raw_task: raw::TaskRef) -> Self {
|
||||
Self {
|
||||
raw_task: Some(raw_task),
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a SpawnToken that represents a failed spawn.
|
||||
pub fn new_failed() -> Self {
|
||||
Self {
|
||||
raw_task: None,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Drop for SpawnToken<S> {
|
||||
fn drop(&mut self) {
|
||||
// TODO deallocate the task instead.
|
||||
panic!("SpawnToken instances may not be dropped. You must pass them to Spawner::spawn()")
|
||||
}
|
||||
}
|
||||
|
||||
/// Error returned when spawning a task.
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum SpawnError {
|
||||
/// Too many instances of this task are already running.
|
||||
///
|
||||
/// By default, a task marked with `#[embassy_executor::task]` can only have one instance
|
||||
/// running at a time. You may allow multiple instances to run in parallel with
|
||||
/// `#[embassy_executor::task(pool_size = 4)]`, at the cost of higher RAM usage.
|
||||
Busy,
|
||||
}
|
||||
|
||||
impl core::fmt::Debug for SpawnError {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
core::fmt::Display::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Display for SpawnError {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
SpawnError::Busy => write!(f, "Busy - Too many instances of this task are already running. Check the `pool_size` attribute of the task."),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
impl defmt::Format for SpawnError {
|
||||
fn format(&self, f: defmt::Formatter) {
|
||||
match self {
|
||||
SpawnError::Busy => defmt::write!(f, "Busy - Too many instances of this task are already running. Check the `pool_size` attribute of the task."),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl core::error::Error for SpawnError {}
|
||||
|
||||
/// Handle to spawn tasks into an executor.
|
||||
///
|
||||
/// This Spawner can spawn any task (Send and non-Send ones), but it can
|
||||
/// only be used in the executor thread (it is not Send itself).
|
||||
///
|
||||
/// If you want to spawn tasks from another thread, use [SendSpawner].
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Spawner {
|
||||
executor: &'static raw::Executor,
|
||||
not_send: PhantomData<*mut ()>,
|
||||
}
|
||||
|
||||
impl Spawner {
|
||||
pub(crate) fn new(executor: &'static raw::Executor) -> Self {
|
||||
Self {
|
||||
executor,
|
||||
not_send: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a Spawner for the current executor.
|
||||
///
|
||||
/// This function is `async` just to get access to the current async
|
||||
/// context. It returns instantly, it does not block/yield.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the current executor is not an Embassy executor.
|
||||
pub fn for_current_executor() -> impl Future<Output = Self> {
|
||||
poll_fn(|cx| {
|
||||
let task = raw::task_from_waker(cx.waker());
|
||||
let executor = unsafe {
|
||||
task.header()
|
||||
.executor
|
||||
.load(Ordering::Relaxed)
|
||||
.as_ref()
|
||||
.unwrap_unchecked()
|
||||
};
|
||||
let executor = unsafe { raw::Executor::wrap(executor) };
|
||||
Poll::Ready(Self::new(executor))
|
||||
})
|
||||
}
|
||||
|
||||
/// Spawn a task into an executor.
|
||||
///
|
||||
/// You obtain the `token` by calling a task function (i.e. one marked with `#[embassy_executor::task]`).
|
||||
pub fn spawn<S>(&self, token: SpawnToken<S>) -> Result<(), SpawnError> {
|
||||
let task = token.raw_task;
|
||||
mem::forget(token);
|
||||
|
||||
match task {
|
||||
Some(task) => {
|
||||
unsafe { self.executor.spawn(task) };
|
||||
Ok(())
|
||||
}
|
||||
None => Err(SpawnError::Busy),
|
||||
}
|
||||
}
|
||||
|
||||
// Used by the `embassy_executor_macros::main!` macro to throw an error when spawn
|
||||
// fails. This is here to allow conditional use of `defmt::unwrap!`
|
||||
// without introducing a `defmt` feature in the `embassy_executor_macros` package,
|
||||
// which would require use of `-Z namespaced-features`.
|
||||
/// Spawn a task into an executor, panicking on failure.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the spawning fails.
|
||||
pub fn must_spawn<S>(&self, token: SpawnToken<S>) {
|
||||
unwrap!(self.spawn(token));
|
||||
}
|
||||
|
||||
/// Convert this Spawner to a SendSpawner. This allows you to send the
|
||||
/// spawner to other threads, but the spawner loses the ability to spawn
|
||||
/// non-Send tasks.
|
||||
pub fn make_send(&self) -> SendSpawner {
|
||||
SendSpawner::new(&self.executor.inner)
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle to spawn tasks into an executor from any thread.
|
||||
///
|
||||
/// This Spawner can be used from any thread (it is Send), but it can
|
||||
/// only spawn Send tasks. The reason for this is spawning is effectively
|
||||
/// "sending" the tasks to the executor thread.
|
||||
///
|
||||
/// If you want to spawn non-Send tasks, use [Spawner].
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct SendSpawner {
|
||||
executor: &'static raw::SyncExecutor,
|
||||
}
|
||||
|
||||
impl SendSpawner {
|
||||
pub(crate) fn new(executor: &'static raw::SyncExecutor) -> Self {
|
||||
Self { executor }
|
||||
}
|
||||
|
||||
/// Get a Spawner for the current executor.
|
||||
///
|
||||
/// This function is `async` just to get access to the current async
|
||||
/// context. It returns instantly, it does not block/yield.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the current executor is not an Embassy executor.
|
||||
pub fn for_current_executor() -> impl Future<Output = Self> {
|
||||
poll_fn(|cx| {
|
||||
let task = raw::task_from_waker(cx.waker());
|
||||
let executor = unsafe {
|
||||
task.header()
|
||||
.executor
|
||||
.load(Ordering::Relaxed)
|
||||
.as_ref()
|
||||
.unwrap_unchecked()
|
||||
};
|
||||
Poll::Ready(Self::new(executor))
|
||||
})
|
||||
}
|
||||
|
||||
/// Spawn a task into an executor.
|
||||
///
|
||||
/// You obtain the `token` by calling a task function (i.e. one marked with `#[embassy_executor::task]`).
|
||||
pub fn spawn<S: Send>(&self, token: SpawnToken<S>) -> Result<(), SpawnError> {
|
||||
let header = token.raw_task;
|
||||
mem::forget(token);
|
||||
|
||||
match header {
|
||||
Some(header) => {
|
||||
unsafe { self.executor.spawn(header) };
|
||||
Ok(())
|
||||
}
|
||||
None => Err(SpawnError::Busy),
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawn a task into an executor, panicking on failure.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the spawning fails.
|
||||
pub fn must_spawn<S: Send>(&self, token: SpawnToken<S>) {
|
||||
unwrap!(self.spawn(token));
|
||||
}
|
||||
}
|
||||
285
embassy-executor/tests/test.rs
Normal file
285
embassy-executor/tests/test.rs
Normal file
@@ -0,0 +1,285 @@
|
||||
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
|
||||
|
||||
use std::boxed::Box;
|
||||
use std::future::poll_fn;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::task::Poll;
|
||||
|
||||
use embassy_executor::raw::Executor;
|
||||
use embassy_executor::task;
|
||||
|
||||
#[export_name = "__pender"]
|
||||
fn __pender(context: *mut ()) {
|
||||
unsafe {
|
||||
let trace = &*(context as *const Trace);
|
||||
trace.push("pend");
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Trace {
|
||||
trace: Arc<Mutex<Vec<&'static str>>>,
|
||||
}
|
||||
|
||||
impl Trace {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
trace: Arc::new(Mutex::new(Vec::new())),
|
||||
}
|
||||
}
|
||||
fn push(&self, value: &'static str) {
|
||||
self.trace.lock().unwrap().push(value)
|
||||
}
|
||||
|
||||
fn get(&self) -> Vec<&'static str> {
|
||||
self.trace.lock().unwrap().clone()
|
||||
}
|
||||
}
|
||||
|
||||
fn setup() -> (&'static Executor, Trace) {
|
||||
let trace = Trace::new();
|
||||
let context = Box::leak(Box::new(trace.clone())) as *mut _ as *mut ();
|
||||
let executor = &*Box::leak(Box::new(Executor::new(context)));
|
||||
|
||||
(executor, trace)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn executor_noop() {
|
||||
let (executor, trace) = setup();
|
||||
unsafe { executor.poll() };
|
||||
assert!(trace.get().is_empty())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn executor_task() {
|
||||
#[task]
|
||||
async fn task1(trace: Trace) {
|
||||
trace.push("poll task1")
|
||||
}
|
||||
|
||||
let (executor, trace) = setup();
|
||||
executor.spawner().spawn(task1(trace.clone())).unwrap();
|
||||
|
||||
unsafe { executor.poll() };
|
||||
unsafe { executor.poll() };
|
||||
|
||||
assert_eq!(
|
||||
trace.get(),
|
||||
&[
|
||||
"pend", // spawning a task pends the executor
|
||||
"poll task1", // poll only once.
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn executor_task_self_wake() {
|
||||
#[task]
|
||||
async fn task1(trace: Trace) {
|
||||
poll_fn(|cx| {
|
||||
trace.push("poll task1");
|
||||
cx.waker().wake_by_ref();
|
||||
Poll::Pending
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
let (executor, trace) = setup();
|
||||
executor.spawner().spawn(task1(trace.clone())).unwrap();
|
||||
|
||||
unsafe { executor.poll() };
|
||||
unsafe { executor.poll() };
|
||||
|
||||
assert_eq!(
|
||||
trace.get(),
|
||||
&[
|
||||
"pend", // spawning a task pends the executor
|
||||
"poll task1", //
|
||||
"pend", // task self-wakes
|
||||
"poll task1", //
|
||||
"pend", // task self-wakes
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn executor_task_self_wake_twice() {
|
||||
#[task]
|
||||
async fn task1(trace: Trace) {
|
||||
poll_fn(|cx| {
|
||||
trace.push("poll task1");
|
||||
cx.waker().wake_by_ref();
|
||||
trace.push("poll task1 wake 2");
|
||||
cx.waker().wake_by_ref();
|
||||
Poll::Pending
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
let (executor, trace) = setup();
|
||||
executor.spawner().spawn(task1(trace.clone())).unwrap();
|
||||
|
||||
unsafe { executor.poll() };
|
||||
unsafe { executor.poll() };
|
||||
|
||||
assert_eq!(
|
||||
trace.get(),
|
||||
&[
|
||||
"pend", // spawning a task pends the executor
|
||||
"poll task1", //
|
||||
"pend", // task self-wakes
|
||||
"poll task1 wake 2", // task self-wakes again, shouldn't pend
|
||||
"poll task1", //
|
||||
"pend", // task self-wakes
|
||||
"poll task1 wake 2", // task self-wakes again, shouldn't pend
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn waking_after_completion_does_not_poll() {
|
||||
use embassy_sync::waitqueue::AtomicWaker;
|
||||
|
||||
#[task]
|
||||
async fn task1(trace: Trace, waker: &'static AtomicWaker) {
|
||||
poll_fn(|cx| {
|
||||
trace.push("poll task1");
|
||||
waker.register(cx.waker());
|
||||
Poll::Ready(())
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
let waker = Box::leak(Box::new(AtomicWaker::new()));
|
||||
|
||||
let (executor, trace) = setup();
|
||||
executor.spawner().spawn(task1(trace.clone(), waker)).unwrap();
|
||||
|
||||
unsafe { executor.poll() };
|
||||
waker.wake();
|
||||
unsafe { executor.poll() };
|
||||
|
||||
// Exited task may be waken but is not polled
|
||||
waker.wake();
|
||||
waker.wake();
|
||||
unsafe { executor.poll() }; // Clears running status
|
||||
|
||||
// Can respawn waken-but-dead task
|
||||
executor.spawner().spawn(task1(trace.clone(), waker)).unwrap();
|
||||
|
||||
unsafe { executor.poll() };
|
||||
|
||||
assert_eq!(
|
||||
trace.get(),
|
||||
&[
|
||||
"pend", // spawning a task pends the executor
|
||||
"poll task1", //
|
||||
"pend", // manual wake, gets cleared by poll
|
||||
"pend", // manual wake, single pend for two wakes
|
||||
"pend", // respawning a task pends the executor
|
||||
"poll task1", //
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn waking_with_old_waker_after_respawn() {
|
||||
use embassy_sync::waitqueue::AtomicWaker;
|
||||
|
||||
async fn yield_now(trace: Trace) {
|
||||
let mut yielded = false;
|
||||
poll_fn(|cx| {
|
||||
if yielded {
|
||||
Poll::Ready(())
|
||||
} else {
|
||||
trace.push("yield_now");
|
||||
yielded = true;
|
||||
cx.waker().wake_by_ref();
|
||||
Poll::Pending
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
#[task]
|
||||
async fn task1(trace: Trace, waker: &'static AtomicWaker) {
|
||||
yield_now(trace.clone()).await;
|
||||
poll_fn(|cx| {
|
||||
trace.push("poll task1");
|
||||
waker.register(cx.waker());
|
||||
Poll::Ready(())
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
let waker = Box::leak(Box::new(AtomicWaker::new()));
|
||||
|
||||
let (executor, trace) = setup();
|
||||
executor.spawner().spawn(task1(trace.clone(), waker)).unwrap();
|
||||
|
||||
unsafe { executor.poll() };
|
||||
unsafe { executor.poll() }; // progress to registering the waker
|
||||
waker.wake();
|
||||
unsafe { executor.poll() };
|
||||
// Task has exited
|
||||
|
||||
assert_eq!(
|
||||
trace.get(),
|
||||
&[
|
||||
"pend", // spawning a task pends the executor
|
||||
"yield_now", //
|
||||
"pend", // yield_now wakes the task
|
||||
"poll task1", //
|
||||
"pend", // task self-wakes
|
||||
]
|
||||
);
|
||||
|
||||
// Can respawn task on another executor
|
||||
let (other_executor, other_trace) = setup();
|
||||
other_executor
|
||||
.spawner()
|
||||
.spawn(task1(other_trace.clone(), waker))
|
||||
.unwrap();
|
||||
|
||||
unsafe { other_executor.poll() }; // just run to the yield_now
|
||||
waker.wake(); // trigger old waker registration
|
||||
unsafe { executor.poll() };
|
||||
unsafe { other_executor.poll() };
|
||||
|
||||
// First executor's trace has not changed
|
||||
assert_eq!(
|
||||
trace.get(),
|
||||
&[
|
||||
"pend", // spawning a task pends the executor
|
||||
"yield_now", //
|
||||
"pend", // yield_now wakes the task
|
||||
"poll task1", //
|
||||
"pend", // task self-wakes
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
other_trace.get(),
|
||||
&[
|
||||
"pend", // spawning a task pends the executor
|
||||
"yield_now", //
|
||||
"pend", // manual wake, gets cleared by poll
|
||||
"poll task1", //
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn executor_task_cfg_args() {
|
||||
// simulate cfg'ing away argument c
|
||||
#[task]
|
||||
async fn task1(a: u32, b: u32, #[cfg(any())] c: u32) {
|
||||
let (_, _) = (a, b);
|
||||
}
|
||||
|
||||
#[task]
|
||||
async fn task2(a: u32, b: u32, #[cfg(all())] c: u32) {
|
||||
let (_, _, _) = (a, b, c);
|
||||
}
|
||||
}
|
||||
23
embassy-executor/tests/ui.rs
Normal file
23
embassy-executor/tests/ui.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
#[cfg(not(miri))]
|
||||
#[test]
|
||||
fn ui() {
|
||||
let t = trybuild::TestCases::new();
|
||||
t.compile_fail("tests/ui/abi.rs");
|
||||
t.compile_fail("tests/ui/bad_return.rs");
|
||||
t.compile_fail("tests/ui/generics.rs");
|
||||
t.compile_fail("tests/ui/impl_trait_nested.rs");
|
||||
t.compile_fail("tests/ui/impl_trait.rs");
|
||||
t.compile_fail("tests/ui/impl_trait_static.rs");
|
||||
t.compile_fail("tests/ui/nonstatic_ref_anon_nested.rs");
|
||||
t.compile_fail("tests/ui/nonstatic_ref_anon.rs");
|
||||
t.compile_fail("tests/ui/nonstatic_ref_elided.rs");
|
||||
t.compile_fail("tests/ui/nonstatic_ref_generic.rs");
|
||||
t.compile_fail("tests/ui/nonstatic_struct_anon.rs");
|
||||
#[cfg(not(feature = "nightly"))] // we can't catch this case with the macro, so the output changes on nightly.
|
||||
t.compile_fail("tests/ui/nonstatic_struct_elided.rs");
|
||||
t.compile_fail("tests/ui/nonstatic_struct_generic.rs");
|
||||
t.compile_fail("tests/ui/not_async.rs");
|
||||
t.compile_fail("tests/ui/self_ref.rs");
|
||||
t.compile_fail("tests/ui/self.rs");
|
||||
t.compile_fail("tests/ui/where_clause.rs");
|
||||
}
|
||||
8
embassy-executor/tests/ui/abi.rs
Normal file
8
embassy-executor/tests/ui/abi.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
|
||||
|
||||
struct Foo<'a>(&'a ());
|
||||
|
||||
#[embassy_executor::task]
|
||||
async extern "C" fn task() {}
|
||||
|
||||
fn main() {}
|
||||
5
embassy-executor/tests/ui/abi.stderr
Normal file
5
embassy-executor/tests/ui/abi.stderr
Normal file
@@ -0,0 +1,5 @@
|
||||
error: task functions must not have an ABI qualifier
|
||||
--> tests/ui/abi.rs:6:1
|
||||
|
|
||||
6 | async extern "C" fn task() {}
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
10
embassy-executor/tests/ui/bad_return.rs
Normal file
10
embassy-executor/tests/ui/bad_return.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
|
||||
|
||||
struct Foo<'a>(&'a ());
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn task() -> u32 {
|
||||
5
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
5
embassy-executor/tests/ui/bad_return.stderr
Normal file
5
embassy-executor/tests/ui/bad_return.stderr
Normal file
@@ -0,0 +1,5 @@
|
||||
error: task functions must either not return a value, return `()` or return `!`
|
||||
--> tests/ui/bad_return.rs:6:1
|
||||
|
|
||||
6 | async fn task() -> u32 {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
8
embassy-executor/tests/ui/generics.rs
Normal file
8
embassy-executor/tests/ui/generics.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
|
||||
|
||||
struct Foo<'a>(&'a ());
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn task<T: Sized>(_t: T) {}
|
||||
|
||||
fn main() {}
|
||||
5
embassy-executor/tests/ui/generics.stderr
Normal file
5
embassy-executor/tests/ui/generics.stderr
Normal file
@@ -0,0 +1,5 @@
|
||||
error: task functions must not be generic
|
||||
--> tests/ui/generics.rs:6:1
|
||||
|
|
||||
6 | async fn task<T: Sized>(_t: T) {}
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
6
embassy-executor/tests/ui/impl_trait.rs
Normal file
6
embassy-executor/tests/ui/impl_trait.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn foo(_x: impl Sized) {}
|
||||
|
||||
fn main() {}
|
||||
5
embassy-executor/tests/ui/impl_trait.stderr
Normal file
5
embassy-executor/tests/ui/impl_trait.stderr
Normal file
@@ -0,0 +1,5 @@
|
||||
error: `impl Trait` is not allowed in task arguments. It is syntax sugar for generics, and tasks can't be generic.
|
||||
--> tests/ui/impl_trait.rs:4:18
|
||||
|
|
||||
4 | async fn foo(_x: impl Sized) {}
|
||||
| ^^^^^^^^^^
|
||||
8
embassy-executor/tests/ui/impl_trait_nested.rs
Normal file
8
embassy-executor/tests/ui/impl_trait_nested.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
|
||||
|
||||
struct Foo<T>(T);
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn foo(_x: Foo<impl Sized + 'static>) {}
|
||||
|
||||
fn main() {}
|
||||
5
embassy-executor/tests/ui/impl_trait_nested.stderr
Normal file
5
embassy-executor/tests/ui/impl_trait_nested.stderr
Normal file
@@ -0,0 +1,5 @@
|
||||
error: `impl Trait` is not allowed in task arguments. It is syntax sugar for generics, and tasks can't be generic.
|
||||
--> tests/ui/impl_trait_nested.rs:6:22
|
||||
|
|
||||
6 | async fn foo(_x: Foo<impl Sized + 'static>) {}
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
6
embassy-executor/tests/ui/impl_trait_static.rs
Normal file
6
embassy-executor/tests/ui/impl_trait_static.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn foo(_x: impl Sized + 'static) {}
|
||||
|
||||
fn main() {}
|
||||
5
embassy-executor/tests/ui/impl_trait_static.stderr
Normal file
5
embassy-executor/tests/ui/impl_trait_static.stderr
Normal file
@@ -0,0 +1,5 @@
|
||||
error: `impl Trait` is not allowed in task arguments. It is syntax sugar for generics, and tasks can't be generic.
|
||||
--> tests/ui/impl_trait_static.rs:4:18
|
||||
|
|
||||
4 | async fn foo(_x: impl Sized + 'static) {}
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
6
embassy-executor/tests/ui/nonstatic_ref_anon.rs
Normal file
6
embassy-executor/tests/ui/nonstatic_ref_anon.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn foo(_x: &'_ u32) {}
|
||||
|
||||
fn main() {}
|
||||
5
embassy-executor/tests/ui/nonstatic_ref_anon.stderr
Normal file
5
embassy-executor/tests/ui/nonstatic_ref_anon.stderr
Normal file
@@ -0,0 +1,5 @@
|
||||
error: Arguments for tasks must live forever. Try using the `'static` lifetime.
|
||||
--> tests/ui/nonstatic_ref_anon.rs:4:19
|
||||
|
|
||||
4 | async fn foo(_x: &'_ u32) {}
|
||||
| ^^
|
||||
6
embassy-executor/tests/ui/nonstatic_ref_anon_nested.rs
Normal file
6
embassy-executor/tests/ui/nonstatic_ref_anon_nested.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn foo(_x: &'static &'_ u32) {}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,5 @@
|
||||
error: Arguments for tasks must live forever. Try using the `'static` lifetime.
|
||||
--> tests/ui/nonstatic_ref_anon_nested.rs:4:28
|
||||
|
|
||||
4 | async fn foo(_x: &'static &'_ u32) {}
|
||||
| ^^
|
||||
6
embassy-executor/tests/ui/nonstatic_ref_elided.rs
Normal file
6
embassy-executor/tests/ui/nonstatic_ref_elided.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn foo(_x: &u32) {}
|
||||
|
||||
fn main() {}
|
||||
5
embassy-executor/tests/ui/nonstatic_ref_elided.stderr
Normal file
5
embassy-executor/tests/ui/nonstatic_ref_elided.stderr
Normal file
@@ -0,0 +1,5 @@
|
||||
error: Arguments for tasks must live forever. Try using the `'static` lifetime.
|
||||
--> tests/ui/nonstatic_ref_elided.rs:4:18
|
||||
|
|
||||
4 | async fn foo(_x: &u32) {}
|
||||
| ^
|
||||
6
embassy-executor/tests/ui/nonstatic_ref_generic.rs
Normal file
6
embassy-executor/tests/ui/nonstatic_ref_generic.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn foo<'a>(_x: &'a u32) {}
|
||||
|
||||
fn main() {}
|
||||
11
embassy-executor/tests/ui/nonstatic_ref_generic.stderr
Normal file
11
embassy-executor/tests/ui/nonstatic_ref_generic.stderr
Normal file
@@ -0,0 +1,11 @@
|
||||
error: task functions must not be generic
|
||||
--> tests/ui/nonstatic_ref_generic.rs:4:1
|
||||
|
|
||||
4 | async fn foo<'a>(_x: &'a u32) {}
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: Arguments for tasks must live forever. Try using the `'static` lifetime.
|
||||
--> tests/ui/nonstatic_ref_generic.rs:4:23
|
||||
|
|
||||
4 | async fn foo<'a>(_x: &'a u32) {}
|
||||
| ^^
|
||||
8
embassy-executor/tests/ui/nonstatic_struct_anon.rs
Normal file
8
embassy-executor/tests/ui/nonstatic_struct_anon.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
|
||||
|
||||
struct Foo<'a>(&'a ());
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn task(_x: Foo<'_>) {}
|
||||
|
||||
fn main() {}
|
||||
5
embassy-executor/tests/ui/nonstatic_struct_anon.stderr
Normal file
5
embassy-executor/tests/ui/nonstatic_struct_anon.stderr
Normal file
@@ -0,0 +1,5 @@
|
||||
error: Arguments for tasks must live forever. Try using the `'static` lifetime.
|
||||
--> tests/ui/nonstatic_struct_anon.rs:6:23
|
||||
|
|
||||
6 | async fn task(_x: Foo<'_>) {}
|
||||
| ^^
|
||||
8
embassy-executor/tests/ui/nonstatic_struct_elided.rs
Normal file
8
embassy-executor/tests/ui/nonstatic_struct_elided.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
|
||||
|
||||
struct Foo<'a>(&'a ());
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn task(_x: Foo) {}
|
||||
|
||||
fn main() {}
|
||||
10
embassy-executor/tests/ui/nonstatic_struct_elided.stderr
Normal file
10
embassy-executor/tests/ui/nonstatic_struct_elided.stderr
Normal file
@@ -0,0 +1,10 @@
|
||||
error[E0726]: implicit elided lifetime not allowed here
|
||||
--> tests/ui/nonstatic_struct_elided.rs:6:19
|
||||
|
|
||||
6 | async fn task(_x: Foo) {}
|
||||
| ^^^ expected lifetime parameter
|
||||
|
|
||||
help: indicate the anonymous lifetime
|
||||
|
|
||||
6 | async fn task(_x: Foo<'_>) {}
|
||||
| ++++
|
||||
8
embassy-executor/tests/ui/nonstatic_struct_generic.rs
Normal file
8
embassy-executor/tests/ui/nonstatic_struct_generic.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
|
||||
|
||||
struct Foo<'a>(&'a ());
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn task<'a>(_x: Foo<'a>) {}
|
||||
|
||||
fn main() {}
|
||||
11
embassy-executor/tests/ui/nonstatic_struct_generic.stderr
Normal file
11
embassy-executor/tests/ui/nonstatic_struct_generic.stderr
Normal file
@@ -0,0 +1,11 @@
|
||||
error: task functions must not be generic
|
||||
--> tests/ui/nonstatic_struct_generic.rs:6:1
|
||||
|
|
||||
6 | async fn task<'a>(_x: Foo<'a>) {}
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: Arguments for tasks must live forever. Try using the `'static` lifetime.
|
||||
--> tests/ui/nonstatic_struct_generic.rs:6:27
|
||||
|
|
||||
6 | async fn task<'a>(_x: Foo<'a>) {}
|
||||
| ^^
|
||||
8
embassy-executor/tests/ui/not_async.rs
Normal file
8
embassy-executor/tests/ui/not_async.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
|
||||
|
||||
struct Foo<'a>(&'a ());
|
||||
|
||||
#[embassy_executor::task]
|
||||
fn task() {}
|
||||
|
||||
fn main() {}
|
||||
5
embassy-executor/tests/ui/not_async.stderr
Normal file
5
embassy-executor/tests/ui/not_async.stderr
Normal file
@@ -0,0 +1,5 @@
|
||||
error: task functions must be async
|
||||
--> tests/ui/not_async.rs:6:1
|
||||
|
|
||||
6 | fn task() {}
|
||||
| ^^^^^^^^^
|
||||
8
embassy-executor/tests/ui/self.rs
Normal file
8
embassy-executor/tests/ui/self.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
|
||||
|
||||
struct Foo<'a>(&'a ());
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn task(self) {}
|
||||
|
||||
fn main() {}
|
||||
13
embassy-executor/tests/ui/self.stderr
Normal file
13
embassy-executor/tests/ui/self.stderr
Normal file
@@ -0,0 +1,13 @@
|
||||
error: task functions must not have `self` arguments
|
||||
--> tests/ui/self.rs:6:15
|
||||
|
|
||||
6 | async fn task(self) {}
|
||||
| ^^^^
|
||||
|
||||
error: `self` parameter is only allowed in associated functions
|
||||
--> tests/ui/self.rs:6:15
|
||||
|
|
||||
6 | async fn task(self) {}
|
||||
| ^^^^ not semantically valid as function parameter
|
||||
|
|
||||
= note: associated functions are those in `impl` or `trait` definitions
|
||||
8
embassy-executor/tests/ui/self_ref.rs
Normal file
8
embassy-executor/tests/ui/self_ref.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
|
||||
|
||||
struct Foo<'a>(&'a ());
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn task(&mut self) {}
|
||||
|
||||
fn main() {}
|
||||
13
embassy-executor/tests/ui/self_ref.stderr
Normal file
13
embassy-executor/tests/ui/self_ref.stderr
Normal file
@@ -0,0 +1,13 @@
|
||||
error: task functions must not have `self` arguments
|
||||
--> tests/ui/self_ref.rs:6:15
|
||||
|
|
||||
6 | async fn task(&mut self) {}
|
||||
| ^^^^^^^^^
|
||||
|
||||
error: `self` parameter is only allowed in associated functions
|
||||
--> tests/ui/self_ref.rs:6:15
|
||||
|
|
||||
6 | async fn task(&mut self) {}
|
||||
| ^^^^^^^^^ not semantically valid as function parameter
|
||||
|
|
||||
= note: associated functions are those in `impl` or `trait` definitions
|
||||
12
embassy-executor/tests/ui/where_clause.rs
Normal file
12
embassy-executor/tests/ui/where_clause.rs
Normal file
@@ -0,0 +1,12 @@
|
||||
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
|
||||
|
||||
struct Foo<'a>(&'a ());
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn task()
|
||||
where
|
||||
(): Sized,
|
||||
{
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
7
embassy-executor/tests/ui/where_clause.stderr
Normal file
7
embassy-executor/tests/ui/where_clause.stderr
Normal file
@@ -0,0 +1,7 @@
|
||||
error: task functions must not have `where` clauses
|
||||
--> tests/ui/where_clause.rs:6:1
|
||||
|
|
||||
6 | / async fn task()
|
||||
7 | | where
|
||||
8 | | (): Sized,
|
||||
| |______________^
|
||||
Reference in New Issue
Block a user