Merge pull request #2216 from embassy-rs/stable
executor: add support for main/task macros in stable Rust
This commit is contained in:
commit
47bac9df70
3
.github/ci/test.sh
vendored
3
.github/ci/test.sh
vendored
@ -15,6 +15,9 @@ export CARGO_NET_GIT_FETCH_WITH_CLI=true
|
|||||||
hashtime restore /ci/cache/filetime.json || true
|
hashtime restore /ci/cache/filetime.json || true
|
||||||
hashtime save /ci/cache/filetime.json
|
hashtime save /ci/cache/filetime.json
|
||||||
|
|
||||||
|
MIRIFLAGS=-Zmiri-ignore-leaks cargo miri test --manifest-path ./embassy-executor/Cargo.toml
|
||||||
|
MIRIFLAGS=-Zmiri-ignore-leaks cargo miri test --manifest-path ./embassy-executor/Cargo.toml --features nightly
|
||||||
|
|
||||||
cargo test --manifest-path ./embassy-sync/Cargo.toml
|
cargo test --manifest-path ./embassy-sync/Cargo.toml
|
||||||
cargo test --manifest-path ./embassy-embedded-hal/Cargo.toml
|
cargo test --manifest-path ./embassy-embedded-hal/Cargo.toml
|
||||||
cargo test --manifest-path ./embassy-hal-internal/Cargo.toml
|
cargo test --manifest-path ./embassy-hal-internal/Cargo.toml
|
||||||
|
@ -27,31 +27,6 @@ default-target = "thumbv7em-none-eabi"
|
|||||||
targets = ["thumbv7em-none-eabi"]
|
targets = ["thumbv7em-none-eabi"]
|
||||||
features = ["nightly", "defmt", "arch-cortex-m", "executor-thread", "executor-interrupt"]
|
features = ["nightly", "defmt", "arch-cortex-m", "executor-thread", "executor-interrupt"]
|
||||||
|
|
||||||
[features]
|
|
||||||
|
|
||||||
# Architecture
|
|
||||||
_arch = [] # some arch was picked
|
|
||||||
arch-std = ["_arch", "critical-section/std"]
|
|
||||||
arch-cortex-m = ["_arch", "dep:cortex-m"]
|
|
||||||
arch-xtensa = ["_arch"]
|
|
||||||
arch-riscv32 = ["_arch", "dep:portable-atomic"]
|
|
||||||
arch-wasm = ["_arch", "dep:wasm-bindgen", "dep:js-sys"]
|
|
||||||
|
|
||||||
# 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 nightly-only features
|
|
||||||
nightly = []
|
|
||||||
|
|
||||||
turbowakers = []
|
|
||||||
|
|
||||||
integrated-timers = ["dep:embassy-time"]
|
|
||||||
|
|
||||||
# Trace interrupt invocations with rtos-trace.
|
|
||||||
rtos-trace-interrupt = ["rtos-trace", "embassy-macros/rtos-trace-interrupt"]
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
defmt = { version = "0.3", optional = true }
|
defmt = { version = "0.3", optional = true }
|
||||||
log = { version = "0.4.14", optional = true }
|
log = { version = "0.4.14", optional = true }
|
||||||
@ -71,3 +46,74 @@ cortex-m = { version = "0.7.6", optional = true }
|
|||||||
# arch-wasm dependencies
|
# arch-wasm dependencies
|
||||||
wasm-bindgen = { version = "0.2.82", optional = true }
|
wasm-bindgen = { version = "0.2.82", optional = true }
|
||||||
js-sys = { version = "0.3", optional = true }
|
js-sys = { version = "0.3", optional = true }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
critical-section = { version = "1.1", features = ["std"] }
|
||||||
|
|
||||||
|
|
||||||
|
[features]
|
||||||
|
|
||||||
|
# Architecture
|
||||||
|
_arch = [] # some arch was picked
|
||||||
|
arch-std = ["_arch", "critical-section/std"]
|
||||||
|
arch-cortex-m = ["_arch", "dep:cortex-m"]
|
||||||
|
arch-xtensa = ["_arch"]
|
||||||
|
arch-riscv32 = ["_arch", "dep:portable-atomic"]
|
||||||
|
arch-wasm = ["_arch", "dep:wasm-bindgen", "dep:js-sys"]
|
||||||
|
|
||||||
|
# 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 nightly-only features
|
||||||
|
nightly = ["embassy-macros/nightly"]
|
||||||
|
|
||||||
|
turbowakers = []
|
||||||
|
|
||||||
|
integrated-timers = ["dep:embassy-time"]
|
||||||
|
|
||||||
|
# BEGIN AUTOGENERATED CONFIG FEATURES
|
||||||
|
# Generated by gen_config.py. DO NOT EDIT.
|
||||||
|
task-arena-size-64 = []
|
||||||
|
task-arena-size-128 = []
|
||||||
|
task-arena-size-192 = []
|
||||||
|
task-arena-size-256 = []
|
||||||
|
task-arena-size-320 = []
|
||||||
|
task-arena-size-384 = []
|
||||||
|
task-arena-size-512 = []
|
||||||
|
task-arena-size-640 = []
|
||||||
|
task-arena-size-768 = []
|
||||||
|
task-arena-size-1024 = []
|
||||||
|
task-arena-size-1280 = []
|
||||||
|
task-arena-size-1536 = []
|
||||||
|
task-arena-size-2048 = []
|
||||||
|
task-arena-size-2560 = []
|
||||||
|
task-arena-size-3072 = []
|
||||||
|
task-arena-size-4096 = [] # Default
|
||||||
|
task-arena-size-5120 = []
|
||||||
|
task-arena-size-6144 = []
|
||||||
|
task-arena-size-8192 = []
|
||||||
|
task-arena-size-10240 = []
|
||||||
|
task-arena-size-12288 = []
|
||||||
|
task-arena-size-16384 = []
|
||||||
|
task-arena-size-20480 = []
|
||||||
|
task-arena-size-24576 = []
|
||||||
|
task-arena-size-32768 = []
|
||||||
|
task-arena-size-40960 = []
|
||||||
|
task-arena-size-49152 = []
|
||||||
|
task-arena-size-65536 = []
|
||||||
|
task-arena-size-81920 = []
|
||||||
|
task-arena-size-98304 = []
|
||||||
|
task-arena-size-131072 = []
|
||||||
|
task-arena-size-163840 = []
|
||||||
|
task-arena-size-196608 = []
|
||||||
|
task-arena-size-262144 = []
|
||||||
|
task-arena-size-327680 = []
|
||||||
|
task-arena-size-393216 = []
|
||||||
|
task-arena-size-524288 = []
|
||||||
|
task-arena-size-655360 = []
|
||||||
|
task-arena-size-786432 = []
|
||||||
|
task-arena-size-1048576 = []
|
||||||
|
|
||||||
|
# END AUTOGENERATED CONFIG FEATURES
|
||||||
|
@ -2,10 +2,36 @@
|
|||||||
|
|
||||||
An async/await executor designed for embedded usage.
|
An async/await executor designed for embedded usage.
|
||||||
|
|
||||||
- No `alloc`, no heap needed. Task futures are statically allocated.
|
- 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.
|
- 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;`.
|
- 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`.
|
- 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.
|
- 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.
|
- 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.
|
- 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 exists, the allocation is not released to the arena, but can be reused to spawn the task again. For multiple-instance tasks (like `#[embassy_executor::task(pool_size = 4)]`), the first spawn will allocate memory for all instances. This is done for performance and to increase predictability (for example, spawning at least 1 instance of every task at boot guarantees an immediate panic if the arena is too small, while allocating instances on-demand could delay the panic to only when the program is under load).
|
||||||
|
|
||||||
|
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, check `Cargo.toml` for the list.
|
||||||
|
- 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.
|
||||||
|
@ -1,6 +1,97 @@
|
|||||||
use std::env;
|
use std::collections::HashMap;
|
||||||
|
use std::fmt::Write;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::{env, fs};
|
||||||
|
|
||||||
|
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() {
|
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();
|
||||||
|
|
||||||
|
// cortex-m targets
|
||||||
let target = env::var("TARGET").unwrap();
|
let target = env::var("TARGET").unwrap();
|
||||||
|
|
||||||
if target.starts_with("thumbv6m-") {
|
if target.starts_with("thumbv6m-") {
|
||||||
|
82
embassy-executor/gen_config.py
Normal file
82
embassy-executor/gen_config.py
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
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"{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)
|
@ -51,7 +51,6 @@ mod thread {
|
|||||||
use core::arch::asm;
|
use core::arch::asm;
|
||||||
use core::marker::PhantomData;
|
use core::marker::PhantomData;
|
||||||
|
|
||||||
#[cfg(feature = "nightly")]
|
|
||||||
pub use embassy_macros::main_cortex_m as main;
|
pub use embassy_macros::main_cortex_m as main;
|
||||||
|
|
||||||
use crate::{raw, Spawner};
|
use crate::{raw, Spawner};
|
||||||
|
@ -7,7 +7,6 @@ pub use thread::*;
|
|||||||
mod thread {
|
mod thread {
|
||||||
use core::marker::PhantomData;
|
use core::marker::PhantomData;
|
||||||
|
|
||||||
#[cfg(feature = "nightly")]
|
|
||||||
pub use embassy_macros::main_riscv as main;
|
pub use embassy_macros::main_riscv as main;
|
||||||
use portable_atomic::{AtomicBool, Ordering};
|
use portable_atomic::{AtomicBool, Ordering};
|
||||||
|
|
||||||
|
@ -8,7 +8,6 @@ mod thread {
|
|||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::sync::{Condvar, Mutex};
|
use std::sync::{Condvar, Mutex};
|
||||||
|
|
||||||
#[cfg(feature = "nightly")]
|
|
||||||
pub use embassy_macros::main_std as main;
|
pub use embassy_macros::main_std as main;
|
||||||
|
|
||||||
use crate::{raw, Spawner};
|
use crate::{raw, Spawner};
|
||||||
|
@ -8,7 +8,6 @@ mod thread {
|
|||||||
|
|
||||||
use core::marker::PhantomData;
|
use core::marker::PhantomData;
|
||||||
|
|
||||||
#[cfg(feature = "nightly")]
|
|
||||||
pub use embassy_macros::main_wasm as main;
|
pub use embassy_macros::main_wasm as main;
|
||||||
use js_sys::Promise;
|
use js_sys::Promise;
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
|
@ -8,7 +8,6 @@ mod thread {
|
|||||||
use core::marker::PhantomData;
|
use core::marker::PhantomData;
|
||||||
use core::sync::atomic::{AtomicBool, Ordering};
|
use core::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
|
||||||
#[cfg(feature = "nightly")]
|
|
||||||
pub use embassy_macros::main_riscv as main;
|
pub use embassy_macros::main_riscv as main;
|
||||||
|
|
||||||
use crate::{raw, Spawner};
|
use crate::{raw, Spawner};
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
#![cfg_attr(not(any(feature = "arch-std", feature = "arch-wasm")), no_std)]
|
#![cfg_attr(not(any(feature = "arch-std", feature = "arch-wasm")), no_std)]
|
||||||
#![cfg_attr(all(feature = "nightly", feature = "arch-xtensa"), feature(asm_experimental_arch))]
|
#![cfg_attr(feature = "arch-xtensa", feature(asm_experimental_arch))]
|
||||||
#![allow(clippy::new_without_default)]
|
#![allow(clippy::new_without_default)]
|
||||||
#![doc = include_str!("../README.md")]
|
#![doc = include_str!("../README.md")]
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
@ -7,7 +7,6 @@
|
|||||||
// This mod MUST go first, so that the others see its macros.
|
// This mod MUST go first, so that the others see its macros.
|
||||||
pub(crate) mod fmt;
|
pub(crate) mod fmt;
|
||||||
|
|
||||||
#[cfg(feature = "nightly")]
|
|
||||||
pub use embassy_macros::task;
|
pub use embassy_macros::task;
|
||||||
|
|
||||||
macro_rules! check_at_most_one {
|
macro_rules! check_at_most_one {
|
||||||
@ -41,28 +40,101 @@ pub mod raw;
|
|||||||
mod spawner;
|
mod spawner;
|
||||||
pub use spawner::*;
|
pub use spawner::*;
|
||||||
|
|
||||||
|
mod config {
|
||||||
|
#![allow(unused)]
|
||||||
|
include!(concat!(env!("OUT_DIR"), "/config.rs"));
|
||||||
|
}
|
||||||
|
|
||||||
/// Implementation details for embassy macros.
|
/// Implementation details for embassy macros.
|
||||||
/// Do not use. Used for macros and HALs only. Not covered by semver guarantees.
|
/// Do not use. Used for macros and HALs only. Not covered by semver guarantees.
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
|
#[cfg(not(feature = "nightly"))]
|
||||||
pub mod _export {
|
pub mod _export {
|
||||||
#[cfg(feature = "rtos-trace")]
|
use core::alloc::Layout;
|
||||||
pub use rtos_trace::trace;
|
use core::cell::{Cell, UnsafeCell};
|
||||||
|
use core::future::Future;
|
||||||
|
use core::mem::MaybeUninit;
|
||||||
|
use core::ptr::null_mut;
|
||||||
|
|
||||||
/// Expands the given block of code when `embassy-executor` is compiled with
|
use critical_section::{CriticalSection, Mutex};
|
||||||
/// the `rtos-trace-interrupt` feature.
|
|
||||||
#[doc(hidden)]
|
use crate::raw::TaskPool;
|
||||||
#[macro_export]
|
|
||||||
#[cfg(feature = "rtos-trace-interrupt")]
|
struct Arena<const N: usize> {
|
||||||
macro_rules! rtos_trace_interrupt {
|
buf: UnsafeCell<MaybeUninit<[u8; N]>>,
|
||||||
($($tt:tt)*) => { $($tt)* };
|
ptr: Mutex<Cell<*mut u8>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Does not expand the given block of code when `embassy-executor` is
|
unsafe impl<const N: usize> Sync for Arena<N> {}
|
||||||
/// compiled without the `rtos-trace-interrupt` feature.
|
unsafe impl<const N: usize> Send for Arena<N> {}
|
||||||
#[doc(hidden)]
|
|
||||||
#[macro_export]
|
impl<const N: usize> Arena<N> {
|
||||||
#[cfg(not(feature = "rtos-trace-interrupt"))]
|
const fn new() -> Self {
|
||||||
macro_rules! rtos_trace_interrupt {
|
Self {
|
||||||
($($tt:tt)*) => {};
|
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 _) }
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
137
embassy-executor/tests/test.rs
Normal file
137
embassy-executor/tests/test.rs
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
#![cfg_attr(feature = "nightly", feature(type_alias_impl_trait))]
|
||||||
|
|
||||||
|
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
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
@ -21,5 +21,4 @@ proc-macro2 = "1.0.29"
|
|||||||
proc-macro = true
|
proc-macro = true
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
# Enabling this cause interrupt::take! to require embassy-executor
|
nightly = []
|
||||||
rtos-trace-interrupt = []
|
|
@ -79,6 +79,7 @@ pub fn run(args: &[NestedMeta], f: syn::ItemFn) -> Result<TokenStream, TokenStre
|
|||||||
task_inner.vis = syn::Visibility::Inherited;
|
task_inner.vis = syn::Visibility::Inherited;
|
||||||
task_inner.sig.ident = task_inner_ident.clone();
|
task_inner.sig.ident = task_inner_ident.clone();
|
||||||
|
|
||||||
|
#[cfg(feature = "nightly")]
|
||||||
let mut task_outer: ItemFn = parse_quote! {
|
let mut task_outer: ItemFn = parse_quote! {
|
||||||
#visibility fn #task_ident(#fargs) -> ::embassy_executor::SpawnToken<impl Sized> {
|
#visibility fn #task_ident(#fargs) -> ::embassy_executor::SpawnToken<impl Sized> {
|
||||||
type Fut = impl ::core::future::Future + 'static;
|
type Fut = impl ::core::future::Future + 'static;
|
||||||
@ -87,6 +88,14 @@ pub fn run(args: &[NestedMeta], f: syn::ItemFn) -> Result<TokenStream, TokenStre
|
|||||||
unsafe { POOL._spawn_async_fn(move || #task_inner_ident(#(#arg_names,)*)) }
|
unsafe { POOL._spawn_async_fn(move || #task_inner_ident(#(#arg_names,)*)) }
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
#[cfg(not(feature = "nightly"))]
|
||||||
|
let mut task_outer: ItemFn = parse_quote! {
|
||||||
|
#visibility fn #task_ident(#fargs) -> ::embassy_executor::SpawnToken<impl Sized> {
|
||||||
|
const POOL_SIZE: usize = #pool_size;
|
||||||
|
static POOL: ::embassy_executor::_export::TaskPoolRef = ::embassy_executor::_export::TaskPoolRef::new();
|
||||||
|
unsafe { POOL.get::<_, POOL_SIZE>()._spawn_async_fn(move || #task_inner_ident(#(#arg_names,)*)) }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
task_outer.attrs.append(&mut task_inner.attrs.clone());
|
task_outer.attrs.append(&mut task_inner.attrs.clone());
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ log = [
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
embassy-sync = { version = "0.4.0", path = "../../embassy-sync" }
|
embassy-sync = { version = "0.4.0", path = "../../embassy-sync" }
|
||||||
embassy-executor = { version = "0.3.3", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "rtos-trace", "rtos-trace-interrupt", "integrated-timers"] }
|
embassy-executor = { version = "0.3.3", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "rtos-trace", "integrated-timers"] }
|
||||||
embassy-time = { version = "0.1.5", path = "../../embassy-time" }
|
embassy-time = { version = "0.1.5", path = "../../embassy-time" }
|
||||||
embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac"] }
|
embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac"] }
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
# https://rust-lang.github.io/rustup-components-history
|
# https://rust-lang.github.io/rustup-components-history
|
||||||
[toolchain]
|
[toolchain]
|
||||||
channel = "nightly-2023-11-01"
|
channel = "nightly-2023-11-01"
|
||||||
components = [ "rust-src", "rustfmt", "llvm-tools" ]
|
components = [ "rust-src", "rustfmt", "llvm-tools", "miri" ]
|
||||||
targets = [
|
targets = [
|
||||||
"thumbv7em-none-eabi",
|
"thumbv7em-none-eabi",
|
||||||
"thumbv7m-none-eabi",
|
"thumbv7m-none-eabi",
|
||||||
|
Loading…
Reference in New Issue
Block a user