From 7614b96fe45240dafe91ae549e712b560e2d4c10 Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Sat, 31 Dec 2022 14:45:13 +0100 Subject: RTIC v2: Initial commit rtic-syntax is now part of RTIC repository --- Cargo.toml | 14 +- macros/.gitignore | 2 + macros/Cargo.toml | 41 +- macros/src/analyze.rs | 42 +- macros/src/bindings.rs | 0 macros/src/codegen.rs | 97 +--- macros/src/codegen/assertions.rs | 10 +- macros/src/codegen/async_dispatchers.rs | 129 +++++ macros/src/codegen/dispatchers.rs | 67 ++- macros/src/codegen/hardware_tasks.rs | 16 +- macros/src/codegen/idle.rs | 15 +- macros/src/codegen/init.rs | 16 +- macros/src/codegen/local_resources.rs | 6 +- macros/src/codegen/local_resources_struct.rs | 19 +- macros/src/codegen/module.rs | 263 +++++----- macros/src/codegen/monotonic.rs | 280 +++++++++++ macros/src/codegen/post_init.rs | 17 +- macros/src/codegen/pre_init.rs | 22 +- macros/src/codegen/shared_resources.rs | 18 +- macros/src/codegen/shared_resources_struct.rs | 40 +- macros/src/codegen/software_tasks.rs | 132 ++--- macros/src/codegen/timer_queue.rs | 33 +- macros/src/codegen/util.rs | 43 +- macros/src/lib.rs | 84 ++-- macros/src/syntax.rs | 158 ++++++ macros/src/syntax/.github/bors.toml | 3 + macros/src/syntax/.github/workflows/build.yml | 213 ++++++++ macros/src/syntax/.github/workflows/changelog.yml | 28 ++ .../workflows/properties/build.properties.json | 6 + macros/src/syntax/.gitignore | 4 + macros/src/syntax/.travis.yml | 31 ++ macros/src/syntax/accessors.rs | 113 +++++ macros/src/syntax/analyze.rs | 448 +++++++++++++++++ macros/src/syntax/ast.rs | 380 +++++++++++++++ macros/src/syntax/check.rs | 66 +++ macros/src/syntax/optimize.rs | 36 ++ macros/src/syntax/parse.rs | 520 ++++++++++++++++++++ macros/src/syntax/parse/app.rs | 539 +++++++++++++++++++++ macros/src/syntax/parse/hardware_task.rs | 96 ++++ macros/src/syntax/parse/idle.rs | 45 ++ macros/src/syntax/parse/init.rs | 52 ++ macros/src/syntax/parse/monotonic.rs | 42 ++ macros/src/syntax/parse/resource.rs | 55 +++ macros/src/syntax/parse/software_task.rs | 86 ++++ macros/src/syntax/parse/util.rs | 338 +++++++++++++ macros/tests/ui.rs | 7 + macros/ui/async-local-resouces.rs | 28 ++ macros/ui/async-local-resouces.stderr | 5 + macros/ui/async-zero-prio-tasks.rs | 19 + macros/ui/async-zero-prio-tasks.stderr | 11 + macros/ui/extern-interrupt-used.rs | 16 + macros/ui/extern-interrupt-used.stderr | 5 + macros/ui/idle-double-local.rs | 9 + macros/ui/idle-double-local.stderr | 5 + macros/ui/idle-double-shared.rs | 9 + macros/ui/idle-double-shared.stderr | 5 + macros/ui/idle-input.rs | 9 + macros/ui/idle-input.stderr | 5 + macros/ui/idle-no-context.rs | 9 + macros/ui/idle-no-context.stderr | 5 + macros/ui/idle-not-divergent.rs | 7 + macros/ui/idle-not-divergent.stderr | 5 + macros/ui/idle-output.rs | 9 + macros/ui/idle-output.stderr | 5 + macros/ui/idle-pub.rs | 9 + macros/ui/idle-pub.stderr | 5 + macros/ui/idle-unsafe.rs | 9 + macros/ui/idle-unsafe.stderr | 5 + macros/ui/init-divergent.rs | 13 + macros/ui/init-divergent.stderr | 5 + macros/ui/init-double-local.rs | 7 + macros/ui/init-double-local.stderr | 5 + macros/ui/init-double-shared.rs | 7 + macros/ui/init-double-shared.stderr | 5 + macros/ui/init-input.rs | 13 + macros/ui/init-input.stderr | 5 + macros/ui/init-no-context.rs | 13 + macros/ui/init-no-context.stderr | 5 + macros/ui/init-output.rs | 9 + macros/ui/init-output.stderr | 5 + macros/ui/init-pub.rs | 13 + macros/ui/init-pub.stderr | 5 + macros/ui/init-unsafe.rs | 7 + macros/ui/init-unsafe.stderr | 5 + macros/ui/interrupt-double.rs | 10 + macros/ui/interrupt-double.stderr | 5 + macros/ui/local-collision-2.rs | 21 + macros/ui/local-collision-2.stderr | 17 + macros/ui/local-collision.rs | 21 + macros/ui/local-collision.stderr | 11 + macros/ui/local-malformed-1.rs | 16 + macros/ui/local-malformed-1.stderr | 5 + macros/ui/local-malformed-2.rs | 16 + macros/ui/local-malformed-2.stderr | 5 + macros/ui/local-malformed-3.rs | 16 + macros/ui/local-malformed-3.stderr | 5 + macros/ui/local-malformed-4.rs | 16 + macros/ui/local-malformed-4.stderr | 5 + macros/ui/local-not-declared.rs | 16 + macros/ui/local-not-declared.stderr | 5 + macros/ui/local-pub.rs | 9 + macros/ui/local-pub.stderr | 5 + macros/ui/local-shared-attribute.rs | 14 + macros/ui/local-shared-attribute.stderr | 5 + macros/ui/local-shared.rs | 28 ++ macros/ui/local-shared.stderr | 11 + macros/ui/monotonic-binds-collision-task.rs | 10 + macros/ui/monotonic-binds-collision-task.stderr | 5 + macros/ui/monotonic-binds-collision.rs | 10 + macros/ui/monotonic-binds-collision.stderr | 5 + macros/ui/monotonic-double-binds.rs | 7 + macros/ui/monotonic-double-binds.stderr | 5 + macros/ui/monotonic-double-default.rs | 7 + macros/ui/monotonic-double-default.stderr | 5 + macros/ui/monotonic-double-prio.rs | 7 + macros/ui/monotonic-double-prio.stderr | 5 + macros/ui/monotonic-double.rs | 10 + macros/ui/monotonic-double.stderr | 5 + macros/ui/monotonic-name-collision.rs | 10 + macros/ui/monotonic-name-collision.stderr | 5 + macros/ui/monotonic-no-binds.rs | 7 + macros/ui/monotonic-no-binds.stderr | 5 + macros/ui/monotonic-no-paran.rs | 8 + macros/ui/monotonic-no-paran.stderr | 5 + macros/ui/monotonic-timer-collision.rs | 10 + macros/ui/monotonic-timer-collision.stderr | 5 + macros/ui/monotonic-with-attrs.rs | 8 + macros/ui/monotonic-with-attrs.stderr | 5 + macros/ui/pub-local.stderr | 5 + macros/ui/pub-shared.stderr | 5 + macros/ui/shared-lock-free.rs | 38 ++ macros/ui/shared-lock-free.stderr | 17 + macros/ui/shared-not-declared.rs | 16 + macros/ui/shared-not-declared.stderr | 5 + macros/ui/shared-pub.rs | 9 + macros/ui/shared-pub.stderr | 5 + macros/ui/task-bind.rs | 7 + macros/ui/task-bind.stderr | 5 + macros/ui/task-divergent.rs | 9 + macros/ui/task-divergent.stderr | 5 + macros/ui/task-double-capacity.rs | 7 + macros/ui/task-double-capacity.stderr | 5 + macros/ui/task-double-local.rs | 7 + macros/ui/task-double-local.stderr | 5 + macros/ui/task-double-priority.rs | 7 + macros/ui/task-double-priority.stderr | 5 + macros/ui/task-double-shared.rs | 7 + macros/ui/task-double-shared.stderr | 5 + macros/ui/task-idle.rs | 13 + macros/ui/task-idle.stderr | 5 + macros/ui/task-init.rs | 17 + macros/ui/task-init.stderr | 5 + macros/ui/task-interrupt-same-prio-spawn.rs | 7 + macros/ui/task-interrupt-same-prio-spawn.stderr | 5 + macros/ui/task-interrupt.rs | 10 + macros/ui/task-interrupt.stderr | 5 + macros/ui/task-no-context.rs | 7 + macros/ui/task-no-context.stderr | 5 + macros/ui/task-priority-too-high.rs | 7 + macros/ui/task-priority-too-high.stderr | 5 + macros/ui/task-priority-too-low.rs | 7 + macros/ui/task-priority-too-low.stderr | 5 + macros/ui/task-pub.rs | 7 + macros/ui/task-pub.stderr | 5 + macros/ui/task-unsafe.rs | 7 + macros/ui/task-unsafe.stderr | 5 + src/lib.rs | 127 +---- 167 files changed, 5222 insertions(+), 605 deletions(-) create mode 100644 macros/.gitignore create mode 100644 macros/src/bindings.rs create mode 100644 macros/src/codegen/async_dispatchers.rs create mode 100644 macros/src/codegen/monotonic.rs create mode 100644 macros/src/syntax.rs create mode 100644 macros/src/syntax/.github/bors.toml create mode 100644 macros/src/syntax/.github/workflows/build.yml create mode 100644 macros/src/syntax/.github/workflows/changelog.yml create mode 100644 macros/src/syntax/.github/workflows/properties/build.properties.json create mode 100644 macros/src/syntax/.gitignore create mode 100644 macros/src/syntax/.travis.yml create mode 100644 macros/src/syntax/accessors.rs create mode 100644 macros/src/syntax/analyze.rs create mode 100644 macros/src/syntax/ast.rs create mode 100644 macros/src/syntax/check.rs create mode 100644 macros/src/syntax/optimize.rs create mode 100644 macros/src/syntax/parse.rs create mode 100644 macros/src/syntax/parse/app.rs create mode 100644 macros/src/syntax/parse/hardware_task.rs create mode 100644 macros/src/syntax/parse/idle.rs create mode 100644 macros/src/syntax/parse/init.rs create mode 100644 macros/src/syntax/parse/monotonic.rs create mode 100644 macros/src/syntax/parse/resource.rs create mode 100644 macros/src/syntax/parse/software_task.rs create mode 100644 macros/src/syntax/parse/util.rs create mode 100644 macros/tests/ui.rs create mode 100644 macros/ui/async-local-resouces.rs create mode 100644 macros/ui/async-local-resouces.stderr create mode 100644 macros/ui/async-zero-prio-tasks.rs create mode 100644 macros/ui/async-zero-prio-tasks.stderr create mode 100644 macros/ui/extern-interrupt-used.rs create mode 100644 macros/ui/extern-interrupt-used.stderr create mode 100644 macros/ui/idle-double-local.rs create mode 100644 macros/ui/idle-double-local.stderr create mode 100644 macros/ui/idle-double-shared.rs create mode 100644 macros/ui/idle-double-shared.stderr create mode 100644 macros/ui/idle-input.rs create mode 100644 macros/ui/idle-input.stderr create mode 100644 macros/ui/idle-no-context.rs create mode 100644 macros/ui/idle-no-context.stderr create mode 100644 macros/ui/idle-not-divergent.rs create mode 100644 macros/ui/idle-not-divergent.stderr create mode 100644 macros/ui/idle-output.rs create mode 100644 macros/ui/idle-output.stderr create mode 100644 macros/ui/idle-pub.rs create mode 100644 macros/ui/idle-pub.stderr create mode 100644 macros/ui/idle-unsafe.rs create mode 100644 macros/ui/idle-unsafe.stderr create mode 100644 macros/ui/init-divergent.rs create mode 100644 macros/ui/init-divergent.stderr create mode 100644 macros/ui/init-double-local.rs create mode 100644 macros/ui/init-double-local.stderr create mode 100644 macros/ui/init-double-shared.rs create mode 100644 macros/ui/init-double-shared.stderr create mode 100644 macros/ui/init-input.rs create mode 100644 macros/ui/init-input.stderr create mode 100644 macros/ui/init-no-context.rs create mode 100644 macros/ui/init-no-context.stderr create mode 100644 macros/ui/init-output.rs create mode 100644 macros/ui/init-output.stderr create mode 100644 macros/ui/init-pub.rs create mode 100644 macros/ui/init-pub.stderr create mode 100644 macros/ui/init-unsafe.rs create mode 100644 macros/ui/init-unsafe.stderr create mode 100644 macros/ui/interrupt-double.rs create mode 100644 macros/ui/interrupt-double.stderr create mode 100644 macros/ui/local-collision-2.rs create mode 100644 macros/ui/local-collision-2.stderr create mode 100644 macros/ui/local-collision.rs create mode 100644 macros/ui/local-collision.stderr create mode 100644 macros/ui/local-malformed-1.rs create mode 100644 macros/ui/local-malformed-1.stderr create mode 100644 macros/ui/local-malformed-2.rs create mode 100644 macros/ui/local-malformed-2.stderr create mode 100644 macros/ui/local-malformed-3.rs create mode 100644 macros/ui/local-malformed-3.stderr create mode 100644 macros/ui/local-malformed-4.rs create mode 100644 macros/ui/local-malformed-4.stderr create mode 100644 macros/ui/local-not-declared.rs create mode 100644 macros/ui/local-not-declared.stderr create mode 100644 macros/ui/local-pub.rs create mode 100644 macros/ui/local-pub.stderr create mode 100644 macros/ui/local-shared-attribute.rs create mode 100644 macros/ui/local-shared-attribute.stderr create mode 100644 macros/ui/local-shared.rs create mode 100644 macros/ui/local-shared.stderr create mode 100644 macros/ui/monotonic-binds-collision-task.rs create mode 100644 macros/ui/monotonic-binds-collision-task.stderr create mode 100644 macros/ui/monotonic-binds-collision.rs create mode 100644 macros/ui/monotonic-binds-collision.stderr create mode 100644 macros/ui/monotonic-double-binds.rs create mode 100644 macros/ui/monotonic-double-binds.stderr create mode 100644 macros/ui/monotonic-double-default.rs create mode 100644 macros/ui/monotonic-double-default.stderr create mode 100644 macros/ui/monotonic-double-prio.rs create mode 100644 macros/ui/monotonic-double-prio.stderr create mode 100644 macros/ui/monotonic-double.rs create mode 100644 macros/ui/monotonic-double.stderr create mode 100644 macros/ui/monotonic-name-collision.rs create mode 100644 macros/ui/monotonic-name-collision.stderr create mode 100644 macros/ui/monotonic-no-binds.rs create mode 100644 macros/ui/monotonic-no-binds.stderr create mode 100644 macros/ui/monotonic-no-paran.rs create mode 100644 macros/ui/monotonic-no-paran.stderr create mode 100644 macros/ui/monotonic-timer-collision.rs create mode 100644 macros/ui/monotonic-timer-collision.stderr create mode 100644 macros/ui/monotonic-with-attrs.rs create mode 100644 macros/ui/monotonic-with-attrs.stderr create mode 100644 macros/ui/pub-local.stderr create mode 100644 macros/ui/pub-shared.stderr create mode 100644 macros/ui/shared-lock-free.rs create mode 100644 macros/ui/shared-lock-free.stderr create mode 100644 macros/ui/shared-not-declared.rs create mode 100644 macros/ui/shared-not-declared.stderr create mode 100644 macros/ui/shared-pub.rs create mode 100644 macros/ui/shared-pub.stderr create mode 100644 macros/ui/task-bind.rs create mode 100644 macros/ui/task-bind.stderr create mode 100644 macros/ui/task-divergent.rs create mode 100644 macros/ui/task-divergent.stderr create mode 100644 macros/ui/task-double-capacity.rs create mode 100644 macros/ui/task-double-capacity.stderr create mode 100644 macros/ui/task-double-local.rs create mode 100644 macros/ui/task-double-local.stderr create mode 100644 macros/ui/task-double-priority.rs create mode 100644 macros/ui/task-double-priority.stderr create mode 100644 macros/ui/task-double-shared.rs create mode 100644 macros/ui/task-double-shared.stderr create mode 100644 macros/ui/task-idle.rs create mode 100644 macros/ui/task-idle.stderr create mode 100644 macros/ui/task-init.rs create mode 100644 macros/ui/task-init.stderr create mode 100644 macros/ui/task-interrupt-same-prio-spawn.rs create mode 100644 macros/ui/task-interrupt-same-prio-spawn.stderr create mode 100644 macros/ui/task-interrupt.rs create mode 100644 macros/ui/task-interrupt.stderr create mode 100644 macros/ui/task-no-context.rs create mode 100644 macros/ui/task-no-context.stderr create mode 100644 macros/ui/task-priority-too-high.rs create mode 100644 macros/ui/task-priority-too-high.stderr create mode 100644 macros/ui/task-priority-too-low.rs create mode 100644 macros/ui/task-priority-too-low.stderr create mode 100644 macros/ui/task-pub.rs create mode 100644 macros/ui/task-pub.stderr create mode 100644 macros/ui/task-unsafe.rs create mode 100644 macros/ui/task-unsafe.stderr diff --git a/Cargo.toml b/Cargo.toml index 68fea4c..d995de4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,27 +1,29 @@ [package] authors = [ "The Real-Time Interrupt-driven Concurrency developers", + "Emil Fresk ", + "Henrik Tjäder ", "Jorge Aparicio ", "Per Lindgren ", ] -categories = ["concurrency", "embedded", "no-std"] +categories = ["concurrency", "embedded", "no-std", "asynchronous"] description = "Real-Time Interrupt-driven Concurrency (RTIC): a concurrency framework for building real-time systems" documentation = "https://rtic.rs/" edition = "2021" -keywords = ["arm", "cortex-m"] +keywords = ["arm", "cortex-m", "risc-v", "embedded", "async", "runtime", "futures", "await", "no-std", "rtos", "bare-metal"] license = "MIT OR Apache-2.0" -name = "cortex-m-rtic" +name = "rtic" readme = "README.md" -repository = "https://github.com/rtic-rs/cortex-m-rtic" +repository = "https://github.com/rtic-rs/rtic" -version = "1.1.4" +version = "2.0.0-alpha.0" [lib] name = "rtic" [dependencies] cortex-m = "0.7.0" -cortex-m-rtic-macros = { path = "macros", version = "1.1.6" } +rtic-macros = { path = "macros", version = "2.0.0-alpha.0" } rtic-monotonic = "1.0.0" rtic-core = "1.0.0" heapless = "0.7.7" diff --git a/macros/.gitignore b/macros/.gitignore new file mode 100644 index 0000000..4fffb2f --- /dev/null +++ b/macros/.gitignore @@ -0,0 +1,2 @@ +/target +/Cargo.lock diff --git a/macros/Cargo.toml b/macros/Cargo.toml index c3f0561..1cc9556 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -1,28 +1,41 @@ [package] authors = [ "The Real-Time Interrupt-driven Concurrency developers", + "Emil Fresk ", + "Henrik Tjäder ", "Jorge Aparicio ", + "Per Lindgren ", ] -categories = ["concurrency", "embedded", "no-std"] -description = "Procedural macros of the cortex-m-rtic crate" -documentation = "https://rtic-rs.github.io/cortex-m-rtic/api/cortex_m_rtic" +categories = ["concurrency", "embedded", "no-std", "asynchronous"] +description = "Procedural macros, syntax parsing, and codegen of the RTIC crate" +documentation = "https://rtic-rs.github.io/rtic/api/rtic" edition = "2021" -keywords = ["arm", "cortex-m"] +keywords = ["arm", "cortex-m", "risc-v", "embedded", "async", "runtime", "futures", "await", "no-std", "rtos", "bare-metal"] license = "MIT OR Apache-2.0" -name = "cortex-m-rtic-macros" +name = "rtic-macros" readme = "../README.md" -repository = "https://github.com/rtic-rs/cortex-m-rtic" -version = "1.1.6" +repository = "https://github.com/rtic-rs/rtic" + +version = "2.0.0-alpha.0" [lib] proc-macro = true +[feature] +default = [] +debugprint = [] +# list of supported codegen backends +thumbv6 = [] +thumbv7 = [] +# riscv-clic = [] +# riscv-ch32 = [] + [dependencies] -proc-macro2 = "1" -proc-macro-error = "1" -quote = "1" -syn = "1" -rtic-syntax = "1.0.3" +indexmap = "1.9.2" +proc-macro2 = "1.0.49" +proc-macro-error = "1.0.4" +quote = "1.0.23" +syn = { version = "1.0.107", features = ["extra-traits", "full"] } -[features] -debugprint = [] +[dev-dependencies] +trybuild = "1.0.73" diff --git a/macros/src/analyze.rs b/macros/src/analyze.rs index d255b7f..ec12cfb 100644 --- a/macros/src/analyze.rs +++ b/macros/src/analyze.rs @@ -1,17 +1,17 @@ use core::ops; use std::collections::{BTreeMap, BTreeSet}; -use rtic_syntax::{ +use crate::syntax::{ analyze::{self, Priority}, - ast::{App, ExternInterrupt}, - P, + ast::{App, Dispatcher}, }; use syn::Ident; /// Extend the upstream `Analysis` struct with our field pub struct Analysis { - parent: P, - pub interrupts: BTreeMap, + parent: analyze::Analysis, + pub interrupts_normal: BTreeMap, + pub interrupts_async: BTreeMap, } impl ops::Deref for Analysis { @@ -23,25 +23,43 @@ impl ops::Deref for Analysis { } // Assign an interrupt to each priority level -pub fn app(analysis: P, app: &App) -> P { +pub fn app(analysis: analyze::Analysis, app: &App) -> Analysis { + let mut available_interrupt = app.args.dispatchers.clone(); + // the set of priorities (each priority only once) let priorities = app .software_tasks .values() + .filter(|task| !task.is_async) + .map(|task| task.args.priority) + .collect::>(); + + let priorities_async = app + .software_tasks + .values() + .filter(|task| task.is_async) .map(|task| task.args.priority) .collect::>(); // map from priorities to interrupts (holding name and attributes) - let interrupts: BTreeMap = priorities + + let interrupts_normal: BTreeMap = priorities .iter() .copied() .rev() - .zip(&app.args.extern_interrupts) - .map(|(p, (id, ext))| (p, (id.clone(), ext.clone()))) + .map(|p| (p, available_interrupt.pop().expect("UNREACHABLE"))) .collect(); - P::new(Analysis { + let interrupts_async: BTreeMap = priorities_async + .iter() + .copied() + .rev() + .map(|p| (p, available_interrupt.pop().expect("UNREACHABLE"))) + .collect(); + + Analysis { parent: analysis, - interrupts, - }) + interrupts_normal, + interrupts_async, + } } diff --git a/macros/src/bindings.rs b/macros/src/bindings.rs new file mode 100644 index 0000000..e69de29 diff --git a/macros/src/codegen.rs b/macros/src/codegen.rs index 89173d4..ef81732 100644 --- a/macros/src/codegen.rs +++ b/macros/src/codegen.rs @@ -1,10 +1,11 @@ use proc_macro2::TokenStream as TokenStream2; use quote::quote; -use rtic_syntax::ast::App; -use crate::{analyze::Analysis, check::Extra}; +use crate::analyze::Analysis; +use crate::syntax::ast::App; mod assertions; +mod async_dispatchers; mod dispatchers; mod hardware_tasks; mod idle; @@ -12,6 +13,7 @@ mod init; mod local_resources; mod local_resources_struct; mod module; +mod monotonic; mod post_init; mod pre_init; mod shared_resources; @@ -21,22 +23,22 @@ mod timer_queue; mod util; #[allow(clippy::too_many_lines)] -pub fn app(app: &App, analysis: &Analysis, extra: &Extra) -> TokenStream2 { +pub fn app(app: &App, analysis: &Analysis) -> TokenStream2 { let mut mod_app = vec![]; let mut mains = vec![]; let mut root = vec![]; let mut user = vec![]; // Generate the `main` function - let assertion_stmts = assertions::codegen(app, analysis, extra); + let assertion_stmts = assertions::codegen(app, analysis); - let pre_init_stmts = pre_init::codegen(app, analysis, extra); + let pre_init_stmts = pre_init::codegen(app, analysis); - let (mod_app_init, root_init, user_init, call_init) = init::codegen(app, analysis, extra); + let (mod_app_init, root_init, user_init, call_init) = init::codegen(app, analysis); let post_init_stmts = post_init::codegen(app, analysis); - let (mod_app_idle, root_idle, user_idle, call_idle) = idle::codegen(app, analysis, extra); + let (mod_app_idle, root_idle, user_idle, call_idle) = idle::codegen(app, analysis); user.push(quote!( #user_init @@ -84,82 +86,25 @@ pub fn app(app: &App, analysis: &Analysis, extra: &Extra) -> TokenStream2 { } )); - let (mod_app_shared_resources, mod_shared_resources) = - shared_resources::codegen(app, analysis, extra); - let (mod_app_local_resources, mod_local_resources) = - local_resources::codegen(app, analysis, extra); + let (mod_app_shared_resources, mod_shared_resources) = shared_resources::codegen(app, analysis); + let (mod_app_local_resources, mod_local_resources) = local_resources::codegen(app, analysis); let (mod_app_hardware_tasks, root_hardware_tasks, user_hardware_tasks) = - hardware_tasks::codegen(app, analysis, extra); + hardware_tasks::codegen(app, analysis); let (mod_app_software_tasks, root_software_tasks, user_software_tasks) = - software_tasks::codegen(app, analysis, extra); + software_tasks::codegen(app, analysis); - let mod_app_dispatchers = dispatchers::codegen(app, analysis, extra); - let mod_app_timer_queue = timer_queue::codegen(app, analysis, extra); + let monotonics = monotonic::codegen(app, analysis); + + let mod_app_dispatchers = dispatchers::codegen(app, analysis); + let mod_app_async_dispatchers = async_dispatchers::codegen(app, analysis); + let mod_app_timer_queue = timer_queue::codegen(app, analysis); let user_imports = &app.user_imports; let user_code = &app.user_code; let name = &app.name; - let device = &extra.device; - - let monotonic_parts: Vec<_> = app - .monotonics - .iter() - .map(|(_, monotonic)| { - let name = &monotonic.ident; - let name_str = &name.to_string(); - let cfgs = &monotonic.cfgs; - let ident = util::monotonic_ident(name_str); - let doc = &format!( - "This module holds the static implementation for `{}::now()`", - name_str - ); - - let default_monotonic = if monotonic.args.default { - quote!( - #(#cfgs)* - pub use #name::now; - ) - } else { - quote!() - }; - - quote! { - #default_monotonic - - #[doc = #doc] - #[allow(non_snake_case)] - #(#cfgs)* - pub mod #name { - - /// Read the current time from this monotonic - pub fn now() -> ::Instant { - rtic::export::interrupt::free(|_| { - use rtic::Monotonic as _; - if let Some(m) = unsafe{ &mut *super::super::#ident.get_mut() } { - m.now() - } else { - ::zero() - } - }) - } - } - } - }) - .collect(); - - let monotonics = if monotonic_parts.is_empty() { - quote!() - } else { - quote!( - pub use rtic::Monotonic as _; - - /// Holds static methods for each monotonic. - pub mod monotonics { - #(#monotonic_parts)* - } - ) - }; + let device = &app.args.device; + let rt_err = util::rt_err_ident(); quote!( @@ -205,6 +150,8 @@ pub fn app(app: &App, analysis: &Analysis, extra: &Extra) -> TokenStream2 { #(#mod_app_dispatchers)* + #(#mod_app_async_dispatchers)* + #(#mod_app_timer_queue)* #(#mains)* diff --git a/macros/src/codegen/assertions.rs b/macros/src/codegen/assertions.rs index 3e0ad61..0f8326c 100644 --- a/macros/src/codegen/assertions.rs +++ b/macros/src/codegen/assertions.rs @@ -1,11 +1,11 @@ use proc_macro2::TokenStream as TokenStream2; use quote::quote; -use crate::{analyze::Analysis, check::Extra, codegen::util}; -use rtic_syntax::ast::App; +use crate::syntax::ast::App; +use crate::{analyze::Analysis, codegen::util}; /// Generates compile-time assertions that check that types implement the `Send` / `Sync` traits -pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> Vec { +pub fn codegen(app: &App, analysis: &Analysis) -> Vec { let mut stmts = vec![]; for ty in &analysis.send_types { @@ -21,7 +21,7 @@ pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> Vec();)); } - let device = &extra.device; + let device = &app.args.device; let chunks_name = util::priority_mask_chunks_ident(); let no_basepri_checks: Vec<_> = app .hardware_tasks @@ -29,9 +29,7 @@ pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> Vec= (#chunks_name * 32) { ::core::panic!("An interrupt out of range is used while in armv6 or armv8m.base"); } diff --git a/macros/src/codegen/async_dispatchers.rs b/macros/src/codegen/async_dispatchers.rs new file mode 100644 index 0000000..8b0e928 --- /dev/null +++ b/macros/src/codegen/async_dispatchers.rs @@ -0,0 +1,129 @@ +use crate::syntax::ast::App; +use crate::{analyze::Analysis, codegen::util}; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +/// Generates task dispatchers +pub fn codegen(app: &App, analysis: &Analysis) -> Vec { + let mut items = vec![]; + + let interrupts = &analysis.interrupts_async; + + // Generate executor definition and priority in global scope + for (name, task) in app.software_tasks.iter() { + if task.is_async { + let type_name = util::internal_task_ident(name, "F"); + let exec_name = util::internal_task_ident(name, "EXEC"); + let prio_name = util::internal_task_ident(name, "PRIORITY"); + + items.push(quote!( + #[allow(non_camel_case_types)] + type #type_name = impl core::future::Future + 'static; + #[allow(non_upper_case_globals)] + static #exec_name: + rtic::RacyCell> = + rtic::RacyCell::new(rtic::export::executor::AsyncTaskExecutor::new()); + + // The executors priority, this can be any value - we will overwrite it when we + // start a task + #[allow(non_upper_case_globals)] + static #prio_name: rtic::RacyCell = + unsafe { rtic::RacyCell::new(rtic::export::Priority::new(0)) }; + )); + } + } + + for (&level, channel) in &analysis.channels { + if channel + .tasks + .iter() + .map(|task_name| !app.software_tasks[task_name].is_async) + .all(|is_not_async| is_not_async) + { + // check if all tasks are not async, if so don't generate this. + continue; + } + + let mut stmts = vec![]; + let device = &app.args.device; + let enum_ = util::interrupt_ident(); + let interrupt = util::suffixed(&interrupts[&level].0.to_string()); + + for name in channel + .tasks + .iter() + .filter(|name| app.software_tasks[*name].is_async) + { + let exec_name = util::internal_task_ident(name, "EXEC"); + let prio_name = util::internal_task_ident(name, "PRIORITY"); + let task = &app.software_tasks[name]; + // let cfgs = &task.cfgs; + let (_, tupled, pats, input_types) = util::regroup_inputs(&task.inputs); + let executor_run_ident = util::executor_run_ident(name); + + let n = util::capacity_literal(channel.capacity as usize + 1); + let rq = util::rq_async_ident(name); + let (rq_ty, rq_expr) = { + ( + quote!(rtic::export::ASYNCRQ<#input_types, #n>), + quote!(rtic::export::Queue::new()), + ) + }; + + items.push(quote!( + #[doc(hidden)] + #[allow(non_camel_case_types)] + #[allow(non_upper_case_globals)] + static #rq: rtic::RacyCell<#rq_ty> = rtic::RacyCell::new(#rq_expr); + )); + + stmts.push(quote!( + if !(&*#exec_name.get()).is_running() { + if let Some(#tupled) = rtic::export::interrupt::free(|_| (&mut *#rq.get_mut()).dequeue()) { + + // The async executor needs a static priority + #prio_name.get_mut().write(rtic::export::Priority::new(PRIORITY)); + let priority: &'static _ = &*#prio_name.get(); + + (&mut *#exec_name.get_mut()).spawn(#name(#name::Context::new(priority) #(,#pats)*)); + #executor_run_ident.store(true, core::sync::atomic::Ordering::Relaxed); + } + } + + if #executor_run_ident.load(core::sync::atomic::Ordering::Relaxed) { + #executor_run_ident.store(false, core::sync::atomic::Ordering::Relaxed); + if (&mut *#exec_name.get_mut()).poll(|| { + #executor_run_ident.store(true, core::sync::atomic::Ordering::Release); + rtic::pend(#device::#enum_::#interrupt); + }) && !rtic::export::interrupt::free(|_| (&*#rq.get_mut()).is_empty()) { + // If the ready queue is not empty and the executor finished, restart this + // dispatch to check if the executor should be restarted. + rtic::pend(#device::#enum_::#interrupt); + } + } + )); + } + + let doc = format!( + "Interrupt handler to dispatch async tasks at priority {}", + level + ); + let attribute = &interrupts[&level].1.attrs; + items.push(quote!( + #[allow(non_snake_case)] + #[doc = #doc] + #[no_mangle] + #(#attribute)* + unsafe fn #interrupt() { + /// The priority of this interrupt handler + const PRIORITY: u8 = #level; + + rtic::export::run(PRIORITY, || { + #(#stmts)* + }); + } + )); + } + + items +} diff --git a/macros/src/codegen/dispatchers.rs b/macros/src/codegen/dispatchers.rs index a90a97c..1a8b404 100644 --- a/macros/src/codegen/dispatchers.rs +++ b/macros/src/codegen/dispatchers.rs @@ -1,21 +1,31 @@ +use crate::syntax::ast::App; +use crate::{analyze::Analysis, codegen::util}; use proc_macro2::TokenStream as TokenStream2; use quote::quote; -use rtic_syntax::ast::App; - -use crate::{analyze::Analysis, check::Extra, codegen::util}; /// Generates task dispatchers -pub fn codegen(app: &App, analysis: &Analysis, _extra: &Extra) -> Vec { +pub fn codegen(app: &App, analysis: &Analysis) -> Vec { let mut items = vec![]; - let interrupts = &analysis.interrupts; + let interrupts = &analysis.interrupts_normal; for (&level, channel) in &analysis.channels { + if channel + .tasks + .iter() + .map(|task_name| app.software_tasks[task_name].is_async) + .all(|is_async| is_async) + { + // check if all tasks are async, if so don't generate this. + continue; + } + let mut stmts = vec![]; let variants = channel .tasks .iter() + .filter(|name| !app.software_tasks[*name].is_async) .map(|name| { let cfgs = &app.software_tasks[name].cfgs; @@ -45,6 +55,7 @@ pub fn codegen(app: &App, analysis: &Analysis, _extra: &Extra) -> Vec), @@ -64,6 +75,13 @@ pub fn codegen(app: &App, analysis: &Analysis, _extra: &Extra) -> Vec = rtic::RacyCell::new(#rq_expr); )); + let interrupt = util::suffixed( + &interrupts + .get(&level) + .expect("RTIC-ICE: Unable to get interrrupt") + .0 + .to_string(), + ); let arms = channel .tasks .iter() @@ -74,23 +92,27 @@ pub fn codegen(app: &App, analysis: &Analysis, _extra: &Extra) -> Vec { - let #tupled = - (&*#inputs - .get()) - .get_unchecked(usize::from(index)) - .as_ptr() - .read(); - (&mut *#fq.get_mut()).split().0.enqueue_unchecked(index); - let priority = &rtic::export::Priority::new(PRIORITY); - #name( - #name::Context::new(priority) - #(,#pats)* - ) - } - ) + if !task.is_async { + quote!( + #(#cfgs)* + #t::#name => { + let #tupled = + (&*#inputs + .get()) + .get_unchecked(usize::from(index)) + .as_ptr() + .read(); + (&mut *#fq.get_mut()).split().0.enqueue_unchecked(index); + let priority = &rtic::export::Priority::new(PRIORITY); + #name( + #name::Context::new(priority) + #(,#pats)* + ) + } + ) + } else { + quote!() + } }) .collect::>(); @@ -103,7 +125,6 @@ pub fn codegen(app: &App, analysis: &Analysis, _extra: &Extra) -> Vec ( // mod_app_hardware_tasks -- interrupt handlers and `${task}Resources` constructors Vec, @@ -33,12 +30,10 @@ pub fn codegen( let priority = task.args.priority; let cfgs = &task.cfgs; let attrs = &task.attrs; - let user_hardware_task_isr_doc = &format!(" User HW task ISR trampoline for {name}"); mod_app.push(quote!( #[allow(non_snake_case)] #[no_mangle] - #[doc = #user_hardware_task_isr_doc] #(#attrs)* #(#cfgs)* unsafe fn #symbol() { @@ -87,19 +82,14 @@ pub fn codegen( local_needs_lt, app, analysis, - extra, )); - let user_hardware_task_doc = &format!(" User HW task: {name}"); if !task.is_extern { let attrs = &task.attrs; - let cfgs = &task.cfgs; let context = &task.context; let stmts = &task.stmts; user_tasks.push(quote!( - #[doc = #user_hardware_task_doc] #(#attrs)* - #(#cfgs)* #[allow(non_snake_case)] fn #name(#context: #name::Context) { use rtic::Mutex as _; diff --git a/macros/src/codegen/idle.rs b/macros/src/codegen/idle.rs index 77a7f9f..9867939 100644 --- a/macros/src/codegen/idle.rs +++ b/macros/src/codegen/idle.rs @@ -1,18 +1,15 @@ -use proc_macro2::TokenStream as TokenStream2; -use quote::quote; -use rtic_syntax::{ast::App, Context}; - +use crate::syntax::{ast::App, Context}; use crate::{ analyze::Analysis, - check::Extra, codegen::{local_resources_struct, module, shared_resources_struct}, }; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; /// Generates support code for `#[idle]` functions pub fn codegen( app: &App, analysis: &Analysis, - extra: &Extra, ) -> ( // mod_app_idle -- the `${idle}Resources` constructor Vec, @@ -57,16 +54,13 @@ pub fn codegen( local_needs_lt, app, analysis, - extra, )); - let idle_doc = " User provided idle function".to_string(); let attrs = &idle.attrs; let context = &idle.context; let stmts = &idle.stmts; let user_idle = Some(quote!( #(#attrs)* - #[doc = #idle_doc] #[allow(non_snake_case)] fn #name(#context: #name::Context) -> ! { use rtic::Mutex as _; @@ -82,6 +76,9 @@ pub fn codegen( (mod_app, root_idle, user_idle, call_idle) } else { + // TODO: No idle defined, check for 0-priority tasks and generate an executor if needed + + // ( vec![], vec![], diff --git a/macros/src/codegen/init.rs b/macros/src/codegen/init.rs index 34f86f2..9a6fe2d 100644 --- a/macros/src/codegen/init.rs +++ b/macros/src/codegen/init.rs @@ -1,11 +1,10 @@ use proc_macro2::TokenStream as TokenStream2; use quote::quote; -use rtic_syntax::{ast::App, Context}; use crate::{ analyze::Analysis, - check::Extra, codegen::{local_resources_struct, module}, + syntax::{ast::App, Context}, }; type CodegenResult = ( @@ -24,7 +23,7 @@ type CodegenResult = ( ); /// Generates support code for `#[init]` functions -pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> CodegenResult { +pub fn codegen(app: &App, analysis: &Analysis) -> CodegenResult { let init = &app.init; let mut local_needs_lt = false; let name = &init.name; @@ -65,27 +64,22 @@ pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> CodegenResult { ) }) .collect(); - - let shared_resources_doc = " RTIC shared resource struct".to_string(); - let local_resources_doc = " RTIC local resource struct".to_string(); root_init.push(quote! { - #[doc = #shared_resources_doc] struct #shared { #(#shared_resources)* } - #[doc = #local_resources_doc] struct #local { #(#local_resources)* } }); + // let locals_pat = locals_pat.iter(); + let user_init_return = quote! {#shared, #local, #name::Monotonics}; - let user_init_doc = " User provided init function".to_string(); let user_init = quote!( #(#attrs)* - #[doc = #user_init_doc] #[inline(always)] #[allow(non_snake_case)] fn #name(#context: #name::Context) -> (#user_init_return) { @@ -105,6 +99,7 @@ pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> CodegenResult { mod_app = Some(constructor); } + // let locals_new = locals_new.iter(); let call_init = quote! { let (shared_resources, local_resources, mut monotonics) = #name(#name::Context::new(core.into())); }; @@ -115,7 +110,6 @@ pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> CodegenResult { local_needs_lt, app, analysis, - extra, )); (mod_app, root_init, user_init, call_init) diff --git a/macros/src/codegen/local_resources.rs b/macros/src/codegen/local_resources.rs index 6e7c1da..6fc63cd 100644 --- a/macros/src/codegen/local_resources.rs +++ b/macros/src/codegen/local_resources.rs @@ -1,8 +1,7 @@ +use crate::syntax::ast::App; +use crate::{analyze::Analysis, codegen::util}; use proc_macro2::TokenStream as TokenStream2; use quote::quote; -use rtic_syntax::ast::App; - -use crate::{analyze::Analysis, check::Extra, codegen::util}; /// Generates `local` variables and local resource proxies /// @@ -10,7 +9,6 @@ use crate::{analyze::Analysis, check::Extra, codegen::util}; pub fn codegen( app: &App, _analysis: &Analysis, - _extra: &Extra, ) -> ( // mod_app -- the `static` variables behind the proxies Vec, diff --git a/macros/src/codegen/local_resources_struct.rs b/macros/src/codegen/local_resources_struct.rs index 74bdbf8..309fd8d 100644 --- a/macros/src/codegen/local_resources_struct.rs +++ b/macros/src/codegen/local_resources_struct.rs @@ -1,9 +1,9 @@ -use proc_macro2::TokenStream as TokenStream2; -use quote::quote; -use rtic_syntax::{ +use crate::syntax::{ ast::{App, TaskLocal}, Context, }; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; use crate::codegen::util; @@ -13,7 +13,13 @@ pub fn codegen(ctxt: Context, needs_lt: &mut bool, app: &App) -> (TokenStream2, let resources = match ctxt { Context::Init => &app.init.args.local_resources, - Context::Idle => &app.idle.as_ref().unwrap().args.local_resources, + Context::Idle => { + &app.idle + .as_ref() + .expect("RTIC-ICE: unable to get idle name") + .args + .local_resources + } Context::HardwareTask(name) => &app.hardware_tasks[name].args.local_resources, Context::SoftwareTask(name) => &app.software_tasks[name].args.local_resources, }; @@ -49,9 +55,7 @@ pub fn codegen(ctxt: Context, needs_lt: &mut bool, app: &App) -> (TokenStream2, util::declared_static_local_resource_ident(name, &task_name) }; - let local_resource_doc = format!(" Local resource `{name}`"); fields.push(quote!( - #[doc = #local_resource_doc] #(#cfgs)* pub #name: &#lt mut #ty )); @@ -84,7 +88,7 @@ pub fn codegen(ctxt: Context, needs_lt: &mut bool, app: &App) -> (TokenStream2, } } - let doc = format!(" Local resources `{}` has access to", ctxt.ident(app)); + let doc = format!("Local resources `{}` has access to", ctxt.ident(app)); let ident = util::local_resources_ident(ctxt, app); let item = quote!( #[allow(non_snake_case)] @@ -98,7 +102,6 @@ pub fn codegen(ctxt: Context, needs_lt: &mut bool, app: &App) -> (TokenStream2, let constructor = quote!( impl<#lt> #ident<#lt> { #[inline(always)] - #[doc(hidden)] pub unsafe fn new() -> Self { #ident { #(#values,)* diff --git a/macros/src/codegen/module.rs b/macros/src/codegen/module.rs index 8dcdbcf..7ac06c5 100644 --- a/macros/src/codegen/module.rs +++ b/macros/src/codegen/module.rs @@ -1,7 +1,7 @@ -use crate::{analyze::Analysis, check::Extra, codegen::util}; +use crate::syntax::{ast::App, Context}; +use crate::{analyze::Analysis, codegen::util}; use proc_macro2::TokenStream as TokenStream2; use quote::quote; -use rtic_syntax::{ast::App, Context}; #[allow(clippy::too_many_lines)] pub fn codegen( @@ -10,12 +10,13 @@ pub fn codegen( local_resources_tick: bool, app: &App, analysis: &Analysis, - extra: &Extra, ) -> TokenStream2 { let mut items = vec![]; let mut module_items = vec![]; let mut fields = vec![]; let mut values = vec![]; + // Used to copy task cfgs to the whole module + let mut task_cfgs = vec![]; let name = ctxt.ident(app); @@ -27,8 +28,8 @@ pub fn codegen( pub core: rtic::export::Peripherals )); - if extra.peripherals { - let device = &extra.device; + if app.args.peripherals { + let device = &app.args.device; fields.push(quote!( /// Device peripherals @@ -52,14 +53,6 @@ pub fn codegen( Context::Idle | Context::HardwareTask(_) | Context::SoftwareTask(_) => {} } - // if ctxt.has_locals(app) { - // let ident = util::locals_ident(ctxt, app); - // module_items.push(quote!( - // #[doc(inline)] - // pub use super::#ident as Locals; - // )); - // } - if ctxt.has_local_resources(app) { let ident = util::local_resources_ident(ctxt, app); let lt = if local_resources_tick { @@ -114,12 +107,8 @@ pub fn codegen( .monotonics .iter() .map(|(_, monotonic)| { - let cfgs = &monotonic.cfgs; let mono = &monotonic.ty; - quote! { - #(#cfgs)* - pub #mono - } + quote! {#mono} }) .collect(); @@ -130,7 +119,7 @@ pub fn codegen( #[allow(non_snake_case)] #[allow(non_camel_case_types)] pub struct #internal_monotonics_ident( - #(#monotonic_types),* + #(pub #monotonic_types),* ); )); @@ -141,10 +130,10 @@ pub fn codegen( } let doc = match ctxt { - Context::Idle => " Idle loop", - Context::Init => " Initialization function", - Context::HardwareTask(_) => " Hardware task", - Context::SoftwareTask(_) => " Software task", + Context::Idle => "Idle loop", + Context::Init => "Initialization function", + Context::HardwareTask(_) => "Hardware task", + Context::SoftwareTask(_) => "Software task", }; let v = Vec::new(); @@ -175,8 +164,8 @@ pub fn codegen( let internal_context_name = util::internal_task_ident(name, "Context"); items.push(quote!( - /// Execution context #(#cfgs)* + /// Execution context #[allow(non_snake_case)] #[allow(non_camel_case_types)] pub struct #internal_context_name<#lt> { @@ -185,7 +174,6 @@ pub fn codegen( #(#cfgs)* impl<#lt> #internal_context_name<#lt> { - #[doc(hidden)] #[inline(always)] pub unsafe fn new(#core #priority) -> Self { #internal_context_name { @@ -196,8 +184,8 @@ pub fn codegen( )); module_items.push(quote!( - #[doc(inline)] #(#cfgs)* + #[doc(inline)] pub use super::#internal_context_name as Context; )); @@ -206,6 +194,8 @@ pub fn codegen( let priority = spawnee.args.priority; let t = util::spawn_t_ident(priority); let cfgs = &spawnee.cfgs; + // Store a copy of the task cfgs + task_cfgs = cfgs.clone(); let (args, tupled, untupled, ty) = util::regroup_inputs(&spawnee.inputs); let args = &args; let tupled = &tupled; @@ -213,112 +203,141 @@ pub fn codegen( let rq = util::rq_ident(priority); let inputs = util::inputs_ident(name); - let device = &extra.device; + let device = &app.args.device; let enum_ = util::interrupt_ident(); - let interrupt = &analysis - .interrupts - .get(&priority) - .expect("RTIC-ICE: interrupt identifer not found") - .0; + let interrupt = if spawnee.is_async { + &analysis + .interrupts_async + .get(&priority) + .expect("RTIC-ICE: interrupt identifer not found") + .0 + } else { + &analysis + .interrupts_normal + .get(&priority) + .expect("RTIC-ICE: interrupt identifer not found") + .0 + }; let internal_spawn_ident = util::internal_task_ident(name, "spawn"); // Spawn caller - items.push(quote!( + if spawnee.is_async { + let rq = util::rq_async_ident(name); + items.push(quote!( - /// Spawns the task directly - #(#cfgs)* - pub fn #internal_spawn_ident(#(#args,)*) -> Result<(), #ty> { - let input = #tupled; + #(#cfgs)* + /// Spawns the task directly + #[allow(non_snake_case)] + #[doc(hidden)] + pub fn #internal_spawn_ident(#(#args,)*) -> Result<(), #ty> { + let input = #tupled; - unsafe { - if let Some(index) = rtic::export::interrupt::free(|_| (&mut *#fq.get_mut()).dequeue()) { - (&mut *#inputs - .get_mut()) - .get_unchecked_mut(usize::from(index)) - .as_mut_ptr() - .write(input); + unsafe { + let r = rtic::export::interrupt::free(|_| (&mut *#rq.get_mut()).enqueue(input)); - rtic::export::interrupt::free(|_| { - (&mut *#rq.get_mut()).enqueue_unchecked((#t::#name, index)); - }); + if r.is_ok() { + rtic::pend(#device::#enum_::#interrupt); + } - rtic::pend(#device::#enum_::#interrupt); + r + } + })); + } else { + items.push(quote!( - Ok(()) - } else { - Err(input) + #(#cfgs)* + /// Spawns the task directly + #[allow(non_snake_case)] + #[doc(hidden)] + pub fn #internal_spawn_ident(#(#args,)*) -> Result<(), #ty> { + let input = #tupled; + + unsafe { + if let Some(index) = rtic::export::interrupt::free(|_| (&mut *#fq.get_mut()).dequeue()) { + (&mut *#inputs + .get_mut()) + .get_unchecked_mut(usize::from(index)) + .as_mut_ptr() + .write(input); + + rtic::export::interrupt::free(|_| { + (&mut *#rq.get_mut()).enqueue_unchecked((#t::#name, index)); + }); + rtic::pend(#device::#enum_::#interrupt); + + Ok(()) + } else { + Err(input) + } } - } - })); + })); + } module_items.push(quote!( - #[doc(inline)] #(#cfgs)* + #[doc(inline)] pub use super::#internal_spawn_ident as spawn; )); // Schedule caller - for (_, monotonic) in &app.monotonics { - let instants = util::monotonic_instants_ident(name, &monotonic.ident); - let monotonic_name = monotonic.ident.to_string(); - - let tq = util::tq_ident(&monotonic.ident.to_string()); - let t = util::schedule_t_ident(); - let m = &monotonic.ident; - let cfgs = &monotonic.cfgs; - let m_ident = util::monotonic_ident(&monotonic_name); - let m_isr = &monotonic.args.binds; - let enum_ = util::interrupt_ident(); - let spawn_handle_string = format!("{}::SpawnHandle", m); - - let (enable_interrupt, pend) = if &*m_isr.to_string() == "SysTick" { - ( - quote!(core::mem::transmute::<_, rtic::export::SYST>(()).enable_interrupt()), - quote!(rtic::export::SCB::set_pendst()), - ) - } else { - let rt_err = util::rt_err_ident(); - ( - quote!(rtic::export::NVIC::unmask(#rt_err::#enum_::#m_isr)), - quote!(rtic::pend(#rt_err::#enum_::#m_isr)), - ) - }; - - let tq_marker = &util::timer_queue_marker_ident(); - - // For future use - // let doc = format!(" RTIC internal: {}:{}", file!(), line!()); - // items.push(quote!(#[doc = #doc])); - let internal_spawn_handle_ident = - util::internal_monotonics_ident(name, m, "SpawnHandle"); - let internal_spawn_at_ident = util::internal_monotonics_ident(name, m, "spawn_at"); - let internal_spawn_after_ident = - util::internal_monotonics_ident(name, m, "spawn_after"); - - if monotonic.args.default { + if !spawnee.is_async { + for (_, monotonic) in &app.monotonics { + let instants = util::monotonic_instants_ident(name, &monotonic.ident); + let monotonic_name = monotonic.ident.to_string(); + + let tq = util::tq_ident(&monotonic.ident.to_string()); + let t = util::schedule_t_ident(); + let m = &monotonic.ident; + let m_ident = util::monotonic_ident(&monotonic_name); + let m_isr = &monotonic.args.binds; + let enum_ = util::interrupt_ident(); + let spawn_handle_string = format!("{}::SpawnHandle", m); + + let (enable_interrupt, pend) = if &*m_isr.to_string() == "SysTick" { + ( + quote!(core::mem::transmute::<_, rtic::export::SYST>(()).enable_interrupt()), + quote!(rtic::export::SCB::set_pendst()), + ) + } else { + let rt_err = util::rt_err_ident(); + ( + quote!(rtic::export::NVIC::unmask(#rt_err::#enum_::#m_isr)), + quote!(rtic::pend(#rt_err::#enum_::#m_isr)), + ) + }; + + let tq_marker = &util::timer_queue_marker_ident(); + + let internal_spawn_handle_ident = + util::internal_monotonics_ident(name, m, "SpawnHandle"); + let internal_spawn_at_ident = util::internal_monotonics_ident(name, m, "spawn_at"); + let internal_spawn_after_ident = + util::internal_monotonics_ident(name, m, "spawn_after"); + + if monotonic.args.default { + module_items.push(quote!( + #[doc(inline)] + pub use #m::spawn_after; + #[doc(inline)] + pub use #m::spawn_at; + #[doc(inline)] + pub use #m::SpawnHandle; + )); + } module_items.push(quote!( - #(#cfgs)* - pub use #m::spawn_after; - #(#cfgs)* - pub use #m::spawn_at; - #(#cfgs)* - pub use #m::SpawnHandle; + pub mod #m { + #[doc(inline)] + pub use super::super::#internal_spawn_after_ident as spawn_after; + #[doc(inline)] + pub use super::super::#internal_spawn_at_ident as spawn_at; + #[doc(inline)] + pub use super::super::#internal_spawn_handle_ident as SpawnHandle; + } )); - } - module_items.push(quote!( - #[doc(hidden)] - #(#cfgs)* - pub mod #m { - pub use super::super::#internal_spawn_after_ident as spawn_after; - pub use super::super::#internal_spawn_at_ident as spawn_at; - pub use super::super::#internal_spawn_handle_ident as SpawnHandle; - } - )); - items.push(quote!( - #[doc(hidden)] + items.push(quote!( #(#cfgs)* #[allow(non_snake_case)] #[allow(non_camel_case_types)] @@ -329,7 +348,6 @@ pub fn codegen( #(#cfgs)* impl core::fmt::Debug for #internal_spawn_handle_ident { - #[doc(hidden)] fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct(#spawn_handle_string).finish() } @@ -340,7 +358,7 @@ pub fn codegen( pub fn cancel(self) -> Result<#ty, ()> { rtic::export::interrupt::free(|_| unsafe { let tq = &mut *#tq.get_mut(); - if let Some((_task, index)) = tq.cancel_marker(self.marker) { + if let Some((_task, index)) = tq.cancel_task_marker(self.marker) { // Get the message let msg = (&*#inputs .get()) @@ -357,9 +375,7 @@ pub fn codegen( }) } - /// Reschedule after #[inline] - #(#cfgs)* pub fn reschedule_after( self, duration: <#m as rtic::Monotonic>::Duration @@ -367,8 +383,6 @@ pub fn codegen( self.reschedule_at(monotonics::#m::now() + duration) } - /// Reschedule at - #(#cfgs)* pub fn reschedule_at( self, instant: <#m as rtic::Monotonic>::Instant @@ -379,16 +393,17 @@ pub fn codegen( let tq = (&mut *#tq.get_mut()); - tq.update_marker(self.marker, marker, instant, || #pend).map(|_| #name::#m::SpawnHandle { marker }) + tq.update_task_marker(self.marker, marker, instant, || #pend).map(|_| #name::#m::SpawnHandle { marker }) }) } } + + #(#cfgs)* /// Spawns the task after a set duration relative to the current time /// /// This will use the time `Instant::new(0)` as baseline if called in `#[init]`, /// so if you use a non-resetable timer use `spawn_at` when in `#[init]` - #(#cfgs)* #[allow(non_snake_case)] pub fn #internal_spawn_after_ident( duration: <#m as rtic::Monotonic>::Duration @@ -424,10 +439,10 @@ pub fn codegen( rtic::export::interrupt::free(|_| { let marker = #tq_marker.get().read(); - let nr = rtic::export::NotReady { - instant, - index, + let nr = rtic::export::TaskNotReady { task: #t::#name, + index, + instant, marker, }; @@ -435,7 +450,7 @@ pub fn codegen( let tq = &mut *#tq.get_mut(); - tq.enqueue_unchecked( + tq.enqueue_task_unchecked( nr, || #enable_interrupt, || #pend, @@ -449,6 +464,7 @@ pub fn codegen( } } )); + } } } @@ -457,8 +473,9 @@ pub fn codegen( } else { quote!( #(#items)* + #[allow(non_snake_case)] - #(#cfgs)* + #(#task_cfgs)* #[doc = #doc] pub mod #name { #(#module_items)* diff --git a/macros/src/codegen/monotonic.rs b/macros/src/codegen/monotonic.rs new file mode 100644 index 0000000..417a1d6 --- /dev/null +++ b/macros/src/codegen/monotonic.rs @@ -0,0 +1,280 @@ +use crate::syntax::ast::App; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +use crate::{analyze::Analysis, codegen::util}; + +/// Generates monotonic module dispatchers +pub fn codegen(app: &App, _analysis: &Analysis) -> TokenStream2 { + let mut monotonic_parts: Vec<_> = Vec::new(); + + let tq_marker = util::timer_queue_marker_ident(); + + for (_, monotonic) in &app.monotonics { + // let instants = util::monotonic_instants_ident(name, &monotonic.ident); + let monotonic_name = monotonic.ident.to_string(); + + let tq = util::tq_ident(&monotonic_name); + let m = &monotonic.ident; + let m_ident = util::monotonic_ident(&monotonic_name); + let m_isr = &monotonic.args.binds; + let enum_ = util::interrupt_ident(); + let name_str = &m.to_string(); + let ident = util::monotonic_ident(name_str); + let doc = &format!( + "This module holds the static implementation for `{}::now()`", + name_str + ); + + let (enable_interrupt, pend) = if &*m_isr.to_string() == "SysTick" { + ( + quote!(core::mem::transmute::<_, rtic::export::SYST>(()).enable_interrupt()), + quote!(rtic::export::SCB::set_pendst()), + ) + } else { + let rt_err = util::rt_err_ident(); + ( + quote!(rtic::export::NVIC::unmask(super::super::#rt_err::#enum_::#m_isr)), + quote!(rtic::pend(super::super::#rt_err::#enum_::#m_isr)), + ) + }; + + let default_monotonic = if monotonic.args.default { + quote!( + #[doc(inline)] + pub use #m::now; + #[doc(inline)] + pub use #m::delay; + #[doc(inline)] + pub use #m::delay_until; + #[doc(inline)] + pub use #m::timeout_at; + #[doc(inline)] + pub use #m::timeout_after; + ) + } else { + quote!() + }; + + monotonic_parts.push(quote! { + #default_monotonic + + #[doc = #doc] + #[allow(non_snake_case)] + pub mod #m { + /// Read the current time from this monotonic + pub fn now() -> ::Instant { + rtic::export::interrupt::free(|_| { + use rtic::Monotonic as _; + if let Some(m) = unsafe{ &mut *super::super::#ident.get_mut() } { + m.now() + } else { + ::zero() + } + }) + } + + /// Delay + #[inline(always)] + #[allow(non_snake_case)] + pub fn delay(duration: ::Duration) + -> DelayFuture { + let until = now() + duration; + DelayFuture { until, waker_storage: None } + } + + /// Delay until a specific time + #[inline(always)] + #[allow(non_snake_case)] + pub fn delay_until(instant: ::Instant) + -> DelayFuture { + let until = instant; + DelayFuture { until, waker_storage: None } + } + + /// Delay future. + #[allow(non_snake_case)] + #[allow(non_camel_case_types)] + pub struct DelayFuture { + until: ::Instant, + waker_storage: Option>>, + } + + impl Drop for DelayFuture { + fn drop(&mut self) { + if let Some(waker_storage) = &mut self.waker_storage { + rtic::export::interrupt::free(|_| unsafe { + let tq = &mut *super::super::#tq.get_mut(); + tq.cancel_waker_marker(waker_storage.val.marker); + }); + } + } + } + + impl core::future::Future for DelayFuture { + type Output = (); + + fn poll( + mut self: core::pin::Pin<&mut Self>, + cx: &mut core::task::Context<'_> + ) -> core::task::Poll { + let mut s = self.as_mut(); + let now = now(); + let until = s.until; + let is_ws_none = s.waker_storage.is_none(); + + if now >= until { + return core::task::Poll::Ready(()); + } else if is_ws_none { + rtic::export::interrupt::free(|_| unsafe { + let marker = super::super::#tq_marker.get().read(); + super::super::#tq_marker.get_mut().write(marker.wrapping_add(1)); + + let nr = s.waker_storage.insert(rtic::export::IntrusiveNode::new(rtic::export::WakerNotReady { + waker: cx.waker().clone(), + instant: until, + marker, + })); + + let tq = &mut *super::super::#tq.get_mut(); + + tq.enqueue_waker( + core::mem::transmute(nr), // Transmute the reference to static + || #enable_interrupt, + || #pend, + (&mut *super::super::#m_ident.get_mut()).as_mut()); + }); + } + + core::task::Poll::Pending + } + } + + /// Timeout future. + #[allow(non_snake_case)] + #[allow(non_camel_case_types)] + pub struct TimeoutFuture { + future: F, + until: ::Instant, + waker_storage: Option>>, + } + + impl Drop for TimeoutFuture { + fn drop(&mut self) { + if let Some(waker_storage) = &mut self.waker_storage { + rtic::export::interrupt::free(|_| unsafe { + let tq = &mut *super::super::#tq.get_mut(); + tq.cancel_waker_marker(waker_storage.val.marker); + }); + } + } + } + + /// Timeout after + #[allow(non_snake_case)] + #[inline(always)] + pub fn timeout_after( + future: F, + duration: ::Duration + ) -> TimeoutFuture { + let until = now() + duration; + TimeoutFuture { + future, + until, + waker_storage: None, + } + } + + /// Timeout at + #[allow(non_snake_case)] + #[inline(always)] + pub fn timeout_at( + future: F, + instant: ::Instant + ) -> TimeoutFuture { + TimeoutFuture { + future, + until: instant, + waker_storage: None, + } + } + + impl core::future::Future for TimeoutFuture + where + F: core::future::Future, + { + type Output = Result; + + fn poll( + self: core::pin::Pin<&mut Self>, + cx: &mut core::task::Context<'_> + ) -> core::task::Poll { + // SAFETY: We don't move the underlying pinned value. + let mut s = unsafe { self.get_unchecked_mut() }; + let future = unsafe { core::pin::Pin::new_unchecked(&mut s.future) }; + let now = now(); + let until = s.until; + let is_ws_none = s.waker_storage.is_none(); + + match future.poll(cx) { + core::task::Poll::Ready(r) => { + if let Some(waker_storage) = &mut s.waker_storage { + rtic::export::interrupt::free(|_| unsafe { + let tq = &mut *super::super::#tq.get_mut(); + tq.cancel_waker_marker(waker_storage.val.marker); + }); + } + + return core::task::Poll::Ready(Ok(r)); + } + core::task::Poll::Pending => { + if now >= until { + // Timeout + return core::task::Poll::Ready(Err(super::TimeoutError)); + } else if is_ws_none { + rtic::export::interrupt::free(|_| unsafe { + let marker = super::super::#tq_marker.get().read(); + super::super::#tq_marker.get_mut().write(marker.wrapping_add(1)); + + let nr = s.waker_storage.insert(rtic::export::IntrusiveNode::new(rtic::export::WakerNotReady { + waker: cx.waker().clone(), + instant: until, + marker, + })); + + let tq = &mut *super::super::#tq.get_mut(); + + tq.enqueue_waker( + core::mem::transmute(nr), // Transmute the reference to static + || #enable_interrupt, + || #pend, + (&mut *super::super::#m_ident.get_mut()).as_mut()); + }); + } + } + } + + core::task::Poll::Pending + } + } + } + }); + } + + if monotonic_parts.is_empty() { + quote!() + } else { + quote!( + pub use rtic::Monotonic as _; + + /// Holds static methods for each monotonic. + pub mod monotonics { + /// A timeout error. + #[derive(Debug)] + pub struct TimeoutError; + + #(#monotonic_parts)* + } + ) + } +} diff --git a/macros/src/codegen/post_init.rs b/macros/src/codegen/post_init.rs index 460b4e2..df5daa1 100644 --- a/macros/src/codegen/post_init.rs +++ b/macros/src/codegen/post_init.rs @@ -1,6 +1,6 @@ +use crate::syntax::ast::App; use proc_macro2::{Span, TokenStream as TokenStream2}; use quote::quote; -use rtic_syntax::ast::App; use syn::Index; use crate::{analyze::Analysis, codegen::util}; @@ -43,28 +43,21 @@ pub fn codegen(app: &App, analysis: &Analysis) -> Vec { } } - for (i, (monotonic_ident, monotonic)) in app.monotonics.iter().enumerate() { + for (i, (monotonic, _)) in app.monotonics.iter().enumerate() { // For future use // let doc = format!(" RTIC internal: {}:{}", file!(), line!()); // stmts.push(quote!(#[doc = #doc])); - let cfgs = &monotonic.cfgs; #[allow(clippy::cast_possible_truncation)] let idx = Index { index: i as u32, span: Span::call_site(), }; - stmts.push(quote!( - #(#cfgs)* - monotonics.#idx.reset(); - )); + stmts.push(quote!(monotonics.#idx.reset();)); // Store the monotonic - let name = util::monotonic_ident(&monotonic_ident.to_string()); - stmts.push(quote!( - #(#cfgs)* - #name.get_mut().write(Some(monotonics.#idx)); - )); + let name = util::monotonic_ident(&monotonic.to_string()); + stmts.push(quote!(#name.get_mut().write(Some(monotonics.#idx));)); } // Enable the interrupts -- this completes the `init`-ialization phase diff --git a/macros/src/codegen/pre_init.rs b/macros/src/codegen/pre_init.rs index 2362cb7..ef3acba 100644 --- a/macros/src/codegen/pre_init.rs +++ b/macros/src/codegen/pre_init.rs @@ -1,11 +1,11 @@ +use crate::syntax::ast::App; use proc_macro2::TokenStream as TokenStream2; use quote::quote; -use rtic_syntax::ast::App; -use crate::{analyze::Analysis, check::Extra, codegen::util}; +use crate::{analyze::Analysis, codegen::util}; /// Generates code that runs before `#[init]` -pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> Vec { +pub fn codegen(app: &App, analysis: &Analysis) -> Vec { let mut stmts = vec![]; let rt_err = util::rt_err_ident(); @@ -15,12 +15,14 @@ pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> Vec Vec ( // mod_app -- the `static` variables behind the proxies Vec, @@ -90,7 +89,7 @@ pub fn codegen( // let doc = format!(" RTIC internal ({} resource): {}:{}", doc, file!(), line!()); mod_app.push(util::impl_mutex( - extra, + app, cfgs, true, &shared_name, @@ -112,10 +111,14 @@ pub fn codegen( }; // Computing mapping of used interrupts to masks - let interrupt_ids = analysis.interrupts.iter().map(|(p, (id, _))| (p, id)); + let interrupt_ids = analysis + .interrupts_normal + .iter() + .map(|(p, (id, _))| (p, id)) + .chain(analysis.interrupts_async.iter().map(|(p, (id, _))| (p, id))); let mut prio_to_masks = HashMap::new(); - let device = &extra.device; + let device = &app.args.device; let mut uses_exceptions_with_resources = false; let mut mask_ids = Vec::new(); @@ -147,8 +150,7 @@ pub fn codegen( None } })) { - #[allow(clippy::or_fun_call)] - let v = prio_to_masks.entry(priority - 1).or_insert(Vec::new()); + let v: &mut Vec<_> = prio_to_masks.entry(priority - 1).or_default(); v.push(quote!(#device::Interrupt::#name as u32)); mask_ids.push(quote!(#device::Interrupt::#name as u32)); } diff --git a/macros/src/codegen/shared_resources_struct.rs b/macros/src/codegen/shared_resources_struct.rs index df36271..1d46aa4 100644 --- a/macros/src/codegen/shared_resources_struct.rs +++ b/macros/src/codegen/shared_resources_struct.rs @@ -1,6 +1,6 @@ +use crate::syntax::{ast::App, Context}; use proc_macro2::TokenStream as TokenStream2; use quote::quote; -use rtic_syntax::{ast::App, Context}; use crate::codegen::util; @@ -10,24 +10,17 @@ pub fn codegen(ctxt: Context, needs_lt: &mut bool, app: &App) -> (TokenStream2, let resources = match ctxt { Context::Init => unreachable!("Tried to generate shared resources struct for init"), - Context::Idle => &app.idle.as_ref().unwrap().args.shared_resources, + Context::Idle => { + &app.idle + .as_ref() + .expect("RTIC-ICE: unable to get idle name") + .args + .shared_resources + } Context::HardwareTask(name) => &app.hardware_tasks[name].args.shared_resources, Context::SoftwareTask(name) => &app.software_tasks[name].args.shared_resources, }; - let v = Vec::new(); - let task_cfgs = match ctxt { - Context::HardwareTask(t) => { - &app.hardware_tasks[t].cfgs - // ... - } - Context::SoftwareTask(t) => { - &app.software_tasks[t].cfgs - // ... - } - _ => &v, - }; - let mut fields = vec![]; let mut values = vec![]; let mut has_cfgs = false; @@ -57,18 +50,14 @@ pub fn codegen(ctxt: Context, needs_lt: &mut bool, app: &App) -> (TokenStream2, quote!('a) }; - let lock_free_resource_doc = format!(" Lock free resource `{name}`"); fields.push(quote!( - #[doc = #lock_free_resource_doc] #(#cfgs)* pub #name: &#lt #mut_ #ty )); } else if access.is_shared() { lt = Some(quote!('a)); - let shared_resource_doc = format!(" Shared resource `{name}`"); fields.push(quote!( - #[doc = #shared_resource_doc] #(#cfgs)* pub #name: &'a #ty )); @@ -76,16 +65,12 @@ pub fn codegen(ctxt: Context, needs_lt: &mut bool, app: &App) -> (TokenStream2, // Resource proxy lt = Some(quote!('a)); - let resource_doc = - format!(" Resource proxy resource `{name}`. Use method `.lock()` to gain access"); fields.push(quote!( - #[doc = #resource_doc] #(#cfgs)* pub #name: shared_resources::#shared_name<'a> )); values.push(quote!( - #[doc(hidden)] #(#cfgs)* #name: shared_resources::#shared_name::new(priority) @@ -95,17 +80,13 @@ pub fn codegen(ctxt: Context, needs_lt: &mut bool, app: &App) -> (TokenStream2, continue; } - let resource_doc; let expr = if access.is_exclusive() { - resource_doc = format!(" Exclusive access resource `{name}`"); quote!(&mut *(&mut *#mangled_name.get_mut()).as_mut_ptr()) } else { - resource_doc = format!(" Non-exclusive access resource `{name}`"); quote!(&*(&*#mangled_name.get()).as_ptr()) }; values.push(quote!( - #[doc = #resource_doc] #(#cfgs)* #name: #expr )); @@ -125,13 +106,12 @@ pub fn codegen(ctxt: Context, needs_lt: &mut bool, app: &App) -> (TokenStream2, } } - let doc = format!(" Shared resources `{}` has access to", ctxt.ident(app)); + let doc = format!("Shared resources `{}` has access to", ctxt.ident(app)); let ident = util::shared_resources_ident(ctxt, app); let item = quote!( #[allow(non_snake_case)] #[allow(non_camel_case_types)] #[doc = #doc] - #(#task_cfgs)* pub struct #ident<#lt> { #(#fields,)* } @@ -143,9 +123,7 @@ pub fn codegen(ctxt: Context, needs_lt: &mut bool, app: &App) -> (TokenStream2, Some(quote!(priority: &#lt rtic::export::Priority)) }; let constructor = quote!( - #(#task_cfgs)* impl<#lt> #ident<#lt> { - #[doc(hidden)] #[inline(always)] pub unsafe fn new(#arg) -> Self { #ident { diff --git a/macros/src/codegen/software_tasks.rs b/macros/src/codegen/software_tasks.rs index 226121d..f9247da 100644 --- a/macros/src/codegen/software_tasks.rs +++ b/macros/src/codegen/software_tasks.rs @@ -1,17 +1,14 @@ -use proc_macro2::TokenStream as TokenStream2; -use quote::quote; -use rtic_syntax::{ast::App, Context}; - +use crate::syntax::{ast::App, Context}; use crate::{ analyze::Analysis, - check::Extra, codegen::{local_resources_struct, module, shared_resources_struct, util}, }; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; pub fn codegen( app: &App, analysis: &Analysis, - extra: &Extra, ) -> ( // mod_app_software_tasks -- free queues, buffers and `${task}Resources` constructors Vec, @@ -27,74 +24,87 @@ pub fn codegen( let mut root = vec![]; let mut user_tasks = vec![]; - for (name, task) in &app.software_tasks { + // Any task + for (name, task) in app.software_tasks.iter() { let inputs = &task.inputs; - let cfgs = &task.cfgs; let (_, _, _, input_ty) = util::regroup_inputs(inputs); let cap = task.args.capacity; let cap_lit = util::capacity_literal(cap as usize); let cap_lit_p1 = util::capacity_literal(cap as usize + 1); - // Create free queues and inputs / instants buffers - let fq = util::fq_ident(name); - - #[allow(clippy::redundant_closure)] - let (fq_ty, fq_expr, mk_uninit): (_, _, Box Option<_>>) = { - ( - quote!(rtic::export::SCFQ<#cap_lit_p1>), - quote!(rtic::export::Queue::new()), - Box::new(|| Some(util::link_section_uninit())), - ) - }; - mod_app.push(quote!( - // /// Queue version of a free-list that keeps track of empty slots in - // /// the following buffers - #(#cfgs)* - #[allow(non_camel_case_types)] - #[allow(non_upper_case_globals)] - #[doc(hidden)] - static #fq: rtic::RacyCell<#fq_ty> = rtic::RacyCell::new(#fq_expr); - )); - - let elems = &(0..cap) - .map(|_| quote!(core::mem::MaybeUninit::uninit())) - .collect::>(); + if !task.is_async { + // Create free queues and inputs / instants buffers + let fq = util::fq_ident(name); - for (_, monotonic) in &app.monotonics { - let instants = util::monotonic_instants_ident(name, &monotonic.ident); - let mono_type = &monotonic.ty; - let cfgs = &monotonic.cfgs; + #[allow(clippy::redundant_closure)] + let (fq_ty, fq_expr, mk_uninit): (_, _, Box Option<_>>) = { + ( + quote!(rtic::export::SCFQ<#cap_lit_p1>), + quote!(rtic::export::Queue::new()), + Box::new(|| Some(util::link_section_uninit())), + ) + }; - let uninit = mk_uninit(); - // For future use - // let doc = format!(" RTIC internal: {}:{}", file!(), line!()); mod_app.push(quote!( + // /// Queue version of a free-list that keeps track of empty slots in + // /// the following buffers + #[allow(non_camel_case_types)] + #[allow(non_upper_case_globals)] + #[doc(hidden)] + static #fq: rtic::RacyCell<#fq_ty> = rtic::RacyCell::new(#fq_expr); + )); + + let elems = &(0..cap) + .map(|_| quote!(core::mem::MaybeUninit::uninit())) + .collect::>(); + + for (_, monotonic) in &app.monotonics { + let instants = util::monotonic_instants_ident(name, &monotonic.ident); + let mono_type = &monotonic.ty; + + let uninit = mk_uninit(); + // For future use + // let doc = format!(" RTIC internal: {}:{}", file!(), line!()); + mod_app.push(quote!( #uninit // /// Buffer that holds the instants associated to the inputs of a task // #[doc = #doc] #[allow(non_camel_case_types)] #[allow(non_upper_case_globals)] #[doc(hidden)] - #(#cfgs)* static #instants: rtic::RacyCell<[core::mem::MaybeUninit<<#mono_type as rtic::Monotonic>::Instant>; #cap_lit]> = rtic::RacyCell::new([#(#elems,)*]); )); + } + + let uninit = mk_uninit(); + let inputs_ident = util::inputs_ident(name); + + // Buffer that holds the inputs of a task + mod_app.push(quote!( + #uninit + #[allow(non_camel_case_types)] + #[allow(non_upper_case_globals)] + #[doc(hidden)] + static #inputs_ident: rtic::RacyCell<[core::mem::MaybeUninit<#input_ty>; #cap_lit]> = + rtic::RacyCell::new([#(#elems,)*]); + )); } - let uninit = mk_uninit(); - let inputs_ident = util::inputs_ident(name); - mod_app.push(quote!( - #uninit - // /// Buffer that holds the inputs of a task - #[allow(non_camel_case_types)] - #[allow(non_upper_case_globals)] - #[doc(hidden)] - #(#cfgs)* - static #inputs_ident: rtic::RacyCell<[core::mem::MaybeUninit<#input_ty>; #cap_lit]> = - rtic::RacyCell::new([#(#elems,)*]); - )); + if task.is_async { + let executor_ident = util::executor_run_ident(name); + mod_app.push(quote!( + #[allow(non_camel_case_types)] + #[allow(non_upper_case_globals)] + #[doc(hidden)] + static #executor_ident: core::sync::atomic::AtomicBool = + core::sync::atomic::AtomicBool::new(false); + )); + } + + let inputs = &task.inputs; // `${task}Resources` let mut shared_needs_lt = false; @@ -130,13 +140,24 @@ pub fn codegen( let attrs = &task.attrs; let cfgs = &task.cfgs; let stmts = &task.stmts; - let user_task_doc = format!(" User SW task {name}"); + let (async_marker, context_lifetime) = if task.is_async { + ( + quote!(async), + if shared_needs_lt || local_needs_lt { + quote!(<'static>) + } else { + quote!() + }, + ) + } else { + (quote!(), quote!()) + }; + user_tasks.push(quote!( - #[doc = #user_task_doc] #(#attrs)* #(#cfgs)* #[allow(non_snake_case)] - fn #name(#context: #name::Context #(,#inputs)*) { + #async_marker fn #name(#context: #name::Context #context_lifetime #(,#inputs)*) { use rtic::Mutex as _; use rtic::mutex::prelude::*; @@ -151,7 +172,6 @@ pub fn codegen( local_needs_lt, app, analysis, - extra, )); } diff --git a/macros/src/codegen/timer_queue.rs b/macros/src/codegen/timer_queue.rs index f5867dc..281148d 100644 --- a/macros/src/codegen/timer_queue.rs +++ b/macros/src/codegen/timer_queue.rs @@ -1,18 +1,18 @@ +use crate::syntax::ast::App; +use crate::{analyze::Analysis, codegen::util}; use proc_macro2::TokenStream as TokenStream2; use quote::quote; -use rtic_syntax::ast::App; - -use crate::{analyze::Analysis, check::Extra, codegen::util}; /// Generates timer queues and timer queue handlers #[allow(clippy::too_many_lines)] -pub fn codegen(app: &App, analysis: &Analysis, _extra: &Extra) -> Vec { +pub fn codegen(app: &App, analysis: &Analysis) -> Vec { let mut items = vec![]; if !app.monotonics.is_empty() { // Generate the marker counter used to track for `cancel` and `reschedule` let tq_marker = util::timer_queue_marker_ident(); items.push(quote!( + // #[doc = #doc] #[doc(hidden)] #[allow(non_camel_case_types)] #[allow(non_upper_case_globals)] @@ -26,6 +26,7 @@ pub fn codegen(app: &App, analysis: &Analysis, _extra: &Extra) -> Vec Vec Vec); + let n_task = util::capacity_literal(cap); + let tq_ty = quote!(rtic::export::TimerQueue<#mono_type, #t, #n_task>); // For future use // let doc = format!(" RTIC internal: {}:{}", file!(), line!()); @@ -76,9 +76,12 @@ pub fn codegen(app: &App, analysis: &Analysis, _extra: &Extra) -> Vec = - rtic::RacyCell::new(rtic::export::TimerQueue(rtic::export::SortedLinkedList::new_u16())); + static #tq: rtic::RacyCell<#tq_ty> = rtic::RacyCell::new( + rtic::export::TimerQueue { + task_queue: rtic::export::SortedLinkedList::new_u16(), + waker_queue: rtic::export::IntrusiveSortedLinkedList::new(), + } + ); )); let mono = util::monotonic_ident(&monotonic_name); @@ -89,7 +92,6 @@ pub fn codegen(app: &App, analysis: &Analysis, _extra: &Extra) -> Vec> = rtic::RacyCell::new(None); )); } @@ -102,6 +104,7 @@ pub fn codegen(app: &App, analysis: &Analysis, _extra: &Extra) -> Vec Vec Vec { - rtic::export::interrupt::free(|_| (&mut *#rq.get_mut()).split().0.enqueue_unchecked((#rqt::#name, index))); + rtic::export::interrupt::free(|_| + (&mut *#rq.get_mut()).split().0.enqueue_unchecked((#rqt::#name, index)) + ); #pend } @@ -128,7 +133,6 @@ pub fn codegen(app: &App, analysis: &Analysis, _extra: &Extra) -> Vec>(); - let cfgs = &monotonic.cfgs; let bound_interrupt = &monotonic.args.binds; let disable_isr = if &*bound_interrupt.to_string() == "SysTick" { quote!(core::mem::transmute::<_, rtic::export::SYST>(()).disable_interrupt()) @@ -139,7 +143,6 @@ pub fn codegen(app: &App, analysis: &Analysis, _extra: &Extra) -> Vec Ident { /// Generates a `Mutex` implementation pub fn impl_mutex( - extra: &Extra, + app: &App, cfgs: &[Attribute], resources_prefix: bool, name: &Ident, @@ -35,7 +33,7 @@ pub fn impl_mutex( (quote!(#name), quote!(self.priority)) }; - let device = &extra.device; + let device = &app.args.device; let masks_name = priority_masks_ident(); quote!( #(#cfgs)* @@ -67,6 +65,11 @@ pub fn inputs_ident(task: &Ident) -> Ident { mark_internal_name(&format!("{}_INPUTS", task)) } +/// Generates an identifier for the `EXECUTOR_RUN` atomics (`async` API) +pub fn executor_run_ident(task: &Ident) -> Ident { + mark_internal_name(&format!("{}_EXECUTOR_RUN", task)) +} + /// Generates an identifier for the `INSTANTS` buffer (`schedule` API) pub fn monotonic_instants_ident(task: &Ident, monotonic: &Ident) -> Ident { mark_internal_name(&format!("{}_{}_INSTANTS", task, monotonic)) @@ -179,7 +182,12 @@ pub fn regroup_inputs( pub fn get_task_name(ctxt: Context, app: &App) -> Ident { let s = match ctxt { Context::Init => app.init.name.to_string(), - Context::Idle => app.idle.as_ref().unwrap().name.to_string(), + Context::Idle => app + .idle + .as_ref() + .expect("RTIC-ICE: unable to find idle name") + .name + .to_string(), Context::HardwareTask(ident) | Context::SoftwareTask(ident) => ident.to_string(), }; @@ -190,7 +198,12 @@ pub fn get_task_name(ctxt: Context, app: &App) -> Ident { pub fn shared_resources_ident(ctxt: Context, app: &App) -> Ident { let mut s = match ctxt { Context::Init => app.init.name.to_string(), - Context::Idle => app.idle.as_ref().unwrap().name.to_string(), + Context::Idle => app + .idle + .as_ref() + .expect("RTIC-ICE: unable to find idle name") + .name + .to_string(), Context::HardwareTask(ident) | Context::SoftwareTask(ident) => ident.to_string(), }; @@ -203,7 +216,12 @@ pub fn shared_resources_ident(ctxt: Context, app: &App) -> Ident { pub fn local_resources_ident(ctxt: Context, app: &App) -> Ident { let mut s = match ctxt { Context::Init => app.init.name.to_string(), - Context::Idle => app.idle.as_ref().unwrap().name.to_string(), + Context::Idle => app + .idle + .as_ref() + .expect("RTIC-ICE: unable to find idle name") + .name + .to_string(), Context::HardwareTask(ident) | Context::SoftwareTask(ident) => ident.to_string(), }; @@ -220,9 +238,14 @@ pub fn rq_ident(priority: u8) -> Ident { mark_internal_name(&format!("P{}_RQ", priority)) } +/// Generates an identifier for a ready queue, async task version +pub fn rq_async_ident(async_task_name: &Ident) -> Ident { + mark_internal_name(&format!("ASYNC_TACK_{}_RQ", async_task_name)) +} + /// Generates an identifier for the `enum` of `schedule`-able tasks pub fn schedule_t_ident() -> Ident { - Ident::new("SCHED_T", Span::call_site()) + mark_internal_name("SCHED_T") } /// Generates an identifier for the `enum` of `spawn`-able tasks @@ -230,7 +253,7 @@ pub fn schedule_t_ident() -> Ident { /// This identifier needs the same structure as the `RQ` identifier because there's one ready queue /// for each of these `T` enums pub fn spawn_t_ident(priority: u8) -> Ident { - Ident::new(&format!("P{}_T", priority), Span::call_site()) + mark_internal_name(&format!("P{}_T", priority)) } /// Suffixed identifier diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 2b52601..7729dcb 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -1,21 +1,46 @@ #![doc( - html_logo_url = "https://raw.githubusercontent.com/rtic-rs/cortex-m-rtic/master/book/en/src/RTIC.svg", - html_favicon_url = "https://raw.githubusercontent.com/rtic-rs/cortex-m-rtic/master/book/en/src/RTIC.svg" + html_logo_url = "https://raw.githubusercontent.com/rtic-rs/rtic/master/book/en/src/RTIC.svg", + html_favicon_url = "https://raw.githubusercontent.com/rtic-rs/rtic/master/book/en/src/RTIC.svg" )] -//deny_warnings_placeholder_for_ci -extern crate proc_macro; +//deny_warnings_placeholder_for_ci use proc_macro::TokenStream; use std::{env, fs, path::Path}; -use rtic_syntax::Settings; - mod analyze; -mod check; +mod bindings; mod codegen; -#[cfg(test)] -mod tests; +mod syntax; + +// Used for mocking the API in testing +#[doc(hidden)] +#[proc_macro_attribute] +pub fn mock_app(args: TokenStream, input: TokenStream) -> TokenStream { + let mut settings = syntax::Settings::default(); + let mut rtic_args = vec![]; + for arg in args.to_string().split(',') { + if arg.trim() == "parse_binds" { + settings.parse_binds = true; + } else if arg.trim() == "parse_extern_interrupt" { + settings.parse_extern_interrupt = true; + } else { + rtic_args.push(arg.to_string()); + } + } + + // rtic_args.push("device = mock".into()); + + let args = rtic_args.join(", ").parse(); + + println!("args: {:?}", args); + + if let Err(e) = syntax::parse(args.unwrap(), input, settings) { + e.to_compile_error().into() + } else { + "fn main() {}".parse().unwrap() + } +} /// Attribute used to declare a RTIC application /// @@ -26,24 +51,19 @@ mod tests; /// Should never panic, cargo feeds a path which is later converted to a string #[proc_macro_attribute] pub fn app(args: TokenStream, input: TokenStream) -> TokenStream { - let mut settings = Settings::default(); + let mut settings = syntax::Settings::default(); settings.optimize_priorities = false; settings.parse_binds = true; settings.parse_extern_interrupt = true; - let (app, analysis) = match rtic_syntax::parse(args, input, settings) { - Err(e) => return e.to_compile_error().into(), - Ok(x) => x, - }; - - let extra = match check::app(&app, &analysis) { + let (app, analysis) = match syntax::parse(args, input, settings) { Err(e) => return e.to_compile_error().into(), Ok(x) => x, }; let analysis = analyze::app(analysis, &app); - let ts = codegen::app(&app, &analysis, &extra); + let ts = codegen::app(&app, &analysis); // Default output path: /target/ let mut out_dir = Path::new("target"); @@ -52,22 +72,7 @@ pub fn app(args: TokenStream, input: TokenStream) -> TokenStream { // TODO don't want to break builds if OUT_DIR is not set, is this ever the case? let out_str = env::var("OUT_DIR").unwrap_or_else(|_| "".to_string()); - // Assuming we are building for a thumbv* target - let target_triple_prefix = "thumbv"; - - // Check for special scenario where default target/ directory is not present - // - // This is configurable in .cargo/config: - // - // [build] - // target-dir = "target" - #[cfg(feature = "debugprint")] - println!("OUT_DIR\n{:#?}", out_str); - - if out_dir.exists() { - #[cfg(feature = "debugprint")] - println!("\ntarget/ exists\n"); - } else { + if !out_dir.exists() { // Set out_dir to OUT_DIR out_dir = Path::new(&out_str); @@ -81,16 +86,11 @@ pub fn app(args: TokenStream, input: TokenStream) -> TokenStream { // If no "target" directory is found, / is used for path in out_dir.ancestors() { if let Some(dir) = path.components().last() { - if dir - .as_os_str() - .to_str() - .unwrap() - .starts_with(target_triple_prefix) - { + let dir = dir.as_os_str().to_str().unwrap(); + + if dir.starts_with("thumbv") || dir.starts_with("riscv") { if let Some(out) = path.parent() { out_dir = out; - #[cfg(feature = "debugprint")] - println!("{:#?}\n", out_dir); break; } // If no parent, just use it @@ -103,8 +103,6 @@ pub fn app(args: TokenStream, input: TokenStream) -> TokenStream { // Try to write the expanded code to disk if let Some(out_str) = out_dir.to_str() { - #[cfg(feature = "debugprint")] - println!("Write file:\n{}/rtic-expansion.rs\n", out_str); fs::write(format!("{}/rtic-expansion.rs", out_str), ts.to_string()).ok(); } diff --git a/macros/src/syntax.rs b/macros/src/syntax.rs new file mode 100644 index 0000000..11b92c1 --- /dev/null +++ b/macros/src/syntax.rs @@ -0,0 +1,158 @@ +#[allow(unused_extern_crates)] +extern crate proc_macro; + +use core::ops; +use proc_macro::TokenStream; + +use indexmap::{IndexMap, IndexSet}; +use proc_macro2::TokenStream as TokenStream2; +use syn::Ident; + +use crate::syntax::ast::App; + +mod accessors; +pub mod analyze; +pub mod ast; +mod check; +mod optimize; +mod parse; + +/// An ordered map keyed by identifier +pub type Map = IndexMap; + +/// An order set +pub type Set = IndexSet; + +/// Immutable pointer +pub struct P { + ptr: Box, +} + +impl P { + /// Boxes `x` making the value immutable + pub fn new(x: T) -> P { + P { ptr: Box::new(x) } + } +} + +impl ops::Deref for P { + type Target = T; + + fn deref(&self) -> &T { + &self.ptr + } +} + +/// Execution context +#[derive(Clone, Copy)] +pub enum Context<'a> { + /// The `idle` context + Idle, + + /// The `init`-ialization function + Init, + + /// A software task: `#[task]` + SoftwareTask(&'a Ident), + + /// A hardware task: `#[exception]` or `#[interrupt]` + HardwareTask(&'a Ident), +} + +impl<'a> Context<'a> { + /// The identifier of this context + pub fn ident(&self, app: &'a App) -> &'a Ident { + match self { + Context::HardwareTask(ident) => ident, + Context::Idle => &app.idle.as_ref().unwrap().name, + Context::Init => &app.init.name, + Context::SoftwareTask(ident) => ident, + } + } + + /// Is this the `idle` context? + pub fn is_idle(&self) -> bool { + matches!(self, Context::Idle) + } + + /// Is this the `init`-ialization context? + pub fn is_init(&self) -> bool { + matches!(self, Context::Init) + } + + /// Whether this context runs only once + pub fn runs_once(&self) -> bool { + self.is_init() || self.is_idle() + } + + /// Whether this context has shared resources + pub fn has_shared_resources(&self, app: &App) -> bool { + match *self { + Context::HardwareTask(name) => { + !app.hardware_tasks[name].args.shared_resources.is_empty() + } + Context::Idle => !app.idle.as_ref().unwrap().args.shared_resources.is_empty(), + Context::Init => false, + Context::SoftwareTask(name) => { + !app.software_tasks[name].args.shared_resources.is_empty() + } + } + } + + /// Whether this context has local resources + pub fn has_local_resources(&self, app: &App) -> bool { + match *self { + Context::HardwareTask(name) => { + !app.hardware_tasks[name].args.local_resources.is_empty() + } + Context::Idle => !app.idle.as_ref().unwrap().args.local_resources.is_empty(), + Context::Init => !app.init.args.local_resources.is_empty(), + Context::SoftwareTask(name) => { + !app.software_tasks[name].args.local_resources.is_empty() + } + } + } +} + +/// Parser and optimizer configuration +#[derive(Default)] +#[non_exhaustive] +pub struct Settings { + /// Whether to accept the `binds` argument in `#[task]` or not + pub parse_binds: bool, + /// Whether to parse `extern` interrupts (functions) or not + pub parse_extern_interrupt: bool, + /// Whether to "compress" priorities or not + pub optimize_priorities: bool, +} + +/// Parses the input of the `#[app]` attribute +pub fn parse( + args: TokenStream, + input: TokenStream, + settings: Settings, +) -> Result<(ast::App, analyze::Analysis), syn::parse::Error> { + parse2(args.into(), input.into(), settings) +} + +/// `proc_macro2::TokenStream` version of `parse` +pub fn parse2( + args: TokenStream2, + input: TokenStream2, + settings: Settings, +) -> Result<(ast::App, analyze::Analysis), syn::parse::Error> { + let mut app = parse::app(args, input, &settings)?; + check::app(&app)?; + optimize::app(&mut app, &settings); + + match analyze::app(&app) { + Err(e) => Err(e), + // If no errors, return the app and analysis results + Ok(analysis) => Ok((app, analysis)), + } +} + +enum Either { + Left(A), + Right(B), +} diff --git a/macros/src/syntax/.github/bors.toml b/macros/src/syntax/.github/bors.toml new file mode 100644 index 0000000..aee6042 --- /dev/null +++ b/macros/src/syntax/.github/bors.toml @@ -0,0 +1,3 @@ +block_labels = ["S-blocked"] +delete_merged_branches = true +status = ["ci"] diff --git a/macros/src/syntax/.github/workflows/build.yml b/macros/src/syntax/.github/workflows/build.yml new file mode 100644 index 0000000..29971b1 --- /dev/null +++ b/macros/src/syntax/.github/workflows/build.yml @@ -0,0 +1,213 @@ +name: Build +on: + pull_request: + push: + branches: + - master + - staging + - trying + - bors/staging + - bors/trying + +env: + CARGO_TERM_COLOR: always + +jobs: + # Run cargo fmt --check + style: + name: style + runs-on: ubuntu-20.04 + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + components: rustfmt + + - name: Fail on warnings + run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs + + - name: cargo fmt --check + uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check + + # Compilation check + check: + name: check + runs-on: ubuntu-20.04 + strategy: + matrix: + target: + - x86_64-unknown-linux-gnu + toolchain: + - stable + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Install Rust ${{ matrix.toolchain }} with target (${{ matrix.target }}) + uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.toolchain }} + target: ${{ matrix.target }} + override: true + + - name: Fail on warnings + run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs + + - name: Cache Dependencies + uses: Swatinem/rust-cache@v1 + + - name: cargo check + uses: actions-rs/cargo@v1 + with: + use-cross: false + command: check + args: --target=${{ matrix.target }} + + # Clippy + clippy: + name: Cargo clippy + runs-on: ubuntu-20.04 + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Install Rust stable + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + target: x86_64-unknown-linux-gnu + override: true + + - name: Fail on warnings + run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs + + - name: Cache Dependencies + uses: Swatinem/rust-cache@v1 + + - name: cargo clippy + uses: actions-rs/cargo@v1 + with: + use-cross: false + command: clippy + + # Verify all examples + testexamples: + name: testexamples + runs-on: ubuntu-20.04 + strategy: + matrix: + target: + - x86_64-unknown-linux-gnu + toolchain: + - stable + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Install Rust ${{ matrix.toolchain }} with target (${{ matrix.target }}) + uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.toolchain }} + target: ${{ matrix.target }} + override: true + + - name: Cache Dependencies + uses: Swatinem/rust-cache@v1 + + - name: Fail on warnings + run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs + + - uses: actions-rs/cargo@v1 + with: + use-cross: false + command: test + args: --examples + + # Run test suite for UI + testui: + name: testui + runs-on: ubuntu-20.04 + strategy: + matrix: + target: + - x86_64-unknown-linux-gnu + toolchain: + - stable + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Install Rust ${{ matrix.toolchain }} with target (${{ matrix.target }}) + uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.toolchain }} + target: ${{ matrix.target }} + override: true + + - name: Cache Dependencies + uses: Swatinem/rust-cache@v1 + + - name: Fail on warnings + run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs + + + - uses: actions-rs/cargo@v1 + with: + use-cross: false + command: test + args: --test ui + + # Run test suite + test: + name: test + runs-on: ubuntu-20.04 + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + target: thumbv7m-none-eabi + override: true + + - name: Cache Dependencies + uses: Swatinem/rust-cache@v1 + + - name: Fail on warnings + run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs + + - uses: actions-rs/cargo@v1 + with: + use-cross: false + command: test + args: --lib + + # Refs: https://github.com/rust-lang/crater/blob/9ab6f9697c901c4a44025cf0a39b73ad5b37d198/.github/workflows/bors.yml#L125-L149 + # + # ALL THE PREVIOUS JOBS NEEDS TO BE ADDED TO THE `needs` SECTION OF THIS JOB! + + ci-success: + name: ci + if: github.event_name == 'push' && success() + needs: + - style + - check + - clippy + - testexamples + - test + - testui + runs-on: ubuntu-20.04 + steps: + - name: Mark the job as a success + run: exit 0 diff --git a/macros/src/syntax/.github/workflows/changelog.yml b/macros/src/syntax/.github/workflows/changelog.yml new file mode 100644 index 0000000..ccf6eb9 --- /dev/null +++ b/macros/src/syntax/.github/workflows/changelog.yml @@ -0,0 +1,28 @@ +# Check that the changelog is updated for all changes. +# +# This is only run for PRs. + +on: + pull_request: + # opened, reopened, synchronize are the default types for pull_request. + # labeled, unlabeled ensure this check is also run if a label is added or removed. + types: [opened, reopened, labeled, unlabeled, synchronize] + +name: Changelog + +jobs: + changelog: + name: Changelog + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v2 + + - name: Check that changelog updated + uses: dangoslen/changelog-enforcer@v3 + with: + changeLogPath: CHANGELOG.md + skipLabels: 'needs-changelog, skip-changelog' + missingUpdateErrorMessage: 'Please add a changelog entry in the CHANGELOG.md file.' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/macros/src/syntax/.github/workflows/properties/build.properties.json b/macros/src/syntax/.github/workflows/properties/build.properties.json new file mode 100644 index 0000000..fd3eed3 --- /dev/null +++ b/macros/src/syntax/.github/workflows/properties/build.properties.json @@ -0,0 +1,6 @@ +{ + "name": "Build", + "description": "RTIC Test Suite", + "iconName": "rust", + "categories": ["Rust"] +} diff --git a/macros/src/syntax/.gitignore b/macros/src/syntax/.gitignore new file mode 100644 index 0000000..f8d7c8b --- /dev/null +++ b/macros/src/syntax/.gitignore @@ -0,0 +1,4 @@ +**/*.rs.bk +.#* +/target/ +Cargo.lock diff --git a/macros/src/syntax/.travis.yml b/macros/src/syntax/.travis.yml new file mode 100644 index 0000000..52d1ffd --- /dev/null +++ b/macros/src/syntax/.travis.yml @@ -0,0 +1,31 @@ +language: rust + +matrix: + include: + # MSRV + - env: TARGET=x86_64-unknown-linux-gnu + rust: 1.36.0 + + - env: TARGET=x86_64-unknown-linux-gnu + rust: stable + +before_install: set -e + +script: + - bash ci/script.sh + +after_script: set +e + +cache: cargo + +before_cache: + - chmod -R a+r $HOME/.cargo; + +branches: + only: + - staging + - trying + +notifications: + email: + on_success: never diff --git a/macros/src/syntax/accessors.rs b/macros/src/syntax/accessors.rs new file mode 100644 index 0000000..e75dde6 --- /dev/null +++ b/macros/src/syntax/accessors.rs @@ -0,0 +1,113 @@ +use syn::Ident; + +use crate::syntax::{ + analyze::Priority, + ast::{Access, App, Local, TaskLocal}, +}; + +impl App { + pub(crate) fn shared_resource_accesses( + &self, + ) -> impl Iterator, &Ident, Access)> { + self.idle + .iter() + .flat_map(|idle| { + idle.args + .shared_resources + .iter() + .map(move |(name, access)| (Some(0), name, *access)) + }) + .chain(self.hardware_tasks.values().flat_map(|task| { + task.args + .shared_resources + .iter() + .map(move |(name, access)| (Some(task.args.priority), name, *access)) + })) + .chain(self.software_tasks.values().flat_map(|task| { + task.args + .shared_resources + .iter() + .map(move |(name, access)| (Some(task.args.priority), name, *access)) + })) + } + + fn is_external(task_local: &TaskLocal) -> bool { + matches!(task_local, TaskLocal::External) + } + + pub(crate) fn local_resource_accesses(&self) -> impl Iterator { + self.init + .args + .local_resources + .iter() + .filter(|(_, task_local)| Self::is_external(task_local)) // Only check the resources declared in `#[local]` + .map(move |(name, _)| name) + .chain(self.idle.iter().flat_map(|idle| { + idle.args + .local_resources + .iter() + .filter(|(_, task_local)| Self::is_external(task_local)) // Only check the resources declared in `#[local]` + .map(move |(name, _)| name) + })) + .chain(self.hardware_tasks.values().flat_map(|task| { + task.args + .local_resources + .iter() + .filter(|(_, task_local)| Self::is_external(task_local)) // Only check the resources declared in `#[local]` + .map(move |(name, _)| name) + })) + .chain(self.software_tasks.values().flat_map(|task| { + task.args + .local_resources + .iter() + .filter(|(_, task_local)| Self::is_external(task_local)) // Only check the resources declared in `#[local]` + .map(move |(name, _)| name) + })) + } + + fn get_declared_local(tl: &TaskLocal) -> Option<&Local> { + match tl { + TaskLocal::External => None, + TaskLocal::Declared(l) => Some(l), + } + } + + /// Get all declared local resources, i.e. `local = [NAME: TYPE = EXPR]`. + /// + /// Returns a vector of (task name, resource name, `Local` struct) + pub fn declared_local_resources(&self) -> Vec<(&Ident, &Ident, &Local)> { + self.init + .args + .local_resources + .iter() + .filter_map(move |(name, tl)| { + Self::get_declared_local(tl).map(|l| (&self.init.name, name, l)) + }) + .chain(self.idle.iter().flat_map(|idle| { + idle.args + .local_resources + .iter() + .filter_map(move |(name, tl)| { + Self::get_declared_local(tl) + .map(|l| (&self.idle.as_ref().unwrap().name, name, l)) + }) + })) + .chain(self.hardware_tasks.iter().flat_map(|(task_name, task)| { + task.args + .local_resources + .iter() + .filter_map(move |(name, tl)| { + Self::get_declared_local(tl).map(|l| (task_name, name, l)) + }) + })) + .chain(self.software_tasks.iter().flat_map(|(task_name, task)| { + task.args + .local_resources + .iter() + .filter_map(move |(name, tl)| { + Self::get_declared_local(tl).map(|l| (task_name, name, l)) + }) + })) + .collect() + } +} diff --git a/macros/src/syntax/analyze.rs b/macros/src/syntax/analyze.rs new file mode 100644 index 0000000..06b23f4 --- /dev/null +++ b/macros/src/syntax/analyze.rs @@ -0,0 +1,448 @@ +//! RTIC application analysis + +use core::cmp; +use std::collections::{BTreeMap, BTreeSet, HashMap}; + +use indexmap::{IndexMap, IndexSet}; +use syn::{Ident, Type}; + +use crate::syntax::{ + ast::{App, LocalResources, TaskLocal}, + Set, +}; + +pub(crate) fn app(app: &App) -> Result { + // Collect all tasks into a vector + type TaskName = String; + type Priority = u8; + + // The task list is a Tuple (Name, Shared Resources, Local Resources, Priority, IsAsync) + let task_resources_list: Vec<(TaskName, Vec<&Ident>, &LocalResources, Priority, bool)> = + Some(&app.init) + .iter() + .map(|ht| { + ( + "init".to_string(), + Vec::new(), + &ht.args.local_resources, + 0, + false, + ) + }) + .chain(app.idle.iter().map(|ht| { + ( + "idle".to_string(), + ht.args + .shared_resources + .iter() + .map(|(v, _)| v) + .collect::>(), + &ht.args.local_resources, + 0, + false, + ) + })) + .chain(app.software_tasks.iter().map(|(name, ht)| { + ( + name.to_string(), + ht.args + .shared_resources + .iter() + .map(|(v, _)| v) + .collect::>(), + &ht.args.local_resources, + ht.args.priority, + ht.is_async, + ) + })) + .chain(app.hardware_tasks.iter().map(|(name, ht)| { + ( + name.to_string(), + ht.args + .shared_resources + .iter() + .map(|(v, _)| v) + .collect::>(), + &ht.args.local_resources, + ht.args.priority, + false, + ) + })) + .collect(); + + let mut error = vec![]; + let mut lf_res_with_error = vec![]; + let mut lf_hash = HashMap::new(); + + // Collect lock free resources + let lock_free: Vec<&Ident> = app + .shared_resources + .iter() + .filter(|(_, r)| r.properties.lock_free) + .map(|(i, _)| i) + .collect(); + + // Check that lock_free resources are correct + for lf_res in lock_free.iter() { + for (task, tr, _, priority, is_async) in task_resources_list.iter() { + for r in tr { + // Get all uses of resources annotated lock_free + if lf_res == r { + // lock_free resources are not allowed in async tasks + if *is_async { + error.push(syn::Error::new( + r.span(), + format!( + "Lock free shared resource {:?} is used by an async tasks, which is forbidden", + r.to_string(), + ), + )); + } + + // HashMap returns the previous existing object if old.key == new.key + if let Some(lf_res) = lf_hash.insert(r.to_string(), (task, r, priority)) { + // Check if priority differ, if it does, append to + // list of resources which will be annotated with errors + if priority != lf_res.2 { + lf_res_with_error.push(lf_res.1); + lf_res_with_error.push(r); + } + // If the resource already violates lock free properties + if lf_res_with_error.contains(&r) { + lf_res_with_error.push(lf_res.1); + lf_res_with_error.push(r); + } + } + } + } + } + } + + // Add error message in the resource struct + for r in lock_free { + if lf_res_with_error.contains(&&r) { + error.push(syn::Error::new( + r.span(), + format!( + "Lock free shared resource {:?} is used by tasks at different priorities", + r.to_string(), + ), + )); + } + } + + // Add error message for each use of the shared resource + for resource in lf_res_with_error.clone() { + error.push(syn::Error::new( + resource.span(), + format!( + "Shared resource {:?} is declared lock free but used by tasks at different priorities", + resource.to_string(), + ), + )); + } + + // Collect local resources + let local: Vec<&Ident> = app.local_resources.iter().map(|(i, _)| i).collect(); + + let mut lr_with_error = vec![]; + let mut lr_hash = HashMap::new(); + + // Check that local resources are not shared + for lr in local { + for (task, _, local_resources, _, _) in task_resources_list.iter() { + for (name, res) in local_resources.iter() { + // Get all uses of resources annotated lock_free + if lr == name { + match res { + TaskLocal::External => { + // HashMap returns the previous existing object if old.key == new.key + if let Some(lr) = lr_hash.insert(name.to_string(), (task, name)) { + lr_with_error.push(lr.1); + lr_with_error.push(name); + } + } + // If a declared local has the same name as the `#[local]` struct, it's an + // direct error + TaskLocal::Declared(_) => { + lr_with_error.push(lr); + lr_with_error.push(name); + } + } + } + } + } + } + + // Add error message for each use of the local resource + for resource in lr_with_error.clone() { + error.push(syn::Error::new( + resource.span(), + format!( + "Local resource {:?} is used by multiple tasks or collides with multiple definitions", + resource.to_string(), + ), + )); + } + + // Check 0-priority async software tasks and idle dependency + for (name, task) in &app.software_tasks { + if task.args.priority == 0 { + // If there is a 0-priority task, there must be no idle + if app.idle.is_some() { + error.push(syn::Error::new( + name.span(), + format!( + "Software task {:?} has priority 0, but `#[idle]` is defined. 0-priority software tasks are only allowed if there is no `#[idle]`.", + name.to_string(), + ) + )); + } + + // 0-priority tasks must be async + if !task.is_async { + error.push(syn::Error::new( + name.span(), + format!( + "Software task {:?} has priority 0, but is not `async`. 0-priority software tasks must be `async`.", + name.to_string(), + ) + )); + } + } + } + + // Collect errors if any and return/halt + if !error.is_empty() { + let mut err = error.get(0).unwrap().clone(); + error.iter().for_each(|e| err.combine(e.clone())); + return Err(err); + } + + // e. Location of resources + let mut used_shared_resource = IndexSet::new(); + let mut ownerships = Ownerships::new(); + let mut sync_types = SyncTypes::new(); + for (prio, name, access) in app.shared_resource_accesses() { + let res = app.shared_resources.get(name).expect("UNREACHABLE"); + + // (e) + // This shared resource is used + used_shared_resource.insert(name.clone()); + + // (c) + if let Some(priority) = prio { + if let Some(ownership) = ownerships.get_mut(name) { + match *ownership { + Ownership::Owned { priority: ceiling } + | Ownership::CoOwned { priority: ceiling } + | Ownership::Contended { ceiling } + if priority != ceiling => + { + *ownership = Ownership::Contended { + ceiling: cmp::max(ceiling, priority), + }; + + if access.is_shared() { + sync_types.insert(res.ty.clone()); + } + } + + Ownership::Owned { priority: ceil } if ceil == priority => { + *ownership = Ownership::CoOwned { priority }; + } + + _ => {} + } + } else { + ownerships.insert(name.clone(), Ownership::Owned { priority }); + } + } + } + + // Create the list of used local resource Idents + let mut used_local_resource = IndexSet::new(); + + for (_, _, locals, _, _) in task_resources_list { + for (local, _) in locals { + used_local_resource.insert(local.clone()); + } + } + + // Most shared resources need to be `Send` + let mut send_types = SendTypes::new(); + let owned_by_idle = Ownership::Owned { priority: 0 }; + for (name, res) in app.shared_resources.iter() { + // Handle not owned by idle + if ownerships + .get(name) + .map(|ownership| *ownership != owned_by_idle) + .unwrap_or(false) + { + send_types.insert(res.ty.clone()); + } + } + + // Most local resources need to be `Send` as well + for (name, res) in app.local_resources.iter() { + if let Some(idle) = &app.idle { + // Only Send if not in idle or not at idle prio + if idle.args.local_resources.get(name).is_none() + && !ownerships + .get(name) + .map(|ownership| *ownership != owned_by_idle) + .unwrap_or(false) + { + send_types.insert(res.ty.clone()); + } + } else { + send_types.insert(res.ty.clone()); + } + } + + let mut channels = Channels::new(); + + for (name, spawnee) in &app.software_tasks { + let spawnee_prio = spawnee.args.priority; + + let channel = channels.entry(spawnee_prio).or_default(); + channel.tasks.insert(name.clone()); + + if !spawnee.args.only_same_priority_spawn { + // Require `Send` if the task can be spawned from other priorities + spawnee.inputs.iter().for_each(|input| { + send_types.insert(input.ty.clone()); + }); + } + } + + // No channel should ever be empty + debug_assert!(channels.values().all(|channel| !channel.tasks.is_empty())); + + // Compute channel capacities + for channel in channels.values_mut() { + channel.capacity = channel + .tasks + .iter() + .map(|name| app.software_tasks[name].args.capacity) + .sum(); + } + + Ok(Analysis { + channels, + shared_resources: used_shared_resource, + local_resources: used_local_resource, + ownerships, + send_types, + sync_types, + }) +} + +/// Priority ceiling +pub type Ceiling = Option; + +/// Task priority +pub type Priority = u8; + +/// Resource name +pub type Resource = Ident; + +/// Task name +pub type Task = Ident; + +/// The result of analyzing an RTIC application +pub struct Analysis { + /// SPSC message channels + pub channels: Channels, + + /// Shared resources + /// + /// If a resource is not listed here it means that's a "dead" (never + /// accessed) resource and the backend should not generate code for it + pub shared_resources: UsedSharedResource, + + /// Local resources + /// + /// If a resource is not listed here it means that's a "dead" (never + /// accessed) resource and the backend should not generate code for it + pub local_resources: UsedLocalResource, + + /// Resource ownership + pub ownerships: Ownerships, + + /// These types must implement the `Send` trait + pub send_types: SendTypes, + + /// These types must implement the `Sync` trait + pub sync_types: SyncTypes, +} + +/// All channels, keyed by dispatch priority +pub type Channels = BTreeMap; + +/// Location of all *used* shared resources +pub type UsedSharedResource = IndexSet; + +/// Location of all *used* local resources +pub type UsedLocalResource = IndexSet; + +/// Resource ownership +pub type Ownerships = IndexMap; + +/// These types must implement the `Send` trait +pub type SendTypes = Set>; + +/// These types must implement the `Sync` trait +pub type SyncTypes = Set>; + +/// A channel used to send messages +#[derive(Debug, Default)] +pub struct Channel { + /// The channel capacity + pub capacity: u8, + + /// Tasks that can be spawned on this channel + pub tasks: BTreeSet, +} + +/// Resource ownership +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum Ownership { + /// Owned by a single task + Owned { + /// Priority of the task that owns this resource + priority: u8, + }, + + /// "Co-owned" by more than one task; all of them have the same priority + CoOwned { + /// Priority of the tasks that co-own this resource + priority: u8, + }, + + /// Contended by more than one task; the tasks have different priorities + Contended { + /// Priority ceiling + ceiling: u8, + }, +} + +impl Ownership { + /// Whether this resource needs to a lock at this priority level + pub fn needs_lock(&self, priority: u8) -> bool { + match self { + Ownership::Owned { .. } | Ownership::CoOwned { .. } => false, + + Ownership::Contended { ceiling } => { + debug_assert!(*ceiling >= priority); + + priority < *ceiling + } + } + } + + /// Whether this resource is exclusively owned + pub fn is_owned(&self) -> bool { + matches!(self, Ownership::Owned { .. }) + } +} diff --git a/macros/src/syntax/ast.rs b/macros/src/syntax/ast.rs new file mode 100644 index 0000000..0f2e36f --- /dev/null +++ b/macros/src/syntax/ast.rs @@ -0,0 +1,380 @@ +//! Abstract Syntax Tree + +use syn::{Attribute, Expr, Ident, Item, ItemUse, Pat, PatType, Path, Stmt, Type}; + +use crate::syntax::Map; + +/// The `#[app]` attribute +#[derive(Debug)] +#[non_exhaustive] +pub struct App { + /// The arguments to the `#[app]` attribute + pub args: AppArgs, + + /// The name of the `const` item on which the `#[app]` attribute has been placed + pub name: Ident, + + /// The `#[init]` function + pub init: Init, + + /// The `#[idle]` function + pub idle: Option, + + /// Monotonic clocks + pub monotonics: Map, + + /// Resources shared between tasks defined in `#[shared]` + pub shared_resources: Map, + + /// Task local resources defined in `#[local]` + pub local_resources: Map, + + /// User imports + pub user_imports: Vec, + + /// User code + pub user_code: Vec, + + /// Hardware tasks: `#[task(binds = ..)]`s + pub hardware_tasks: Map, + + /// Software tasks: `#[task]` + pub software_tasks: Map, +} + +/// Interrupts used to dispatch software tasks +pub type Dispatchers = Map; + +/// Interrupt that could be used to dispatch software tasks +#[derive(Debug, Clone)] +#[non_exhaustive] +pub struct Dispatcher { + /// Attributes that will apply to this interrupt handler + pub attrs: Vec, +} + +/// The arguments of the `#[app]` attribute +#[derive(Debug)] +pub struct AppArgs { + /// Device + pub device: Path, + + /// Peripherals + pub peripherals: bool, + + /// Interrupts used to dispatch software tasks + pub dispatchers: Dispatchers, +} + +/// The `init`-ialization function +#[derive(Debug)] +#[non_exhaustive] +pub struct Init { + /// `init` context metadata + pub args: InitArgs, + + /// Attributes that will apply to this `init` function + pub attrs: Vec, + + /// The name of the `#[init]` function + pub name: Ident, + + /// The context argument + pub context: Box, + + /// The statements that make up this `init` function + pub stmts: Vec, + + /// The name of the user provided shared resources struct + pub user_shared_struct: Ident, + + /// The name of the user provided local resources struct + pub user_local_struct: Ident, +} + +/// `init` context metadata +#[derive(Debug)] +#[non_exhaustive] +pub struct InitArgs { + /// Local resources that can be accessed from this context + pub local_resources: LocalResources, +} + +impl Default for InitArgs { + fn default() -> Self { + Self { + local_resources: LocalResources::new(), + } + } +} + +/// The `idle` context +#[derive(Debug)] +#[non_exhaustive] +pub struct Idle { + /// `idle` context metadata + pub args: IdleArgs, + + /// Attributes that will apply to this `idle` function + pub attrs: Vec, + + /// The name of the `#[idle]` function + pub name: Ident, + + /// The context argument + pub context: Box, + + /// The statements that make up this `idle` function + pub stmts: Vec, +} + +/// `idle` context metadata +#[derive(Debug)] +#[non_exhaustive] +pub struct IdleArgs { + /// Local resources that can be accessed from this context + pub local_resources: LocalResources, + + /// Shared resources that can be accessed from this context + pub shared_resources: SharedResources, +} + +impl Default for IdleArgs { + fn default() -> Self { + Self { + local_resources: LocalResources::new(), + shared_resources: SharedResources::new(), + } + } +} + +/// Shared resource properties +#[derive(Debug)] +pub struct SharedResourceProperties { + /// A lock free (exclusive resource) + pub lock_free: bool, +} + +/// A shared resource, defined in `#[shared]` +#[derive(Debug)] +#[non_exhaustive] +pub struct SharedResource { + /// `#[cfg]` attributes like `#[cfg(debug_assertions)]` + pub cfgs: Vec, + + /// `#[doc]` attributes like `/// this is a docstring` + pub docs: Vec, + + /// Attributes that will apply to this resource + pub attrs: Vec, + + /// The type of this resource + pub ty: Box, + + /// Shared resource properties + pub properties: SharedResourceProperties, +} + +/// A local resource, defined in `#[local]` +#[derive(Debug)] +#[non_exhaustive] +pub struct LocalResource { + /// `#[cfg]` attributes like `#[cfg(debug_assertions)]` + pub cfgs: Vec, + + /// `#[doc]` attributes like `/// this is a docstring` + pub docs: Vec, + + /// Attributes that will apply to this resource + pub attrs: Vec, + + /// The type of this resource + pub ty: Box, +} + +/// Monotonic +#[derive(Debug)] +#[non_exhaustive] +pub struct Monotonic { + /// `#[cfg]` attributes like `#[cfg(debug_assertions)]` + pub cfgs: Vec, + + /// The identifier of the monotonic + pub ident: Ident, + + /// The type of this monotonic + pub ty: Box, + + /// Monotonic args + pub args: MonotonicArgs, +} + +/// Monotonic metadata +#[derive(Debug)] +#[non_exhaustive] +pub struct MonotonicArgs { + /// The interrupt or exception that this monotonic is bound to + pub binds: Ident, + + /// The priority of this monotonic + pub priority: Option, + + /// If this is the default monotonic + pub default: bool, +} + +/// A software task +#[derive(Debug)] +#[non_exhaustive] +pub struct SoftwareTask { + /// Software task metadata + pub args: SoftwareTaskArgs, + + /// `#[cfg]` attributes like `#[cfg(debug_assertions)]` + pub cfgs: Vec, + + /// Attributes that will apply to this interrupt handler + pub attrs: Vec, + + /// The context argument + pub context: Box, + + /// The inputs of this software task + pub inputs: Vec, + + /// The statements that make up the task handler + pub stmts: Vec, + + /// The task is declared externally + pub is_extern: bool, + + /// If the task is marked as `async` + pub is_async: bool, +} + +/// Software task metadata +#[derive(Debug)] +#[non_exhaustive] +pub struct SoftwareTaskArgs { + /// The task capacity: the maximum number of pending messages that can be queued + pub capacity: u8, + + /// The priority of this task + pub priority: u8, + + /// Local resources that can be accessed from this context + pub local_resources: LocalResources, + + /// Shared resources that can be accessed from this context + pub shared_resources: SharedResources, + + /// Only same priority tasks can spawn this task + pub only_same_priority_spawn: bool, +} + +impl Default for SoftwareTaskArgs { + fn default() -> Self { + Self { + capacity: 1, + priority: 1, + local_resources: LocalResources::new(), + shared_resources: SharedResources::new(), + only_same_priority_spawn: false, + } + } +} + +/// A hardware task +#[derive(Debug)] +#[non_exhaustive] +pub struct HardwareTask { + /// Hardware task metadata + pub args: HardwareTaskArgs, + + /// `#[cfg]` attributes like `#[cfg(debug_assertions)]` + pub cfgs: Vec, + + /// Attributes that will apply to this interrupt handler + pub attrs: Vec, + + /// The context argument + pub context: Box, + + /// The statements that make up the task handler + pub stmts: Vec, + + /// The task is declared externally + pub is_extern: bool, +} + +/// Hardware task metadata +#[derive(Debug)] +#[non_exhaustive] +pub struct HardwareTaskArgs { + /// The interrupt or exception that this task is bound to + pub binds: Ident, + + /// The priority of this task + pub priority: u8, + + /// Local resources that can be accessed from this context + pub local_resources: LocalResources, + + /// Shared resources that can be accessed from this context + pub shared_resources: SharedResources, +} + +/// A `static mut` variable local to and owned by a context +#[derive(Debug)] +#[non_exhaustive] +pub struct Local { + /// Attributes like `#[link_section]` + pub attrs: Vec, + + /// `#[cfg]` attributes like `#[cfg(debug_assertions)]` + pub cfgs: Vec, + + /// Type + pub ty: Box, + + /// Initial value + pub expr: Box, +} + +/// A wrapper of the 2 kinds of locals that tasks can have +#[derive(Debug)] +#[non_exhaustive] +pub enum TaskLocal { + /// The local is declared externally (i.e. `#[local]` struct) + External, + /// The local is declared in the task + Declared(Local), +} + +/// Resource access +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum Access { + /// `[x]`, a mutable resource + Exclusive, + + /// `[&x]`, a static non-mutable resource + Shared, +} + +impl Access { + /// Is this enum in the `Exclusive` variant? + pub fn is_exclusive(&self) -> bool { + *self == Access::Exclusive + } + + /// Is this enum in the `Shared` variant? + pub fn is_shared(&self) -> bool { + *self == Access::Shared + } +} + +/// Shared resource access list in task attribute +pub type SharedResources = Map; + +/// Local resource access/declaration list in task attribute +pub type LocalResources = Map; diff --git a/macros/src/syntax/check.rs b/macros/src/syntax/check.rs new file mode 100644 index 0000000..989d418 --- /dev/null +++ b/macros/src/syntax/check.rs @@ -0,0 +1,66 @@ +use std::collections::HashSet; + +use syn::parse; + +use crate::syntax::ast::App; + +pub fn app(app: &App) -> parse::Result<()> { + // Check that all referenced resources have been declared + // Check that resources are NOT `Exclusive`-ly shared + let mut owners = HashSet::new(); + for (_, name, access) in app.shared_resource_accesses() { + if app.shared_resources.get(name).is_none() { + return Err(parse::Error::new( + name.span(), + "this shared resource has NOT been declared", + )); + } + + if access.is_exclusive() { + owners.insert(name); + } + } + + for name in app.local_resource_accesses() { + if app.local_resources.get(name).is_none() { + return Err(parse::Error::new( + name.span(), + "this local resource has NOT been declared", + )); + } + } + + // Check that no resource has both types of access (`Exclusive` & `Shared`) + let exclusive_accesses = app + .shared_resource_accesses() + .filter_map(|(priority, name, access)| { + if priority.is_some() && access.is_exclusive() { + Some(name) + } else { + None + } + }) + .collect::>(); + for (_, name, access) in app.shared_resource_accesses() { + if access.is_shared() && exclusive_accesses.contains(name) { + return Err(parse::Error::new( + name.span(), + "this implementation doesn't support shared (`&-`) - exclusive (`&mut-`) locks; use `x` instead of `&x`", + )); + } + } + + // check that dispatchers are not used as hardware tasks + for task in app.hardware_tasks.values() { + let binds = &task.args.binds; + + if app.args.dispatchers.contains_key(binds) { + return Err(parse::Error::new( + binds.span(), + "dispatcher interrupts can't be used as hardware tasks", + )); + } + } + + Ok(()) +} diff --git a/macros/src/syntax/optimize.rs b/macros/src/syntax/optimize.rs new file mode 100644 index 0000000..87a6258 --- /dev/null +++ b/macros/src/syntax/optimize.rs @@ -0,0 +1,36 @@ +use std::collections::{BTreeSet, HashMap}; + +use crate::syntax::{ast::App, Settings}; + +pub fn app(app: &mut App, settings: &Settings) { + // "compress" priorities + // If the user specified, for example, task priorities of "1, 3, 6", + // compress them into "1, 2, 3" as to leave no gaps + if settings.optimize_priorities { + // all task priorities ordered in ascending order + let priorities = app + .hardware_tasks + .values() + .map(|task| Some(task.args.priority)) + .chain( + app.software_tasks + .values() + .map(|task| Some(task.args.priority)), + ) + .collect::>(); + + let map = priorities + .iter() + .cloned() + .zip(1..) + .collect::>(); + + for task in app.hardware_tasks.values_mut() { + task.args.priority = map[&Some(task.args.priority)]; + } + + for task in app.software_tasks.values_mut() { + task.args.priority = map[&Some(task.args.priority)]; + } + } +} diff --git a/macros/src/syntax/parse.rs b/macros/src/syntax/parse.rs new file mode 100644 index 0000000..74f94f2 --- /dev/null +++ b/macros/src/syntax/parse.rs @@ -0,0 +1,520 @@ +mod app; +mod hardware_task; +mod idle; +mod init; +mod monotonic; +mod resource; +mod software_task; +mod util; + +use proc_macro2::TokenStream as TokenStream2; +use syn::{ + braced, parenthesized, + parse::{self, Parse, ParseStream, Parser}, + token::{self, Brace}, + Ident, Item, LitBool, LitInt, Path, Token, +}; + +use crate::syntax::{ + ast::{ + App, AppArgs, HardwareTaskArgs, IdleArgs, InitArgs, MonotonicArgs, SoftwareTaskArgs, + TaskLocal, + }, + Either, Settings, +}; + +// Parse the app, both app arguments and body (input) +pub fn app(args: TokenStream2, input: TokenStream2, settings: &Settings) -> parse::Result { + let args = AppArgs::parse(args)?; + let input: Input = syn::parse2(input)?; + + App::parse(args, input, settings) +} + +pub(crate) struct Input { + _mod_token: Token![mod], + pub ident: Ident, + _brace_token: Brace, + pub items: Vec, +} + +impl Parse for Input { + fn parse(input: ParseStream<'_>) -> parse::Result { + fn parse_items(input: ParseStream<'_>) -> parse::Result> { + let mut items = vec![]; + + while !input.is_empty() { + items.push(input.parse()?); + } + + Ok(items) + } + + let content; + + let _mod_token = input.parse()?; + let ident = input.parse()?; + let _brace_token = braced!(content in input); + let items = content.call(parse_items)?; + + Ok(Input { + _mod_token, + ident, + _brace_token, + items, + }) + } +} + +fn init_args(tokens: TokenStream2) -> parse::Result { + (|input: ParseStream<'_>| -> parse::Result { + if input.is_empty() { + return Ok(InitArgs::default()); + } + + let mut local_resources = None; + + let content; + parenthesized!(content in input); + + if !content.is_empty() { + loop { + // Parse identifier name + let ident: Ident = content.parse()?; + // Handle equal sign + let _: Token![=] = content.parse()?; + + match &*ident.to_string() { + "local" => { + if local_resources.is_some() { + return Err(parse::Error::new( + ident.span(), + "argument appears more than once", + )); + } + + local_resources = Some(util::parse_local_resources(&content)?); + } + _ => { + return Err(parse::Error::new(ident.span(), "unexpected argument")); + } + } + + if content.is_empty() { + break; + } + // Handle comma: , + let _: Token![,] = content.parse()?; + } + } + + if let Some(locals) = &local_resources { + for (ident, task_local) in locals { + if let TaskLocal::External = task_local { + return Err(parse::Error::new( + ident.span(), + "only declared local resources are allowed in init", + )); + } + } + } + + Ok(InitArgs { + local_resources: local_resources.unwrap_or_default(), + }) + }) + .parse2(tokens) +} + +fn idle_args(tokens: TokenStream2) -> parse::Result { + (|input: ParseStream<'_>| -> parse::Result { + if input.is_empty() { + return Ok(IdleArgs::default()); + } + + let mut shared_resources = None; + let mut local_resources = None; + + let content; + parenthesized!(content in input); + if !content.is_empty() { + loop { + // Parse identifier name + let ident: Ident = content.parse()?; + // Handle equal sign + let _: Token![=] = content.parse()?; + + match &*ident.to_string() { + "shared" => { + if shared_resources.is_some() { + return Err(parse::Error::new( + ident.span(), + "argument appears more than once", + )); + } + + shared_resources = Some(util::parse_shared_resources(&content)?); + } + + "local" => { + if local_resources.is_some() { + return Err(parse::Error::new( + ident.span(), + "argument appears more than once", + )); + } + + local_resources = Some(util::parse_local_resources(&content)?); + } + + _ => { + return Err(parse::Error::new(ident.span(), "unexpected argument")); + } + } + if content.is_empty() { + break; + } + + // Handle comma: , + let _: Token![,] = content.parse()?; + } + } + + Ok(IdleArgs { + shared_resources: shared_resources.unwrap_or_default(), + local_resources: local_resources.unwrap_or_default(), + }) + }) + .parse2(tokens) +} + +fn task_args( + tokens: TokenStream2, + settings: &Settings, +) -> parse::Result> { + (|input: ParseStream<'_>| -> parse::Result> { + if input.is_empty() { + return Ok(Either::Right(SoftwareTaskArgs::default())); + } + + let mut binds = None; + let mut capacity = None; + let mut priority = None; + let mut shared_resources = None; + let mut local_resources = None; + let mut prio_span = None; + let mut only_same_priority_spawn = false; + let mut only_same_prio_span = None; + + let content; + parenthesized!(content in input); + loop { + if content.is_empty() { + break; + } + + // Parse identifier name + let ident: Ident = content.parse()?; + let ident_s = ident.to_string(); + + if ident_s == "only_same_priority_spawn_please_fix_me" { + if only_same_priority_spawn { + return Err(parse::Error::new( + ident.span(), + "argument appears more than once", + )); + } + + only_same_priority_spawn = true; + only_same_prio_span = Some(ident.span()); + + if content.is_empty() { + break; + } + + // Handle comma: , + let _: Token![,] = content.parse()?; + + continue; + } + + // Handle equal sign + let _: Token![=] = content.parse()?; + + match &*ident_s { + "binds" if !settings.parse_binds => { + return Err(parse::Error::new( + ident.span(), + "Unexpected bind in task argument. Binds are only parsed if Settings::parse_binds is set.", + )); + } + + "binds" if settings.parse_binds => { + if binds.is_some() { + return Err(parse::Error::new( + ident.span(), + "argument appears more than once", + )); + } + + if capacity.is_some() { + return Err(parse::Error::new( + ident.span(), + "hardware tasks can't use the `capacity` argument", + )); + } + + // Parse identifier name + let ident = content.parse()?; + + binds = Some(ident); + } + + "capacity" => { + if capacity.is_some() { + return Err(parse::Error::new( + ident.span(), + "argument appears more than once", + )); + } + + if binds.is_some() { + return Err(parse::Error::new( + ident.span(), + "hardware tasks can't use the `capacity` argument", + )); + } + + // #lit + let lit: LitInt = content.parse()?; + + if !lit.suffix().is_empty() { + return Err(parse::Error::new( + lit.span(), + "this literal must be unsuffixed", + )); + } + + let value = lit.base10_parse::().ok(); + if value.is_none() || value == Some(0) { + return Err(parse::Error::new( + lit.span(), + "this literal must be in the range 1...255", + )); + } + + capacity = Some(value.unwrap()); + } + + "priority" => { + if priority.is_some() { + return Err(parse::Error::new( + ident.span(), + "argument appears more than once", + )); + } + + // #lit + let lit: LitInt = content.parse()?; + + if !lit.suffix().is_empty() { + return Err(parse::Error::new( + lit.span(), + "this literal must be unsuffixed", + )); + } + + let value = lit.base10_parse::().ok(); + if value.is_none() { + return Err(parse::Error::new( + lit.span(), + "this literal must be in the range 0...255", + )); + } + + prio_span = Some(lit.span()); + priority = Some(value.unwrap()); + } + + "shared" => { + if shared_resources.is_some() { + return Err(parse::Error::new( + ident.span(), + "argument appears more than once", + )); + } + + shared_resources = Some(util::parse_shared_resources(&content)?); + } + + "local" => { + if local_resources.is_some() { + return Err(parse::Error::new( + ident.span(), + "argument appears more than once", + )); + } + + local_resources = Some(util::parse_local_resources(&content)?); + } + + + _ => { + return Err(parse::Error::new(ident.span(), "unexpected argument")); + } + } + + if content.is_empty() { + break; + } + + // Handle comma: , + let _: Token![,] = content.parse()?; + } + let priority = priority.unwrap_or(1); + let shared_resources = shared_resources.unwrap_or_default(); + let local_resources = local_resources.unwrap_or_default(); + + Ok(if let Some(binds) = binds { + if priority == 0 { + return Err(parse::Error::new( + prio_span.unwrap(), + "hardware tasks are not allowed to be at priority 0", + )); + } + + if only_same_priority_spawn { + return Err(parse::Error::new( + only_same_prio_span.unwrap(), + "hardware tasks are not allowed to be spawned, `only_same_priority_spawn_please_fix_me` is only for software tasks", + )); + } + + Either::Left(HardwareTaskArgs { + binds, + priority, + shared_resources, + local_resources, + }) + } else { + Either::Right(SoftwareTaskArgs { + capacity: capacity.unwrap_or(1), + priority, + shared_resources, + local_resources, + only_same_priority_spawn, + }) + }) + }) + .parse2(tokens) +} + +fn monotonic_args(path: Path, tokens: TokenStream2) -> parse::Result { + (|input: ParseStream<'_>| -> parse::Result { + let mut binds = None; + let mut priority = None; + let mut default = None; + + if !input.peek(token::Paren) { + return Err(parse::Error::new( + path.segments.first().unwrap().ident.span(), + "expected opening ( in #[monotonic( ... )]", + )); + } + + let content; + parenthesized!(content in input); + + if !content.is_empty() { + loop { + // Parse identifier name + let ident: Ident = content.parse()?; + // Handle equal sign + let _: Token![=] = content.parse()?; + + match &*ident.to_string() { + "binds" => { + if binds.is_some() { + return Err(parse::Error::new( + ident.span(), + "argument appears more than once", + )); + } + // Parse identifier name + let ident = content.parse()?; + + binds = Some(ident); + } + + "priority" => { + if priority.is_some() { + return Err(parse::Error::new( + ident.span(), + "argument appears more than once", + )); + } + + // #lit + let lit: LitInt = content.parse()?; + + if !lit.suffix().is_empty() { + return Err(parse::Error::new( + lit.span(), + "this literal must be unsuffixed", + )); + } + + let value = lit.base10_parse::().ok(); + if value.is_none() || value == Some(0) { + return Err(parse::Error::new( + lit.span(), + "this literal must be in the range 1...255", + )); + } + + priority = Some(value.unwrap()); + } + + "default" => { + if default.is_some() { + return Err(parse::Error::new( + ident.span(), + "argument appears more than once", + )); + } + + let lit: LitBool = content.parse()?; + default = Some(lit.value); + } + + _ => { + return Err(parse::Error::new(ident.span(), "unexpected argument")); + } + } + if content.is_empty() { + break; + } + + // Handle comma: , + let _: Token![,] = content.parse()?; + } + } + + let binds = if let Some(r) = binds { + r + } else { + return Err(parse::Error::new( + content.span(), + "`binds = ...` is missing", + )); + }; + let default = default.unwrap_or(false); + + Ok(MonotonicArgs { + binds, + priority, + default, + }) + }) + .parse2(tokens) +} diff --git a/macros/src/syntax/parse/app.rs b/macros/src/syntax/parse/app.rs new file mode 100644 index 0000000..7eb415d --- /dev/null +++ b/macros/src/syntax/parse/app.rs @@ -0,0 +1,539 @@ +use std::collections::HashSet; + +// use indexmap::map::Entry; +use proc_macro2::TokenStream as TokenStream2; +use syn::{ + parse::{self, ParseStream, Parser}, + spanned::Spanned, + Expr, ExprArray, Fields, ForeignItem, Ident, Item, LitBool, Path, Token, Type, Visibility, +}; + +use super::Input; +use crate::syntax::{ + ast::{ + App, AppArgs, Dispatcher, Dispatchers, HardwareTask, Idle, IdleArgs, Init, InitArgs, + LocalResource, Monotonic, MonotonicArgs, SharedResource, SoftwareTask, + }, + parse::{self as syntax_parse, util}, + Either, Map, Set, Settings, +}; + +impl AppArgs { + pub(crate) fn parse(tokens: TokenStream2) -> parse::Result { + (|input: ParseStream<'_>| -> parse::Result { + let mut custom = Set::new(); + let mut device = None; + let mut peripherals = true; + let mut dispatchers = Dispatchers::new(); + + loop { + if input.is_empty() { + break; + } + + // #ident = .. + let ident: Ident = input.parse()?; + let _eq_token: Token![=] = input.parse()?; + + if custom.contains(&ident) { + return Err(parse::Error::new( + ident.span(), + "argument appears more than once", + )); + } + + custom.insert(ident.clone()); + + let ks = ident.to_string(); + + match &*ks { + "device" => { + if let Ok(p) = input.parse::() { + device = Some(p); + } else { + return Err(parse::Error::new( + ident.span(), + "unexpected argument value; this should be a path", + )); + } + } + + "peripherals" => { + if let Ok(p) = input.parse::() { + peripherals = p.value; + } else { + return Err(parse::Error::new( + ident.span(), + "unexpected argument value; this should be a boolean", + )); + } + } + + "dispatchers" => { + if let Ok(p) = input.parse::() { + for e in p.elems { + match e { + Expr::Path(ep) => { + let path = ep.path; + let ident = if path.leading_colon.is_some() + || path.segments.len() != 1 + { + return Err(parse::Error::new( + path.span(), + "interrupt must be an identifier, not a path", + )); + } else { + path.segments[0].ident.clone() + }; + let span = ident.span(); + if dispatchers.contains_key(&ident) { + return Err(parse::Error::new( + span, + "this extern interrupt is listed more than once", + )); + } else { + dispatchers + .insert(ident, Dispatcher { attrs: ep.attrs }); + } + } + _ => { + return Err(parse::Error::new( + e.span(), + "interrupt must be an identifier", + )); + } + } + } + } else { + return Err(parse::Error::new( + ident.span(), + // increasing the length of the error message will break rustfmt + "unexpected argument value; expected an array", + )); + } + } + _ => { + return Err(parse::Error::new(ident.span(), "unexpected argument")); + } + } + + if input.is_empty() { + break; + } + + // , + let _: Token![,] = input.parse()?; + } + + let device = if let Some(device) = device { + device + } else { + return Err(parse::Error::new(input.span(), "missing `device = ...`")); + }; + + Ok(AppArgs { + device, + peripherals, + dispatchers, + }) + }) + .parse2(tokens) + } +} + +impl App { + pub(crate) fn parse(args: AppArgs, input: Input, settings: &Settings) -> parse::Result { + let mut init = None; + let mut idle = None; + + let mut shared_resources_ident = None; + let mut shared_resources = Map::new(); + let mut local_resources_ident = None; + let mut local_resources = Map::new(); + let mut monotonics = Map::new(); + let mut hardware_tasks = Map::new(); + let mut software_tasks = Map::new(); + let mut user_imports = vec![]; + let mut user_code = vec![]; + + let mut seen_idents = HashSet::::new(); + let mut bindings = HashSet::::new(); + let mut monotonic_types = HashSet::::new(); + + let mut check_binding = |ident: &Ident| { + if bindings.contains(ident) { + return Err(parse::Error::new( + ident.span(), + "this interrupt is already bound", + )); + } else { + bindings.insert(ident.clone()); + } + + Ok(()) + }; + + let mut check_ident = |ident: &Ident| { + if seen_idents.contains(ident) { + return Err(parse::Error::new( + ident.span(), + "this identifier has already been used", + )); + } else { + seen_idents.insert(ident.clone()); + } + + Ok(()) + }; + + let mut check_monotonic = |ty: &Type| { + if monotonic_types.contains(ty) { + return Err(parse::Error::new( + ty.span(), + "this type is already used by another monotonic", + )); + } else { + monotonic_types.insert(ty.clone()); + } + + Ok(()) + }; + + for mut item in input.items { + match item { + Item::Fn(mut item) => { + let span = item.sig.ident.span(); + if let Some(pos) = item + .attrs + .iter() + .position(|attr| util::attr_eq(attr, "init")) + { + let args = InitArgs::parse(item.attrs.remove(pos).tokens)?; + + // If an init function already exists, error + if init.is_some() { + return Err(parse::Error::new( + span, + "`#[init]` function must appear at most once", + )); + } + + check_ident(&item.sig.ident)?; + + init = Some(Init::parse(args, item)?); + } else if let Some(pos) = item + .attrs + .iter() + .position(|attr| util::attr_eq(attr, "idle")) + { + let args = IdleArgs::parse(item.attrs.remove(pos).tokens)?; + + // If an idle function already exists, error + if idle.is_some() { + return Err(parse::Error::new( + span, + "`#[idle]` function must appear at most once", + )); + } + + check_ident(&item.sig.ident)?; + + idle = Some(Idle::parse(args, item)?); + } else if let Some(pos) = item + .attrs + .iter() + .position(|attr| util::attr_eq(attr, "task")) + { + if hardware_tasks.contains_key(&item.sig.ident) + || software_tasks.contains_key(&item.sig.ident) + { + return Err(parse::Error::new( + span, + "this task is defined multiple times", + )); + } + + match syntax_parse::task_args(item.attrs.remove(pos).tokens, settings)? { + Either::Left(args) => { + check_binding(&args.binds)?; + check_ident(&item.sig.ident)?; + + hardware_tasks.insert( + item.sig.ident.clone(), + HardwareTask::parse(args, item)?, + ); + } + + Either::Right(args) => { + check_ident(&item.sig.ident)?; + + software_tasks.insert( + item.sig.ident.clone(), + SoftwareTask::parse(args, item)?, + ); + } + } + } else { + // Forward normal functions + user_code.push(Item::Fn(item.clone())); + } + } + + Item::Struct(ref mut struct_item) => { + // Match structures with the attribute #[shared], name of structure is not + // important + if let Some(_pos) = struct_item + .attrs + .iter() + .position(|attr| util::attr_eq(attr, "shared")) + { + let span = struct_item.ident.span(); + + shared_resources_ident = Some(struct_item.ident.clone()); + + if !shared_resources.is_empty() { + return Err(parse::Error::new( + span, + "`#[shared]` struct must appear at most once", + )); + } + + if struct_item.vis != Visibility::Inherited { + return Err(parse::Error::new( + struct_item.span(), + "this item must have inherited / private visibility", + )); + } + + if let Fields::Named(fields) = &mut struct_item.fields { + for field in &mut fields.named { + let ident = field.ident.as_ref().expect("UNREACHABLE"); + + if shared_resources.contains_key(ident) { + return Err(parse::Error::new( + ident.span(), + "this resource is listed more than once", + )); + } + + shared_resources.insert( + ident.clone(), + SharedResource::parse(field, ident.span())?, + ); + } + } else { + return Err(parse::Error::new( + struct_item.span(), + "this `struct` must have named fields", + )); + } + } else if let Some(_pos) = struct_item + .attrs + .iter() + .position(|attr| util::attr_eq(attr, "local")) + { + let span = struct_item.ident.span(); + + local_resources_ident = Some(struct_item.ident.clone()); + + if !local_resources.is_empty() { + return Err(parse::Error::new( + span, + "`#[local]` struct must appear at most once", + )); + } + + if struct_item.vis != Visibility::Inherited { + return Err(parse::Error::new( + struct_item.span(), + "this item must have inherited / private visibility", + )); + } + + if let Fields::Named(fields) = &mut struct_item.fields { + for field in &mut fields.named { + let ident = field.ident.as_ref().expect("UNREACHABLE"); + + if local_resources.contains_key(ident) { + return Err(parse::Error::new( + ident.span(), + "this resource is listed more than once", + )); + } + + local_resources.insert( + ident.clone(), + LocalResource::parse(field, ident.span())?, + ); + } + } else { + return Err(parse::Error::new( + struct_item.span(), + "this `struct` must have named fields", + )); + } + } else { + // Structure without the #[resources] attribute should just be passed along + user_code.push(item.clone()); + } + } + + Item::ForeignMod(mod_) => { + if !util::abi_is_rust(&mod_.abi) { + return Err(parse::Error::new( + mod_.abi.extern_token.span(), + "this `extern` block must use the \"Rust\" ABI", + )); + } + + for item in mod_.items { + if let ForeignItem::Fn(mut item) = item { + let span = item.sig.ident.span(); + if let Some(pos) = item + .attrs + .iter() + .position(|attr| util::attr_eq(attr, "task")) + { + if hardware_tasks.contains_key(&item.sig.ident) + || software_tasks.contains_key(&item.sig.ident) + { + return Err(parse::Error::new( + span, + "this task is defined multiple times", + )); + } + + if item.attrs.len() != 1 { + return Err(parse::Error::new( + span, + "`extern` task required `#[task(..)]` attribute", + )); + } + + match syntax_parse::task_args( + item.attrs.remove(pos).tokens, + settings, + )? { + Either::Left(args) => { + check_binding(&args.binds)?; + check_ident(&item.sig.ident)?; + + hardware_tasks.insert( + item.sig.ident.clone(), + HardwareTask::parse_foreign(args, item)?, + ); + } + + Either::Right(args) => { + check_ident(&item.sig.ident)?; + + software_tasks.insert( + item.sig.ident.clone(), + SoftwareTask::parse_foreign(args, item)?, + ); + } + } + } else { + return Err(parse::Error::new( + span, + "`extern` task required `#[task(..)]` attribute", + )); + } + } else { + return Err(parse::Error::new( + item.span(), + "this item must live outside the `#[app]` module", + )); + } + } + } + Item::Use(itemuse_) => { + // Store the user provided use-statements + user_imports.push(itemuse_.clone()); + } + Item::Type(ref mut type_item) => { + // Match types with the attribute #[monotonic] + if let Some(pos) = type_item + .attrs + .iter() + .position(|attr| util::attr_eq(attr, "monotonic")) + { + let span = type_item.ident.span(); + + if monotonics.contains_key(&type_item.ident) { + return Err(parse::Error::new( + span, + "`#[monotonic(...)]` on a specific type must appear at most once", + )); + } + + if type_item.vis != Visibility::Inherited { + return Err(parse::Error::new( + type_item.span(), + "this item must have inherited / private visibility", + )); + } + + check_monotonic(&*type_item.ty)?; + + let m = type_item.attrs.remove(pos); + let args = MonotonicArgs::parse(m)?; + + check_binding(&args.binds)?; + + let monotonic = Monotonic::parse(args, type_item, span)?; + + monotonics.insert(type_item.ident.clone(), monotonic); + } + + // All types are passed on + user_code.push(item.clone()); + } + _ => { + // Anything else within the module should not make any difference + user_code.push(item.clone()); + } + } + } + + let shared_resources_ident = + shared_resources_ident.expect("No `#[shared]` resource struct defined"); + let local_resources_ident = + local_resources_ident.expect("No `#[local]` resource struct defined"); + let init = init.expect("No `#[init]` function defined"); + + if shared_resources_ident != init.user_shared_struct { + return Err(parse::Error::new( + init.user_shared_struct.span(), + format!( + "This name and the one defined on `#[shared]` are not the same. Should this be `{}`?", + shared_resources_ident + ), + )); + } + + if local_resources_ident != init.user_local_struct { + return Err(parse::Error::new( + init.user_local_struct.span(), + format!( + "This name and the one defined on `#[local]` are not the same. Should this be `{}`?", + local_resources_ident + ), + )); + } + + Ok(App { + args, + name: input.ident, + init, + idle, + monotonics, + shared_resources, + local_resources, + user_imports, + user_code, + hardware_tasks, + software_tasks, + }) + } +} diff --git a/macros/src/syntax/parse/hardware_task.rs b/macros/src/syntax/parse/hardware_task.rs new file mode 100644 index 0000000..304bfcd --- /dev/null +++ b/macros/src/syntax/parse/hardware_task.rs @@ -0,0 +1,96 @@ +use syn::{parse, ForeignItemFn, ItemFn, Stmt}; + +use crate::syntax::parse::util::FilterAttrs; +use crate::syntax::{ + ast::{HardwareTask, HardwareTaskArgs}, + parse::util, +}; + +impl HardwareTask { + pub(crate) fn parse(args: HardwareTaskArgs, item: ItemFn) -> parse::Result { + let span = item.sig.ident.span(); + let valid_signature = util::check_fn_signature(&item, false) + && item.sig.inputs.len() == 1 + && util::type_is_unit(&item.sig.output); + + let name = item.sig.ident.to_string(); + + if name == "init" || name == "idle" { + return Err(parse::Error::new( + span, + "tasks cannot be named `init` or `idle`", + )); + } + + if valid_signature { + if let Some((context, Ok(rest))) = util::parse_inputs(item.sig.inputs, &name) { + if rest.is_empty() { + let FilterAttrs { cfgs, attrs, .. } = util::filter_attributes(item.attrs); + + return Ok(HardwareTask { + args, + cfgs, + attrs, + context, + stmts: item.block.stmts, + is_extern: false, + }); + } + } + } + + Err(parse::Error::new( + span, + &format!( + "this task handler must have type signature `fn({}::Context)`", + name + ), + )) + } +} + +impl HardwareTask { + pub(crate) fn parse_foreign( + args: HardwareTaskArgs, + item: ForeignItemFn, + ) -> parse::Result { + let span = item.sig.ident.span(); + let valid_signature = util::check_foreign_fn_signature(&item, false) + && item.sig.inputs.len() == 1 + && util::type_is_unit(&item.sig.output); + + let name = item.sig.ident.to_string(); + + if name == "init" || name == "idle" { + return Err(parse::Error::new( + span, + "tasks cannot be named `init` or `idle`", + )); + } + + if valid_signature { + if let Some((context, Ok(rest))) = util::parse_inputs(item.sig.inputs, &name) { + if rest.is_empty() { + let FilterAttrs { cfgs, attrs, .. } = util::filter_attributes(item.attrs); + + return Ok(HardwareTask { + args, + cfgs, + attrs, + context, + stmts: Vec::::new(), + is_extern: true, + }); + } + } + } + + Err(parse::Error::new( + span, + &format!( + "this task handler must have type signature `fn({}::Context)`", + name + ), + )) + } +} diff --git a/macros/src/syntax/parse/idle.rs b/macros/src/syntax/parse/idle.rs new file mode 100644 index 0000000..d9f3a99 --- /dev/null +++ b/macros/src/syntax/parse/idle.rs @@ -0,0 +1,45 @@ +use proc_macro2::TokenStream as TokenStream2; +use syn::{parse, ItemFn}; + +use crate::syntax::{ + ast::{Idle, IdleArgs}, + parse::util, +}; + +impl IdleArgs { + pub(crate) fn parse(tokens: TokenStream2) -> parse::Result { + crate::syntax::parse::idle_args(tokens) + } +} + +impl Idle { + pub(crate) fn parse(args: IdleArgs, item: ItemFn) -> parse::Result { + let valid_signature = util::check_fn_signature(&item, false) + && item.sig.inputs.len() == 1 + && util::type_is_bottom(&item.sig.output); + + let name = item.sig.ident.to_string(); + + if valid_signature { + if let Some((context, Ok(rest))) = util::parse_inputs(item.sig.inputs, &name) { + if rest.is_empty() { + return Ok(Idle { + args, + attrs: item.attrs, + context, + name: item.sig.ident, + stmts: item.block.stmts, + }); + } + } + } + + Err(parse::Error::new( + item.sig.ident.span(), + &format!( + "this `#[idle]` function must have signature `fn({}::Context) -> !`", + name + ), + )) + } +} diff --git a/macros/src/syntax/parse/init.rs b/macros/src/syntax/parse/init.rs new file mode 100644 index 0000000..727ee20 --- /dev/null +++ b/macros/src/syntax/parse/init.rs @@ -0,0 +1,52 @@ +use proc_macro2::TokenStream as TokenStream2; + +use syn::{parse, ItemFn}; + +use crate::syntax::{ + ast::{Init, InitArgs}, + parse::{self as syntax_parse, util}, +}; + +impl InitArgs { + pub(crate) fn parse(tokens: TokenStream2) -> parse::Result { + syntax_parse::init_args(tokens) + } +} + +impl Init { + pub(crate) fn parse(args: InitArgs, item: ItemFn) -> parse::Result { + let valid_signature = util::check_fn_signature(&item, false) && item.sig.inputs.len() == 1; + + let span = item.sig.ident.span(); + + let name = item.sig.ident.to_string(); + + if valid_signature { + if let Ok((user_shared_struct, user_local_struct)) = + util::type_is_init_return(&item.sig.output, &name) + { + if let Some((context, Ok(rest))) = util::parse_inputs(item.sig.inputs, &name) { + if rest.is_empty() { + return Ok(Init { + args, + attrs: item.attrs, + context, + name: item.sig.ident, + stmts: item.block.stmts, + user_shared_struct, + user_local_struct, + }); + } + } + } + } + + Err(parse::Error::new( + span, + &format!( + "the `#[init]` function must have signature `fn({}::Context) -> (Shared resources struct, Local resources struct, {0}::Monotonics)`", + name + ), + )) + } +} diff --git a/macros/src/syntax/parse/monotonic.rs b/macros/src/syntax/parse/monotonic.rs new file mode 100644 index 0000000..0583233 --- /dev/null +++ b/macros/src/syntax/parse/monotonic.rs @@ -0,0 +1,42 @@ +use proc_macro2::Span; +use syn::Attribute; +use syn::{parse, spanned::Spanned, ItemType, Visibility}; + +use crate::syntax::parse::util::FilterAttrs; +use crate::syntax::{ + ast::{Monotonic, MonotonicArgs}, + parse::util, +}; + +impl MonotonicArgs { + pub(crate) fn parse(attr: Attribute) -> parse::Result { + crate::syntax::parse::monotonic_args(attr.path, attr.tokens) + } +} + +impl Monotonic { + pub(crate) fn parse(args: MonotonicArgs, item: &ItemType, span: Span) -> parse::Result { + if item.vis != Visibility::Inherited { + return Err(parse::Error::new( + span, + "this field must have inherited / private visibility", + )); + } + + let FilterAttrs { cfgs, attrs, .. } = util::filter_attributes(item.attrs.clone()); + + if !attrs.is_empty() { + return Err(parse::Error::new( + attrs[0].path.span(), + "Monotonic does not support attributes other than `#[cfg]`", + )); + } + + Ok(Monotonic { + cfgs, + ident: item.ident.clone(), + ty: item.ty.clone(), + args, + }) + } +} diff --git a/macros/src/syntax/parse/resource.rs b/macros/src/syntax/parse/resource.rs new file mode 100644 index 0000000..ff10057 --- /dev/null +++ b/macros/src/syntax/parse/resource.rs @@ -0,0 +1,55 @@ +use proc_macro2::Span; +use syn::{parse, Field, Visibility}; + +use crate::syntax::parse::util::FilterAttrs; +use crate::syntax::{ + ast::{LocalResource, SharedResource, SharedResourceProperties}, + parse::util, +}; + +impl SharedResource { + pub(crate) fn parse(item: &Field, span: Span) -> parse::Result { + if item.vis != Visibility::Inherited { + return Err(parse::Error::new( + span, + "this field must have inherited / private visibility", + )); + } + + let FilterAttrs { + cfgs, + mut attrs, + docs, + } = util::filter_attributes(item.attrs.clone()); + + let lock_free = util::extract_lock_free(&mut attrs)?; + + Ok(SharedResource { + cfgs, + attrs, + docs, + ty: Box::new(item.ty.clone()), + properties: SharedResourceProperties { lock_free }, + }) + } +} + +impl LocalResource { + pub(crate) fn parse(item: &Field, span: Span) -> parse::Result { + if item.vis != Visibility::Inherited { + return Err(parse::Error::new( + span, + "this field must have inherited / private visibility", + )); + } + + let FilterAttrs { cfgs, attrs, docs } = util::filter_attributes(item.attrs.clone()); + + Ok(LocalResource { + cfgs, + attrs, + docs, + ty: Box::new(item.ty.clone()), + }) + } +} diff --git a/macros/src/syntax/parse/software_task.rs b/macros/src/syntax/parse/software_task.rs new file mode 100644 index 0000000..2b1ac4a --- /dev/null +++ b/macros/src/syntax/parse/software_task.rs @@ -0,0 +1,86 @@ +use syn::{parse, ForeignItemFn, ItemFn, Stmt}; + +use crate::syntax::parse::util::FilterAttrs; +use crate::syntax::{ + ast::{SoftwareTask, SoftwareTaskArgs}, + parse::util, +}; + +impl SoftwareTask { + pub(crate) fn parse(args: SoftwareTaskArgs, item: ItemFn) -> parse::Result { + let valid_signature = + util::check_fn_signature(&item, true) && util::type_is_unit(&item.sig.output); + + let span = item.sig.ident.span(); + + let name = item.sig.ident.to_string(); + + let is_async = item.sig.asyncness.is_some(); + + if valid_signature { + if let Some((context, Ok(inputs))) = util::parse_inputs(item.sig.inputs, &name) { + let FilterAttrs { cfgs, attrs, .. } = util::filter_attributes(item.attrs); + + return Ok(SoftwareTask { + args, + attrs, + cfgs, + context, + inputs, + stmts: item.block.stmts, + is_extern: false, + is_async, + }); + } + } + + Err(parse::Error::new( + span, + &format!( + "this task handler must have type signature `(async) fn({}::Context, ..)`", + name + ), + )) + } +} + +impl SoftwareTask { + pub(crate) fn parse_foreign( + args: SoftwareTaskArgs, + item: ForeignItemFn, + ) -> parse::Result { + let valid_signature = + util::check_foreign_fn_signature(&item, true) && util::type_is_unit(&item.sig.output); + + let span = item.sig.ident.span(); + + let name = item.sig.ident.to_string(); + + let is_async = item.sig.asyncness.is_some(); + + if valid_signature { + if let Some((context, Ok(inputs))) = util::parse_inputs(item.sig.inputs, &name) { + let FilterAttrs { cfgs, attrs, .. } = util::filter_attributes(item.attrs); + + return Ok(SoftwareTask { + args, + attrs, + cfgs, + context, + inputs, + stmts: Vec::::new(), + is_extern: true, + is_async, + }); + } + } + + Err(parse::Error::new( + span, + &format!( + "this task handler must have type signature `(async) fn({}::Context, ..)`", + name + ), + )) + } +} diff --git a/macros/src/syntax/parse/util.rs b/macros/src/syntax/parse/util.rs new file mode 100644 index 0000000..3fa51ef --- /dev/null +++ b/macros/src/syntax/parse/util.rs @@ -0,0 +1,338 @@ +use syn::{ + bracketed, + parse::{self, ParseStream}, + punctuated::Punctuated, + spanned::Spanned, + Abi, AttrStyle, Attribute, Expr, FnArg, ForeignItemFn, Ident, ItemFn, Pat, PatType, Path, + PathArguments, ReturnType, Token, Type, Visibility, +}; + +use crate::syntax::{ + ast::{Access, Local, LocalResources, SharedResources, TaskLocal}, + Map, +}; + +pub fn abi_is_rust(abi: &Abi) -> bool { + match &abi.name { + None => true, + Some(s) => s.value() == "Rust", + } +} + +pub fn attr_eq(attr: &Attribute, name: &str) -> bool { + attr.style == AttrStyle::Outer && attr.path.segments.len() == 1 && { + let segment = attr.path.segments.first().unwrap(); + segment.arguments == PathArguments::None && *segment.ident.to_string() == *name + } +} + +/// checks that a function signature +/// +/// - has no bounds (like where clauses) +/// - is not `async` +/// - is not `const` +/// - is not `unsafe` +/// - is not generic (has no type parameters) +/// - is not variadic +/// - uses the Rust ABI (and not e.g. "C") +pub fn check_fn_signature(item: &ItemFn, allow_async: bool) -> bool { + item.vis == Visibility::Inherited + && item.sig.constness.is_none() + && (item.sig.asyncness.is_none() || allow_async) + && item.sig.abi.is_none() + && item.sig.unsafety.is_none() + && item.sig.generics.params.is_empty() + && item.sig.generics.where_clause.is_none() + && item.sig.variadic.is_none() +} + +#[allow(dead_code)] +pub fn check_foreign_fn_signature(item: &ForeignItemFn, allow_async: bool) -> bool { + item.vis == Visibility::Inherited + && item.sig.constness.is_none() + && (item.sig.asyncness.is_none() || allow_async) + && item.sig.abi.is_none() + && item.sig.unsafety.is_none() + && item.sig.generics.params.is_empty() + && item.sig.generics.where_clause.is_none() + && item.sig.variadic.is_none() +} + +pub struct FilterAttrs { + pub cfgs: Vec, + pub docs: Vec, + pub attrs: Vec, +} + +pub fn filter_attributes(input_attrs: Vec) -> FilterAttrs { + let mut cfgs = vec![]; + let mut docs = vec![]; + let mut attrs = vec![]; + + for attr in input_attrs { + if attr_eq(&attr, "cfg") { + cfgs.push(attr); + } else if attr_eq(&attr, "doc") { + docs.push(attr); + } else { + attrs.push(attr); + } + } + + FilterAttrs { cfgs, docs, attrs } +} + +pub fn extract_lock_free(attrs: &mut Vec) -> parse::Result { + if let Some(pos) = attrs.iter().position(|attr| attr_eq(attr, "lock_free")) { + attrs.remove(pos); + Ok(true) + } else { + Ok(false) + } +} + +pub fn parse_shared_resources(content: ParseStream<'_>) -> parse::Result { + let inner; + bracketed!(inner in content); + + let mut resources = Map::new(); + for e in inner.call(Punctuated::::parse_terminated)? { + let err = Err(parse::Error::new( + e.span(), + "identifier appears more than once in list", + )); + let (access, path) = match e { + Expr::Path(e) => (Access::Exclusive, e.path), + + Expr::Reference(ref r) if r.mutability.is_none() => match &*r.expr { + Expr::Path(e) => (Access::Shared, e.path.clone()), + + _ => return err, + }, + + _ => return err, + }; + + let ident = extract_resource_name_ident(path)?; + + if resources.contains_key(&ident) { + return Err(parse::Error::new( + ident.span(), + "resource appears more than once in list", + )); + } + + resources.insert(ident, access); + } + + Ok(resources) +} + +fn extract_resource_name_ident(path: Path) -> parse::Result { + if path.leading_colon.is_some() + || path.segments.len() != 1 + || path.segments[0].arguments != PathArguments::None + { + Err(parse::Error::new( + path.span(), + "resource must be an identifier, not a path", + )) + } else { + Ok(path.segments[0].ident.clone()) + } +} + +pub fn parse_local_resources(content: ParseStream<'_>) -> parse::Result { + let inner; + bracketed!(inner in content); + + let mut resources = Map::new(); + + for e in inner.call(Punctuated::::parse_terminated)? { + let err = Err(parse::Error::new( + e.span(), + "identifier appears more than once in list", + )); + + let (name, local) = match e { + // local = [IDENT], + Expr::Path(path) => { + if !path.attrs.is_empty() { + return Err(parse::Error::new( + path.span(), + "attributes are not supported here", + )); + } + + let ident = extract_resource_name_ident(path.path)?; + // let (cfgs, attrs) = extract_cfgs(path.attrs); + + (ident, TaskLocal::External) + } + + // local = [IDENT: TYPE = EXPR] + Expr::Assign(e) => { + let (name, ty, cfgs, attrs) = match *e.left { + Expr::Type(t) => { + // Extract name and attributes + let (name, cfgs, attrs) = match *t.expr { + Expr::Path(path) => { + let name = extract_resource_name_ident(path.path)?; + let FilterAttrs { cfgs, attrs, .. } = filter_attributes(path.attrs); + + (name, cfgs, attrs) + } + _ => return err, + }; + + let ty = t.ty; + + // Error check + match &*ty { + Type::Array(_) => {} + Type::Path(_) => {} + Type::Ptr(_) => {} + Type::Tuple(_) => {} + _ => return Err(parse::Error::new( + ty.span(), + "unsupported type, must be an array, tuple, pointer or type path", + )), + }; + + (name, ty, cfgs, attrs) + } + e => return Err(parse::Error::new(e.span(), "malformed, expected a type")), + }; + + let expr = e.right; // Expr + + ( + name, + TaskLocal::Declared(Local { + attrs, + cfgs, + ty, + expr, + }), + ) + } + + expr => { + return Err(parse::Error::new( + expr.span(), + "malformed, expected 'IDENT: TYPE = EXPR'", + )) + } + }; + + resources.insert(name, local); + } + + Ok(resources) +} + +type ParseInputResult = Option<(Box, Result, FnArg>)>; + +pub fn parse_inputs(inputs: Punctuated, name: &str) -> ParseInputResult { + let mut inputs = inputs.into_iter(); + + match inputs.next() { + Some(FnArg::Typed(first)) => { + if type_is_path(&first.ty, &[name, "Context"]) { + let rest = inputs + .map(|arg| match arg { + FnArg::Typed(arg) => Ok(arg), + _ => Err(arg), + }) + .collect::, _>>(); + + Some((first.pat, rest)) + } else { + None + } + } + + _ => None, + } +} + +pub fn type_is_bottom(ty: &ReturnType) -> bool { + if let ReturnType::Type(_, ty) = ty { + matches!(**ty, Type::Never(_)) + } else { + false + } +} + +fn extract_init_resource_name_ident(ty: Type) -> Result { + match ty { + Type::Path(path) => { + let path = path.path; + + if path.leading_colon.is_some() + || path.segments.len() != 1 + || path.segments[0].arguments != PathArguments::None + { + Err(()) + } else { + Ok(path.segments[0].ident.clone()) + } + } + _ => Err(()), + } +} + +/// Checks Init's return type, return the user provided types for analysis +pub fn type_is_init_return(ty: &ReturnType, name: &str) -> Result<(Ident, Ident), ()> { + match ty { + ReturnType::Default => Err(()), + + ReturnType::Type(_, ty) => match &**ty { + Type::Tuple(t) => { + // return should be: + // fn -> (User's #[shared] struct, User's #[local] struct, {name}::Monotonics) + // + // We check the length and the last one here, analysis checks that the user + // provided structs are correct. + if t.elems.len() == 3 && type_is_path(&t.elems[2], &[name, "Monotonics"]) { + return Ok(( + extract_init_resource_name_ident(t.elems[0].clone())?, + extract_init_resource_name_ident(t.elems[1].clone())?, + )); + } + + Err(()) + } + + _ => Err(()), + }, + } +} + +pub fn type_is_path(ty: &Type, segments: &[&str]) -> bool { + match ty { + Type::Path(tpath) if tpath.qself.is_none() => { + tpath.path.segments.len() == segments.len() + && tpath + .path + .segments + .iter() + .zip(segments) + .all(|(lhs, rhs)| lhs.ident == **rhs) + } + + _ => false, + } +} + +pub fn type_is_unit(ty: &ReturnType) -> bool { + if let ReturnType::Type(_, ty) = ty { + if let Type::Tuple(ref tuple) = **ty { + tuple.elems.is_empty() + } else { + false + } + } else { + true + } +} diff --git a/macros/tests/ui.rs b/macros/tests/ui.rs new file mode 100644 index 0000000..9fb88a1 --- /dev/null +++ b/macros/tests/ui.rs @@ -0,0 +1,7 @@ +use trybuild::TestCases; + +#[test] +fn ui() { + let t = TestCases::new(); + t.compile_fail("ui/*.rs"); +} diff --git a/macros/ui/async-local-resouces.rs b/macros/ui/async-local-resouces.rs new file mode 100644 index 0000000..1ba5865 --- /dev/null +++ b/macros/ui/async-local-resouces.rs @@ -0,0 +1,28 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared { + #[lock_free] + e: u32, + } + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {} + + // e ok + #[task(priority = 1, shared = [e])] + fn uart0(cx: uart0::Context) {} + + // e ok + #[task(priority = 1, shared = [e])] + fn uart1(cx: uart1::Context) {} + + // e not ok + #[task(priority = 1, shared = [e])] + async fn async_task(cx: async_task::Context) {} +} diff --git a/macros/ui/async-local-resouces.stderr b/macros/ui/async-local-resouces.stderr new file mode 100644 index 0000000..7ce7517 --- /dev/null +++ b/macros/ui/async-local-resouces.stderr @@ -0,0 +1,5 @@ +error: Lock free shared resource "e" is used by an async tasks, which is forbidden + --> ui/async-local-resouces.rs:26:36 + | +26 | #[task(priority = 1, shared = [e])] + | ^ diff --git a/macros/ui/async-zero-prio-tasks.rs b/macros/ui/async-zero-prio-tasks.rs new file mode 100644 index 0000000..91e0990 --- /dev/null +++ b/macros/ui/async-zero-prio-tasks.rs @@ -0,0 +1,19 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {} + + #[task(priority = 0)] + fn foo(_: foo::Context) {} + + #[idle] + fn idle(_: idle::Context) -> ! {} +} diff --git a/macros/ui/async-zero-prio-tasks.stderr b/macros/ui/async-zero-prio-tasks.stderr new file mode 100644 index 0000000..d617feb --- /dev/null +++ b/macros/ui/async-zero-prio-tasks.stderr @@ -0,0 +1,11 @@ +error: Software task "foo" has priority 0, but `#[idle]` is defined. 0-priority software tasks are only allowed if there is no `#[idle]`. + --> ui/async-zero-prio-tasks.rs:15:8 + | +15 | fn foo(_: foo::Context) {} + | ^^^ + +error: Software task "foo" has priority 0, but is not `async`. 0-priority software tasks must be `async`. + --> ui/async-zero-prio-tasks.rs:15:8 + | +15 | fn foo(_: foo::Context) {} + | ^^^ diff --git a/macros/ui/extern-interrupt-used.rs b/macros/ui/extern-interrupt-used.rs new file mode 100644 index 0000000..71dc50f --- /dev/null +++ b/macros/ui/extern-interrupt-used.rs @@ -0,0 +1,16 @@ +#![no_main] + +#[rtic_macros::mock_app(parse_extern_interrupt, parse_binds, device = mock, dispatchers = [EXTI0])] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {} + + #[task(binds = EXTI0)] + fn foo(_: foo::Context) {} +} diff --git a/macros/ui/extern-interrupt-used.stderr b/macros/ui/extern-interrupt-used.stderr new file mode 100644 index 0000000..f9510d7 --- /dev/null +++ b/macros/ui/extern-interrupt-used.stderr @@ -0,0 +1,5 @@ +error: dispatcher interrupts can't be used as hardware tasks + --> $DIR/extern-interrupt-used.rs:14:20 + | +14 | #[task(binds = EXTI0)] + | ^^^^^ diff --git a/macros/ui/idle-double-local.rs b/macros/ui/idle-double-local.rs new file mode 100644 index 0000000..54e67d3 --- /dev/null +++ b/macros/ui/idle-double-local.rs @@ -0,0 +1,9 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[idle(local = [A], local = [B])] + fn idle(_: idle::Context) -> ! { + loop {} + } +} diff --git a/macros/ui/idle-double-local.stderr b/macros/ui/idle-double-local.stderr new file mode 100644 index 0000000..d3ba4ec --- /dev/null +++ b/macros/ui/idle-double-local.stderr @@ -0,0 +1,5 @@ +error: argument appears more than once + --> $DIR/idle-double-local.rs:5:25 + | +5 | #[idle(local = [A], local = [B])] + | ^^^^^ diff --git a/macros/ui/idle-double-shared.rs b/macros/ui/idle-double-shared.rs new file mode 100644 index 0000000..f66cb93 --- /dev/null +++ b/macros/ui/idle-double-shared.rs @@ -0,0 +1,9 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[idle(shared = [A], shared = [B])] + fn idle(_: idle::Context) -> ! { + loop {} + } +} diff --git a/macros/ui/idle-double-shared.stderr b/macros/ui/idle-double-shared.stderr new file mode 100644 index 0000000..84864a1 --- /dev/null +++ b/macros/ui/idle-double-shared.stderr @@ -0,0 +1,5 @@ +error: argument appears more than once + --> $DIR/idle-double-shared.rs:5:26 + | +5 | #[idle(shared = [A], shared = [B])] + | ^^^^^^ diff --git a/macros/ui/idle-input.rs b/macros/ui/idle-input.rs new file mode 100644 index 0000000..c896b1c --- /dev/null +++ b/macros/ui/idle-input.rs @@ -0,0 +1,9 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[idle] + fn idle(_: idle::Context, _undef: u32) -> ! { + loop {} + } +} diff --git a/macros/ui/idle-input.stderr b/macros/ui/idle-input.stderr new file mode 100644 index 0000000..34c38fc --- /dev/null +++ b/macros/ui/idle-input.stderr @@ -0,0 +1,5 @@ +error: this `#[idle]` function must have signature `fn(idle::Context) -> !` + --> ui/idle-input.rs:6:8 + | +6 | fn idle(_: idle::Context, _undef: u32) -> ! { + | ^^^^ diff --git a/macros/ui/idle-no-context.rs b/macros/ui/idle-no-context.rs new file mode 100644 index 0000000..bab4680 --- /dev/null +++ b/macros/ui/idle-no-context.rs @@ -0,0 +1,9 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[idle] + fn idle() -> ! { + loop {} + } +} diff --git a/macros/ui/idle-no-context.stderr b/macros/ui/idle-no-context.stderr new file mode 100644 index 0000000..c9f4b3d --- /dev/null +++ b/macros/ui/idle-no-context.stderr @@ -0,0 +1,5 @@ +error: this `#[idle]` function must have signature `fn(idle::Context) -> !` + --> ui/idle-no-context.rs:6:8 + | +6 | fn idle() -> ! { + | ^^^^ diff --git a/macros/ui/idle-not-divergent.rs b/macros/ui/idle-not-divergent.rs new file mode 100644 index 0000000..d1ae8b1 --- /dev/null +++ b/macros/ui/idle-not-divergent.rs @@ -0,0 +1,7 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[idle] + fn idle(_: idle::Context) {} +} diff --git a/macros/ui/idle-not-divergent.stderr b/macros/ui/idle-not-divergent.stderr new file mode 100644 index 0000000..e318f58 --- /dev/null +++ b/macros/ui/idle-not-divergent.stderr @@ -0,0 +1,5 @@ +error: this `#[idle]` function must have signature `fn(idle::Context) -> !` + --> ui/idle-not-divergent.rs:6:8 + | +6 | fn idle(_: idle::Context) {} + | ^^^^ diff --git a/macros/ui/idle-output.rs b/macros/ui/idle-output.rs new file mode 100644 index 0000000..1662157 --- /dev/null +++ b/macros/ui/idle-output.rs @@ -0,0 +1,9 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[idle] + fn idle(_: idle::Context) -> u32 { + 0 + } +} diff --git a/macros/ui/idle-output.stderr b/macros/ui/idle-output.stderr new file mode 100644 index 0000000..7070e25 --- /dev/null +++ b/macros/ui/idle-output.stderr @@ -0,0 +1,5 @@ +error: this `#[idle]` function must have signature `fn(idle::Context) -> !` + --> ui/idle-output.rs:6:8 + | +6 | fn idle(_: idle::Context) -> u32 { + | ^^^^ diff --git a/macros/ui/idle-pub.rs b/macros/ui/idle-pub.rs new file mode 100644 index 0000000..0d8dd01 --- /dev/null +++ b/macros/ui/idle-pub.rs @@ -0,0 +1,9 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[idle] + pub fn idle(_: idle::Context) -> ! { + loop {} + } +} diff --git a/macros/ui/idle-pub.stderr b/macros/ui/idle-pub.stderr new file mode 100644 index 0000000..aa46ac3 --- /dev/null +++ b/macros/ui/idle-pub.stderr @@ -0,0 +1,5 @@ +error: this `#[idle]` function must have signature `fn(idle::Context) -> !` + --> ui/idle-pub.rs:6:12 + | +6 | pub fn idle(_: idle::Context) -> ! { + | ^^^^ diff --git a/macros/ui/idle-unsafe.rs b/macros/ui/idle-unsafe.rs new file mode 100644 index 0000000..3422ef2 --- /dev/null +++ b/macros/ui/idle-unsafe.rs @@ -0,0 +1,9 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[idle] + unsafe fn idle(_: idle::Context) -> ! { + loop {} + } +} diff --git a/macros/ui/idle-unsafe.stderr b/macros/ui/idle-unsafe.stderr new file mode 100644 index 0000000..a416800 --- /dev/null +++ b/macros/ui/idle-unsafe.stderr @@ -0,0 +1,5 @@ +error: this `#[idle]` function must have signature `fn(idle::Context) -> !` + --> ui/idle-unsafe.rs:6:15 + | +6 | unsafe fn idle(_: idle::Context) -> ! { + | ^^^^ diff --git a/macros/ui/init-divergent.rs b/macros/ui/init-divergent.rs new file mode 100644 index 0000000..5e4e96a --- /dev/null +++ b/macros/ui/init-divergent.rs @@ -0,0 +1,13 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> ! {} +} diff --git a/macros/ui/init-divergent.stderr b/macros/ui/init-divergent.stderr new file mode 100644 index 0000000..2d5cc39 --- /dev/null +++ b/macros/ui/init-divergent.stderr @@ -0,0 +1,5 @@ +error: the `#[init]` function must have signature `fn(init::Context) -> (Shared resources struct, Local resources struct, init::Monotonics)` + --> $DIR/init-divergent.rs:12:8 + | +12 | fn init(_: init::Context) -> ! {} + | ^^^^ diff --git a/macros/ui/init-double-local.rs b/macros/ui/init-double-local.rs new file mode 100644 index 0000000..5f6d7ac --- /dev/null +++ b/macros/ui/init-double-local.rs @@ -0,0 +1,7 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[init(local = [A], local = [B])] + fn init(_: init::Context) {} +} diff --git a/macros/ui/init-double-local.stderr b/macros/ui/init-double-local.stderr new file mode 100644 index 0000000..5ffd2c1 --- /dev/null +++ b/macros/ui/init-double-local.stderr @@ -0,0 +1,5 @@ +error: argument appears more than once + --> $DIR/init-double-local.rs:5:25 + | +5 | #[init(local = [A], local = [B])] + | ^^^^^ diff --git a/macros/ui/init-double-shared.rs b/macros/ui/init-double-shared.rs new file mode 100644 index 0000000..4503c87 --- /dev/null +++ b/macros/ui/init-double-shared.rs @@ -0,0 +1,7 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[init(shared = [A], shared = [B])] + fn init(_: init::Context) {} +} diff --git a/macros/ui/init-double-shared.stderr b/macros/ui/init-double-shared.stderr new file mode 100644 index 0000000..b6b1f6d --- /dev/null +++ b/macros/ui/init-double-shared.stderr @@ -0,0 +1,5 @@ +error: unexpected argument + --> $DIR/init-double-shared.rs:5:12 + | +5 | #[init(shared = [A], shared = [B])] + | ^^^^^^ diff --git a/macros/ui/init-input.rs b/macros/ui/init-input.rs new file mode 100644 index 0000000..ac2a1bd --- /dev/null +++ b/macros/ui/init-input.rs @@ -0,0 +1,13 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context, _undef: u32) -> (Shared, Local, init::Monotonics) {} +} diff --git a/macros/ui/init-input.stderr b/macros/ui/init-input.stderr new file mode 100644 index 0000000..983c469 --- /dev/null +++ b/macros/ui/init-input.stderr @@ -0,0 +1,5 @@ +error: the `#[init]` function must have signature `fn(init::Context) -> (Shared resources struct, Local resources struct, init::Monotonics)` + --> $DIR/init-input.rs:12:8 + | +12 | fn init(_: init::Context, _undef: u32) -> (Shared, Local, init::Monotonics) {} + | ^^^^ diff --git a/macros/ui/init-no-context.rs b/macros/ui/init-no-context.rs new file mode 100644 index 0000000..a74093a --- /dev/null +++ b/macros/ui/init-no-context.rs @@ -0,0 +1,13 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init() -> (Shared, Local, init::Monotonics) {} +} diff --git a/macros/ui/init-no-context.stderr b/macros/ui/init-no-context.stderr new file mode 100644 index 0000000..742e2ab --- /dev/null +++ b/macros/ui/init-no-context.stderr @@ -0,0 +1,5 @@ +error: the `#[init]` function must have signature `fn(init::Context) -> (Shared resources struct, Local resources struct, init::Monotonics)` + --> $DIR/init-no-context.rs:12:8 + | +12 | fn init() -> (Shared, Local, init::Monotonics) {} + | ^^^^ diff --git a/macros/ui/init-output.rs b/macros/ui/init-output.rs new file mode 100644 index 0000000..7057c95 --- /dev/null +++ b/macros/ui/init-output.rs @@ -0,0 +1,9 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[init] + fn init(_: init::Context) -> u32 { + 0 + } +} diff --git a/macros/ui/init-output.stderr b/macros/ui/init-output.stderr new file mode 100644 index 0000000..03e982c --- /dev/null +++ b/macros/ui/init-output.stderr @@ -0,0 +1,5 @@ +error: the `#[init]` function must have signature `fn(init::Context) -> (Shared resources struct, Local resources struct, init::Monotonics)` + --> $DIR/init-output.rs:6:8 + | +6 | fn init(_: init::Context) -> u32 { + | ^^^^ diff --git a/macros/ui/init-pub.rs b/macros/ui/init-pub.rs new file mode 100644 index 0000000..43375e4 --- /dev/null +++ b/macros/ui/init-pub.rs @@ -0,0 +1,13 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + pub fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {} +} diff --git a/macros/ui/init-pub.stderr b/macros/ui/init-pub.stderr new file mode 100644 index 0000000..eb68e1e --- /dev/null +++ b/macros/ui/init-pub.stderr @@ -0,0 +1,5 @@ +error: the `#[init]` function must have signature `fn(init::Context) -> (Shared resources struct, Local resources struct, init::Monotonics)` + --> $DIR/init-pub.rs:12:12 + | +12 | pub fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {} + | ^^^^ diff --git a/macros/ui/init-unsafe.rs b/macros/ui/init-unsafe.rs new file mode 100644 index 0000000..b5d391d --- /dev/null +++ b/macros/ui/init-unsafe.rs @@ -0,0 +1,7 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[init] + unsafe fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {} +} diff --git a/macros/ui/init-unsafe.stderr b/macros/ui/init-unsafe.stderr new file mode 100644 index 0000000..2a48533 --- /dev/null +++ b/macros/ui/init-unsafe.stderr @@ -0,0 +1,5 @@ +error: the `#[init]` function must have signature `fn(init::Context) -> (Shared resources struct, Local resources struct, init::Monotonics)` + --> $DIR/init-unsafe.rs:6:15 + | +6 | unsafe fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {} + | ^^^^ diff --git a/macros/ui/interrupt-double.rs b/macros/ui/interrupt-double.rs new file mode 100644 index 0000000..1133c5c --- /dev/null +++ b/macros/ui/interrupt-double.rs @@ -0,0 +1,10 @@ +#![no_main] + +#[rtic_macros::mock_app(parse_binds, device = mock)] +mod app { + #[task(binds = UART0)] + fn foo(_: foo::Context) {} + + #[task(binds = UART0)] + fn bar(_: bar::Context) {} +} diff --git a/macros/ui/interrupt-double.stderr b/macros/ui/interrupt-double.stderr new file mode 100644 index 0000000..62b979b --- /dev/null +++ b/macros/ui/interrupt-double.stderr @@ -0,0 +1,5 @@ +error: this interrupt is already bound + --> $DIR/interrupt-double.rs:8:20 + | +8 | #[task(binds = UART0)] + | ^^^^^ diff --git a/macros/ui/local-collision-2.rs b/macros/ui/local-collision-2.rs new file mode 100644 index 0000000..7bd092c --- /dev/null +++ b/macros/ui/local-collision-2.rs @@ -0,0 +1,21 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local { + a: u32, + } + + fn bar(_: bar::Context) {} + + #[task(local = [a: u8 = 3])] + fn bar(_: bar::Context) {} + + #[init(local = [a: u16 = 2])] + fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {} +} + diff --git a/macros/ui/local-collision-2.stderr b/macros/ui/local-collision-2.stderr new file mode 100644 index 0000000..1e4c5fa --- /dev/null +++ b/macros/ui/local-collision-2.stderr @@ -0,0 +1,17 @@ +error: Local resource "a" is used by multiple tasks or collides with multiple definitions + --> $DIR/local-collision-2.rs:10:9 + | +10 | a: u32, + | ^ + +error: Local resource "a" is used by multiple tasks or collides with multiple definitions + --> $DIR/local-collision-2.rs:18:21 + | +18 | #[init(local = [a: u16 = 2])] + | ^ + +error: Local resource "a" is used by multiple tasks or collides with multiple definitions + --> $DIR/local-collision-2.rs:15:21 + | +15 | #[task(local = [a: u8 = 3])] + | ^ diff --git a/macros/ui/local-collision.rs b/macros/ui/local-collision.rs new file mode 100644 index 0000000..7dbe976 --- /dev/null +++ b/macros/ui/local-collision.rs @@ -0,0 +1,21 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local { + a: u32, + } + + #[task(local = [a])] + fn foo(_: foo::Context) {} + + #[task(local = [a: u8 = 3])] + fn bar(_: bar::Context) {} + + #[init] + fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {} +} diff --git a/macros/ui/local-collision.stderr b/macros/ui/local-collision.stderr new file mode 100644 index 0000000..1ba1da9 --- /dev/null +++ b/macros/ui/local-collision.stderr @@ -0,0 +1,11 @@ +error: Local resource "a" is used by multiple tasks or collides with multiple definitions + --> $DIR/local-collision.rs:10:9 + | +10 | a: u32, + | ^ + +error: Local resource "a" is used by multiple tasks or collides with multiple definitions + --> $DIR/local-collision.rs:16:21 + | +16 | #[task(local = [a: u8 = 3])] + | ^ diff --git a/macros/ui/local-malformed-1.rs b/macros/ui/local-malformed-1.rs new file mode 100644 index 0000000..7efcd9c --- /dev/null +++ b/macros/ui/local-malformed-1.rs @@ -0,0 +1,16 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[task(local = [a:])] + fn foo(_: foo::Context) {} + + #[init] + fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {} +} diff --git a/macros/ui/local-malformed-1.stderr b/macros/ui/local-malformed-1.stderr new file mode 100644 index 0000000..d15c324 --- /dev/null +++ b/macros/ui/local-malformed-1.stderr @@ -0,0 +1,5 @@ +error: unexpected end of input, expected one of: `for`, parentheses, `fn`, `unsafe`, `extern`, identifier, `::`, `<`, square brackets, `*`, `&`, `!`, `impl`, `_`, lifetime + --> ui/local-malformed-1.rs:11:23 + | +11 | #[task(local = [a:])] + | ^ diff --git a/macros/ui/local-malformed-2.rs b/macros/ui/local-malformed-2.rs new file mode 100644 index 0000000..ce5f891 --- /dev/null +++ b/macros/ui/local-malformed-2.rs @@ -0,0 +1,16 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[task(local = [a: u32])] + fn foo(_: foo::Context) {} + + #[init] + fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {} +} diff --git a/macros/ui/local-malformed-2.stderr b/macros/ui/local-malformed-2.stderr new file mode 100644 index 0000000..ceb0405 --- /dev/null +++ b/macros/ui/local-malformed-2.stderr @@ -0,0 +1,5 @@ +error: malformed, expected 'IDENT: TYPE = EXPR' + --> ui/local-malformed-2.rs:11:21 + | +11 | #[task(local = [a: u32])] + | ^ diff --git a/macros/ui/local-malformed-3.rs b/macros/ui/local-malformed-3.rs new file mode 100644 index 0000000..935dc2c --- /dev/null +++ b/macros/ui/local-malformed-3.rs @@ -0,0 +1,16 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[task(local = [a: u32 =])] + fn foo(_: foo::Context) {} + + #[init] + fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {} +} diff --git a/macros/ui/local-malformed-3.stderr b/macros/ui/local-malformed-3.stderr new file mode 100644 index 0000000..61af4f3 --- /dev/null +++ b/macros/ui/local-malformed-3.stderr @@ -0,0 +1,5 @@ +error: unexpected end of input, expected expression + --> ui/local-malformed-3.rs:11:29 + | +11 | #[task(local = [a: u32 =])] + | ^ diff --git a/macros/ui/local-malformed-4.rs b/macros/ui/local-malformed-4.rs new file mode 100644 index 0000000..49661b5 --- /dev/null +++ b/macros/ui/local-malformed-4.rs @@ -0,0 +1,16 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[task(local = [a = u32])] + fn foo(_: foo::Context) {} + + #[init] + fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {} +} diff --git a/macros/ui/local-malformed-4.stderr b/macros/ui/local-malformed-4.stderr new file mode 100644 index 0000000..0f7d9e7 --- /dev/null +++ b/macros/ui/local-malformed-4.stderr @@ -0,0 +1,5 @@ +error: malformed, expected a type + --> ui/local-malformed-4.rs:11:21 + | +11 | #[task(local = [a = u32])] + | ^ diff --git a/macros/ui/local-not-declared.rs b/macros/ui/local-not-declared.rs new file mode 100644 index 0000000..5a38b3d --- /dev/null +++ b/macros/ui/local-not-declared.rs @@ -0,0 +1,16 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[task(local = [A])] + fn foo(_: foo::Context) {} + + #[init] + fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {} +} diff --git a/macros/ui/local-not-declared.stderr b/macros/ui/local-not-declared.stderr new file mode 100644 index 0000000..540b4bb --- /dev/null +++ b/macros/ui/local-not-declared.stderr @@ -0,0 +1,5 @@ +error: this local resource has NOT been declared + --> $DIR/local-not-declared.rs:11:21 + | +11 | #[task(local = [A])] + | ^ diff --git a/macros/ui/local-pub.rs b/macros/ui/local-pub.rs new file mode 100644 index 0000000..8c51754 --- /dev/null +++ b/macros/ui/local-pub.rs @@ -0,0 +1,9 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[local] + struct Local { + pub x: u32, + } +} diff --git a/macros/ui/local-pub.stderr b/macros/ui/local-pub.stderr new file mode 100644 index 0000000..041bc59 --- /dev/null +++ b/macros/ui/local-pub.stderr @@ -0,0 +1,5 @@ +error: this field must have inherited / private visibility + --> $DIR/local-pub.rs:7:13 + | +7 | pub x: u32, + | ^ diff --git a/macros/ui/local-shared-attribute.rs b/macros/ui/local-shared-attribute.rs new file mode 100644 index 0000000..1ccce4a --- /dev/null +++ b/macros/ui/local-shared-attribute.rs @@ -0,0 +1,14 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[task(local = [ + #[test] + a: u32 = 0, // Ok + #[test] + b, // Error + ])] + fn foo(_: foo::Context) { + + } +} diff --git a/macros/ui/local-shared-attribute.stderr b/macros/ui/local-shared-attribute.stderr new file mode 100644 index 0000000..5c15fb5 --- /dev/null +++ b/macros/ui/local-shared-attribute.stderr @@ -0,0 +1,5 @@ +error: attributes are not supported here + --> $DIR/local-shared-attribute.rs:8:9 + | +8 | #[test] + | ^ diff --git a/macros/ui/local-shared.rs b/macros/ui/local-shared.rs new file mode 100644 index 0000000..f6fb491 --- /dev/null +++ b/macros/ui/local-shared.rs @@ -0,0 +1,28 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local { + l1: u32, + l2: u32, + } + + #[init] + fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {} + + // l2 ok + #[idle(local = [l2])] + fn idle(cx: idle::Context) -> ! {} + + // l1 rejected (not local) + #[task(priority = 1, local = [l1])] + fn uart0(cx: uart0::Context) {} + + // l1 rejected (not lock_free) + #[task(priority = 2, local = [l1])] + fn uart1(cx: uart1::Context) {} +} diff --git a/macros/ui/local-shared.stderr b/macros/ui/local-shared.stderr new file mode 100644 index 0000000..0d22db3 --- /dev/null +++ b/macros/ui/local-shared.stderr @@ -0,0 +1,11 @@ +error: Local resource "l1" is used by multiple tasks or collides with multiple definitions + --> $DIR/local-shared.rs:22:35 + | +22 | #[task(priority = 1, local = [l1])] + | ^^ + +error: Local resource "l1" is used by multiple tasks or collides with multiple definitions + --> $DIR/local-shared.rs:26:35 + | +26 | #[task(priority = 2, local = [l1])] + | ^^ diff --git a/macros/ui/monotonic-binds-collision-task.rs b/macros/ui/monotonic-binds-collision-task.rs new file mode 100644 index 0000000..57c59c1 --- /dev/null +++ b/macros/ui/monotonic-binds-collision-task.rs @@ -0,0 +1,10 @@ +#![no_main] + +#[rtic_macros::mock_app(parse_extern_interrupt, parse_binds, device = mock)] +mod app { + #[monotonic(binds = Tim1)] + type Fast1 = hal::Tim1Monotonic; + + #[task(binds = Tim1)] + fn foo(_: foo::Context) {} +} diff --git a/macros/ui/monotonic-binds-collision-task.stderr b/macros/ui/monotonic-binds-collision-task.stderr new file mode 100644 index 0000000..8f84986 --- /dev/null +++ b/macros/ui/monotonic-binds-collision-task.stderr @@ -0,0 +1,5 @@ +error: this interrupt is already bound + --> $DIR/monotonic-binds-collision-task.rs:8:20 + | +8 | #[task(binds = Tim1)] + | ^^^^ diff --git a/macros/ui/monotonic-binds-collision.rs b/macros/ui/monotonic-binds-collision.rs new file mode 100644 index 0000000..4e54814 --- /dev/null +++ b/macros/ui/monotonic-binds-collision.rs @@ -0,0 +1,10 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[monotonic(binds = Tim1)] + type Fast1 = hal::Tim1Monotonic; + + #[monotonic(binds = Tim1)] + type Fast2 = hal::Tim2Monotonic; +} diff --git a/macros/ui/monotonic-binds-collision.stderr b/macros/ui/monotonic-binds-collision.stderr new file mode 100644 index 0000000..62b764b --- /dev/null +++ b/macros/ui/monotonic-binds-collision.stderr @@ -0,0 +1,5 @@ +error: this interrupt is already bound + --> $DIR/monotonic-binds-collision.rs:8:25 + | +8 | #[monotonic(binds = Tim1)] + | ^^^^ diff --git a/macros/ui/monotonic-double-binds.rs b/macros/ui/monotonic-double-binds.rs new file mode 100644 index 0000000..1705dc4 --- /dev/null +++ b/macros/ui/monotonic-double-binds.rs @@ -0,0 +1,7 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[monotonic(binds = Tim1, binds = Tim2)] + type Fast = hal::Tim1Monotonic; +} diff --git a/macros/ui/monotonic-double-binds.stderr b/macros/ui/monotonic-double-binds.stderr new file mode 100644 index 0000000..c7313df --- /dev/null +++ b/macros/ui/monotonic-double-binds.stderr @@ -0,0 +1,5 @@ +error: argument appears more than once + --> $DIR/monotonic-double-binds.rs:5:31 + | +5 | #[monotonic(binds = Tim1, binds = Tim2)] + | ^^^^^ diff --git a/macros/ui/monotonic-double-default.rs b/macros/ui/monotonic-double-default.rs new file mode 100644 index 0000000..dc4eac6 --- /dev/null +++ b/macros/ui/monotonic-double-default.rs @@ -0,0 +1,7 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[monotonic(binds = Tim1, default = true, default = false)] + type Fast = hal::Tim1Monotonic; +} diff --git a/macros/ui/monotonic-double-default.stderr b/macros/ui/monotonic-double-default.stderr new file mode 100644 index 0000000..9819d04 --- /dev/null +++ b/macros/ui/monotonic-double-default.stderr @@ -0,0 +1,5 @@ +error: argument appears more than once + --> $DIR/monotonic-double-default.rs:5:47 + | +5 | #[monotonic(binds = Tim1, default = true, default = false)] + | ^^^^^^^ diff --git a/macros/ui/monotonic-double-prio.rs b/macros/ui/monotonic-double-prio.rs new file mode 100644 index 0000000..4330ddb --- /dev/null +++ b/macros/ui/monotonic-double-prio.rs @@ -0,0 +1,7 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[monotonic(binds = Tim1, priority = 1, priority = 2)] + type Fast = hal::Tim1Monotonic; +} diff --git a/macros/ui/monotonic-double-prio.stderr b/macros/ui/monotonic-double-prio.stderr new file mode 100644 index 0000000..fa888e2 --- /dev/null +++ b/macros/ui/monotonic-double-prio.stderr @@ -0,0 +1,5 @@ +error: argument appears more than once + --> $DIR/monotonic-double-prio.rs:5:45 + | +5 | #[monotonic(binds = Tim1, priority = 1, priority = 2)] + | ^^^^^^^^ diff --git a/macros/ui/monotonic-double.rs b/macros/ui/monotonic-double.rs new file mode 100644 index 0000000..3c43fae --- /dev/null +++ b/macros/ui/monotonic-double.rs @@ -0,0 +1,10 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[monotonic(binds = Tim1)] + type Fast = hal::Tim1Monotonic; + + #[monotonic(binds = Tim1)] + type Fast = hal::Tim1Monotonic; +} diff --git a/macros/ui/monotonic-double.stderr b/macros/ui/monotonic-double.stderr new file mode 100644 index 0000000..9fab84c --- /dev/null +++ b/macros/ui/monotonic-double.stderr @@ -0,0 +1,5 @@ +error: `#[monotonic(...)]` on a specific type must appear at most once + --> ui/monotonic-double.rs:9:10 + | +9 | type Fast = hal::Tim1Monotonic; + | ^^^^ diff --git a/macros/ui/monotonic-name-collision.rs b/macros/ui/monotonic-name-collision.rs new file mode 100644 index 0000000..d8d4431 --- /dev/null +++ b/macros/ui/monotonic-name-collision.rs @@ -0,0 +1,10 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[monotonic(binds = Tim1)] + type Fast1 = hal::Tim1Monotonic; + + #[monotonic(binds = Tim2)] + type Fast1 = hal::Tim2Monotonic; +} diff --git a/macros/ui/monotonic-name-collision.stderr b/macros/ui/monotonic-name-collision.stderr new file mode 100644 index 0000000..6557ee5 --- /dev/null +++ b/macros/ui/monotonic-name-collision.stderr @@ -0,0 +1,5 @@ +error: `#[monotonic(...)]` on a specific type must appear at most once + --> ui/monotonic-name-collision.rs:9:10 + | +9 | type Fast1 = hal::Tim2Monotonic; + | ^^^^^ diff --git a/macros/ui/monotonic-no-binds.rs b/macros/ui/monotonic-no-binds.rs new file mode 100644 index 0000000..462d73e --- /dev/null +++ b/macros/ui/monotonic-no-binds.rs @@ -0,0 +1,7 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[monotonic()] + type Fast = hal::Tim1Monotonic; +} diff --git a/macros/ui/monotonic-no-binds.stderr b/macros/ui/monotonic-no-binds.stderr new file mode 100644 index 0000000..0ef7b60 --- /dev/null +++ b/macros/ui/monotonic-no-binds.stderr @@ -0,0 +1,5 @@ +error: `binds = ...` is missing + --> $DIR/monotonic-no-binds.rs:5:17 + | +5 | #[monotonic()] + | ^ diff --git a/macros/ui/monotonic-no-paran.rs b/macros/ui/monotonic-no-paran.rs new file mode 100644 index 0000000..e294bc8 --- /dev/null +++ b/macros/ui/monotonic-no-paran.rs @@ -0,0 +1,8 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[monotonic] + type Fast = hal::Tim1Monotonic; +} + diff --git a/macros/ui/monotonic-no-paran.stderr b/macros/ui/monotonic-no-paran.stderr new file mode 100644 index 0000000..c2b32c5 --- /dev/null +++ b/macros/ui/monotonic-no-paran.stderr @@ -0,0 +1,5 @@ +error: expected opening ( in #[monotonic( ... )] + --> ui/monotonic-no-paran.rs:5:7 + | +5 | #[monotonic] + | ^^^^^^^^^ diff --git a/macros/ui/monotonic-timer-collision.rs b/macros/ui/monotonic-timer-collision.rs new file mode 100644 index 0000000..5663ad7 --- /dev/null +++ b/macros/ui/monotonic-timer-collision.rs @@ -0,0 +1,10 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[monotonic(binds = Tim1)] + type Fast1 = hal::Tim1Monotonic; + + #[monotonic(binds = Tim2)] + type Fast2 = hal::Tim1Monotonic; +} diff --git a/macros/ui/monotonic-timer-collision.stderr b/macros/ui/monotonic-timer-collision.stderr new file mode 100644 index 0000000..239b96b --- /dev/null +++ b/macros/ui/monotonic-timer-collision.stderr @@ -0,0 +1,5 @@ +error: this type is already used by another monotonic + --> $DIR/monotonic-timer-collision.rs:9:18 + | +9 | type Fast2 = hal::Tim1Monotonic; + | ^^^ diff --git a/macros/ui/monotonic-with-attrs.rs b/macros/ui/monotonic-with-attrs.rs new file mode 100644 index 0000000..7c63fbb --- /dev/null +++ b/macros/ui/monotonic-with-attrs.rs @@ -0,0 +1,8 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[no_mangle] + #[monotonic(binds = Tim1)] + type Fast = hal::Tim1Monotonic; +} diff --git a/macros/ui/monotonic-with-attrs.stderr b/macros/ui/monotonic-with-attrs.stderr new file mode 100644 index 0000000..62655d8 --- /dev/null +++ b/macros/ui/monotonic-with-attrs.stderr @@ -0,0 +1,5 @@ +error: Monotonic does not support attributes other than `#[cfg]` + --> $DIR/monotonic-with-attrs.rs:5:7 + | +5 | #[no_mangle] + | ^^^^^^^^^ diff --git a/macros/ui/pub-local.stderr b/macros/ui/pub-local.stderr new file mode 100644 index 0000000..dee818c --- /dev/null +++ b/macros/ui/pub-local.stderr @@ -0,0 +1,5 @@ +error: this field must have inherited / private visibility + --> $DIR/pub-local.rs:7:13 + | +7 | pub x: u32, + | ^ diff --git a/macros/ui/pub-shared.stderr b/macros/ui/pub-shared.stderr new file mode 100644 index 0000000..0fdb1ff --- /dev/null +++ b/macros/ui/pub-shared.stderr @@ -0,0 +1,5 @@ +error: this field must have inherited / private visibility + --> $DIR/pub-shared.rs:7:13 + | +7 | pub x: u32, + | ^ diff --git a/macros/ui/shared-lock-free.rs b/macros/ui/shared-lock-free.rs new file mode 100644 index 0000000..c7f8a16 --- /dev/null +++ b/macros/ui/shared-lock-free.rs @@ -0,0 +1,38 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared { + // An exclusive, early resource + #[lock_free] + e1: u32, + + // An exclusive, late resource + #[lock_free] + e2: u32, + } + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {} + + // e2 ok + #[idle(shared = [e2])] + fn idle(cx: idle::Context) -> ! { + debug::exit(debug::EXIT_SUCCESS); + loop {} + } + + // e1 rejected (not lock_free) + #[task(priority = 1, shared = [e1])] + fn uart0(cx: uart0::Context) { + *cx.resources.e1 += 10; + } + + // e1 rejected (not lock_free) + #[task(priority = 2, shared = [e1])] + fn uart1(cx: uart1::Context) {} +} diff --git a/macros/ui/shared-lock-free.stderr b/macros/ui/shared-lock-free.stderr new file mode 100644 index 0000000..c6820e8 --- /dev/null +++ b/macros/ui/shared-lock-free.stderr @@ -0,0 +1,17 @@ +error: Lock free shared resource "e1" is used by tasks at different priorities + --> $DIR/shared-lock-free.rs:9:9 + | +9 | e1: u32, + | ^^ + +error: Shared resource "e1" is declared lock free but used by tasks at different priorities + --> $DIR/shared-lock-free.rs:30:36 + | +30 | #[task(priority = 1, shared = [e1])] + | ^^ + +error: Shared resource "e1" is declared lock free but used by tasks at different priorities + --> $DIR/shared-lock-free.rs:36:36 + | +36 | #[task(priority = 2, shared = [e1])] + | ^^ diff --git a/macros/ui/shared-not-declared.rs b/macros/ui/shared-not-declared.rs new file mode 100644 index 0000000..aca4178 --- /dev/null +++ b/macros/ui/shared-not-declared.rs @@ -0,0 +1,16 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[task(shared = [A])] + fn foo(_: foo::Context) {} + + #[init] + fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {} +} diff --git a/macros/ui/shared-not-declared.stderr b/macros/ui/shared-not-declared.stderr new file mode 100644 index 0000000..c174251 --- /dev/null +++ b/macros/ui/shared-not-declared.stderr @@ -0,0 +1,5 @@ +error: this shared resource has NOT been declared + --> $DIR/shared-not-declared.rs:11:22 + | +11 | #[task(shared = [A])] + | ^ diff --git a/macros/ui/shared-pub.rs b/macros/ui/shared-pub.rs new file mode 100644 index 0000000..10351fd --- /dev/null +++ b/macros/ui/shared-pub.rs @@ -0,0 +1,9 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared { + pub x: u32, + } +} diff --git a/macros/ui/shared-pub.stderr b/macros/ui/shared-pub.stderr new file mode 100644 index 0000000..8f761c6 --- /dev/null +++ b/macros/ui/shared-pub.stderr @@ -0,0 +1,5 @@ +error: this field must have inherited / private visibility + --> $DIR/shared-pub.rs:7:13 + | +7 | pub x: u32, + | ^ diff --git a/macros/ui/task-bind.rs b/macros/ui/task-bind.rs new file mode 100644 index 0000000..de60524 --- /dev/null +++ b/macros/ui/task-bind.rs @@ -0,0 +1,7 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[task(binds = UART0)] + fn foo(_: foo::Context) {} +} diff --git a/macros/ui/task-bind.stderr b/macros/ui/task-bind.stderr new file mode 100644 index 0000000..60cfdc8 --- /dev/null +++ b/macros/ui/task-bind.stderr @@ -0,0 +1,5 @@ +error: Unexpected bind in task argument. Binds are only parsed if Settings::parse_binds is set. + --> $DIR/task-bind.rs:5:12 + | +5 | #[task(binds = UART0)] + | ^^^^^ diff --git a/macros/ui/task-divergent.rs b/macros/ui/task-divergent.rs new file mode 100644 index 0000000..5a471f3 --- /dev/null +++ b/macros/ui/task-divergent.rs @@ -0,0 +1,9 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[task] + fn foo(_: foo::Context) -> ! { + loop {} + } +} diff --git a/macros/ui/task-divergent.stderr b/macros/ui/task-divergent.stderr new file mode 100644 index 0000000..b25ca5d --- /dev/null +++ b/macros/ui/task-divergent.stderr @@ -0,0 +1,5 @@ +error: this task handler must have type signature `(async) fn(foo::Context, ..)` + --> ui/task-divergent.rs:6:8 + | +6 | fn foo(_: foo::Context) -> ! { + | ^^^ diff --git a/macros/ui/task-double-capacity.rs b/macros/ui/task-double-capacity.rs new file mode 100644 index 0000000..806d973 --- /dev/null +++ b/macros/ui/task-double-capacity.rs @@ -0,0 +1,7 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[task(capacity = 1, capacity = 2)] + fn foo(_: foo::Context) {} +} diff --git a/macros/ui/task-double-capacity.stderr b/macros/ui/task-double-capacity.stderr new file mode 100644 index 0000000..f73bca5 --- /dev/null +++ b/macros/ui/task-double-capacity.stderr @@ -0,0 +1,5 @@ +error: argument appears more than once + --> $DIR/task-double-capacity.rs:5:26 + | +5 | #[task(capacity = 1, capacity = 2)] + | ^^^^^^^^ diff --git a/macros/ui/task-double-local.rs b/macros/ui/task-double-local.rs new file mode 100644 index 0000000..2e465d7 --- /dev/null +++ b/macros/ui/task-double-local.rs @@ -0,0 +1,7 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[task(local = [A], local = [B])] + fn foo(_: foo::Context) {} +} diff --git a/macros/ui/task-double-local.stderr b/macros/ui/task-double-local.stderr new file mode 100644 index 0000000..654ed33 --- /dev/null +++ b/macros/ui/task-double-local.stderr @@ -0,0 +1,5 @@ +error: argument appears more than once + --> $DIR/task-double-local.rs:5:25 + | +5 | #[task(local = [A], local = [B])] + | ^^^^^ diff --git a/macros/ui/task-double-priority.rs b/macros/ui/task-double-priority.rs new file mode 100644 index 0000000..0a2ef0d --- /dev/null +++ b/macros/ui/task-double-priority.rs @@ -0,0 +1,7 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[task(priority = 1, priority = 2)] + fn foo(_: foo::Context) {} +} diff --git a/macros/ui/task-double-priority.stderr b/macros/ui/task-double-priority.stderr new file mode 100644 index 0000000..3d06dc6 --- /dev/null +++ b/macros/ui/task-double-priority.stderr @@ -0,0 +1,5 @@ +error: argument appears more than once + --> $DIR/task-double-priority.rs:5:26 + | +5 | #[task(priority = 1, priority = 2)] + | ^^^^^^^^ diff --git a/macros/ui/task-double-shared.rs b/macros/ui/task-double-shared.rs new file mode 100644 index 0000000..3b4d411 --- /dev/null +++ b/macros/ui/task-double-shared.rs @@ -0,0 +1,7 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[task(shared = [A], shared = [B])] + fn foo(_: foo::Context) {} +} diff --git a/macros/ui/task-double-shared.stderr b/macros/ui/task-double-shared.stderr new file mode 100644 index 0000000..6952f06 --- /dev/null +++ b/macros/ui/task-double-shared.stderr @@ -0,0 +1,5 @@ +error: argument appears more than once + --> $DIR/task-double-shared.rs:5:26 + | +5 | #[task(shared = [A], shared = [B])] + | ^^^^^^ diff --git a/macros/ui/task-idle.rs b/macros/ui/task-idle.rs new file mode 100644 index 0000000..3be6e28 --- /dev/null +++ b/macros/ui/task-idle.rs @@ -0,0 +1,13 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[idle] + fn foo(_: foo::Context) -> ! { + loop {} + } + + // name collides with `#[idle]` function + #[task] + fn foo(_: foo::Context) {} +} diff --git a/macros/ui/task-idle.stderr b/macros/ui/task-idle.stderr new file mode 100644 index 0000000..ba4fc94 --- /dev/null +++ b/macros/ui/task-idle.stderr @@ -0,0 +1,5 @@ +error: this identifier has already been used + --> $DIR/task-idle.rs:12:8 + | +12 | fn foo(_: foo::Context) {} + | ^^^ diff --git a/macros/ui/task-init.rs b/macros/ui/task-init.rs new file mode 100644 index 0000000..bab3805 --- /dev/null +++ b/macros/ui/task-init.rs @@ -0,0 +1,17 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn foo(_: foo::Context) -> (Shared, Local, foo::Monotonics) {} + + // name collides with `#[idle]` function + #[task] + fn foo(_: foo::Context) {} +} diff --git a/macros/ui/task-init.stderr b/macros/ui/task-init.stderr new file mode 100644 index 0000000..911af37 --- /dev/null +++ b/macros/ui/task-init.stderr @@ -0,0 +1,5 @@ +error: this identifier has already been used + --> $DIR/task-init.rs:16:8 + | +16 | fn foo(_: foo::Context) {} + | ^^^ diff --git a/macros/ui/task-interrupt-same-prio-spawn.rs b/macros/ui/task-interrupt-same-prio-spawn.rs new file mode 100644 index 0000000..741e60e --- /dev/null +++ b/macros/ui/task-interrupt-same-prio-spawn.rs @@ -0,0 +1,7 @@ +#![no_main] + +#[rtic_macros::mock_app(parse_binds, device = mock)] +mod app { + #[task(binds = SysTick, only_same_priority_spawn_please_fix_me)] + fn foo(_: foo::Context) {} +} diff --git a/macros/ui/task-interrupt-same-prio-spawn.stderr b/macros/ui/task-interrupt-same-prio-spawn.stderr new file mode 100644 index 0000000..171b850 --- /dev/null +++ b/macros/ui/task-interrupt-same-prio-spawn.stderr @@ -0,0 +1,5 @@ +error: hardware tasks are not allowed to be spawned, `only_same_priority_spawn_please_fix_me` is only for software tasks + --> ui/task-interrupt-same-prio-spawn.rs:5:29 + | +5 | #[task(binds = SysTick, only_same_priority_spawn_please_fix_me)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/macros/ui/task-interrupt.rs b/macros/ui/task-interrupt.rs new file mode 100644 index 0000000..71fef9a --- /dev/null +++ b/macros/ui/task-interrupt.rs @@ -0,0 +1,10 @@ +#![no_main] + +#[rtic_macros::mock_app(parse_binds, device = mock)] +mod app { + #[task(binds = SysTick)] + fn foo(_: foo::Context) {} + + #[task] + fn foo(_: foo::Context) {} +} diff --git a/macros/ui/task-interrupt.stderr b/macros/ui/task-interrupt.stderr new file mode 100644 index 0000000..6efb0f9 --- /dev/null +++ b/macros/ui/task-interrupt.stderr @@ -0,0 +1,5 @@ +error: this task is defined multiple times + --> $DIR/task-interrupt.rs:9:8 + | +9 | fn foo(_: foo::Context) {} + | ^^^ diff --git a/macros/ui/task-no-context.rs b/macros/ui/task-no-context.rs new file mode 100644 index 0000000..e2da625 --- /dev/null +++ b/macros/ui/task-no-context.rs @@ -0,0 +1,7 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[task] + fn foo() {} +} diff --git a/macros/ui/task-no-context.stderr b/macros/ui/task-no-context.stderr new file mode 100644 index 0000000..8bf3438 --- /dev/null +++ b/macros/ui/task-no-context.stderr @@ -0,0 +1,5 @@ +error: this task handler must have type signature `(async) fn(foo::Context, ..)` + --> ui/task-no-context.rs:6:8 + | +6 | fn foo() {} + | ^^^ diff --git a/macros/ui/task-priority-too-high.rs b/macros/ui/task-priority-too-high.rs new file mode 100644 index 0000000..8c32beb --- /dev/null +++ b/macros/ui/task-priority-too-high.rs @@ -0,0 +1,7 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[task(priority = 256)] + fn foo(_: foo::Context) {} +} diff --git a/macros/ui/task-priority-too-high.stderr b/macros/ui/task-priority-too-high.stderr new file mode 100644 index 0000000..5790c88 --- /dev/null +++ b/macros/ui/task-priority-too-high.stderr @@ -0,0 +1,5 @@ +error: this literal must be in the range 0...255 + --> ui/task-priority-too-high.rs:5:23 + | +5 | #[task(priority = 256)] + | ^^^ diff --git a/macros/ui/task-priority-too-low.rs b/macros/ui/task-priority-too-low.rs new file mode 100644 index 0000000..beed4de --- /dev/null +++ b/macros/ui/task-priority-too-low.rs @@ -0,0 +1,7 @@ +#![no_main] + +#[rtic_macros::mock_app(parse_binds, device = mock)] +mod app { + #[task(binds = UART0, priority = 0)] + fn foo(_: foo::Context) {} +} diff --git a/macros/ui/task-priority-too-low.stderr b/macros/ui/task-priority-too-low.stderr new file mode 100644 index 0000000..85c8660 --- /dev/null +++ b/macros/ui/task-priority-too-low.stderr @@ -0,0 +1,5 @@ +error: hardware tasks are not allowed to be at priority 0 + --> ui/task-priority-too-low.rs:5:38 + | +5 | #[task(binds = UART0, priority = 0)] + | ^ diff --git a/macros/ui/task-pub.rs b/macros/ui/task-pub.rs new file mode 100644 index 0000000..3cbd523 --- /dev/null +++ b/macros/ui/task-pub.rs @@ -0,0 +1,7 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[task] + pub fn foo(_: foo::Context) {} +} diff --git a/macros/ui/task-pub.stderr b/macros/ui/task-pub.stderr new file mode 100644 index 0000000..56e09b1 --- /dev/null +++ b/macros/ui/task-pub.stderr @@ -0,0 +1,5 @@ +error: this task handler must have type signature `(async) fn(foo::Context, ..)` + --> ui/task-pub.rs:6:12 + | +6 | pub fn foo(_: foo::Context) {} + | ^^^ diff --git a/macros/ui/task-unsafe.rs b/macros/ui/task-unsafe.rs new file mode 100644 index 0000000..44255f0 --- /dev/null +++ b/macros/ui/task-unsafe.rs @@ -0,0 +1,7 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[task] + unsafe fn foo(_: foo::Context) {} +} diff --git a/macros/ui/task-unsafe.stderr b/macros/ui/task-unsafe.stderr new file mode 100644 index 0000000..424c5af --- /dev/null +++ b/macros/ui/task-unsafe.stderr @@ -0,0 +1,5 @@ +error: this task handler must have type signature `(async) fn(foo::Context, ..)` + --> ui/task-unsafe.rs:6:15 + | +6 | unsafe fn foo(_: foo::Context) {} + | ^^^ diff --git a/src/lib.rs b/src/lib.rs index 0c0d0cc..7d12d9a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,123 +1,14 @@ -//! Real-Time Interrupt-driven Concurrency (RTIC) framework for ARM Cortex-M microcontrollers. -//! -//! **IMPORTANT**: This crate is published as [`cortex-m-rtic`] on crates.io but the name of the -//! library is `rtic`. -//! -//! [`cortex-m-rtic`]: https://crates.io/crates/cortex-m-rtic -//! -//! The user level documentation can be found [here]. -//! -//! [here]: https://rtic.rs -//! -//! Don't forget to check the documentation of the `#[app]` attribute (listed under the reexports -//! section), which is the main component of the framework. -//! -//! # Minimum Supported Rust Version (MSRV) -//! -//! This crate is compiled and tested with the latest toolchain (rolling) as of the release date. -//! If you run into compilation errors, try the latest stable release of the rust toolchain. -//! -//! # Semantic Versioning -//! -//! Like the Rust project, this crate adheres to [SemVer]: breaking changes in the API and semantics -//! require a *semver bump* (since 1.0.0 a new major version release), with the exception of breaking changes -//! that fix soundness issues -- those are considered bug fixes and can be landed in a new patch -//! release. -//! -//! [SemVer]: https://semver.org/spec/v2.0.0.html - -#![deny(missing_docs)] -#![deny(rust_2021_compatibility)] -#![deny(rust_2018_compatibility)] -#![deny(rust_2018_idioms)] -#![no_std] -#![doc( - html_logo_url = "https://raw.githubusercontent.com/rtic-rs/cortex-m-rtic/master/book/en/src/RTIC.svg", - html_favicon_url = "https://raw.githubusercontent.com/rtic-rs/cortex-m-rtic/master/book/en/src/RTIC.svg" -)] -//deny_warnings_placeholder_for_ci -#![allow(clippy::inline_always)] - -use cortex_m::{interrupt::InterruptNumber, peripheral::NVIC}; -pub use cortex_m_rtic_macros::app; -pub use rtic_core::{prelude as mutex_prelude, Exclusive, Mutex}; -pub use rtic_monotonic::{self, Monotonic}; - -/// module `mutex::prelude` provides `Mutex` and multi-lock variants. Recommended over `mutex_prelude` -pub mod mutex { - pub use rtic_core::prelude; - pub use rtic_core::Mutex; -} - -#[doc(hidden)] -pub mod export; -#[doc(hidden)] -mod tq; - -/// Sets the given `interrupt` as pending -/// -/// This is a convenience function around -/// [`NVIC::pend`](../cortex_m/peripheral/struct.NVIC.html#method.pend) -pub fn pend(interrupt: I) -where - I: InterruptNumber, -{ - NVIC::pend(interrupt); +pub fn add(left: usize, right: usize) -> usize { + left + right } -use core::cell::UnsafeCell; +#[cfg(test)] +mod tests { + use super::*; -/// Internal replacement for `static mut T` -/// -/// Used to represent RTIC Resources -/// -/// Soundness: -/// 1) Unsafe API for internal use only -/// 2) ``get_mut(&self) -> *mut T`` -/// returns a raw mutable pointer to the inner T -/// casting to &mut T is under control of RTIC -/// RTIC ensures &mut T to be unique under Rust aliasing rules. -/// -/// Implementation uses the underlying ``UnsafeCell`` -/// self.0.get() -> *mut T -/// -/// 3) get(&self) -> *const T -/// returns a raw immutable (const) pointer to the inner T -/// casting to &T is under control of RTIC -/// RTIC ensures &T to be shared under Rust aliasing rules. -/// -/// Implementation uses the underlying ``UnsafeCell`` -/// self.0.get() -> *mut T, demoted to *const T -/// -#[repr(transparent)] -pub struct RacyCell(UnsafeCell); - -impl RacyCell { - /// Create a ``RacyCell`` - #[inline(always)] - pub const fn new(value: T) -> Self { - RacyCell(UnsafeCell::new(value)) - } - - /// Get `*mut T` - /// - /// # Safety - /// - /// See documentation notes for [`RacyCell`] - #[inline(always)] - pub unsafe fn get_mut(&self) -> *mut T { - self.0.get() - } - - /// Get `*const T` - /// - /// # Safety - /// - /// See documentation notes for [`RacyCell`] - #[inline(always)] - pub unsafe fn get(&self) -> *const T { - self.0.get() + #[test] + fn it_works() { + let result = add(2, 2); + assert_eq!(result, 4); } } - -unsafe impl Sync for RacyCell {} -- cgit v1.2.3 From 582c602912592ec7ebea3096aefa02aea99c2143 Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Mon, 2 Jan 2023 14:34:05 +0100 Subject: Old xtask test pass --- ci/expected/async-delay.run | 7 + ci/expected/async-infinite-loop.run | 6 + ci/expected/async-task-multiple-prios.run | 5 + ci/expected/async-task.run | 3 + ci/expected/async-timeout.run | 5 + ci/expected/periodic-at.run | 6 +- ci/expected/periodic-at2.run | 10 +- examples/async-delay.rs | 67 +++++ examples/async-infinite-loop.rs | 57 ++++ examples/async-task-multiple-prios.rs | 76 +++++ examples/async-task.rs | 61 ++++ examples/async-timeout.rs | 87 ++++++ examples/binds.rs | 13 +- examples/cancel-reschedule.rs | 9 +- examples/capacity.rs | 5 +- examples/cfg-whole-task.rs | 17 +- examples/common.rs | 7 +- examples/complex.rs | 54 ++-- examples/declared_locals.rs | 1 - examples/destructure.rs | 5 +- examples/extern_binds.rs | 12 +- examples/extern_spawn.rs | 3 +- examples/generics.rs | 10 +- examples/hardware.rs | 13 +- examples/idle-wfi.rs | 5 +- examples/idle.rs | 5 +- examples/init.rs | 3 +- examples/locals.rs | 11 +- examples/lock-free.rs | 5 +- examples/lock.rs | 11 +- examples/message.rs | 7 +- examples/message_passing.rs | 3 +- examples/multilock.rs | 3 +- examples/not-sync.rs | 4 - examples/only-shared-access.rs | 5 +- examples/periodic-at.rs | 7 +- examples/periodic-at2.rs | 11 +- examples/periodic.rs | 9 +- examples/peripherals-taken.rs | 4 +- examples/pool.rs | 2 - examples/preempt.rs | 10 +- examples/ramfunc.rs | 3 +- examples/resource-user-struct.rs | 5 +- examples/schedule.rs | 9 +- examples/shared.rs | 5 +- examples/spawn.rs | 5 +- examples/static.rs | 3 +- examples/t-binds.rs | 1 - examples/t-htask-main.rs | 2 - examples/t-idle-main.rs | 2 - examples/t-schedule.rs | 1 - examples/t-spawn.rs | 1 - examples/task.rs | 11 +- macros/src/codegen/local_resources_struct.rs | 1 - macros/src/syntax.rs | 21 -- macros/src/syntax/analyze.rs | 42 +-- src/export.rs | 134 ++++++++- src/lib.rs | 129 +++++++- src/sll.rs | 421 +++++++++++++++++++++++++++ src/tq.rs | 275 +++++++++++++---- ui/extern-interrupt-not-enough.stderr | 4 +- ui/task-priority-too-high.rs | 2 +- ui/task-priority-too-high.stderr | 8 + xtask/src/command.rs | 3 +- 64 files changed, 1417 insertions(+), 315 deletions(-) create mode 100644 ci/expected/async-delay.run create mode 100644 ci/expected/async-infinite-loop.run create mode 100644 ci/expected/async-task-multiple-prios.run create mode 100644 ci/expected/async-task.run create mode 100644 ci/expected/async-timeout.run create mode 100644 examples/async-delay.rs create mode 100644 examples/async-infinite-loop.rs create mode 100644 examples/async-task-multiple-prios.rs create mode 100644 examples/async-task.rs create mode 100644 examples/async-timeout.rs create mode 100644 src/sll.rs diff --git a/ci/expected/async-delay.run b/ci/expected/async-delay.run new file mode 100644 index 0000000..61852ab --- /dev/null +++ b/ci/expected/async-delay.run @@ -0,0 +1,7 @@ +init +hello from bar +hello from baz +hello from foo +bye from foo +bye from bar +bye from baz diff --git a/ci/expected/async-infinite-loop.run b/ci/expected/async-infinite-loop.run new file mode 100644 index 0000000..f9fd4e4 --- /dev/null +++ b/ci/expected/async-infinite-loop.run @@ -0,0 +1,6 @@ +init +hello from async 0 +hello from async 1 +hello from async 2 +hello from async 3 +hello from async 4 diff --git a/ci/expected/async-task-multiple-prios.run b/ci/expected/async-task-multiple-prios.run new file mode 100644 index 0000000..9b0f533 --- /dev/null +++ b/ci/expected/async-task-multiple-prios.run @@ -0,0 +1,5 @@ +init +hello from normal 2 +hello from async 2 +hello from normal 1 +hello from async 1 diff --git a/ci/expected/async-task.run b/ci/expected/async-task.run new file mode 100644 index 0000000..f7ce3a6 --- /dev/null +++ b/ci/expected/async-task.run @@ -0,0 +1,3 @@ +init +hello from normal +hello from async diff --git a/ci/expected/async-timeout.run b/ci/expected/async-timeout.run new file mode 100644 index 0000000..a807423 --- /dev/null +++ b/ci/expected/async-timeout.run @@ -0,0 +1,5 @@ +init +hello from bar +hello from foo +foo no timeout +bar timeout diff --git a/ci/expected/periodic-at.run b/ci/expected/periodic-at.run index 54020f9..bf5bb06 100644 --- a/ci/expected/periodic-at.run +++ b/ci/expected/periodic-at.run @@ -1,4 +1,4 @@ foo Instant { ticks: 0 } -foo Instant { ticks: 100 } -foo Instant { ticks: 200 } -foo Instant { ticks: 300 } +foo Instant { ticks: 10 } +foo Instant { ticks: 20 } +foo Instant { ticks: 30 } diff --git a/ci/expected/periodic-at2.run b/ci/expected/periodic-at2.run index 47adbef..6e56421 100644 --- a/ci/expected/periodic-at2.run +++ b/ci/expected/periodic-at2.run @@ -1,7 +1,7 @@ foo Instant { ticks: 0 } bar Instant { ticks: 10 } -foo Instant { ticks: 110 } -bar Instant { ticks: 120 } -foo Instant { ticks: 220 } -bar Instant { ticks: 230 } -foo Instant { ticks: 330 } +foo Instant { ticks: 30 } +bar Instant { ticks: 40 } +foo Instant { ticks: 60 } +bar Instant { ticks: 70 } +foo Instant { ticks: 90 } diff --git a/examples/async-delay.rs b/examples/async-delay.rs new file mode 100644 index 0000000..7802bda --- /dev/null +++ b/examples/async-delay.rs @@ -0,0 +1,67 @@ +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0, UART0], peripherals = true)] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use systick_monotonic::*; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[monotonic(binds = SysTick, default = true)] + type MyMono = Systick<100>; + + #[init] + fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { + hprintln!("init").unwrap(); + + foo::spawn().ok(); + bar::spawn().ok(); + baz::spawn().ok(); + + ( + Shared {}, + Local {}, + init::Monotonics(Systick::new(cx.core.SYST, 12_000_000)), + ) + } + + #[idle] + fn idle(_: idle::Context) -> ! { + // debug::exit(debug::EXIT_SUCCESS); + loop { + // hprintln!("idle"); + cortex_m::asm::wfi(); // put the MCU in sleep mode until interrupt occurs + } + } + + #[task] + async fn foo(_cx: foo::Context) { + hprintln!("hello from foo").ok(); + monotonics::delay(100.millis()).await; + hprintln!("bye from foo").ok(); + } + + #[task] + async fn bar(_cx: bar::Context) { + hprintln!("hello from bar").ok(); + monotonics::delay(200.millis()).await; + hprintln!("bye from bar").ok(); + } + + #[task] + async fn baz(_cx: baz::Context) { + hprintln!("hello from baz").ok(); + monotonics::delay(300.millis()).await; + hprintln!("bye from baz").ok(); + + debug::exit(debug::EXIT_SUCCESS); + } +} diff --git a/examples/async-infinite-loop.rs b/examples/async-infinite-loop.rs new file mode 100644 index 0000000..7615818 --- /dev/null +++ b/examples/async-infinite-loop.rs @@ -0,0 +1,57 @@ +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0, UART0], peripherals = true)] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use systick_monotonic::*; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[monotonic(binds = SysTick, default = true)] + type MyMono = Systick<100>; + + #[init] + fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { + hprintln!("init").unwrap(); + + foo::spawn().ok(); + + ( + Shared {}, + Local {}, + init::Monotonics(Systick::new(cx.core.SYST, 12_000_000)), + ) + } + + #[idle] + fn idle(_: idle::Context) -> ! { + loop { + cortex_m::asm::wfi(); // put the MCU in sleep mode until interrupt occurs + } + } + + // Infinite loops are not allowed in RTIC, however in async tasks they are - if there is an + // await inside the loop. + #[task] + async fn foo(_cx: foo::Context) { + let mut i = 0; + loop { + if i == 5 { + debug::exit(debug::EXIT_SUCCESS); + } + + hprintln!("hello from async {}", i).ok(); + monotonics::delay(100.millis()).await; // This makes it okey! + + i += 1; + } + } +} diff --git a/examples/async-task-multiple-prios.rs b/examples/async-task-multiple-prios.rs new file mode 100644 index 0000000..3e19798 --- /dev/null +++ b/examples/async-task-multiple-prios.rs @@ -0,0 +1,76 @@ +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use panic_semihosting as _; + +// NOTES: +// +// - Async tasks cannot have `#[lock_free]` resources, as they can interleve and each async +// task can have a mutable reference stored. +// - Spawning an async task equates to it being polled once. + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0, QEI0, UART0, UART1], peripherals = true)] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use systick_monotonic::*; + + #[shared] + struct Shared { + a: u32, + b: u32, + } + + #[local] + struct Local {} + + #[monotonic(binds = SysTick, default = true)] + type MyMono = Systick<100>; + + #[init] + fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { + hprintln!("init").unwrap(); + + normal_task::spawn().ok(); + async_task::spawn().ok(); + normal_task2::spawn().ok(); + async_task2::spawn().ok(); + + ( + Shared { a: 0, b: 0 }, + Local {}, + init::Monotonics(Systick::new(cx.core.SYST, 12_000_000)), + ) + } + + #[idle] + fn idle(_: idle::Context) -> ! { + // debug::exit(debug::EXIT_SUCCESS); + loop { + // hprintln!("idle"); + cortex_m::asm::wfi(); // put the MCU in sleep mode until interrupt occurs + } + } + + #[task(priority = 1, shared = [a, b])] + fn normal_task(_cx: normal_task::Context) { + hprintln!("hello from normal 1").ok(); + } + + #[task(priority = 1, shared = [a, b])] + async fn async_task(_cx: async_task::Context) { + hprintln!("hello from async 1").ok(); + + debug::exit(debug::EXIT_SUCCESS); + } + + #[task(priority = 2, shared = [a, b])] + fn normal_task2(_cx: normal_task2::Context) { + hprintln!("hello from normal 2").ok(); + } + + #[task(priority = 2, shared = [a, b])] + async fn async_task2(_cx: async_task2::Context) { + hprintln!("hello from async 2").ok(); + } +} diff --git a/examples/async-task.rs b/examples/async-task.rs new file mode 100644 index 0000000..4d25ec4 --- /dev/null +++ b/examples/async-task.rs @@ -0,0 +1,61 @@ +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use panic_semihosting as _; + +// NOTES: +// +// - Async tasks cannot have `#[lock_free]` resources, as they can interleve and each async +// task can have a mutable reference stored. +// - Spawning an async task equates to it being polled once. + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0, UART0], peripherals = true)] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use systick_monotonic::*; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[monotonic(binds = SysTick, default = true)] + type MyMono = Systick<100>; + + #[init] + fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { + hprintln!("init").unwrap(); + + normal_task::spawn().ok(); + async_task::spawn().ok(); + + ( + Shared {}, + Local {}, + init::Monotonics(Systick::new(cx.core.SYST, 12_000_000)), + ) + } + + #[idle] + fn idle(_: idle::Context) -> ! { + // debug::exit(debug::EXIT_SUCCESS); + loop { + // hprintln!("idle"); + cortex_m::asm::wfi(); // put the MCU in sleep mode until interrupt occurs + } + } + + #[task] + fn normal_task(_cx: normal_task::Context) { + hprintln!("hello from normal").ok(); + } + + #[task] + async fn async_task(_cx: async_task::Context) { + hprintln!("hello from async").ok(); + + debug::exit(debug::EXIT_SUCCESS); + } +} diff --git a/examples/async-timeout.rs b/examples/async-timeout.rs new file mode 100644 index 0000000..3f68df7 --- /dev/null +++ b/examples/async-timeout.rs @@ -0,0 +1,87 @@ +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use panic_semihosting as _; + +// NOTES: +// +// - Async tasks cannot have `#[lock_free]` resources, as they can interleve and each async +// task can have a mutable reference stored. +// - Spawning an async task equates to it being polled once. + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0, UART0], peripherals = true)] +mod app { + use core::{ + future::Future, + pin::Pin, + task::{Context, Poll}, + }; + use cortex_m_semihosting::{debug, hprintln}; + use systick_monotonic::*; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[monotonic(binds = SysTick, default = true)] + type MyMono = Systick<100>; + + #[init] + fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { + hprintln!("init").unwrap(); + + foo::spawn().ok(); + bar::spawn().ok(); + + ( + Shared {}, + Local {}, + init::Monotonics(Systick::new(cx.core.SYST, 12_000_000)), + ) + } + + #[idle] + fn idle(_: idle::Context) -> ! { + loop { + cortex_m::asm::wfi(); // put the MCU in sleep mode until interrupt occurs + } + } + + #[task] + async fn foo(_cx: foo::Context) { + hprintln!("hello from foo").ok(); + + // This will not timeout + match monotonics::timeout_after(monotonics::delay(100.millis()), 200.millis()).await { + Ok(_) => hprintln!("foo no timeout").ok(), + Err(_) => hprintln!("foo timeout").ok(), + }; + } + + #[task] + async fn bar(_cx: bar::Context) { + hprintln!("hello from bar").ok(); + + // This will timeout + match monotonics::timeout_after(NeverEndingFuture {}, 300.millis()).await { + Ok(_) => hprintln!("bar no timeout").ok(), + Err(_) => hprintln!("bar timeout").ok(), + }; + + debug::exit(debug::EXIT_SUCCESS); + } + + pub struct NeverEndingFuture {} + + impl Future for NeverEndingFuture { + type Output = (); + + fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll { + // Never finish + Poll::Pending + } + } +} diff --git a/examples/binds.rs b/examples/binds.rs index 1b0c8c5..56565cb 100644 --- a/examples/binds.rs +++ b/examples/binds.rs @@ -2,7 +2,6 @@ #![deny(unsafe_code)] #![deny(warnings)] -#![deny(missing_docs)] #![no_main] #![no_std] @@ -24,22 +23,21 @@ mod app { fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { rtic::pend(Interrupt::UART0); - hprintln!("init"); + hprintln!("init").unwrap(); (Shared {}, Local {}, init::Monotonics()) } #[idle] fn idle(_: idle::Context) -> ! { - hprintln!("idle"); + hprintln!("idle").unwrap(); rtic::pend(Interrupt::UART0); + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + loop { - // Exit moved after nop to ensure that rtic::pend gets - // to run before exiting cortex_m::asm::nop(); - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator } } @@ -51,6 +49,7 @@ mod app { "foo called {} time{}", *cx.local.times, if *cx.local.times > 1 { "s" } else { "" } - ); + ) + .unwrap(); } } diff --git a/examples/cancel-reschedule.rs b/examples/cancel-reschedule.rs index 36c496b..a38a9c4 100644 --- a/examples/cancel-reschedule.rs +++ b/examples/cancel-reschedule.rs @@ -2,7 +2,6 @@ #![deny(unsafe_code)] #![deny(warnings)] -#![deny(missing_docs)] #![no_main] #![no_std] @@ -29,7 +28,7 @@ mod app { // Initialize the monotonic (SysTick rate in QEMU is 12 MHz) let mono = Systick::new(systick, 12_000_000); - hprintln!("init"); + hprintln!("init").ok(); // Schedule `foo` to run 1 second in the future foo::spawn_after(1.secs()).unwrap(); @@ -43,7 +42,7 @@ mod app { #[task] fn foo(_: foo::Context) { - hprintln!("foo"); + hprintln!("foo").ok(); // Schedule `bar` to run 2 seconds in the future (1 second after foo runs) let spawn_handle = baz::spawn_after(2.secs()).unwrap(); @@ -52,7 +51,7 @@ mod app { #[task] fn bar(_: bar::Context, baz_handle: baz::SpawnHandle, do_reschedule: bool) { - hprintln!("bar"); + hprintln!("bar").ok(); if do_reschedule { // Reschedule baz 2 seconds from now, instead of the original 1 second @@ -68,7 +67,7 @@ mod app { #[task] fn baz(_: baz::Context) { - hprintln!("baz"); + hprintln!("baz").ok(); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator } } diff --git a/examples/capacity.rs b/examples/capacity.rs index 550829b..a617269 100644 --- a/examples/capacity.rs +++ b/examples/capacity.rs @@ -2,7 +2,6 @@ #![deny(unsafe_code)] #![deny(warnings)] -#![deny(missing_docs)] #![no_main] #![no_std] @@ -38,12 +37,12 @@ mod app { #[task(capacity = 4)] fn foo(_: foo::Context, x: u32) { - hprintln!("foo({})", x); + hprintln!("foo({})", x).unwrap(); } #[task] fn bar(_: bar::Context) { - hprintln!("bar"); + hprintln!("bar").unwrap(); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator } diff --git a/examples/cfg-whole-task.rs b/examples/cfg-whole-task.rs index 17f31f4..f41866d 100644 --- a/examples/cfg-whole-task.rs +++ b/examples/cfg-whole-task.rs @@ -2,7 +2,6 @@ #![deny(unsafe_code)] #![deny(warnings)] -#![deny(missing_docs)] #![no_main] #![no_std] @@ -82,19 +81,6 @@ mod app { // .. } - // The whole task should disappear, - // currently still present in the Tasks enum - #[cfg(never)] - #[task(binds = UART1, shared = [count])] - fn foo3(mut _cx: foo3::Context) { - #[cfg(debug_assertions)] - { - _cx.shared.count.lock(|count| *count += 10); - - log::spawn(_cx.shared.count.lock(|count| *count)).unwrap(); - } - } - #[cfg(debug_assertions)] #[task(capacity = 2)] fn log(_: log::Context, n: u32) { @@ -102,6 +88,7 @@ mod app { "foo has been called {} time{}", n, if n == 1 { "" } else { "s" } - ); + ) + .ok(); } } diff --git a/examples/common.rs b/examples/common.rs index 74ee8db..1fe671e 100644 --- a/examples/common.rs +++ b/examples/common.rs @@ -2,7 +2,6 @@ #![deny(unsafe_code)] #![deny(warnings)] -#![deny(missing_docs)] #![no_main] #![no_std] @@ -74,7 +73,7 @@ mod app { // This task is only spawned once in `init`, hence this task will run // only once - hprintln!("foo"); + hprintln!("foo").ok(); } // Software task, also not bound to a hardware interrupt @@ -82,7 +81,7 @@ mod app { // The resources `s1` and `s2` are shared between all other tasks. #[task(shared = [s1, s2], local = [l2])] fn bar(_: bar::Context) { - hprintln!("bar"); + hprintln!("bar").ok(); // Run `bar` once per second bar::spawn_after(1.secs()).unwrap(); @@ -98,6 +97,6 @@ mod app { // Note that RTIC does NOT clear the interrupt flag, this is up to the // user - hprintln!("UART0 interrupt!"); + hprintln!("UART0 interrupt!").ok(); } } diff --git a/examples/complex.rs b/examples/complex.rs index 73df025..e5cf6db 100644 --- a/examples/complex.rs +++ b/examples/complex.rs @@ -2,7 +2,6 @@ #![deny(unsafe_code)] #![deny(warnings)] -#![deny(missing_docs)] #![no_main] #![no_std] @@ -26,7 +25,7 @@ mod app { #[init] fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { - hprintln!("init"); + hprintln!("init").unwrap(); ( Shared { @@ -41,31 +40,31 @@ mod app { #[idle(shared = [s2, s3])] fn idle(mut cx: idle::Context) -> ! { - hprintln!("idle p0 started"); + hprintln!("idle p0 started").ok(); rtic::pend(Interrupt::GPIOC); cx.shared.s3.lock(|s| { - hprintln!("idle enter lock s3 {}", s); - hprintln!("idle pend t0"); + hprintln!("idle enter lock s3 {}", s).ok(); + hprintln!("idle pend t0").ok(); rtic::pend(Interrupt::GPIOA); // t0 p2, with shared ceiling 3 - hprintln!("idle pend t1"); + hprintln!("idle pend t1").ok(); rtic::pend(Interrupt::GPIOB); // t1 p3, with shared ceiling 3 - hprintln!("idle pend t2"); + hprintln!("idle pend t2").ok(); rtic::pend(Interrupt::GPIOC); // t2 p4, no sharing - hprintln!("idle still in lock s3 {}", s); + hprintln!("idle still in lock s3 {}", s).ok(); }); - hprintln!("\nback in idle"); + hprintln!("\nback in idle").ok(); cx.shared.s2.lock(|s| { - hprintln!("enter lock s2 {}", s); - hprintln!("idle pend t0"); + hprintln!("enter lock s2 {}", s).ok(); + hprintln!("idle pend t0").ok(); rtic::pend(Interrupt::GPIOA); // t0 p2, with shared ceiling 2 - hprintln!("idle pend t1"); + hprintln!("idle pend t1").ok(); rtic::pend(Interrupt::GPIOB); // t1 p3, no sharing - hprintln!("idle pend t2"); + hprintln!("idle pend t2").ok(); rtic::pend(Interrupt::GPIOC); // t2 p4, no sharing - hprintln!("idle still in lock s2 {}", s); + hprintln!("idle still in lock s2 {}", s).ok(); }); - hprintln!("\nidle exit"); + hprintln!("\nidle exit").ok(); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator @@ -83,8 +82,9 @@ mod app { "t0 p2 called {} time{}", *cx.local.times, if *cx.local.times > 1 { "s" } else { "" } - ); - hprintln!("t0 p2 exit"); + ) + .ok(); + hprintln!("t0 p2 exit").ok(); } #[task(binds = GPIOB, priority = 3, local = [times: u32 = 0], shared = [s3, s4])] @@ -96,18 +96,19 @@ mod app { "t1 p3 called {} time{}", *cx.local.times, if *cx.local.times > 1 { "s" } else { "" } - ); + ) + .ok(); cx.shared.s4.lock(|s| { - hprintln!("t1 enter lock s4 {}", s); - hprintln!("t1 pend t0"); + hprintln!("t1 enter lock s4 {}", s).ok(); + hprintln!("t1 pend t0").ok(); rtic::pend(Interrupt::GPIOA); // t0 p2, with shared ceiling 2 - hprintln!("t1 pend t2"); + hprintln!("t1 pend t2").ok(); rtic::pend(Interrupt::GPIOC); // t2 p4, no sharing - hprintln!("t1 still in lock s4 {}", s); + hprintln!("t1 still in lock s4 {}", s).ok(); }); - hprintln!("t1 p3 exit"); + hprintln!("t1 p3 exit").ok(); } #[task(binds = GPIOC, priority = 4, local = [times: u32 = 0], shared = [s4])] @@ -119,12 +120,13 @@ mod app { "t2 p4 called {} time{}", *cx.local.times, if *cx.local.times > 1 { "s" } else { "" } - ); + ) + .unwrap(); cx.shared.s4.lock(|s| { - hprintln!("enter lock s4 {}", s); + hprintln!("enter lock s4 {}", s).ok(); *s += 1; }); - hprintln!("t3 p4 exit"); + hprintln!("t3 p4 exit").ok(); } } diff --git a/examples/declared_locals.rs b/examples/declared_locals.rs index cb62149..52d354b 100644 --- a/examples/declared_locals.rs +++ b/examples/declared_locals.rs @@ -2,7 +2,6 @@ #![deny(unsafe_code)] #![deny(warnings)] -#![deny(missing_docs)] #![no_main] #![no_std] diff --git a/examples/destructure.rs b/examples/destructure.rs index 70b0dd7..6019c22 100644 --- a/examples/destructure.rs +++ b/examples/destructure.rs @@ -2,7 +2,6 @@ #![deny(unsafe_code)] #![deny(warnings)] -#![deny(missing_docs)] #![no_main] #![no_std] @@ -43,7 +42,7 @@ mod app { let b = cx.shared.b; let c = cx.shared.c; - hprintln!("foo: a = {}, b = {}, c = {}", a, b, c); + hprintln!("foo: a = {}, b = {}, c = {}", a, b, c).unwrap(); } // De-structure-ing syntax @@ -51,6 +50,6 @@ mod app { fn bar(cx: bar::Context) { let bar::SharedResources { a, b, c } = cx.shared; - hprintln!("bar: a = {}, b = {}, c = {}", a, b, c); + hprintln!("bar: a = {}, b = {}, c = {}", a, b, c).unwrap(); } } diff --git a/examples/extern_binds.rs b/examples/extern_binds.rs index bfc85cf..4dc6633 100644 --- a/examples/extern_binds.rs +++ b/examples/extern_binds.rs @@ -2,7 +2,6 @@ #![deny(unsafe_code)] #![deny(warnings)] -#![deny(missing_docs)] #![no_main] #![no_std] @@ -11,7 +10,7 @@ use panic_semihosting as _; // Free function implementing the interrupt bound task `foo`. fn foo(_: app::foo::Context) { - hprintln!("foo called"); + hprintln!("foo called").ok(); } #[rtic::app(device = lm3s6965)] @@ -30,22 +29,21 @@ mod app { fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { rtic::pend(Interrupt::UART0); - hprintln!("init"); + hprintln!("init").unwrap(); (Shared {}, Local {}, init::Monotonics()) } #[idle] fn idle(_: idle::Context) -> ! { - hprintln!("idle"); + hprintln!("idle").unwrap(); rtic::pend(Interrupt::UART0); + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + loop { cortex_m::asm::nop(); - // Exit moved after nop to ensure that rtic::pend gets - // to run before exiting - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator } } diff --git a/examples/extern_spawn.rs b/examples/extern_spawn.rs index 446d31a..7f9b5a5 100644 --- a/examples/extern_spawn.rs +++ b/examples/extern_spawn.rs @@ -2,7 +2,6 @@ #![deny(unsafe_code)] #![deny(warnings)] -#![deny(missing_docs)] #![no_main] #![no_std] @@ -11,7 +10,7 @@ use panic_semihosting as _; // Free function implementing the spawnable task `foo`. fn foo(_c: app::foo::Context, x: i32, y: u32) { - hprintln!("foo {}, {}", x, y); + hprintln!("foo {}, {}", x, y).unwrap(); if x == 2 { debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator } diff --git a/examples/generics.rs b/examples/generics.rs index bc4959f..72b861b 100644 --- a/examples/generics.rs +++ b/examples/generics.rs @@ -2,7 +2,6 @@ #![deny(unsafe_code)] #![deny(warnings)] -#![deny(missing_docs)] #![no_main] #![no_std] @@ -33,22 +32,19 @@ mod app { #[task(binds = UART0, shared = [shared], local = [state: u32 = 0])] fn uart0(c: uart0::Context) { - hprintln!("UART0(STATE = {})", *c.local.state); + hprintln!("UART0(STATE = {})", *c.local.state).unwrap(); // second argument has type `shared::shared` super::advance(c.local.state, c.shared.shared); rtic::pend(Interrupt::UART1); - // Exit moved after nop to ensure that rtic::pend gets - // to run before exiting - cortex_m::asm::nop(); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator } #[task(binds = UART1, priority = 2, shared = [shared], local = [state: u32 = 0])] fn uart1(c: uart1::Context) { - hprintln!("UART1(STATE = {})", *c.local.state); + hprintln!("UART1(STATE = {})", *c.local.state).unwrap(); // second argument has type `shared::shared` super::advance(c.local.state, c.shared.shared); @@ -65,5 +61,5 @@ fn advance(state: &mut u32, mut shared: impl Mutex) { (old, *shared) }); - hprintln!("shared: {} -> {}", old, new); + hprintln!("shared: {} -> {}", old, new).unwrap(); } diff --git a/examples/hardware.rs b/examples/hardware.rs index a7fdb47..6063224 100644 --- a/examples/hardware.rs +++ b/examples/hardware.rs @@ -2,7 +2,6 @@ #![deny(unsafe_code)] #![deny(warnings)] -#![deny(missing_docs)] #![no_main] #![no_std] @@ -25,7 +24,7 @@ mod app { // `init` returns because interrupts are disabled rtic::pend(Interrupt::UART0); // equivalent to NVIC::pend - hprintln!("init"); + hprintln!("init").unwrap(); (Shared {}, Local {}, init::Monotonics()) } @@ -34,15 +33,14 @@ mod app { fn idle(_: idle::Context) -> ! { // interrupts are enabled again; the `UART0` handler runs at this point - hprintln!("idle"); + hprintln!("idle").unwrap(); rtic::pend(Interrupt::UART0); + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + loop { - // Exit moved after nop to ensure that rtic::pend gets - // to run before exiting cortex_m::asm::nop(); - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator } } @@ -55,6 +53,7 @@ mod app { "UART0 called {} time{}", *cx.local.times, if *cx.local.times > 1 { "s" } else { "" } - ); + ) + .unwrap(); } } diff --git a/examples/idle-wfi.rs b/examples/idle-wfi.rs index 5e52620..4a8a8de 100644 --- a/examples/idle-wfi.rs +++ b/examples/idle-wfi.rs @@ -2,7 +2,6 @@ #![deny(unsafe_code)] #![deny(warnings)] -#![deny(missing_docs)] #![no_main] #![no_std] @@ -20,7 +19,7 @@ mod app { #[init] fn init(mut cx: init::Context) -> (Shared, Local, init::Monotonics) { - hprintln!("init"); + hprintln!("init").unwrap(); // Set the ARM SLEEPONEXIT bit to go to sleep after handling interrupts // See https://developer.arm.com/docs/100737/0100/power-management/sleep-mode/sleep-on-exit-bit @@ -34,7 +33,7 @@ mod app { // Locals in idle have lifetime 'static let _x: &'static mut u32 = cx.local.x; - hprintln!("idle"); + hprintln!("idle").unwrap(); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator diff --git a/examples/idle.rs b/examples/idle.rs index ccec9bf..55d6b15 100644 --- a/examples/idle.rs +++ b/examples/idle.rs @@ -2,7 +2,6 @@ #![deny(unsafe_code)] #![deny(warnings)] -#![deny(missing_docs)] #![no_main] #![no_std] @@ -20,7 +19,7 @@ mod app { #[init] fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { - hprintln!("init"); + hprintln!("init").unwrap(); (Shared {}, Local {}, init::Monotonics()) } @@ -30,7 +29,7 @@ mod app { // Locals in idle have lifetime 'static let _x: &'static mut u32 = cx.local.x; - hprintln!("idle"); + hprintln!("idle").unwrap(); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator diff --git a/examples/init.rs b/examples/init.rs index afd3b98..b8a5bc5 100644 --- a/examples/init.rs +++ b/examples/init.rs @@ -2,7 +2,6 @@ #![deny(unsafe_code)] #![deny(warnings)] -#![deny(missing_docs)] #![no_main] #![no_std] @@ -33,7 +32,7 @@ mod app { // to indicate that this is a critical seciton let _cs_token: bare_metal::CriticalSection = cx.cs; - hprintln!("init"); + hprintln!("init").unwrap(); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator diff --git a/examples/locals.rs b/examples/locals.rs index 9e112be..aa5d0fe 100644 --- a/examples/locals.rs +++ b/examples/locals.rs @@ -2,8 +2,6 @@ #![deny(unsafe_code)] #![deny(warnings)] -#![deny(missing_docs)] -#![deny(missing_docs)] #![no_main] #![no_std] @@ -18,11 +16,8 @@ mod app { #[local] struct Local { - /// Local foo local_to_foo: i64, - /// Local bar local_to_bar: i64, - /// Local idle local_to_idle: i64, } @@ -50,7 +45,7 @@ mod app { let local_to_idle = cx.local.local_to_idle; *local_to_idle += 1; - hprintln!("idle: local_to_idle = {}", local_to_idle); + hprintln!("idle: local_to_idle = {}", local_to_idle).unwrap(); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator @@ -74,7 +69,7 @@ mod app { // error: no `local_to_bar` field in `foo::LocalResources` // cx.local.local_to_bar += 1; - hprintln!("foo: local_to_foo = {}", local_to_foo); + hprintln!("foo: local_to_foo = {}", local_to_foo).unwrap(); } // `local_to_bar` can only be accessed from this context @@ -86,6 +81,6 @@ mod app { // error: no `local_to_foo` field in `bar::LocalResources` // cx.local.local_to_foo += 1; - hprintln!("bar: local_to_bar = {}", local_to_bar); + hprintln!("bar: local_to_bar = {}", local_to_bar).unwrap(); } } diff --git a/examples/lock-free.rs b/examples/lock-free.rs index 6e5faad..ea6ff1b 100644 --- a/examples/lock-free.rs +++ b/examples/lock-free.rs @@ -2,7 +2,6 @@ #![deny(unsafe_code)] #![deny(warnings)] -#![deny(missing_docs)] #![no_main] #![no_std] @@ -34,7 +33,7 @@ mod app { *c.shared.counter += 1; // <- no lock API required let counter = *c.shared.counter; - hprintln!(" foo = {}", counter); + hprintln!(" foo = {}", counter).unwrap(); } #[task(shared = [counter])] // <- same priority @@ -43,7 +42,7 @@ mod app { *c.shared.counter += 1; // <- no lock API required let counter = *c.shared.counter; - hprintln!(" bar = {}", counter); + hprintln!(" bar = {}", counter).unwrap(); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator } diff --git a/examples/lock.rs b/examples/lock.rs index 5b3e0bc..f1a1696 100644 --- a/examples/lock.rs +++ b/examples/lock.rs @@ -2,7 +2,6 @@ #![deny(unsafe_code)] #![deny(warnings)] -#![deny(missing_docs)] #![no_main] #![no_std] @@ -30,7 +29,7 @@ mod app { // when omitted priority is assumed to be `1` #[task(shared = [shared])] fn foo(mut c: foo::Context) { - hprintln!("A"); + hprintln!("A").unwrap(); // the lower priority task requires a critical section to access the data c.shared.shared.lock(|shared| { @@ -40,7 +39,7 @@ mod app { // bar will *not* run right now due to the critical section bar::spawn().unwrap(); - hprintln!("B - shared = {}", *shared); + hprintln!("B - shared = {}", *shared).unwrap(); // baz does not contend for `shared` so it's allowed to run now baz::spawn().unwrap(); @@ -48,7 +47,7 @@ mod app { // critical section is over: bar can now start - hprintln!("E"); + hprintln!("E").unwrap(); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator } @@ -62,11 +61,11 @@ mod app { *shared }); - hprintln!("D - shared = {}", shared); + hprintln!("D - shared = {}", shared).unwrap(); } #[task(priority = 3)] fn baz(_: baz::Context) { - hprintln!("C"); + hprintln!("C").unwrap(); } } diff --git a/examples/message.rs b/examples/message.rs index 8a6a12d..76c5675 100644 --- a/examples/message.rs +++ b/examples/message.rs @@ -2,7 +2,6 @@ #![deny(unsafe_code)] #![deny(warnings)] -#![deny(missing_docs)] #![no_main] #![no_std] @@ -27,7 +26,7 @@ mod app { #[task(local = [count: u32 = 0])] fn foo(cx: foo::Context) { - hprintln!("foo"); + hprintln!("foo").unwrap(); bar::spawn(*cx.local.count).unwrap(); *cx.local.count += 1; @@ -35,14 +34,14 @@ mod app { #[task] fn bar(_: bar::Context, x: u32) { - hprintln!("bar({})", x); + hprintln!("bar({})", x).unwrap(); baz::spawn(x + 1, x + 2).unwrap(); } #[task] fn baz(_: baz::Context, x: u32, y: u32) { - hprintln!("baz({}, {})", x, y); + hprintln!("baz({}, {})", x, y).unwrap(); if x + y > 4 { debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator diff --git a/examples/message_passing.rs b/examples/message_passing.rs index 9550a50..ffa9537 100644 --- a/examples/message_passing.rs +++ b/examples/message_passing.rs @@ -2,7 +2,6 @@ #![deny(unsafe_code)] #![deny(warnings)] -#![deny(missing_docs)] #![no_main] #![no_std] @@ -30,7 +29,7 @@ mod app { #[task(capacity = 3)] fn foo(_c: foo::Context, x: i32, y: u32) { - hprintln!("foo {}, {}", x, y); + hprintln!("foo {}, {}", x, y).unwrap(); if x == 2 { debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator } diff --git a/examples/multilock.rs b/examples/multilock.rs index c7085cd..d99bae6 100644 --- a/examples/multilock.rs +++ b/examples/multilock.rs @@ -2,7 +2,6 @@ #![deny(unsafe_code)] #![deny(warnings)] -#![deny(missing_docs)] #![no_main] #![no_std] @@ -49,7 +48,7 @@ mod app { *s2 += 1; *s3 += 1; - hprintln!("Multiple locks, s1: {}, s2: {}, s3: {}", *s1, *s2, *s3); + hprintln!("Multiple locks, s1: {}, s2: {}, s3: {}", *s1, *s2, *s3).unwrap(); }); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator diff --git a/examples/not-sync.rs b/examples/not-sync.rs index 68af04a..aa79ad5 100644 --- a/examples/not-sync.rs +++ b/examples/not-sync.rs @@ -2,16 +2,13 @@ // #![deny(unsafe_code)] #![deny(warnings)] -#![deny(missing_docs)] #![no_main] #![no_std] use core::marker::PhantomData; use panic_semihosting as _; -/// Not sync pub struct NotSync { - /// Phantom action _0: PhantomData<*const ()>, } @@ -25,7 +22,6 @@ mod app { #[shared] struct Shared { - /// This resource is not Sync shared: NotSync, } diff --git a/examples/only-shared-access.rs b/examples/only-shared-access.rs index b32827a..8b0a77e 100644 --- a/examples/only-shared-access.rs +++ b/examples/only-shared-access.rs @@ -2,7 +2,6 @@ #![deny(unsafe_code)] #![deny(warnings)] -#![deny(missing_docs)] #![no_main] #![no_std] @@ -31,13 +30,13 @@ mod app { #[task(shared = [&key])] fn foo(cx: foo::Context) { let key: &u32 = cx.shared.key; - hprintln!("foo(key = {:#x})", key); + hprintln!("foo(key = {:#x})", key).unwrap(); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator } #[task(priority = 2, shared = [&key])] fn bar(cx: bar::Context) { - hprintln!("bar(key = {:#x})", cx.shared.key); + hprintln!("bar(key = {:#x})", cx.shared.key).unwrap(); } } diff --git a/examples/periodic-at.rs b/examples/periodic-at.rs index ad8a549..ca68ed5 100644 --- a/examples/periodic-at.rs +++ b/examples/periodic-at.rs @@ -2,7 +2,6 @@ #![deny(unsafe_code)] #![deny(warnings)] -#![deny(missing_docs)] #![no_main] #![no_std] @@ -36,15 +35,15 @@ mod app { #[task(local = [cnt: u32 = 0])] fn foo(cx: foo::Context, instant: fugit::TimerInstantU64<100>) { - hprintln!("foo {:?}", instant); + hprintln!("foo {:?}", instant).ok(); *cx.local.cnt += 1; if *cx.local.cnt == 4 { debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator } - // Periodic ever 1 seconds - let next_instant = instant + 1.secs(); + // Periodic every 100 milliseconds + let next_instant = instant + 100.millis(); foo::spawn_at(next_instant, next_instant).unwrap(); } } diff --git a/examples/periodic-at2.rs b/examples/periodic-at2.rs index 4719bdb..ec9adcc 100644 --- a/examples/periodic-at2.rs +++ b/examples/periodic-at2.rs @@ -2,7 +2,6 @@ #![deny(unsafe_code)] #![deny(warnings)] -#![deny(missing_docs)] #![no_main] #![no_std] @@ -29,7 +28,7 @@ mod app { // Initialize the monotonic (SysTick rate in QEMU is 12 MHz) let mut mono = Systick::new(systick, 12_000_000); - foo::spawn_after(1.secs(), mono.now()).unwrap(); + foo::spawn_after(200.millis(), mono.now()).unwrap(); (Shared {}, Local {}, init::Monotonics(mono)) } @@ -37,7 +36,7 @@ mod app { // Using the explicit type of the timer implementation #[task(local = [cnt: u32 = 0])] fn foo(cx: foo::Context, instant: fugit::TimerInstantU64<100>) { - hprintln!("foo {:?}", instant); + hprintln!("foo {:?}", instant).ok(); *cx.local.cnt += 1; if *cx.local.cnt == 4 { @@ -53,10 +52,10 @@ mod app { // This remains agnostic to the timer implementation #[task(local = [cnt: u32 = 0])] fn bar(_cx: bar::Context, instant: ::Instant) { - hprintln!("bar {:?}", instant); + hprintln!("bar {:?}", instant).ok(); - // Spawn a new message with 1s offset to spawned time - let next_instant = instant + 1.secs(); + // Spawn a new message with 200ms offset to spawned time + let next_instant = instant + 200.millis(); foo::spawn_at(next_instant, next_instant).unwrap(); } } diff --git a/examples/periodic.rs b/examples/periodic.rs index 13ca7c8..2f9e8e6 100644 --- a/examples/periodic.rs +++ b/examples/periodic.rs @@ -2,7 +2,6 @@ #![deny(unsafe_code)] #![deny(warnings)] -#![deny(missing_docs)] #![no_main] #![no_std] @@ -29,21 +28,21 @@ mod app { // Initialize the monotonic (SysTick rate in QEMU is 12 MHz) let mono = Systick::new(systick, 12_000_000); - foo::spawn_after(1.secs()).unwrap(); + foo::spawn_after(100.millis()).unwrap(); (Shared {}, Local {}, init::Monotonics(mono)) } #[task(local = [cnt: u32 = 0])] fn foo(cx: foo::Context) { - hprintln!("foo"); + hprintln!("foo").ok(); *cx.local.cnt += 1; if *cx.local.cnt == 4 { debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator } - // Periodic ever 1 seconds - foo::spawn_after(1.secs()).unwrap(); + // Periodic every 100ms + foo::spawn_after(100.millis()).unwrap(); } } diff --git a/examples/peripherals-taken.rs b/examples/peripherals-taken.rs index cc9b9a1..d542c0e 100644 --- a/examples/peripherals-taken.rs +++ b/examples/peripherals-taken.rs @@ -1,7 +1,5 @@ -//! examples/peripherals-taken.rs -#![deny(warnings)] #![deny(unsafe_code)] -#![deny(missing_docs)] +#![deny(warnings)] #![no_main] #![no_std] diff --git a/examples/pool.rs b/examples/pool.rs index 4c551be..5aadd24 100644 --- a/examples/pool.rs +++ b/examples/pool.rs @@ -2,8 +2,6 @@ #![deny(unsafe_code)] #![deny(warnings)] -// pool!() generates a struct without docs -//#![deny(missing_docs)] #![no_main] #![no_std] diff --git a/examples/preempt.rs b/examples/preempt.rs index 3c7f242..d0c8cc7 100644 --- a/examples/preempt.rs +++ b/examples/preempt.rs @@ -25,21 +25,21 @@ mod app { #[task(priority = 1)] fn foo(_: foo::Context) { - hprintln!("foo - start"); + hprintln!("foo - start").unwrap(); baz::spawn().unwrap(); - hprintln!("foo - end"); + hprintln!("foo - end").unwrap(); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator } #[task(priority = 2)] fn bar(_: bar::Context) { - hprintln!(" bar"); + hprintln!(" bar").unwrap(); } #[task(priority = 2)] fn baz(_: baz::Context) { - hprintln!(" baz - start"); + hprintln!(" baz - start").unwrap(); bar::spawn().unwrap(); - hprintln!(" baz - end"); + hprintln!(" baz - end").unwrap(); } } diff --git a/examples/ramfunc.rs b/examples/ramfunc.rs index 956a255..b3b8012 100644 --- a/examples/ramfunc.rs +++ b/examples/ramfunc.rs @@ -1,7 +1,6 @@ //! examples/ramfunc.rs #![deny(warnings)] -#![deny(missing_docs)] #![no_main] #![no_std] @@ -34,7 +33,7 @@ mod app { #[inline(never)] #[task] fn foo(_: foo::Context) { - hprintln!("foo"); + hprintln!("foo").unwrap(); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator } diff --git a/examples/resource-user-struct.rs b/examples/resource-user-struct.rs index 37a8856..ae1918d 100644 --- a/examples/resource-user-struct.rs +++ b/examples/resource-user-struct.rs @@ -2,7 +2,6 @@ #![deny(unsafe_code)] #![deny(warnings)] -#![deny(missing_docs)] #![no_main] #![no_std] @@ -56,7 +55,7 @@ mod app { *shared }); - hprintln!("UART0: shared = {}", shared); + hprintln!("UART0: shared = {}", shared).unwrap(); } // `shared` can be accessed from this context @@ -67,6 +66,6 @@ mod app { *shared }); - hprintln!("UART1: shared = {}", shared); + hprintln!("UART1: shared = {}", shared).unwrap(); } } diff --git a/examples/schedule.rs b/examples/schedule.rs index 9b86929..5bad5a3 100644 --- a/examples/schedule.rs +++ b/examples/schedule.rs @@ -2,7 +2,6 @@ #![deny(unsafe_code)] #![deny(warnings)] -#![deny(missing_docs)] #![no_main] #![no_std] @@ -29,7 +28,7 @@ mod app { // Initialize the monotonic (SysTick rate in QEMU is 12 MHz) let mono = Systick::new(systick, 12_000_000); - hprintln!("init"); + hprintln!("init").ok(); // Schedule `foo` to run 1 second in the future foo::spawn_after(1.secs()).unwrap(); @@ -43,7 +42,7 @@ mod app { #[task] fn foo(_: foo::Context) { - hprintln!("foo"); + hprintln!("foo").ok(); // Schedule `bar` to run 2 seconds in the future (1 second after foo runs) bar::spawn_after(1.secs()).unwrap(); @@ -51,7 +50,7 @@ mod app { #[task] fn bar(_: bar::Context) { - hprintln!("bar"); + hprintln!("bar").ok(); // Schedule `baz` to run 1 seconds from now, but with a specific time instant. baz::spawn_at(monotonics::now() + 1.secs()).unwrap(); @@ -59,7 +58,7 @@ mod app { #[task] fn baz(_: baz::Context) { - hprintln!("baz"); + hprintln!("baz").ok(); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator } } diff --git a/examples/shared.rs b/examples/shared.rs index b43a19a..d87dca5 100644 --- a/examples/shared.rs +++ b/examples/shared.rs @@ -2,7 +2,6 @@ #![deny(unsafe_code)] #![deny(warnings)] -#![deny(missing_docs)] #![no_main] #![no_std] @@ -16,9 +15,7 @@ mod app { #[shared] struct Shared { - /// Producer p: Producer<'static, u32, 5>, - /// Consumer c: Consumer<'static, u32, 5>, } @@ -37,7 +34,7 @@ mod app { fn idle(mut c: idle::Context) -> ! { loop { if let Some(byte) = c.shared.c.lock(|c| c.dequeue()) { - hprintln!("received message: {}", byte); + hprintln!("received message: {}", byte).unwrap(); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator } else { diff --git a/examples/spawn.rs b/examples/spawn.rs index 50ae7e7..2db1ab8 100644 --- a/examples/spawn.rs +++ b/examples/spawn.rs @@ -2,7 +2,6 @@ #![deny(unsafe_code)] #![deny(warnings)] -#![deny(missing_docs)] #![no_main] #![no_std] @@ -20,7 +19,7 @@ mod app { #[init] fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { - hprintln!("init"); + hprintln!("init").unwrap(); foo::spawn().unwrap(); (Shared {}, Local {}, init::Monotonics()) @@ -28,7 +27,7 @@ mod app { #[task] fn foo(_: foo::Context) { - hprintln!("foo"); + hprintln!("foo").unwrap(); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator } diff --git a/examples/static.rs b/examples/static.rs index efafcc7..c9aa604 100644 --- a/examples/static.rs +++ b/examples/static.rs @@ -2,7 +2,6 @@ #![deny(unsafe_code)] #![deny(warnings)] -#![deny(missing_docs)] #![no_main] #![no_std] @@ -38,7 +37,7 @@ mod app { loop { // Lock-free access to the same underlying queue! if let Some(data) = c.local.c.dequeue() { - hprintln!("received message: {}", data); + hprintln!("received message: {}", data).unwrap(); // Run foo until data if data == 3 { diff --git a/examples/t-binds.rs b/examples/t-binds.rs index 822a2ee..12479c0 100644 --- a/examples/t-binds.rs +++ b/examples/t-binds.rs @@ -2,7 +2,6 @@ #![deny(unsafe_code)] #![deny(warnings)] -#![deny(missing_docs)] #![no_main] #![no_std] diff --git a/examples/t-htask-main.rs b/examples/t-htask-main.rs index 2b17b2e..37189fa 100644 --- a/examples/t-htask-main.rs +++ b/examples/t-htask-main.rs @@ -1,7 +1,5 @@ -//! examples/t-htask-main.rs #![deny(unsafe_code)] #![deny(warnings)] -#![deny(missing_docs)] #![no_main] #![no_std] diff --git a/examples/t-idle-main.rs b/examples/t-idle-main.rs index 48635b2..1adc9bf 100644 --- a/examples/t-idle-main.rs +++ b/examples/t-idle-main.rs @@ -1,7 +1,5 @@ -//! examples/t-idle-main.rs #![deny(unsafe_code)] #![deny(warnings)] -#![deny(missing_docs)] #![no_main] #![no_std] diff --git a/examples/t-schedule.rs b/examples/t-schedule.rs index f3979dd..5ec4208 100644 --- a/examples/t-schedule.rs +++ b/examples/t-schedule.rs @@ -2,7 +2,6 @@ #![deny(unsafe_code)] #![deny(warnings)] -#![deny(missing_docs)] #![no_main] #![no_std] diff --git a/examples/t-spawn.rs b/examples/t-spawn.rs index 7483a84..2bd771d 100644 --- a/examples/t-spawn.rs +++ b/examples/t-spawn.rs @@ -2,7 +2,6 @@ #![deny(unsafe_code)] #![deny(warnings)] -#![deny(missing_docs)] #![no_main] #![no_std] diff --git a/examples/task.rs b/examples/task.rs index 9757f2f..2c53aa2 100644 --- a/examples/task.rs +++ b/examples/task.rs @@ -2,7 +2,6 @@ #![deny(unsafe_code)] #![deny(warnings)] -#![deny(missing_docs)] #![no_main] #![no_std] @@ -27,31 +26,31 @@ mod app { #[task] fn foo(_: foo::Context) { - hprintln!("foo - start"); + hprintln!("foo - start").unwrap(); // spawns `bar` onto the task scheduler // `foo` and `bar` have the same priority so `bar` will not run until // after `foo` terminates bar::spawn().unwrap(); - hprintln!("foo - middle"); + hprintln!("foo - middle").unwrap(); // spawns `baz` onto the task scheduler // `baz` has higher priority than `foo` so it immediately preempts `foo` baz::spawn().unwrap(); - hprintln!("foo - end"); + hprintln!("foo - end").unwrap(); } #[task] fn bar(_: bar::Context) { - hprintln!("bar"); + hprintln!("bar").unwrap(); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator } #[task(priority = 2)] fn baz(_: baz::Context) { - hprintln!("baz"); + hprintln!("baz").unwrap(); } } diff --git a/macros/src/codegen/local_resources_struct.rs b/macros/src/codegen/local_resources_struct.rs index 309fd8d..6bcf4fa 100644 --- a/macros/src/codegen/local_resources_struct.rs +++ b/macros/src/codegen/local_resources_struct.rs @@ -37,7 +37,6 @@ pub fn codegen(ctxt: Context, needs_lt: &mut bool, app: &App) -> (TokenStream2, (&r.cfgs, &r.ty, false) } TaskLocal::Declared(r) => (&r.cfgs, &r.ty, true), - _ => unreachable!(), }; has_cfgs |= !cfgs.is_empty(); diff --git a/macros/src/syntax.rs b/macros/src/syntax.rs index 11b92c1..09b2ab3 100644 --- a/macros/src/syntax.rs +++ b/macros/src/syntax.rs @@ -1,7 +1,6 @@ #[allow(unused_extern_crates)] extern crate proc_macro; -use core::ops; use proc_macro::TokenStream; use indexmap::{IndexMap, IndexSet}; @@ -23,26 +22,6 @@ pub type Map = IndexMap; /// An order set pub type Set = IndexSet; -/// Immutable pointer -pub struct P { - ptr: Box, -} - -impl P { - /// Boxes `x` making the value immutable - pub fn new(x: T) -> P { - P { ptr: Box::new(x) } - } -} - -impl ops::Deref for P { - type Target = T; - - fn deref(&self) -> &T { - &self.ptr - } -} - /// Execution context #[derive(Clone, Copy)] pub enum Context<'a> { diff --git a/macros/src/syntax/analyze.rs b/macros/src/syntax/analyze.rs index 06b23f4..44960b9 100644 --- a/macros/src/syntax/analyze.rs +++ b/macros/src/syntax/analyze.rs @@ -338,8 +338,8 @@ pub(crate) fn app(app: &App) -> Result { }) } -/// Priority ceiling -pub type Ceiling = Option; +// /// Priority ceiling +// pub type Ceiling = Option; /// Task priority pub type Priority = u8; @@ -427,22 +427,22 @@ pub enum Ownership { }, } -impl Ownership { - /// Whether this resource needs to a lock at this priority level - pub fn needs_lock(&self, priority: u8) -> bool { - match self { - Ownership::Owned { .. } | Ownership::CoOwned { .. } => false, - - Ownership::Contended { ceiling } => { - debug_assert!(*ceiling >= priority); - - priority < *ceiling - } - } - } - - /// Whether this resource is exclusively owned - pub fn is_owned(&self) -> bool { - matches!(self, Ownership::Owned { .. }) - } -} +// impl Ownership { +// /// Whether this resource needs to a lock at this priority level +// pub fn needs_lock(&self, priority: u8) -> bool { +// match self { +// Ownership::Owned { .. } | Ownership::CoOwned { .. } => false, +// +// Ownership::Contended { ceiling } => { +// debug_assert!(*ceiling >= priority); +// +// priority < *ceiling +// } +// } +// } +// +// /// Whether this resource is exclusively owned +// pub fn is_owned(&self) -> bool { +// matches!(self, Ownership::Owned { .. }) +// } +// } diff --git a/src/export.rs b/src/export.rs index 6f2a1b6..da4a691 100644 --- a/src/export.rs +++ b/src/export.rs @@ -1,11 +1,13 @@ #![allow(clippy::inline_always)] +pub use crate::{ + sll::{IntrusiveSortedLinkedList, Node as IntrusiveNode}, + tq::{TaskNotReady, TimerQueue, WakerNotReady}, +}; +pub use bare_metal::CriticalSection; use core::{ cell::Cell, sync::atomic::{AtomicBool, Ordering}, }; - -pub use crate::tq::{NotReady, TimerQueue}; -pub use bare_metal::CriticalSection; pub use cortex_m::{ asm::nop, asm::wfi, @@ -16,10 +18,134 @@ pub use cortex_m::{ pub use heapless::sorted_linked_list::SortedLinkedList; pub use heapless::spsc::Queue; pub use heapless::BinaryHeap; +pub use heapless::Vec; pub use rtic_monotonic as monotonic; +pub mod idle_executor { + use core::{ + future::Future, + pin::Pin, + task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, + }; + + fn no_op(_: *const ()) {} + fn no_op_clone(_: *const ()) -> RawWaker { + noop_raw_waker() + } + + static IDLE_WAKER_TABLE: RawWakerVTable = RawWakerVTable::new(no_op_clone, no_op, no_op, no_op); + + #[inline] + fn noop_raw_waker() -> RawWaker { + RawWaker::new(core::ptr::null(), &IDLE_WAKER_TABLE) + } + + pub struct IdleExecutor + where + T: Future, + { + idle: T, + } + + impl IdleExecutor + where + T: Future, + { + #[inline(always)] + pub fn new(idle: T) -> Self { + Self { idle } + } + + #[inline(always)] + pub fn run(&mut self) -> ! { + let w = unsafe { Waker::from_raw(noop_raw_waker()) }; + let mut ctxt = Context::from_waker(&w); + loop { + match unsafe { Pin::new_unchecked(&mut self.idle) }.poll(&mut ctxt) { + Poll::Pending => { + // All ok! + } + Poll::Ready(_) => { + // The idle executor will never return + unreachable!() + } + } + } + } + } +} + +pub mod executor { + use core::{ + future::Future, + mem, + pin::Pin, + task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, + }; + + static WAKER_VTABLE: RawWakerVTable = + RawWakerVTable::new(waker_clone, waker_wake, waker_wake, waker_drop); + + unsafe fn waker_clone(p: *const ()) -> RawWaker { + RawWaker::new(p, &WAKER_VTABLE) + } + + unsafe fn waker_wake(p: *const ()) { + // The only thing we need from a waker is the function to call to pend the async + // dispatcher. + let f: fn() = mem::transmute(p); + f(); + } + + unsafe fn waker_drop(_: *const ()) { + // nop + } + + //============ + // AsyncTaskExecutor + + pub struct AsyncTaskExecutor { + task: Option, + } + + impl AsyncTaskExecutor { + pub const fn new() -> Self { + Self { task: None } + } + + pub fn is_running(&self) -> bool { + self.task.is_some() + } + + pub fn spawn(&mut self, future: F) { + self.task = Some(future); + } + + pub fn poll(&mut self, wake: fn()) -> bool { + if let Some(future) = &mut self.task { + unsafe { + let waker = Waker::from_raw(RawWaker::new(wake as *const (), &WAKER_VTABLE)); + let mut cx = Context::from_waker(&waker); + let future = Pin::new_unchecked(future); + + match future.poll(&mut cx) { + Poll::Ready(_) => { + self.task = None; + true // Only true if we finished now + } + Poll::Pending => false, + } + } + } else { + false + } + } + } +} + pub type SCFQ = Queue; pub type SCRQ = Queue<(T, u8), N>; +pub type ASYNCRQ = Queue; /// Mask is used to store interrupt masks on systems without a BASEPRI register (M0, M0+, M23). /// It needs to be large enough to cover all the relevant interrupts in use. @@ -117,7 +243,7 @@ impl Priority { /// /// Will overwrite the current Priority #[inline(always)] - pub unsafe fn new(value: u8) -> Self { + pub const unsafe fn new(value: u8) -> Self { Priority { inner: Cell::new(value), } diff --git a/src/lib.rs b/src/lib.rs index 7d12d9a..da556a5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,14 +1,125 @@ -pub fn add(left: usize, right: usize) -> usize { - left + right +//! Real-Time Interrupt-driven Concurrency (RTIC) framework for ARM Cortex-M microcontrollers. +//! +//! **IMPORTANT**: This crate is published as [`cortex-m-rtic`] on crates.io but the name of the +//! library is `rtic`. +//! +//! [`cortex-m-rtic`]: https://crates.io/crates/cortex-m-rtic +//! +//! The user level documentation can be found [here]. +//! +//! [here]: https://rtic.rs +//! +//! Don't forget to check the documentation of the `#[app]` attribute (listed under the reexports +//! section), which is the main component of the framework. +//! +//! # Minimum Supported Rust Version (MSRV) +//! +//! This crate is compiled and tested with the latest toolchain (rolling) as of the release date. +//! If you run into compilation errors, try the latest stable release of the rust toolchain. +//! +//! # Semantic Versioning +//! +//! Like the Rust project, this crate adheres to [SemVer]: breaking changes in the API and semantics +//! require a *semver bump* (since 1.0.0 a new major version release), with the exception of breaking changes +//! that fix soundness issues -- those are considered bug fixes and can be landed in a new patch +//! release. +//! +//! [SemVer]: https://semver.org/spec/v2.0.0.html + +#![deny(missing_docs)] +#![deny(rust_2021_compatibility)] +#![deny(rust_2018_compatibility)] +#![deny(rust_2018_idioms)] +#![no_std] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/rtic-rs/cortex-m-rtic/master/book/en/src/RTIC.svg", + html_favicon_url = "https://raw.githubusercontent.com/rtic-rs/cortex-m-rtic/master/book/en/src/RTIC.svg" +)] +//deny_warnings_placeholder_for_ci +#![allow(clippy::inline_always)] + +use cortex_m::{interrupt::InterruptNumber, peripheral::NVIC}; +pub use rtic_core::{prelude as mutex_prelude, Exclusive, Mutex}; +pub use rtic_macros::app; +pub use rtic_monotonic::{self, Monotonic}; + +/// module `mutex::prelude` provides `Mutex` and multi-lock variants. Recommended over `mutex_prelude` +pub mod mutex { + pub use rtic_core::prelude; + pub use rtic_core::Mutex; +} + +#[doc(hidden)] +pub mod export; +#[doc(hidden)] +pub mod sll; +#[doc(hidden)] +mod tq; + +/// Sets the given `interrupt` as pending +/// +/// This is a convenience function around +/// [`NVIC::pend`](../cortex_m/peripheral/struct.NVIC.html#method.pend) +pub fn pend(interrupt: I) +where + I: InterruptNumber, +{ + NVIC::pend(interrupt); } -#[cfg(test)] -mod tests { - use super::*; +use core::cell::UnsafeCell; - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); +/// Internal replacement for `static mut T` +/// +/// Used to represent RTIC Resources +/// +/// Soundness: +/// 1) Unsafe API for internal use only +/// 2) ``get_mut(&self) -> *mut T`` +/// returns a raw mutable pointer to the inner T +/// casting to &mut T is under control of RTIC +/// RTIC ensures &mut T to be unique under Rust aliasing rules. +/// +/// Implementation uses the underlying ``UnsafeCell`` +/// self.0.get() -> *mut T +/// +/// 3) get(&self) -> *const T +/// returns a raw immutable (const) pointer to the inner T +/// casting to &T is under control of RTIC +/// RTIC ensures &T to be shared under Rust aliasing rules. +/// +/// Implementation uses the underlying ``UnsafeCell`` +/// self.0.get() -> *mut T, demoted to *const T +/// +#[repr(transparent)] +pub struct RacyCell(UnsafeCell); + +impl RacyCell { + /// Create a ``RacyCell`` + #[inline(always)] + pub const fn new(value: T) -> Self { + RacyCell(UnsafeCell::new(value)) + } + + /// Get `*mut T` + /// + /// # Safety + /// + /// See documentation notes for [`RacyCell`] + #[inline(always)] + pub unsafe fn get_mut(&self) -> *mut T { + self.0.get() + } + + /// Get `*const T` + /// + /// # Safety + /// + /// See documentation notes for [`RacyCell`] + #[inline(always)] + pub unsafe fn get(&self) -> *const T { + self.0.get() } } + +unsafe impl Sync for RacyCell {} diff --git a/src/sll.rs b/src/sll.rs new file mode 100644 index 0000000..43b53c1 --- /dev/null +++ b/src/sll.rs @@ -0,0 +1,421 @@ +//! An intrusive sorted priority linked list, designed for use in `Future`s in RTIC. +use core::cmp::Ordering; +use core::fmt; +use core::marker::PhantomData; +use core::ops::{Deref, DerefMut}; +use core::ptr::NonNull; + +/// Marker for Min sorted [`IntrusiveSortedLinkedList`]. +pub struct Min; + +/// Marker for Max sorted [`IntrusiveSortedLinkedList`]. +pub struct Max; + +/// The linked list kind: min-list or max-list +pub trait Kind: private::Sealed { + #[doc(hidden)] + fn ordering() -> Ordering; +} + +impl Kind for Min { + fn ordering() -> Ordering { + Ordering::Less + } +} + +impl Kind for Max { + fn ordering() -> Ordering { + Ordering::Greater + } +} + +/// Sealed traits +mod private { + pub trait Sealed {} +} + +impl private::Sealed for Max {} +impl private::Sealed for Min {} + +/// A node in the [`IntrusiveSortedLinkedList`]. +pub struct Node { + pub val: T, + next: Option>>, +} + +impl Node { + pub fn new(val: T) -> Self { + Self { val, next: None } + } +} + +/// The linked list. +pub struct IntrusiveSortedLinkedList<'a, T, K> { + head: Option>>, + _kind: PhantomData, + _lt: PhantomData<&'a ()>, +} + +impl<'a, T, K> fmt::Debug for IntrusiveSortedLinkedList<'a, T, K> +where + T: Ord + core::fmt::Debug, + K: Kind, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut l = f.debug_list(); + let mut current = self.head; + + while let Some(head) = current { + let head = unsafe { head.as_ref() }; + current = head.next; + + l.entry(&head.val); + } + + l.finish() + } +} + +impl<'a, T, K> IntrusiveSortedLinkedList<'a, T, K> +where + T: Ord, + K: Kind, +{ + pub const fn new() -> Self { + Self { + head: None, + _kind: PhantomData, + _lt: PhantomData, + } + } + + // Push to the list. + pub fn push(&mut self, new: &'a mut Node) { + unsafe { + if let Some(head) = self.head { + if head.as_ref().val.cmp(&new.val) != K::ordering() { + // This is newer than head, replace head + new.next = self.head; + self.head = Some(NonNull::new_unchecked(new)); + } else { + // It's not head, search the list for the correct placement + let mut current = head; + + while let Some(next) = current.as_ref().next { + if next.as_ref().val.cmp(&new.val) != K::ordering() { + break; + } + + current = next; + } + + new.next = current.as_ref().next; + current.as_mut().next = Some(NonNull::new_unchecked(new)); + } + } else { + // List is empty, place at head + self.head = Some(NonNull::new_unchecked(new)) + } + } + } + + /// Get an iterator over the sorted list. + pub fn iter(&self) -> Iter<'_, T, K> { + Iter { + _list: self, + index: self.head, + } + } + + /// Find an element in the list that can be changed and resorted. + pub fn find_mut(&mut self, mut f: F) -> Option> + where + F: FnMut(&T) -> bool, + { + let head = self.head?; + + // Special-case, first element + if f(&unsafe { head.as_ref() }.val) { + return Some(FindMut { + is_head: true, + prev_index: None, + index: self.head, + list: self, + maybe_changed: false, + }); + } + + let mut current = head; + + while let Some(next) = unsafe { current.as_ref() }.next { + if f(&unsafe { next.as_ref() }.val) { + return Some(FindMut { + is_head: false, + prev_index: Some(current), + index: Some(next), + list: self, + maybe_changed: false, + }); + } + + current = next; + } + + None + } + + /// Peek at the first element. + pub fn peek(&self) -> Option<&T> { + self.head.map(|head| unsafe { &head.as_ref().val }) + } + + /// Pops the first element in the list. + /// + /// Complexity is worst-case `O(1)`. + pub fn pop(&mut self) -> Option<&'a Node> { + if let Some(head) = self.head { + let v = unsafe { head.as_ref() }; + self.head = v.next; + Some(v) + } else { + None + } + } + + /// Checks if the linked list is empty. + #[inline] + pub fn is_empty(&self) -> bool { + self.head.is_none() + } +} + +/// Iterator for the linked list. +pub struct Iter<'a, T, K> +where + T: Ord, + K: Kind, +{ + _list: &'a IntrusiveSortedLinkedList<'a, T, K>, + index: Option>>, +} + +impl<'a, T, K> Iterator for Iter<'a, T, K> +where + T: Ord, + K: Kind, +{ + type Item = &'a T; + + fn next(&mut self) -> Option { + let index = self.index?; + + let node = unsafe { index.as_ref() }; + self.index = node.next; + + Some(&node.val) + } +} + +/// Comes from [`IntrusiveSortedLinkedList::find_mut`]. +pub struct FindMut<'a, 'b, T, K> +where + T: Ord + 'b, + K: Kind, +{ + list: &'a mut IntrusiveSortedLinkedList<'b, T, K>, + is_head: bool, + prev_index: Option>>, + index: Option>>, + maybe_changed: bool, +} + +impl<'a, 'b, T, K> FindMut<'a, 'b, T, K> +where + T: Ord, + K: Kind, +{ + unsafe fn pop_internal(&mut self) -> &'b mut Node { + if self.is_head { + // If it is the head element, we can do a normal pop + let mut head = self.list.head.unwrap_unchecked(); + let v = head.as_mut(); + self.list.head = v.next; + v + } else { + // Somewhere in the list + let mut prev = self.prev_index.unwrap_unchecked(); + let mut curr = self.index.unwrap_unchecked(); + + // Re-point the previous index + prev.as_mut().next = curr.as_ref().next; + + curr.as_mut() + } + } + + /// This will pop the element from the list. + /// + /// Complexity is worst-case `O(1)`. + #[inline] + pub fn pop(mut self) -> &'b mut Node { + unsafe { self.pop_internal() } + } + + /// This will resort the element into the correct position in the list if needed. The resorting + /// will only happen if the element has been accessed mutably. + /// + /// Same as calling `drop`. + /// + /// Complexity is worst-case `O(N)`. + #[inline] + pub fn finish(self) { + drop(self) + } +} + +impl<'b, T, K> Drop for FindMut<'_, 'b, T, K> +where + T: Ord + 'b, + K: Kind, +{ + fn drop(&mut self) { + // Only resort the list if the element has changed + if self.maybe_changed { + unsafe { + let val = self.pop_internal(); + self.list.push(val); + } + } + } +} + +impl Deref for FindMut<'_, '_, T, K> +where + T: Ord, + K: Kind, +{ + type Target = T; + + fn deref(&self) -> &Self::Target { + unsafe { &self.index.unwrap_unchecked().as_ref().val } + } +} + +impl DerefMut for FindMut<'_, '_, T, K> +where + T: Ord, + K: Kind, +{ + fn deref_mut(&mut self) -> &mut Self::Target { + self.maybe_changed = true; + unsafe { &mut self.index.unwrap_unchecked().as_mut().val } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn const_new() { + static mut _V1: IntrusiveSortedLinkedList = IntrusiveSortedLinkedList::new(); + } + + #[test] + fn test_peek() { + let mut ll: IntrusiveSortedLinkedList = IntrusiveSortedLinkedList::new(); + + let mut a = Node { val: 1, next: None }; + ll.push(&mut a); + assert_eq!(ll.peek().unwrap(), &1); + + let mut a = Node { val: 2, next: None }; + ll.push(&mut a); + assert_eq!(ll.peek().unwrap(), &2); + + let mut a = Node { val: 3, next: None }; + ll.push(&mut a); + assert_eq!(ll.peek().unwrap(), &3); + + let mut ll: IntrusiveSortedLinkedList = IntrusiveSortedLinkedList::new(); + + let mut a = Node { val: 2, next: None }; + ll.push(&mut a); + assert_eq!(ll.peek().unwrap(), &2); + + let mut a = Node { val: 1, next: None }; + ll.push(&mut a); + assert_eq!(ll.peek().unwrap(), &1); + + let mut a = Node { val: 3, next: None }; + ll.push(&mut a); + assert_eq!(ll.peek().unwrap(), &1); + } + + #[test] + fn test_empty() { + let ll: IntrusiveSortedLinkedList = IntrusiveSortedLinkedList::new(); + + assert!(ll.is_empty()) + } + + #[test] + fn test_updating() { + let mut ll: IntrusiveSortedLinkedList = IntrusiveSortedLinkedList::new(); + + let mut a = Node { val: 1, next: None }; + ll.push(&mut a); + + let mut a = Node { val: 2, next: None }; + ll.push(&mut a); + + let mut a = Node { val: 3, next: None }; + ll.push(&mut a); + + let mut find = ll.find_mut(|v| *v == 2).unwrap(); + + *find += 1000; + find.finish(); + + assert_eq!(ll.peek().unwrap(), &1002); + + let mut find = ll.find_mut(|v| *v == 3).unwrap(); + + *find += 1000; + find.finish(); + + assert_eq!(ll.peek().unwrap(), &1003); + + // Remove largest element + ll.find_mut(|v| *v == 1003).unwrap().pop(); + + assert_eq!(ll.peek().unwrap(), &1002); + } + + #[test] + fn test_updating_1() { + let mut ll: IntrusiveSortedLinkedList = IntrusiveSortedLinkedList::new(); + + let mut a = Node { val: 1, next: None }; + ll.push(&mut a); + + let v = ll.pop().unwrap(); + + assert_eq!(v.val, 1); + } + + #[test] + fn test_updating_2() { + let mut ll: IntrusiveSortedLinkedList = IntrusiveSortedLinkedList::new(); + + let mut a = Node { val: 1, next: None }; + ll.push(&mut a); + + let mut find = ll.find_mut(|v| *v == 1).unwrap(); + + *find += 1000; + find.finish(); + + assert_eq!(ll.peek().unwrap(), &1001); + } +} diff --git a/src/tq.rs b/src/tq.rs index 0f585ba..daa91c8 100644 --- a/src/tq.rs +++ b/src/tq.rs @@ -1,29 +1,28 @@ -use crate::Monotonic; +use crate::{ + sll::{IntrusiveSortedLinkedList, Min as IsslMin, Node as IntrusiveNode}, + Monotonic, +}; use core::cmp::Ordering; -use heapless::sorted_linked_list::{LinkedIndexU16, Min, SortedLinkedList}; +use core::task::Waker; +use heapless::sorted_linked_list::{LinkedIndexU16, Min as SllMin, SortedLinkedList}; -pub struct TimerQueue( - pub SortedLinkedList, LinkedIndexU16, Min, N>, -) +pub struct TimerQueue<'a, Mono, Task, const N_TASK: usize> where Mono: Monotonic, - Task: Copy; + Task: Copy, +{ + pub task_queue: SortedLinkedList, LinkedIndexU16, SllMin, N_TASK>, + pub waker_queue: IntrusiveSortedLinkedList<'a, WakerNotReady, IsslMin>, +} -impl TimerQueue +impl<'a, Mono, Task, const N_TASK: usize> TimerQueue<'a, Mono, Task, N_TASK> where - Mono: Monotonic, + Mono: Monotonic + 'a, Task: Copy, { - /// # Safety - /// - /// Writing to memory with a transmute in order to enable - /// interrupts of the ``SysTick`` timer - /// - /// Enqueue a task without checking if it is full - #[inline] - pub unsafe fn enqueue_unchecked( - &mut self, - nr: NotReady, + fn check_if_enable( + &self, + instant: Mono::Instant, enable_interrupt: F1, pend_handler: F2, mono: Option<&mut Mono>, @@ -33,11 +32,17 @@ where { // Check if the top contains a non-empty element and if that element is // greater than nr - let if_heap_max_greater_than_nr = - self.0.peek().map_or(true, |head| nr.instant < head.instant); + let if_task_heap_max_greater_than_nr = self + .task_queue + .peek() + .map_or(true, |head| instant < head.instant); + let if_waker_heap_max_greater_than_nr = self + .waker_queue + .peek() + .map_or(true, |head| instant < head.instant); - if if_heap_max_greater_than_nr { - if Mono::DISABLE_INTERRUPT_ON_EMPTY_QUEUE && self.0.is_empty() { + if if_task_heap_max_greater_than_nr || if_waker_heap_max_greater_than_nr { + if Mono::DISABLE_INTERRUPT_ON_EMPTY_QUEUE && self.is_empty() { if let Some(mono) = mono { mono.enable_timer(); } @@ -46,19 +51,49 @@ where pend_handler(); } + } - self.0.push_unchecked(nr); + /// Enqueue a task without checking if it is full + #[inline] + pub unsafe fn enqueue_task_unchecked( + &mut self, + nr: TaskNotReady, + enable_interrupt: F1, + pend_handler: F2, + mono: Option<&mut Mono>, + ) where + F1: FnOnce(), + F2: FnOnce(), + { + self.check_if_enable(nr.instant, enable_interrupt, pend_handler, mono); + self.task_queue.push_unchecked(nr); } - /// Check if the timer queue is empty. + /// Enqueue a waker + #[inline] + pub fn enqueue_waker( + &mut self, + nr: &'a mut IntrusiveNode>, + enable_interrupt: F1, + pend_handler: F2, + mono: Option<&mut Mono>, + ) where + F1: FnOnce(), + F2: FnOnce(), + { + self.check_if_enable(nr.val.instant, enable_interrupt, pend_handler, mono); + self.waker_queue.push(nr); + } + + /// Check if all the timer queue is empty. #[inline] pub fn is_empty(&self) -> bool { - self.0.is_empty() + self.task_queue.is_empty() && self.waker_queue.is_empty() } - /// Cancel the marker value - pub fn cancel_marker(&mut self, marker: u32) -> Option<(Task, u8)> { - if let Some(val) = self.0.find_mut(|nr| nr.marker == marker) { + /// Cancel the marker value for a task + pub fn cancel_task_marker(&mut self, marker: u32) -> Option<(Task, u8)> { + if let Some(val) = self.task_queue.find_mut(|nr| nr.marker == marker) { let nr = val.pop(); Some((nr.task, nr.index)) @@ -67,16 +102,23 @@ where } } - /// Update the instant at an marker value to a new instant + /// Cancel the marker value for a waker + pub fn cancel_waker_marker(&mut self, marker: u32) { + if let Some(val) = self.waker_queue.find_mut(|nr| nr.marker == marker) { + let _ = val.pop(); + } + } + + /// Update the instant at an marker value for a task to a new instant #[allow(clippy::result_unit_err)] - pub fn update_marker( + pub fn update_task_marker( &mut self, marker: u32, new_marker: u32, instant: Mono::Instant, pend_handler: F, ) -> Result<(), ()> { - if let Some(mut val) = self.0.find_mut(|nr| nr.marker == marker) { + if let Some(mut val) = self.task_queue.find_mut(|nr| nr.marker == marker) { val.instant = instant; val.marker = new_marker; @@ -89,6 +131,62 @@ where } } + fn dequeue_task_queue( + &mut self, + instant: Mono::Instant, + mono: &mut Mono, + ) -> Option<(Task, u8)> { + if instant <= mono.now() { + // task became ready + let nr = unsafe { self.task_queue.pop_unchecked() }; + Some((nr.task, nr.index)) + } else { + // Set compare + mono.set_compare(instant); + + // Double check that the instant we set is really in the future, else + // dequeue. If the monotonic is fast enough it can happen that from the + // read of now to the set of the compare, the time can overflow. This is to + // guard against this. + if instant <= mono.now() { + let nr = unsafe { self.task_queue.pop_unchecked() }; + Some((nr.task, nr.index)) + } else { + None + } + } + } + + fn dequeue_waker_queue(&mut self, instant: Mono::Instant, mono: &mut Mono) -> bool { + let mut did_wake = false; + + if instant <= mono.now() { + // Task became ready, wake the waker + if let Some(v) = self.waker_queue.pop() { + v.val.waker.wake_by_ref(); + + did_wake = true; + } + } else { + // Set compare + mono.set_compare(instant); + + // Double check that the instant we set is really in the future, else + // dequeue. If the monotonic is fast enough it can happen that from the + // read of now to the set of the compare, the time can overflow. This is to + // guard against this. + if instant <= mono.now() { + if let Some(v) = self.waker_queue.pop() { + v.val.waker.wake_by_ref(); + + did_wake = true; + } + } + } + + did_wake + } + /// Dequeue a task from the ``TimerQueue`` pub fn dequeue(&mut self, disable_interrupt: F, mono: &mut Mono) -> Option<(Task, u8)> where @@ -96,59 +194,72 @@ where { mono.clear_compare_flag(); - if let Some(instant) = self.0.peek().map(|p| p.instant) { - if instant <= mono.now() { - // task became ready - let nr = unsafe { self.0.pop_unchecked() }; + loop { + let tq = self.task_queue.peek().map(|p| p.instant); + let wq = self.waker_queue.peek().map(|p| p.instant); - Some((nr.task, nr.index)) - } else { - // Set compare - mono.set_compare(instant); - - // Double check that the instant we set is really in the future, else - // dequeue. If the monotonic is fast enough it can happen that from the - // read of now to the set of the compare, the time can overflow. This is to - // guard against this. - if instant <= mono.now() { - let nr = unsafe { self.0.pop_unchecked() }; - - Some((nr.task, nr.index)) - } else { - None + let dequeue_task; + let instant; + + match (tq, wq) { + (Some(tq_instant), Some(wq_instant)) => { + if tq_instant <= wq_instant { + dequeue_task = true; + instant = tq_instant; + } else { + dequeue_task = false; + instant = wq_instant; + } + } + (Some(tq_instant), None) => { + dequeue_task = true; + instant = tq_instant; + } + (None, Some(wq_instant)) => { + dequeue_task = false; + instant = wq_instant; + } + (None, None) => { + // The queue is empty, disable the interrupt. + if Mono::DISABLE_INTERRUPT_ON_EMPTY_QUEUE { + disable_interrupt(); + mono.disable_timer(); + } + + return None; } - } - } else { - // The queue is empty, disable the interrupt. - if Mono::DISABLE_INTERRUPT_ON_EMPTY_QUEUE { - disable_interrupt(); - mono.disable_timer(); } - None + if dequeue_task { + return self.dequeue_task_queue(instant, mono); + } else if !self.dequeue_waker_queue(instant, mono) { + return None; + } else { + // Run the dequeue again + } } } } -pub struct NotReady +pub struct TaskNotReady where Task: Copy, Mono: Monotonic, { + pub task: Task, pub index: u8, pub instant: Mono::Instant, - pub task: Task, pub marker: u32, } -impl Eq for NotReady +impl Eq for TaskNotReady where Task: Copy, Mono: Monotonic, { } -impl Ord for NotReady +impl Ord for TaskNotReady where Task: Copy, Mono: Monotonic, @@ -158,7 +269,7 @@ where } } -impl PartialEq for NotReady +impl PartialEq for TaskNotReady where Task: Copy, Mono: Monotonic, @@ -168,7 +279,7 @@ where } } -impl PartialOrd for NotReady +impl PartialOrd for TaskNotReady where Task: Copy, Mono: Monotonic, @@ -177,3 +288,41 @@ where Some(self.cmp(other)) } } + +pub struct WakerNotReady +where + Mono: Monotonic, +{ + pub waker: Waker, + pub instant: Mono::Instant, + pub marker: u32, +} + +impl Eq for WakerNotReady where Mono: Monotonic {} + +impl Ord for WakerNotReady +where + Mono: Monotonic, +{ + fn cmp(&self, other: &Self) -> Ordering { + self.instant.cmp(&other.instant) + } +} + +impl PartialEq for WakerNotReady +where + Mono: Monotonic, +{ + fn eq(&self, other: &Self) -> bool { + self.instant == other.instant + } +} + +impl PartialOrd for WakerNotReady +where + Mono: Monotonic, +{ + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} diff --git a/ui/extern-interrupt-not-enough.stderr b/ui/extern-interrupt-not-enough.stderr index a667c58..d8c01b9 100644 --- a/ui/extern-interrupt-not-enough.stderr +++ b/ui/extern-interrupt-not-enough.stderr @@ -1,5 +1,5 @@ -error: not enough interrupts to dispatch all software tasks (need: 1; given: 0) - --> $DIR/extern-interrupt-not-enough.rs:17:8 +error: not enough interrupts to dispatch all software and async tasks (need: 1; given: 0) - one interrupt is needed per priority and sync/async task + --> ui/extern-interrupt-not-enough.rs:17:8 | 17 | fn a(_: a::Context) {} | ^ diff --git a/ui/task-priority-too-high.rs b/ui/task-priority-too-high.rs index e7e0cce..46ab561 100644 --- a/ui/task-priority-too-high.rs +++ b/ui/task-priority-too-high.rs @@ -9,7 +9,7 @@ mod app { struct Local {} #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { + fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { (Shared {}, Local {}, init::Monotonics()) } diff --git a/ui/task-priority-too-high.stderr b/ui/task-priority-too-high.stderr index 026124c..a7a15eb 100644 --- a/ui/task-priority-too-high.stderr +++ b/ui/task-priority-too-high.stderr @@ -1,3 +1,11 @@ +warning: unused variable: `cx` + --> ui/task-priority-too-high.rs:12:13 + | +12 | fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { + | ^^ help: if this is intentional, prefix it with an underscore: `_cx` + | + = note: `#[warn(unused_variables)]` on by default + error[E0080]: evaluation of constant value failed --> ui/task-priority-too-high.rs:3:1 | diff --git a/xtask/src/command.rs b/xtask/src/command.rs index 100888c..889540c 100644 --- a/xtask/src/command.rs +++ b/xtask/src/command.rs @@ -47,6 +47,7 @@ impl<'a> CargoCommand<'a> { mode, } => { let mut args = vec![ + "+nightly", self.name(), "--example", example, @@ -69,7 +70,7 @@ impl<'a> CargoCommand<'a> { features, mode, } => { - let mut args = vec![self.name(), "--examples", "--target", target]; + let mut args = vec!["+nightly", self.name(), "--examples", "--target", target]; if let Some(feature_name) = features { args.extend_from_slice(&["--features", feature_name]); -- cgit v1.2.3 From 9829d0ac07180967208403610bc9a25249b9fe85 Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Mon, 2 Jan 2023 14:58:37 +0100 Subject: Add check again --- macros/src/check.rs | 28 ++++++---------------------- macros/src/lib.rs | 6 ++++++ ui/extern-interrupt-not-enough.stderr | 2 +- 3 files changed, 13 insertions(+), 23 deletions(-) diff --git a/macros/src/check.rs b/macros/src/check.rs index b0ad6f8..312b84d 100644 --- a/macros/src/check.rs +++ b/macros/src/check.rs @@ -1,18 +1,12 @@ use std::collections::HashSet; -use proc_macro2::Span; -use rtic_syntax::{analyze::Analysis, ast::App}; -use syn::{parse, Path}; +use crate::syntax::ast::App; +use syn::parse; -pub struct Extra { - pub device: Path, - pub peripherals: bool, -} - -pub fn app(app: &App, _analysis: &Analysis) -> parse::Result { +pub fn app(app: &App) -> parse::Result<()> { // Check that external (device-specific) interrupts are not named after known (Cortex-M) // exceptions - for name in app.args.extern_interrupts.keys() { + for name in app.args.dispatchers.keys() { let name_s = name.to_string(); match &*name_s { @@ -41,7 +35,7 @@ pub fn app(app: &App, _analysis: &Analysis) -> parse::Result { .collect::>(); let need = priorities.len(); - let given = app.args.extern_interrupts.len(); + let given = app.args.dispatchers.len(); if need > given { let s = { format!( @@ -72,15 +66,5 @@ pub fn app(app: &App, _analysis: &Analysis) -> parse::Result { } } - if let Some(device) = app.args.device.clone() { - Ok(Extra { - device, - peripherals: app.args.peripherals, - }) - } else { - Err(parse::Error::new( - Span::call_site(), - "a `device` argument must be specified in `#[rtic::app]`", - )) - } + Ok(()) } diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 7729dcb..1bda8d2 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -10,6 +10,7 @@ use std::{env, fs, path::Path}; mod analyze; mod bindings; +mod check; mod codegen; mod syntax; @@ -61,6 +62,11 @@ pub fn app(args: TokenStream, input: TokenStream) -> TokenStream { Ok(x) => x, }; + match check::app(&app) { + Err(e) => return e.to_compile_error().into(), + _ => {} + } + let analysis = analyze::app(analysis, &app); let ts = codegen::app(&app, &analysis); diff --git a/ui/extern-interrupt-not-enough.stderr b/ui/extern-interrupt-not-enough.stderr index d8c01b9..6f28b7a 100644 --- a/ui/extern-interrupt-not-enough.stderr +++ b/ui/extern-interrupt-not-enough.stderr @@ -1,4 +1,4 @@ -error: not enough interrupts to dispatch all software and async tasks (need: 1; given: 0) - one interrupt is needed per priority and sync/async task +error: not enough interrupts to dispatch all software tasks (need: 1; given: 0) --> ui/extern-interrupt-not-enough.rs:17:8 | 17 | fn a(_: a::Context) {} -- cgit v1.2.3 From d7ed7a8b9f78344f6855fa1c2655ae0d85e44068 Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Tue, 3 Jan 2023 07:51:35 +0100 Subject: syntax: Remove parse settings struct --- macros/src/lib.rs | 27 ++------------------------- macros/src/syntax.rs | 24 ++++-------------------- macros/src/syntax/optimize.rs | 2 +- macros/src/syntax/parse.rs | 20 +++++--------------- macros/src/syntax/parse/app.rs | 11 ++++------- macros/ui/extern-interrupt-used.rs | 2 +- macros/ui/interrupt-double.rs | 2 +- macros/ui/monotonic-binds-collision-task.rs | 2 +- macros/ui/task-bind.rs | 7 ------- macros/ui/task-bind.stderr | 5 ----- macros/ui/task-interrupt-same-prio-spawn.rs | 2 +- macros/ui/task-interrupt.rs | 2 +- macros/ui/task-priority-too-low.rs | 2 +- 13 files changed, 22 insertions(+), 86 deletions(-) delete mode 100644 macros/ui/task-bind.rs delete mode 100644 macros/ui/task-bind.stderr diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 1bda8d2..34f2bb6 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -18,25 +18,7 @@ mod syntax; #[doc(hidden)] #[proc_macro_attribute] pub fn mock_app(args: TokenStream, input: TokenStream) -> TokenStream { - let mut settings = syntax::Settings::default(); - let mut rtic_args = vec![]; - for arg in args.to_string().split(',') { - if arg.trim() == "parse_binds" { - settings.parse_binds = true; - } else if arg.trim() == "parse_extern_interrupt" { - settings.parse_extern_interrupt = true; - } else { - rtic_args.push(arg.to_string()); - } - } - - // rtic_args.push("device = mock".into()); - - let args = rtic_args.join(", ").parse(); - - println!("args: {:?}", args); - - if let Err(e) = syntax::parse(args.unwrap(), input, settings) { + if let Err(e) = syntax::parse(args, input) { e.to_compile_error().into() } else { "fn main() {}".parse().unwrap() @@ -52,12 +34,7 @@ pub fn mock_app(args: TokenStream, input: TokenStream) -> TokenStream { /// Should never panic, cargo feeds a path which is later converted to a string #[proc_macro_attribute] pub fn app(args: TokenStream, input: TokenStream) -> TokenStream { - let mut settings = syntax::Settings::default(); - settings.optimize_priorities = false; - settings.parse_binds = true; - settings.parse_extern_interrupt = true; - - let (app, analysis) = match syntax::parse(args, input, settings) { + let (app, analysis) = match syntax::parse(args, input) { Err(e) => return e.to_compile_error().into(), Ok(x) => x, }; diff --git a/macros/src/syntax.rs b/macros/src/syntax.rs index 09b2ab3..d6f5a47 100644 --- a/macros/src/syntax.rs +++ b/macros/src/syntax.rs @@ -13,7 +13,6 @@ mod accessors; pub mod analyze; pub mod ast; mod check; -mod optimize; mod parse; /// An ordered map keyed by identifier @@ -31,10 +30,10 @@ pub enum Context<'a> { /// The `init`-ialization function Init, - /// A software task: `#[task]` + /// A async software task SoftwareTask(&'a Ident), - /// A hardware task: `#[exception]` or `#[interrupt]` + /// A hardware task HardwareTask(&'a Ident), } @@ -93,36 +92,21 @@ impl<'a> Context<'a> { } } -/// Parser and optimizer configuration -#[derive(Default)] -#[non_exhaustive] -pub struct Settings { - /// Whether to accept the `binds` argument in `#[task]` or not - pub parse_binds: bool, - /// Whether to parse `extern` interrupts (functions) or not - pub parse_extern_interrupt: bool, - /// Whether to "compress" priorities or not - pub optimize_priorities: bool, -} - /// Parses the input of the `#[app]` attribute pub fn parse( args: TokenStream, input: TokenStream, - settings: Settings, ) -> Result<(ast::App, analyze::Analysis), syn::parse::Error> { - parse2(args.into(), input.into(), settings) + parse2(args.into(), input.into()) } /// `proc_macro2::TokenStream` version of `parse` pub fn parse2( args: TokenStream2, input: TokenStream2, - settings: Settings, ) -> Result<(ast::App, analyze::Analysis), syn::parse::Error> { - let mut app = parse::app(args, input, &settings)?; + let app = parse::app(args, input)?; check::app(&app)?; - optimize::app(&mut app, &settings); match analyze::app(&app) { Err(e) => Err(e), diff --git a/macros/src/syntax/optimize.rs b/macros/src/syntax/optimize.rs index 87a6258..e83ba31 100644 --- a/macros/src/syntax/optimize.rs +++ b/macros/src/syntax/optimize.rs @@ -1,6 +1,6 @@ use std::collections::{BTreeSet, HashMap}; -use crate::syntax::{ast::App, Settings}; +use crate::syntax::ast::App; pub fn app(app: &mut App, settings: &Settings) { // "compress" priorities diff --git a/macros/src/syntax/parse.rs b/macros/src/syntax/parse.rs index 74f94f2..ceedaa9 100644 --- a/macros/src/syntax/parse.rs +++ b/macros/src/syntax/parse.rs @@ -20,15 +20,15 @@ use crate::syntax::{ App, AppArgs, HardwareTaskArgs, IdleArgs, InitArgs, MonotonicArgs, SoftwareTaskArgs, TaskLocal, }, - Either, Settings, + Either, }; // Parse the app, both app arguments and body (input) -pub fn app(args: TokenStream2, input: TokenStream2, settings: &Settings) -> parse::Result { +pub fn app(args: TokenStream2, input: TokenStream2) -> parse::Result { let args = AppArgs::parse(args)?; let input: Input = syn::parse2(input)?; - App::parse(args, input, settings) + App::parse(args, input) } pub(crate) struct Input { @@ -188,10 +188,7 @@ fn idle_args(tokens: TokenStream2) -> parse::Result { .parse2(tokens) } -fn task_args( - tokens: TokenStream2, - settings: &Settings, -) -> parse::Result> { +fn task_args(tokens: TokenStream2) -> parse::Result> { (|input: ParseStream<'_>| -> parse::Result> { if input.is_empty() { return Ok(Either::Right(SoftwareTaskArgs::default())); @@ -242,14 +239,7 @@ fn task_args( let _: Token![=] = content.parse()?; match &*ident_s { - "binds" if !settings.parse_binds => { - return Err(parse::Error::new( - ident.span(), - "Unexpected bind in task argument. Binds are only parsed if Settings::parse_binds is set.", - )); - } - - "binds" if settings.parse_binds => { + "binds" => { if binds.is_some() { return Err(parse::Error::new( ident.span(), diff --git a/macros/src/syntax/parse/app.rs b/macros/src/syntax/parse/app.rs index 7eb415d..dd7c399 100644 --- a/macros/src/syntax/parse/app.rs +++ b/macros/src/syntax/parse/app.rs @@ -15,7 +15,7 @@ use crate::syntax::{ LocalResource, Monotonic, MonotonicArgs, SharedResource, SoftwareTask, }, parse::{self as syntax_parse, util}, - Either, Map, Set, Settings, + Either, Map, Set, }; impl AppArgs { @@ -142,7 +142,7 @@ impl AppArgs { } impl App { - pub(crate) fn parse(args: AppArgs, input: Input, settings: &Settings) -> parse::Result { + pub(crate) fn parse(args: AppArgs, input: Input) -> parse::Result { let mut init = None; let mut idle = None; @@ -253,7 +253,7 @@ impl App { )); } - match syntax_parse::task_args(item.attrs.remove(pos).tokens, settings)? { + match syntax_parse::task_args(item.attrs.remove(pos).tokens)? { Either::Left(args) => { check_binding(&args.binds)?; check_ident(&item.sig.ident)?; @@ -410,10 +410,7 @@ impl App { )); } - match syntax_parse::task_args( - item.attrs.remove(pos).tokens, - settings, - )? { + match syntax_parse::task_args(item.attrs.remove(pos).tokens)? { Either::Left(args) => { check_binding(&args.binds)?; check_ident(&item.sig.ident)?; diff --git a/macros/ui/extern-interrupt-used.rs b/macros/ui/extern-interrupt-used.rs index 71dc50f..a6e0b3b 100644 --- a/macros/ui/extern-interrupt-used.rs +++ b/macros/ui/extern-interrupt-used.rs @@ -1,6 +1,6 @@ #![no_main] -#[rtic_macros::mock_app(parse_extern_interrupt, parse_binds, device = mock, dispatchers = [EXTI0])] +#[rtic_macros::mock_app(device = mock, dispatchers = [EXTI0])] mod app { #[shared] struct Shared {} diff --git a/macros/ui/interrupt-double.rs b/macros/ui/interrupt-double.rs index 1133c5c..e2addc7 100644 --- a/macros/ui/interrupt-double.rs +++ b/macros/ui/interrupt-double.rs @@ -1,6 +1,6 @@ #![no_main] -#[rtic_macros::mock_app(parse_binds, device = mock)] +#[rtic_macros::mock_app(device = mock)] mod app { #[task(binds = UART0)] fn foo(_: foo::Context) {} diff --git a/macros/ui/monotonic-binds-collision-task.rs b/macros/ui/monotonic-binds-collision-task.rs index 57c59c1..1c58f9d 100644 --- a/macros/ui/monotonic-binds-collision-task.rs +++ b/macros/ui/monotonic-binds-collision-task.rs @@ -1,6 +1,6 @@ #![no_main] -#[rtic_macros::mock_app(parse_extern_interrupt, parse_binds, device = mock)] +#[rtic_macros::mock_app(device = mock)] mod app { #[monotonic(binds = Tim1)] type Fast1 = hal::Tim1Monotonic; diff --git a/macros/ui/task-bind.rs b/macros/ui/task-bind.rs deleted file mode 100644 index de60524..0000000 --- a/macros/ui/task-bind.rs +++ /dev/null @@ -1,7 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[task(binds = UART0)] - fn foo(_: foo::Context) {} -} diff --git a/macros/ui/task-bind.stderr b/macros/ui/task-bind.stderr deleted file mode 100644 index 60cfdc8..0000000 --- a/macros/ui/task-bind.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: Unexpected bind in task argument. Binds are only parsed if Settings::parse_binds is set. - --> $DIR/task-bind.rs:5:12 - | -5 | #[task(binds = UART0)] - | ^^^^^ diff --git a/macros/ui/task-interrupt-same-prio-spawn.rs b/macros/ui/task-interrupt-same-prio-spawn.rs index 741e60e..1d5f1f8 100644 --- a/macros/ui/task-interrupt-same-prio-spawn.rs +++ b/macros/ui/task-interrupt-same-prio-spawn.rs @@ -1,6 +1,6 @@ #![no_main] -#[rtic_macros::mock_app(parse_binds, device = mock)] +#[rtic_macros::mock_app(device = mock)] mod app { #[task(binds = SysTick, only_same_priority_spawn_please_fix_me)] fn foo(_: foo::Context) {} diff --git a/macros/ui/task-interrupt.rs b/macros/ui/task-interrupt.rs index 71fef9a..5e063de 100644 --- a/macros/ui/task-interrupt.rs +++ b/macros/ui/task-interrupt.rs @@ -1,6 +1,6 @@ #![no_main] -#[rtic_macros::mock_app(parse_binds, device = mock)] +#[rtic_macros::mock_app(device = mock)] mod app { #[task(binds = SysTick)] fn foo(_: foo::Context) {} diff --git a/macros/ui/task-priority-too-low.rs b/macros/ui/task-priority-too-low.rs index beed4de..16e0557 100644 --- a/macros/ui/task-priority-too-low.rs +++ b/macros/ui/task-priority-too-low.rs @@ -1,6 +1,6 @@ #![no_main] -#[rtic_macros::mock_app(parse_binds, device = mock)] +#[rtic_macros::mock_app(device = mock)] mod app { #[task(binds = UART0, priority = 0)] fn foo(_: foo::Context) {} -- cgit v1.2.3 From f8352122a301c30db7c7851ebf50ad1608ebdad3 Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Tue, 3 Jan 2023 15:10:59 +0100 Subject: Min codegen --- macros/src/analyze.rs | 23 +-- macros/src/codegen.rs | 29 +-- macros/src/codegen/assertions.rs | 5 - macros/src/codegen/async_dispatchers.rs | 66 +++---- macros/src/codegen/dispatchers.rs | 146 --------------- macros/src/codegen/module.rs | 300 ++----------------------------- macros/src/codegen/monotonic.rs | 280 ----------------------------- macros/src/codegen/post_init.rs | 20 +-- macros/src/codegen/pre_init.rs | 68 +------ macros/src/codegen/shared_resources.rs | 6 +- macros/src/codegen/software_tasks.rs | 179 ------------------ macros/src/codegen/timer_queue.rs | 170 ------------------ macros/src/codegen/util.rs | 111 +----------- macros/src/syntax/analyze.rs | 57 +----- macros/src/syntax/ast.rs | 50 +----- macros/src/syntax/parse.rs | 121 +------------ macros/src/syntax/parse/app.rs | 58 +----- macros/src/syntax/parse/hardware_task.rs | 44 +++-- macros/src/syntax/parse/idle.rs | 18 +- macros/src/syntax/parse/init.rs | 22 ++- macros/src/syntax/parse/software_task.rs | 26 ++- macros/src/syntax/parse/util.rs | 26 ++- 22 files changed, 130 insertions(+), 1695 deletions(-) delete mode 100644 macros/src/codegen/dispatchers.rs delete mode 100644 macros/src/codegen/monotonic.rs delete mode 100644 macros/src/codegen/software_tasks.rs delete mode 100644 macros/src/codegen/timer_queue.rs diff --git a/macros/src/analyze.rs b/macros/src/analyze.rs index ec12cfb..cb42ad6 100644 --- a/macros/src/analyze.rs +++ b/macros/src/analyze.rs @@ -10,8 +10,7 @@ use syn::Ident; /// Extend the upstream `Analysis` struct with our field pub struct Analysis { parent: analyze::Analysis, - pub interrupts_normal: BTreeMap, - pub interrupts_async: BTreeMap, + pub interrupts: BTreeMap, } impl ops::Deref for Analysis { @@ -30,27 +29,12 @@ pub fn app(analysis: analyze::Analysis, app: &App) -> Analysis { let priorities = app .software_tasks .values() - .filter(|task| !task.is_async) - .map(|task| task.args.priority) - .collect::>(); - - let priorities_async = app - .software_tasks - .values() - .filter(|task| task.is_async) .map(|task| task.args.priority) .collect::>(); // map from priorities to interrupts (holding name and attributes) - let interrupts_normal: BTreeMap = priorities - .iter() - .copied() - .rev() - .map(|p| (p, available_interrupt.pop().expect("UNREACHABLE"))) - .collect(); - - let interrupts_async: BTreeMap = priorities_async + let interrupts: BTreeMap = priorities .iter() .copied() .rev() @@ -59,7 +43,6 @@ pub fn app(analysis: analyze::Analysis, app: &App) -> Analysis { Analysis { parent: analysis, - interrupts_normal, - interrupts_async, + interrupts, } } diff --git a/macros/src/codegen.rs b/macros/src/codegen.rs index ef81732..618d9f3 100644 --- a/macros/src/codegen.rs +++ b/macros/src/codegen.rs @@ -6,20 +6,20 @@ use crate::syntax::ast::App; mod assertions; mod async_dispatchers; -mod dispatchers; +// mod dispatchers; mod hardware_tasks; mod idle; mod init; mod local_resources; mod local_resources_struct; mod module; -mod monotonic; +// mod monotonic; mod post_init; mod pre_init; mod shared_resources; mod shared_resources_struct; -mod software_tasks; -mod timer_queue; +// mod software_tasks; +// mod timer_queue; mod util; #[allow(clippy::too_many_lines)] @@ -92,14 +92,7 @@ pub fn app(app: &App, analysis: &Analysis) -> TokenStream2 { let (mod_app_hardware_tasks, root_hardware_tasks, user_hardware_tasks) = hardware_tasks::codegen(app, analysis); - let (mod_app_software_tasks, root_software_tasks, user_software_tasks) = - software_tasks::codegen(app, analysis); - - let monotonics = monotonic::codegen(app, analysis); - - let mod_app_dispatchers = dispatchers::codegen(app, analysis); let mod_app_async_dispatchers = async_dispatchers::codegen(app, analysis); - let mod_app_timer_queue = timer_queue::codegen(app, analysis); let user_imports = &app.user_imports; let user_code = &app.user_code; let name = &app.name; @@ -113,8 +106,6 @@ pub fn app(app: &App, analysis: &Analysis) -> TokenStream2 { /// Always include the device crate which contains the vector table use #device as #rt_err; - #monotonics - #(#user_imports)* /// User code from within the module @@ -125,8 +116,6 @@ pub fn app(app: &App, analysis: &Analysis) -> TokenStream2 { #(#user_hardware_tasks)* - #(#user_software_tasks)* - #(#root)* #mod_shared_resources @@ -135,9 +124,7 @@ pub fn app(app: &App, analysis: &Analysis) -> TokenStream2 { #(#root_hardware_tasks)* - #(#root_software_tasks)* - - /// App module + /// app module #(#mod_app)* #(#mod_app_shared_resources)* @@ -146,14 +133,8 @@ pub fn app(app: &App, analysis: &Analysis) -> TokenStream2 { #(#mod_app_hardware_tasks)* - #(#mod_app_software_tasks)* - - #(#mod_app_dispatchers)* - #(#mod_app_async_dispatchers)* - #(#mod_app_timer_queue)* - #(#mains)* } ) diff --git a/macros/src/codegen/assertions.rs b/macros/src/codegen/assertions.rs index 0f8326c..dd94aa6 100644 --- a/macros/src/codegen/assertions.rs +++ b/macros/src/codegen/assertions.rs @@ -16,11 +16,6 @@ pub fn codegen(app: &App, analysis: &Analysis) -> Vec { stmts.push(quote!(rtic::export::assert_sync::<#ty>();)); } - for (_, monotonic) in &app.monotonics { - let ty = &monotonic.ty; - stmts.push(quote!(rtic::export::assert_monotonic::<#ty>();)); - } - let device = &app.args.device; let chunks_name = util::priority_mask_chunks_ident(); let no_basepri_checks: Vec<_> = app diff --git a/macros/src/codegen/async_dispatchers.rs b/macros/src/codegen/async_dispatchers.rs index 8b0e928..aa854d7 100644 --- a/macros/src/codegen/async_dispatchers.rs +++ b/macros/src/codegen/async_dispatchers.rs @@ -7,65 +7,47 @@ use quote::quote; pub fn codegen(app: &App, analysis: &Analysis) -> Vec { let mut items = vec![]; - let interrupts = &analysis.interrupts_async; + let interrupts = &analysis.interrupts; // Generate executor definition and priority in global scope - for (name, task) in app.software_tasks.iter() { - if task.is_async { - let type_name = util::internal_task_ident(name, "F"); - let exec_name = util::internal_task_ident(name, "EXEC"); - let prio_name = util::internal_task_ident(name, "PRIORITY"); + for (name, _) in app.software_tasks.iter() { + let type_name = util::internal_task_ident(name, "F"); + let exec_name = util::internal_task_ident(name, "EXEC"); + let prio_name = util::internal_task_ident(name, "PRIORITY"); - items.push(quote!( - #[allow(non_camel_case_types)] - type #type_name = impl core::future::Future + 'static; - #[allow(non_upper_case_globals)] - static #exec_name: - rtic::RacyCell> = - rtic::RacyCell::new(rtic::export::executor::AsyncTaskExecutor::new()); - - // The executors priority, this can be any value - we will overwrite it when we - // start a task - #[allow(non_upper_case_globals)] - static #prio_name: rtic::RacyCell = - unsafe { rtic::RacyCell::new(rtic::export::Priority::new(0)) }; - )); - } + items.push(quote!( + #[allow(non_camel_case_types)] + type #type_name = impl core::future::Future + 'static; + #[allow(non_upper_case_globals)] + static #exec_name: + rtic::RacyCell> = + rtic::RacyCell::new(rtic::export::executor::AsyncTaskExecutor::new()); + + // The executors priority, this can be any value - we will overwrite it when we + // start a task + #[allow(non_upper_case_globals)] + static #prio_name: rtic::RacyCell = + unsafe { rtic::RacyCell::new(rtic::export::Priority::new(0)) }; + )); } for (&level, channel) in &analysis.channels { - if channel - .tasks - .iter() - .map(|task_name| !app.software_tasks[task_name].is_async) - .all(|is_not_async| is_not_async) - { - // check if all tasks are not async, if so don't generate this. - continue; - } - let mut stmts = vec![]; let device = &app.args.device; let enum_ = util::interrupt_ident(); let interrupt = util::suffixed(&interrupts[&level].0.to_string()); - for name in channel - .tasks - .iter() - .filter(|name| app.software_tasks[*name].is_async) - { + for name in channel.tasks.iter() { let exec_name = util::internal_task_ident(name, "EXEC"); let prio_name = util::internal_task_ident(name, "PRIORITY"); - let task = &app.software_tasks[name]; + // let task = &app.software_tasks[name]; // let cfgs = &task.cfgs; - let (_, tupled, pats, input_types) = util::regroup_inputs(&task.inputs); let executor_run_ident = util::executor_run_ident(name); - let n = util::capacity_literal(channel.capacity as usize + 1); let rq = util::rq_async_ident(name); let (rq_ty, rq_expr) = { ( - quote!(rtic::export::ASYNCRQ<#input_types, #n>), + quote!(rtic::export::ASYNCRQ<(), 2>), // TODO: This needs updating to a counter instead of a queue quote!(rtic::export::Queue::new()), ) }; @@ -79,13 +61,13 @@ pub fn codegen(app: &App, analysis: &Analysis) -> Vec { stmts.push(quote!( if !(&*#exec_name.get()).is_running() { - if let Some(#tupled) = rtic::export::interrupt::free(|_| (&mut *#rq.get_mut()).dequeue()) { + if let Some(()) = rtic::export::interrupt::free(|_| (&mut *#rq.get_mut()).dequeue()) { // The async executor needs a static priority #prio_name.get_mut().write(rtic::export::Priority::new(PRIORITY)); let priority: &'static _ = &*#prio_name.get(); - (&mut *#exec_name.get_mut()).spawn(#name(#name::Context::new(priority) #(,#pats)*)); + (&mut *#exec_name.get_mut()).spawn(#name(#name::Context::new(priority))); #executor_run_ident.store(true, core::sync::atomic::Ordering::Relaxed); } } diff --git a/macros/src/codegen/dispatchers.rs b/macros/src/codegen/dispatchers.rs deleted file mode 100644 index 1a8b404..0000000 --- a/macros/src/codegen/dispatchers.rs +++ /dev/null @@ -1,146 +0,0 @@ -use crate::syntax::ast::App; -use crate::{analyze::Analysis, codegen::util}; -use proc_macro2::TokenStream as TokenStream2; -use quote::quote; - -/// Generates task dispatchers -pub fn codegen(app: &App, analysis: &Analysis) -> Vec { - let mut items = vec![]; - - let interrupts = &analysis.interrupts_normal; - - for (&level, channel) in &analysis.channels { - if channel - .tasks - .iter() - .map(|task_name| app.software_tasks[task_name].is_async) - .all(|is_async| is_async) - { - // check if all tasks are async, if so don't generate this. - continue; - } - - let mut stmts = vec![]; - - let variants = channel - .tasks - .iter() - .filter(|name| !app.software_tasks[*name].is_async) - .map(|name| { - let cfgs = &app.software_tasks[name].cfgs; - - quote!( - #(#cfgs)* - #name - ) - }) - .collect::>(); - - // For future use - // let doc = format!( - // "Software tasks to be dispatched at priority level {}", - // level, - // ); - let t = util::spawn_t_ident(level); - items.push(quote!( - #[allow(non_snake_case)] - #[allow(non_camel_case_types)] - #[derive(Clone, Copy)] - // #[doc = #doc] - #[doc(hidden)] - pub enum #t { - #(#variants,)* - } - )); - - let n = util::capacity_literal(channel.capacity as usize + 1); - let rq = util::rq_ident(level); - // let (_, _, _, input_ty) = util::regroup_inputs(inputs); - let (rq_ty, rq_expr) = { - ( - quote!(rtic::export::SCRQ<#t, #n>), - quote!(rtic::export::Queue::new()), - ) - }; - - // For future use - // let doc = format!( - // "Queue of tasks ready to be dispatched at priority level {}", - // level - // ); - items.push(quote!( - #[doc(hidden)] - #[allow(non_camel_case_types)] - #[allow(non_upper_case_globals)] - static #rq: rtic::RacyCell<#rq_ty> = rtic::RacyCell::new(#rq_expr); - )); - - let interrupt = util::suffixed( - &interrupts - .get(&level) - .expect("RTIC-ICE: Unable to get interrrupt") - .0 - .to_string(), - ); - let arms = channel - .tasks - .iter() - .map(|name| { - let task = &app.software_tasks[name]; - let cfgs = &task.cfgs; - let fq = util::fq_ident(name); - let inputs = util::inputs_ident(name); - let (_, tupled, pats, _) = util::regroup_inputs(&task.inputs); - - if !task.is_async { - quote!( - #(#cfgs)* - #t::#name => { - let #tupled = - (&*#inputs - .get()) - .get_unchecked(usize::from(index)) - .as_ptr() - .read(); - (&mut *#fq.get_mut()).split().0.enqueue_unchecked(index); - let priority = &rtic::export::Priority::new(PRIORITY); - #name( - #name::Context::new(priority) - #(,#pats)* - ) - } - ) - } else { - quote!() - } - }) - .collect::>(); - - stmts.push(quote!( - while let Some((task, index)) = (&mut *#rq.get_mut()).split().1.dequeue() { - match task { - #(#arms)* - } - } - )); - - let doc = format!("Interrupt handler to dispatch tasks at priority {}", level); - let attribute = &interrupts[&level].1.attrs; - items.push(quote!( - #[allow(non_snake_case)] - #[doc = #doc] - #[no_mangle] - #(#attribute)* - unsafe fn #interrupt() { - /// The priority of this interrupt handler - const PRIORITY: u8 = #level; - - rtic::export::run(PRIORITY, || { - #(#stmts)* - }); - } - )); - } - - items -} diff --git a/macros/src/codegen/module.rs b/macros/src/codegen/module.rs index 7ac06c5..eb0cb65 100644 --- a/macros/src/codegen/module.rs +++ b/macros/src/codegen/module.rs @@ -102,33 +102,6 @@ pub fn codegen( values.push(quote!(shared: #name::SharedResources::new(#priority))); } - if let Context::Init = ctxt { - let monotonic_types: Vec<_> = app - .monotonics - .iter() - .map(|(_, monotonic)| { - let mono = &monotonic.ty; - quote! {#mono} - }) - .collect(); - - let internal_monotonics_ident = util::mark_internal_name("Monotonics"); - - items.push(quote!( - /// Monotonics used by the system - #[allow(non_snake_case)] - #[allow(non_camel_case_types)] - pub struct #internal_monotonics_ident( - #(pub #monotonic_types),* - ); - )); - - module_items.push(quote!( - #[doc(inline)] - pub use super::#internal_monotonics_ident as Monotonics; - )); - } - let doc = match ctxt { Context::Idle => "Idle loop", Context::Init => "Initialization function", @@ -192,280 +165,45 @@ pub fn codegen( if let Context::SoftwareTask(..) = ctxt { let spawnee = &app.software_tasks[name]; let priority = spawnee.args.priority; - let t = util::spawn_t_ident(priority); let cfgs = &spawnee.cfgs; // Store a copy of the task cfgs task_cfgs = cfgs.clone(); - let (args, tupled, untupled, ty) = util::regroup_inputs(&spawnee.inputs); - let args = &args; - let tupled = &tupled; - let fq = util::fq_ident(name); - let rq = util::rq_ident(priority); - let inputs = util::inputs_ident(name); let device = &app.args.device; let enum_ = util::interrupt_ident(); - let interrupt = if spawnee.is_async { - &analysis - .interrupts_async - .get(&priority) - .expect("RTIC-ICE: interrupt identifer not found") - .0 - } else { - &analysis - .interrupts_normal - .get(&priority) - .expect("RTIC-ICE: interrupt identifer not found") - .0 - }; + let interrupt = &analysis + .interrupts + .get(&priority) + .expect("RTIC-ICE: interrupt identifer not found") + .0; let internal_spawn_ident = util::internal_task_ident(name, "spawn"); // Spawn caller - if spawnee.is_async { - let rq = util::rq_async_ident(name); - items.push(quote!( - - #(#cfgs)* - /// Spawns the task directly - #[allow(non_snake_case)] - #[doc(hidden)] - pub fn #internal_spawn_ident(#(#args,)*) -> Result<(), #ty> { - let input = #tupled; - - unsafe { - let r = rtic::export::interrupt::free(|_| (&mut *#rq.get_mut()).enqueue(input)); + let rq = util::rq_async_ident(name); + items.push(quote!( - if r.is_ok() { - rtic::pend(#device::#enum_::#interrupt); - } + #(#cfgs)* + /// Spawns the task directly + #[allow(non_snake_case)] + #[doc(hidden)] + pub fn #internal_spawn_ident() -> Result<(), ()> { + unsafe { + let r = rtic::export::interrupt::free(|_| (&mut *#rq.get_mut()).enqueue(())); - r + if r.is_ok() { + rtic::pend(#device::#enum_::#interrupt); } - })); - } else { - items.push(quote!( - #(#cfgs)* - /// Spawns the task directly - #[allow(non_snake_case)] - #[doc(hidden)] - pub fn #internal_spawn_ident(#(#args,)*) -> Result<(), #ty> { - let input = #tupled; - - unsafe { - if let Some(index) = rtic::export::interrupt::free(|_| (&mut *#fq.get_mut()).dequeue()) { - (&mut *#inputs - .get_mut()) - .get_unchecked_mut(usize::from(index)) - .as_mut_ptr() - .write(input); - - rtic::export::interrupt::free(|_| { - (&mut *#rq.get_mut()).enqueue_unchecked((#t::#name, index)); - }); - rtic::pend(#device::#enum_::#interrupt); - - Ok(()) - } else { - Err(input) - } - } - - })); - } + r + } + })); module_items.push(quote!( #(#cfgs)* #[doc(inline)] pub use super::#internal_spawn_ident as spawn; )); - - // Schedule caller - if !spawnee.is_async { - for (_, monotonic) in &app.monotonics { - let instants = util::monotonic_instants_ident(name, &monotonic.ident); - let monotonic_name = monotonic.ident.to_string(); - - let tq = util::tq_ident(&monotonic.ident.to_string()); - let t = util::schedule_t_ident(); - let m = &monotonic.ident; - let m_ident = util::monotonic_ident(&monotonic_name); - let m_isr = &monotonic.args.binds; - let enum_ = util::interrupt_ident(); - let spawn_handle_string = format!("{}::SpawnHandle", m); - - let (enable_interrupt, pend) = if &*m_isr.to_string() == "SysTick" { - ( - quote!(core::mem::transmute::<_, rtic::export::SYST>(()).enable_interrupt()), - quote!(rtic::export::SCB::set_pendst()), - ) - } else { - let rt_err = util::rt_err_ident(); - ( - quote!(rtic::export::NVIC::unmask(#rt_err::#enum_::#m_isr)), - quote!(rtic::pend(#rt_err::#enum_::#m_isr)), - ) - }; - - let tq_marker = &util::timer_queue_marker_ident(); - - let internal_spawn_handle_ident = - util::internal_monotonics_ident(name, m, "SpawnHandle"); - let internal_spawn_at_ident = util::internal_monotonics_ident(name, m, "spawn_at"); - let internal_spawn_after_ident = - util::internal_monotonics_ident(name, m, "spawn_after"); - - if monotonic.args.default { - module_items.push(quote!( - #[doc(inline)] - pub use #m::spawn_after; - #[doc(inline)] - pub use #m::spawn_at; - #[doc(inline)] - pub use #m::SpawnHandle; - )); - } - module_items.push(quote!( - pub mod #m { - #[doc(inline)] - pub use super::super::#internal_spawn_after_ident as spawn_after; - #[doc(inline)] - pub use super::super::#internal_spawn_at_ident as spawn_at; - #[doc(inline)] - pub use super::super::#internal_spawn_handle_ident as SpawnHandle; - } - )); - - items.push(quote!( - #(#cfgs)* - #[allow(non_snake_case)] - #[allow(non_camel_case_types)] - pub struct #internal_spawn_handle_ident { - #[doc(hidden)] - marker: u32, - } - - #(#cfgs)* - impl core::fmt::Debug for #internal_spawn_handle_ident { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_struct(#spawn_handle_string).finish() - } - } - - #(#cfgs)* - impl #internal_spawn_handle_ident { - pub fn cancel(self) -> Result<#ty, ()> { - rtic::export::interrupt::free(|_| unsafe { - let tq = &mut *#tq.get_mut(); - if let Some((_task, index)) = tq.cancel_task_marker(self.marker) { - // Get the message - let msg = (&*#inputs - .get()) - .get_unchecked(usize::from(index)) - .as_ptr() - .read(); - // Return the index to the free queue - (&mut *#fq.get_mut()).split().0.enqueue_unchecked(index); - - Ok(msg) - } else { - Err(()) - } - }) - } - - #[inline] - pub fn reschedule_after( - self, - duration: <#m as rtic::Monotonic>::Duration - ) -> Result { - self.reschedule_at(monotonics::#m::now() + duration) - } - - pub fn reschedule_at( - self, - instant: <#m as rtic::Monotonic>::Instant - ) -> Result { - rtic::export::interrupt::free(|_| unsafe { - let marker = #tq_marker.get().read(); - #tq_marker.get_mut().write(marker.wrapping_add(1)); - - let tq = (&mut *#tq.get_mut()); - - tq.update_task_marker(self.marker, marker, instant, || #pend).map(|_| #name::#m::SpawnHandle { marker }) - }) - } - } - - - #(#cfgs)* - /// Spawns the task after a set duration relative to the current time - /// - /// This will use the time `Instant::new(0)` as baseline if called in `#[init]`, - /// so if you use a non-resetable timer use `spawn_at` when in `#[init]` - #[allow(non_snake_case)] - pub fn #internal_spawn_after_ident( - duration: <#m as rtic::Monotonic>::Duration - #(,#args)* - ) -> Result<#name::#m::SpawnHandle, #ty> - { - let instant = monotonics::#m::now(); - - #internal_spawn_at_ident(instant + duration #(,#untupled)*) - } - - #(#cfgs)* - /// Spawns the task at a fixed time instant - #[allow(non_snake_case)] - pub fn #internal_spawn_at_ident( - instant: <#m as rtic::Monotonic>::Instant - #(,#args)* - ) -> Result<#name::#m::SpawnHandle, #ty> { - unsafe { - let input = #tupled; - if let Some(index) = rtic::export::interrupt::free(|_| (&mut *#fq.get_mut()).dequeue()) { - (&mut *#inputs - .get_mut()) - .get_unchecked_mut(usize::from(index)) - .as_mut_ptr() - .write(input); - - (&mut *#instants - .get_mut()) - .get_unchecked_mut(usize::from(index)) - .as_mut_ptr() - .write(instant); - - rtic::export::interrupt::free(|_| { - let marker = #tq_marker.get().read(); - let nr = rtic::export::TaskNotReady { - task: #t::#name, - index, - instant, - marker, - }; - - #tq_marker.get_mut().write(#tq_marker.get().read().wrapping_add(1)); - - let tq = &mut *#tq.get_mut(); - - tq.enqueue_task_unchecked( - nr, - || #enable_interrupt, - || #pend, - (&mut *#m_ident.get_mut()).as_mut()); - - Ok(#name::#m::SpawnHandle { marker }) - }) - } else { - Err(input) - } - } - } - )); - } - } } if items.is_empty() { diff --git a/macros/src/codegen/monotonic.rs b/macros/src/codegen/monotonic.rs deleted file mode 100644 index 417a1d6..0000000 --- a/macros/src/codegen/monotonic.rs +++ /dev/null @@ -1,280 +0,0 @@ -use crate::syntax::ast::App; -use proc_macro2::TokenStream as TokenStream2; -use quote::quote; - -use crate::{analyze::Analysis, codegen::util}; - -/// Generates monotonic module dispatchers -pub fn codegen(app: &App, _analysis: &Analysis) -> TokenStream2 { - let mut monotonic_parts: Vec<_> = Vec::new(); - - let tq_marker = util::timer_queue_marker_ident(); - - for (_, monotonic) in &app.monotonics { - // let instants = util::monotonic_instants_ident(name, &monotonic.ident); - let monotonic_name = monotonic.ident.to_string(); - - let tq = util::tq_ident(&monotonic_name); - let m = &monotonic.ident; - let m_ident = util::monotonic_ident(&monotonic_name); - let m_isr = &monotonic.args.binds; - let enum_ = util::interrupt_ident(); - let name_str = &m.to_string(); - let ident = util::monotonic_ident(name_str); - let doc = &format!( - "This module holds the static implementation for `{}::now()`", - name_str - ); - - let (enable_interrupt, pend) = if &*m_isr.to_string() == "SysTick" { - ( - quote!(core::mem::transmute::<_, rtic::export::SYST>(()).enable_interrupt()), - quote!(rtic::export::SCB::set_pendst()), - ) - } else { - let rt_err = util::rt_err_ident(); - ( - quote!(rtic::export::NVIC::unmask(super::super::#rt_err::#enum_::#m_isr)), - quote!(rtic::pend(super::super::#rt_err::#enum_::#m_isr)), - ) - }; - - let default_monotonic = if monotonic.args.default { - quote!( - #[doc(inline)] - pub use #m::now; - #[doc(inline)] - pub use #m::delay; - #[doc(inline)] - pub use #m::delay_until; - #[doc(inline)] - pub use #m::timeout_at; - #[doc(inline)] - pub use #m::timeout_after; - ) - } else { - quote!() - }; - - monotonic_parts.push(quote! { - #default_monotonic - - #[doc = #doc] - #[allow(non_snake_case)] - pub mod #m { - /// Read the current time from this monotonic - pub fn now() -> ::Instant { - rtic::export::interrupt::free(|_| { - use rtic::Monotonic as _; - if let Some(m) = unsafe{ &mut *super::super::#ident.get_mut() } { - m.now() - } else { - ::zero() - } - }) - } - - /// Delay - #[inline(always)] - #[allow(non_snake_case)] - pub fn delay(duration: ::Duration) - -> DelayFuture { - let until = now() + duration; - DelayFuture { until, waker_storage: None } - } - - /// Delay until a specific time - #[inline(always)] - #[allow(non_snake_case)] - pub fn delay_until(instant: ::Instant) - -> DelayFuture { - let until = instant; - DelayFuture { until, waker_storage: None } - } - - /// Delay future. - #[allow(non_snake_case)] - #[allow(non_camel_case_types)] - pub struct DelayFuture { - until: ::Instant, - waker_storage: Option>>, - } - - impl Drop for DelayFuture { - fn drop(&mut self) { - if let Some(waker_storage) = &mut self.waker_storage { - rtic::export::interrupt::free(|_| unsafe { - let tq = &mut *super::super::#tq.get_mut(); - tq.cancel_waker_marker(waker_storage.val.marker); - }); - } - } - } - - impl core::future::Future for DelayFuture { - type Output = (); - - fn poll( - mut self: core::pin::Pin<&mut Self>, - cx: &mut core::task::Context<'_> - ) -> core::task::Poll { - let mut s = self.as_mut(); - let now = now(); - let until = s.until; - let is_ws_none = s.waker_storage.is_none(); - - if now >= until { - return core::task::Poll::Ready(()); - } else if is_ws_none { - rtic::export::interrupt::free(|_| unsafe { - let marker = super::super::#tq_marker.get().read(); - super::super::#tq_marker.get_mut().write(marker.wrapping_add(1)); - - let nr = s.waker_storage.insert(rtic::export::IntrusiveNode::new(rtic::export::WakerNotReady { - waker: cx.waker().clone(), - instant: until, - marker, - })); - - let tq = &mut *super::super::#tq.get_mut(); - - tq.enqueue_waker( - core::mem::transmute(nr), // Transmute the reference to static - || #enable_interrupt, - || #pend, - (&mut *super::super::#m_ident.get_mut()).as_mut()); - }); - } - - core::task::Poll::Pending - } - } - - /// Timeout future. - #[allow(non_snake_case)] - #[allow(non_camel_case_types)] - pub struct TimeoutFuture { - future: F, - until: ::Instant, - waker_storage: Option>>, - } - - impl Drop for TimeoutFuture { - fn drop(&mut self) { - if let Some(waker_storage) = &mut self.waker_storage { - rtic::export::interrupt::free(|_| unsafe { - let tq = &mut *super::super::#tq.get_mut(); - tq.cancel_waker_marker(waker_storage.val.marker); - }); - } - } - } - - /// Timeout after - #[allow(non_snake_case)] - #[inline(always)] - pub fn timeout_after( - future: F, - duration: ::Duration - ) -> TimeoutFuture { - let until = now() + duration; - TimeoutFuture { - future, - until, - waker_storage: None, - } - } - - /// Timeout at - #[allow(non_snake_case)] - #[inline(always)] - pub fn timeout_at( - future: F, - instant: ::Instant - ) -> TimeoutFuture { - TimeoutFuture { - future, - until: instant, - waker_storage: None, - } - } - - impl core::future::Future for TimeoutFuture - where - F: core::future::Future, - { - type Output = Result; - - fn poll( - self: core::pin::Pin<&mut Self>, - cx: &mut core::task::Context<'_> - ) -> core::task::Poll { - // SAFETY: We don't move the underlying pinned value. - let mut s = unsafe { self.get_unchecked_mut() }; - let future = unsafe { core::pin::Pin::new_unchecked(&mut s.future) }; - let now = now(); - let until = s.until; - let is_ws_none = s.waker_storage.is_none(); - - match future.poll(cx) { - core::task::Poll::Ready(r) => { - if let Some(waker_storage) = &mut s.waker_storage { - rtic::export::interrupt::free(|_| unsafe { - let tq = &mut *super::super::#tq.get_mut(); - tq.cancel_waker_marker(waker_storage.val.marker); - }); - } - - return core::task::Poll::Ready(Ok(r)); - } - core::task::Poll::Pending => { - if now >= until { - // Timeout - return core::task::Poll::Ready(Err(super::TimeoutError)); - } else if is_ws_none { - rtic::export::interrupt::free(|_| unsafe { - let marker = super::super::#tq_marker.get().read(); - super::super::#tq_marker.get_mut().write(marker.wrapping_add(1)); - - let nr = s.waker_storage.insert(rtic::export::IntrusiveNode::new(rtic::export::WakerNotReady { - waker: cx.waker().clone(), - instant: until, - marker, - })); - - let tq = &mut *super::super::#tq.get_mut(); - - tq.enqueue_waker( - core::mem::transmute(nr), // Transmute the reference to static - || #enable_interrupt, - || #pend, - (&mut *super::super::#m_ident.get_mut()).as_mut()); - }); - } - } - } - - core::task::Poll::Pending - } - } - } - }); - } - - if monotonic_parts.is_empty() { - quote!() - } else { - quote!( - pub use rtic::Monotonic as _; - - /// Holds static methods for each monotonic. - pub mod monotonics { - /// A timeout error. - #[derive(Debug)] - pub struct TimeoutError; - - #(#monotonic_parts)* - } - ) - } -} diff --git a/macros/src/codegen/post_init.rs b/macros/src/codegen/post_init.rs index df5daa1..e8183b9 100644 --- a/macros/src/codegen/post_init.rs +++ b/macros/src/codegen/post_init.rs @@ -1,7 +1,6 @@ use crate::syntax::ast::App; -use proc_macro2::{Span, TokenStream as TokenStream2}; +use proc_macro2::TokenStream as TokenStream2; use quote::quote; -use syn::Index; use crate::{analyze::Analysis, codegen::util}; @@ -43,23 +42,6 @@ pub fn codegen(app: &App, analysis: &Analysis) -> Vec { } } - for (i, (monotonic, _)) in app.monotonics.iter().enumerate() { - // For future use - // let doc = format!(" RTIC internal: {}:{}", file!(), line!()); - // stmts.push(quote!(#[doc = #doc])); - - #[allow(clippy::cast_possible_truncation)] - let idx = Index { - index: i as u32, - span: Span::call_site(), - }; - stmts.push(quote!(monotonics.#idx.reset();)); - - // Store the monotonic - let name = util::monotonic_ident(&monotonic.to_string()); - stmts.push(quote!(#name.get_mut().write(Some(monotonics.#idx));)); - } - // Enable the interrupts -- this completes the `init`-ialization phase stmts.push(quote!(rtic::export::interrupt::enable();)); diff --git a/macros/src/codegen/pre_init.rs b/macros/src/codegen/pre_init.rs index ef3acba..1492688 100644 --- a/macros/src/codegen/pre_init.rs +++ b/macros/src/codegen/pre_init.rs @@ -13,20 +13,6 @@ pub fn codegen(app: &App, analysis: &Analysis) -> Vec { // Disable interrupts -- `init` must run with interrupts disabled stmts.push(quote!(rtic::export::interrupt::disable();)); - // Populate the FreeQueue - for (name, task) in &app.software_tasks { - if task.is_async { - continue; - } - - let cap = task.args.capacity; - let fq_ident = util::fq_ident(name); - - stmts.push(quote!( - (0..#cap).for_each(|i| (&mut *#fq_ident.get_mut()).enqueue_unchecked(i)); - )); - } - stmts.push(quote!( // To set the variable in cortex_m so the peripherals cannot be taken multiple times let mut core: rtic::export::Peripherals = rtic::export::Peripherals::steal().into(); @@ -42,11 +28,7 @@ pub fn codegen(app: &App, analysis: &Analysis) -> Vec { stmts.push(quote!(let _ = #rt_err::#interrupt::#name;)); } - let interrupt_ids = analysis - .interrupts_normal - .iter() - .map(|(p, (id, _))| (p, id)) - .chain(analysis.interrupts_async.iter().map(|(p, (id, _))| (p, id))); + let interrupt_ids = analysis.interrupts.iter().map(|(p, (id, _))| (p, id)); // Unmask interrupts and set their priorities for (&priority, name) in interrupt_ids.chain(app.hardware_tasks.values().filter_map(|task| { @@ -101,53 +83,5 @@ pub fn codegen(app: &App, analysis: &Analysis) -> Vec { );)); } - // Initialize monotonic's interrupts - for (_, monotonic) in &app.monotonics { - let priority = if let Some(prio) = monotonic.args.priority { - quote! { #prio } - } else { - quote! { (1 << #nvic_prio_bits) } - }; - let binds = &monotonic.args.binds; - - let name = &monotonic.ident; - let es = format!( - "Maximum priority used by monotonic '{}' is more than supported by hardware", - name - ); - // Compile time assert that this priority is supported by the device - stmts.push(quote!( - const _: () = if (1 << #nvic_prio_bits) < #priority as usize { ::core::panic!(#es); }; - )); - - let mono_type = &monotonic.ty; - - if &*binds.to_string() == "SysTick" { - stmts.push(quote!( - core.SCB.set_priority( - rtic::export::SystemHandler::SysTick, - rtic::export::logical2hw(#priority, #nvic_prio_bits), - ); - - // Always enable monotonic interrupts if they should never be off - if !<#mono_type as rtic::Monotonic>::DISABLE_INTERRUPT_ON_EMPTY_QUEUE { - core::mem::transmute::<_, rtic::export::SYST>(()) - .enable_interrupt(); - } - )); - } else { - stmts.push(quote!( - core.NVIC.set_priority( - #rt_err::#interrupt::#binds, - rtic::export::logical2hw(#priority, #nvic_prio_bits), - ); - - // Always enable monotonic interrupts if they should never be off - if !<#mono_type as rtic::Monotonic>::DISABLE_INTERRUPT_ON_EMPTY_QUEUE { - rtic::export::NVIC::unmask(#rt_err::#interrupt::#binds); - } - )); - } - } stmts } diff --git a/macros/src/codegen/shared_resources.rs b/macros/src/codegen/shared_resources.rs index 66f3800..b63e743 100644 --- a/macros/src/codegen/shared_resources.rs +++ b/macros/src/codegen/shared_resources.rs @@ -111,11 +111,7 @@ pub fn codegen( }; // Computing mapping of used interrupts to masks - let interrupt_ids = analysis - .interrupts_normal - .iter() - .map(|(p, (id, _))| (p, id)) - .chain(analysis.interrupts_async.iter().map(|(p, (id, _))| (p, id))); + let interrupt_ids = analysis.interrupts.iter().map(|(p, (id, _))| (p, id)); let mut prio_to_masks = HashMap::new(); let device = &app.args.device; diff --git a/macros/src/codegen/software_tasks.rs b/macros/src/codegen/software_tasks.rs deleted file mode 100644 index f9247da..0000000 --- a/macros/src/codegen/software_tasks.rs +++ /dev/null @@ -1,179 +0,0 @@ -use crate::syntax::{ast::App, Context}; -use crate::{ - analyze::Analysis, - codegen::{local_resources_struct, module, shared_resources_struct, util}, -}; -use proc_macro2::TokenStream as TokenStream2; -use quote::quote; - -pub fn codegen( - app: &App, - analysis: &Analysis, -) -> ( - // mod_app_software_tasks -- free queues, buffers and `${task}Resources` constructors - Vec, - // root_software_tasks -- items that must be placed in the root of the crate: - // - `${task}Locals` structs - // - `${task}Resources` structs - // - `${task}` modules - Vec, - // user_software_tasks -- the `#[task]` functions written by the user - Vec, -) { - let mut mod_app = vec![]; - let mut root = vec![]; - let mut user_tasks = vec![]; - - // Any task - for (name, task) in app.software_tasks.iter() { - let inputs = &task.inputs; - let (_, _, _, input_ty) = util::regroup_inputs(inputs); - - let cap = task.args.capacity; - let cap_lit = util::capacity_literal(cap as usize); - let cap_lit_p1 = util::capacity_literal(cap as usize + 1); - - if !task.is_async { - // Create free queues and inputs / instants buffers - let fq = util::fq_ident(name); - - #[allow(clippy::redundant_closure)] - let (fq_ty, fq_expr, mk_uninit): (_, _, Box Option<_>>) = { - ( - quote!(rtic::export::SCFQ<#cap_lit_p1>), - quote!(rtic::export::Queue::new()), - Box::new(|| Some(util::link_section_uninit())), - ) - }; - - mod_app.push(quote!( - // /// Queue version of a free-list that keeps track of empty slots in - // /// the following buffers - #[allow(non_camel_case_types)] - #[allow(non_upper_case_globals)] - #[doc(hidden)] - static #fq: rtic::RacyCell<#fq_ty> = rtic::RacyCell::new(#fq_expr); - )); - - let elems = &(0..cap) - .map(|_| quote!(core::mem::MaybeUninit::uninit())) - .collect::>(); - - for (_, monotonic) in &app.monotonics { - let instants = util::monotonic_instants_ident(name, &monotonic.ident); - let mono_type = &monotonic.ty; - - let uninit = mk_uninit(); - // For future use - // let doc = format!(" RTIC internal: {}:{}", file!(), line!()); - mod_app.push(quote!( - #uninit - // /// Buffer that holds the instants associated to the inputs of a task - // #[doc = #doc] - #[allow(non_camel_case_types)] - #[allow(non_upper_case_globals)] - #[doc(hidden)] - static #instants: - rtic::RacyCell<[core::mem::MaybeUninit<<#mono_type as rtic::Monotonic>::Instant>; #cap_lit]> = - rtic::RacyCell::new([#(#elems,)*]); - )); - } - - let uninit = mk_uninit(); - let inputs_ident = util::inputs_ident(name); - - // Buffer that holds the inputs of a task - mod_app.push(quote!( - #uninit - #[allow(non_camel_case_types)] - #[allow(non_upper_case_globals)] - #[doc(hidden)] - static #inputs_ident: rtic::RacyCell<[core::mem::MaybeUninit<#input_ty>; #cap_lit]> = - rtic::RacyCell::new([#(#elems,)*]); - )); - } - - if task.is_async { - let executor_ident = util::executor_run_ident(name); - mod_app.push(quote!( - #[allow(non_camel_case_types)] - #[allow(non_upper_case_globals)] - #[doc(hidden)] - static #executor_ident: core::sync::atomic::AtomicBool = - core::sync::atomic::AtomicBool::new(false); - )); - } - - let inputs = &task.inputs; - - // `${task}Resources` - let mut shared_needs_lt = false; - let mut local_needs_lt = false; - - // `${task}Locals` - if !task.args.local_resources.is_empty() { - let (item, constructor) = local_resources_struct::codegen( - Context::SoftwareTask(name), - &mut local_needs_lt, - app, - ); - - root.push(item); - - mod_app.push(constructor); - } - - if !task.args.shared_resources.is_empty() { - let (item, constructor) = shared_resources_struct::codegen( - Context::SoftwareTask(name), - &mut shared_needs_lt, - app, - ); - - root.push(item); - - mod_app.push(constructor); - } - - if !&task.is_extern { - let context = &task.context; - let attrs = &task.attrs; - let cfgs = &task.cfgs; - let stmts = &task.stmts; - let (async_marker, context_lifetime) = if task.is_async { - ( - quote!(async), - if shared_needs_lt || local_needs_lt { - quote!(<'static>) - } else { - quote!() - }, - ) - } else { - (quote!(), quote!()) - }; - - user_tasks.push(quote!( - #(#attrs)* - #(#cfgs)* - #[allow(non_snake_case)] - #async_marker fn #name(#context: #name::Context #context_lifetime #(,#inputs)*) { - use rtic::Mutex as _; - use rtic::mutex::prelude::*; - - #(#stmts)* - } - )); - } - - root.push(module::codegen( - Context::SoftwareTask(name), - shared_needs_lt, - local_needs_lt, - app, - analysis, - )); - } - - (mod_app, root, user_tasks) -} diff --git a/macros/src/codegen/timer_queue.rs b/macros/src/codegen/timer_queue.rs deleted file mode 100644 index 281148d..0000000 --- a/macros/src/codegen/timer_queue.rs +++ /dev/null @@ -1,170 +0,0 @@ -use crate::syntax::ast::App; -use crate::{analyze::Analysis, codegen::util}; -use proc_macro2::TokenStream as TokenStream2; -use quote::quote; - -/// Generates timer queues and timer queue handlers -#[allow(clippy::too_many_lines)] -pub fn codegen(app: &App, analysis: &Analysis) -> Vec { - let mut items = vec![]; - - if !app.monotonics.is_empty() { - // Generate the marker counter used to track for `cancel` and `reschedule` - let tq_marker = util::timer_queue_marker_ident(); - items.push(quote!( - // #[doc = #doc] - #[doc(hidden)] - #[allow(non_camel_case_types)] - #[allow(non_upper_case_globals)] - static #tq_marker: rtic::RacyCell = rtic::RacyCell::new(0); - )); - - let t = util::schedule_t_ident(); - - // Enumeration of `schedule`-able tasks - { - let variants = app - .software_tasks - .iter() - .filter(|(_, task)| !task.is_async) - .map(|(name, task)| { - let cfgs = &task.cfgs; - - quote!( - #(#cfgs)* - #name - ) - }) - .collect::>(); - - // For future use - // let doc = "Tasks that can be scheduled".to_string(); - items.push(quote!( - // #[doc = #doc] - #[doc(hidden)] - #[allow(non_camel_case_types)] - #[derive(Clone, Copy)] - pub enum #t { - #(#variants,)* - } - )); - } - } - - for (_, monotonic) in &app.monotonics { - let monotonic_name = monotonic.ident.to_string(); - let tq = util::tq_ident(&monotonic_name); - let t = util::schedule_t_ident(); - let mono_type = &monotonic.ty; - let m_ident = util::monotonic_ident(&monotonic_name); - - // Static variables and resource proxy - { - // For future use - // let doc = &format!("Timer queue for {}", monotonic_name); - let cap: usize = app - .software_tasks - .iter() - .map(|(_name, task)| task.args.capacity as usize) - .sum(); - let n_task = util::capacity_literal(cap); - let tq_ty = quote!(rtic::export::TimerQueue<#mono_type, #t, #n_task>); - - // For future use - // let doc = format!(" RTIC internal: {}:{}", file!(), line!()); - items.push(quote!( - #[doc(hidden)] - #[allow(non_camel_case_types)] - #[allow(non_upper_case_globals)] - static #tq: rtic::RacyCell<#tq_ty> = rtic::RacyCell::new( - rtic::export::TimerQueue { - task_queue: rtic::export::SortedLinkedList::new_u16(), - waker_queue: rtic::export::IntrusiveSortedLinkedList::new(), - } - ); - )); - - let mono = util::monotonic_ident(&monotonic_name); - // For future use - // let doc = &format!("Storage for {}", monotonic_name); - - items.push(quote!( - #[doc(hidden)] - #[allow(non_camel_case_types)] - #[allow(non_upper_case_globals)] - static #mono: rtic::RacyCell> = rtic::RacyCell::new(None); - )); - } - - // Timer queue handler - { - let enum_ = util::interrupt_ident(); - let rt_err = util::rt_err_ident(); - - let arms = app - .software_tasks - .iter() - .filter(|(_, task)| !task.is_async) - .map(|(name, task)| { - let cfgs = &task.cfgs; - let priority = task.args.priority; - let rq = util::rq_ident(priority); - let rqt = util::spawn_t_ident(priority); - - // The interrupt that runs the task dispatcher - let interrupt = &analysis.interrupts_normal.get(&priority).expect("RTIC-ICE: interrupt not found").0; - - let pend = { - quote!( - rtic::pend(#rt_err::#enum_::#interrupt); - ) - }; - - quote!( - #(#cfgs)* - #t::#name => { - rtic::export::interrupt::free(|_| - (&mut *#rq.get_mut()).split().0.enqueue_unchecked((#rqt::#name, index)) - ); - - #pend - } - ) - }) - .collect::>(); - - let bound_interrupt = &monotonic.args.binds; - let disable_isr = if &*bound_interrupt.to_string() == "SysTick" { - quote!(core::mem::transmute::<_, rtic::export::SYST>(()).disable_interrupt()) - } else { - quote!(rtic::export::NVIC::mask(#rt_err::#enum_::#bound_interrupt)) - }; - - items.push(quote!( - #[no_mangle] - #[allow(non_snake_case)] - unsafe fn #bound_interrupt() { - while let Some((task, index)) = rtic::export::interrupt::free(|_| - if let Some(mono) = (&mut *#m_ident.get_mut()).as_mut() { - (&mut *#tq.get_mut()).dequeue(|| #disable_isr, mono) - } else { - // We can only use the timer queue if `init` has returned, and it - // writes the `Some(monotonic)` we are accessing here. - core::hint::unreachable_unchecked() - }) - { - match task { - #(#arms)* - } - } - - rtic::export::interrupt::free(|_| if let Some(mono) = (&mut *#m_ident.get_mut()).as_mut() { - mono.on_interrupt(); - }); - } - )); - } - } - - items -} diff --git a/macros/src/codegen/util.rs b/macros/src/codegen/util.rs index 151906d..61bde98 100644 --- a/macros/src/codegen/util.rs +++ b/macros/src/codegen/util.rs @@ -3,20 +3,10 @@ use core::sync::atomic::{AtomicUsize, Ordering}; use crate::syntax::{ast::App, Context}; use proc_macro2::{Span, TokenStream as TokenStream2}; use quote::quote; -use syn::{Attribute, Ident, LitInt, PatType}; +use syn::{Attribute, Ident}; const RTIC_INTERNAL: &str = "__rtic_internal"; -/// Turns `capacity` into an unsuffixed integer literal -pub fn capacity_literal(capacity: usize) -> LitInt { - LitInt::new(&capacity.to_string(), Span::call_site()) -} - -/// Identifier for the free queue -pub fn fq_ident(task: &Ident) -> Ident { - mark_internal_name(&format!("{}_FQ", task)) -} - /// Generates a `Mutex` implementation pub fn impl_mutex( app: &App, @@ -60,30 +50,16 @@ pub fn impl_mutex( ) } -/// Generates an identifier for the `INPUTS` buffer (`spawn` & `schedule` API) -pub fn inputs_ident(task: &Ident) -> Ident { - mark_internal_name(&format!("{}_INPUTS", task)) -} - /// Generates an identifier for the `EXECUTOR_RUN` atomics (`async` API) pub fn executor_run_ident(task: &Ident) -> Ident { mark_internal_name(&format!("{}_EXECUTOR_RUN", task)) } -/// Generates an identifier for the `INSTANTS` buffer (`schedule` API) -pub fn monotonic_instants_ident(task: &Ident, monotonic: &Ident) -> Ident { - mark_internal_name(&format!("{}_{}_INSTANTS", task, monotonic)) -} - pub fn interrupt_ident() -> Ident { let span = Span::call_site(); Ident::new("interrupt", span) } -pub fn timer_queue_marker_ident() -> Ident { - mark_internal_name("TIMER_QUEUE_MARKER") -} - /// Whether `name` is an exception with configurable priority pub fn is_exception(name: &Ident) -> bool { let s = name.to_string(); @@ -106,11 +82,6 @@ pub fn mark_internal_name(name: &str) -> Ident { Ident::new(&format!("{}_{}", RTIC_INTERNAL, name), Span::call_site()) } -/// Generate an internal identifier for monotonics -pub fn internal_monotonics_ident(task: &Ident, monotonic: &Ident, ident_name: &str) -> Ident { - mark_internal_name(&format!("{}_{}_{}", task, monotonic, ident_name,)) -} - /// Generate an internal identifier for tasks pub fn internal_task_ident(task: &Ident, ident_name: &str) -> Ident { mark_internal_name(&format!("{}_{}", task, ident_name)) @@ -129,55 +100,6 @@ pub fn link_section_uninit() -> TokenStream2 { quote!(#[link_section = #section]) } -// Regroups the inputs of a task -// -// `inputs` could be &[`input: Foo`] OR &[`mut x: i32`, `ref y: i64`] -pub fn regroup_inputs( - inputs: &[PatType], -) -> ( - // args e.g. &[`_0`], &[`_0: i32`, `_1: i64`] - Vec, - // tupled e.g. `_0`, `(_0, _1)` - TokenStream2, - // untupled e.g. &[`_0`], &[`_0`, `_1`] - Vec, - // ty e.g. `Foo`, `(i32, i64)` - TokenStream2, -) { - if inputs.len() == 1 { - let ty = &inputs[0].ty; - - ( - vec![quote!(_0: #ty)], - quote!(_0), - vec![quote!(_0)], - quote!(#ty), - ) - } else { - let mut args = vec![]; - let mut pats = vec![]; - let mut tys = vec![]; - - for (i, input) in inputs.iter().enumerate() { - let i = Ident::new(&format!("_{}", i), Span::call_site()); - let ty = &input.ty; - - args.push(quote!(#i: #ty)); - - pats.push(quote!(#i)); - - tys.push(quote!(#ty)); - } - - let tupled = { - let pats = pats.clone(); - quote!((#(#pats,)*)) - }; - let ty = quote!((#(#tys,)*)); - (args, tupled, pats, ty) - } -} - /// Get the ident for the name of the task pub fn get_task_name(ctxt: Context, app: &App) -> Ident { let s = match ctxt { @@ -230,48 +152,17 @@ pub fn local_resources_ident(ctxt: Context, app: &App) -> Ident { mark_internal_name(&s) } -/// Generates an identifier for a ready queue -/// -/// There may be several task dispatchers, one for each priority level. -/// The ready queues are SPSC queues -pub fn rq_ident(priority: u8) -> Ident { - mark_internal_name(&format!("P{}_RQ", priority)) -} - /// Generates an identifier for a ready queue, async task version pub fn rq_async_ident(async_task_name: &Ident) -> Ident { mark_internal_name(&format!("ASYNC_TACK_{}_RQ", async_task_name)) } -/// Generates an identifier for the `enum` of `schedule`-able tasks -pub fn schedule_t_ident() -> Ident { - mark_internal_name("SCHED_T") -} - -/// Generates an identifier for the `enum` of `spawn`-able tasks -/// -/// This identifier needs the same structure as the `RQ` identifier because there's one ready queue -/// for each of these `T` enums -pub fn spawn_t_ident(priority: u8) -> Ident { - mark_internal_name(&format!("P{}_T", priority)) -} - /// Suffixed identifier pub fn suffixed(name: &str) -> Ident { let span = Span::call_site(); Ident::new(name, span) } -/// Generates an identifier for a timer queue -pub fn tq_ident(name: &str) -> Ident { - mark_internal_name(&format!("TQ_{}", name)) -} - -/// Generates an identifier for monotonic timer storage -pub fn monotonic_ident(name: &str) -> Ident { - mark_internal_name(&format!("MONOTONIC_STORAGE_{}", name)) -} - pub fn static_shared_resource_ident(name: &Ident) -> Ident { mark_internal_name(&format!("shared_resource_{}", name)) } diff --git a/macros/src/syntax/analyze.rs b/macros/src/syntax/analyze.rs index 44960b9..ff0577d 100644 --- a/macros/src/syntax/analyze.rs +++ b/macros/src/syntax/analyze.rs @@ -16,19 +16,11 @@ pub(crate) fn app(app: &App) -> Result { type TaskName = String; type Priority = u8; - // The task list is a Tuple (Name, Shared Resources, Local Resources, Priority, IsAsync) - let task_resources_list: Vec<(TaskName, Vec<&Ident>, &LocalResources, Priority, bool)> = + // The task list is a Tuple (Name, Shared Resources, Local Resources, Priority) + let task_resources_list: Vec<(TaskName, Vec<&Ident>, &LocalResources, Priority)> = Some(&app.init) .iter() - .map(|ht| { - ( - "init".to_string(), - Vec::new(), - &ht.args.local_resources, - 0, - false, - ) - }) + .map(|ht| ("init".to_string(), Vec::new(), &ht.args.local_resources, 0)) .chain(app.idle.iter().map(|ht| { ( "idle".to_string(), @@ -39,7 +31,6 @@ pub(crate) fn app(app: &App) -> Result { .collect::>(), &ht.args.local_resources, 0, - false, ) })) .chain(app.software_tasks.iter().map(|(name, ht)| { @@ -52,7 +43,6 @@ pub(crate) fn app(app: &App) -> Result { .collect::>(), &ht.args.local_resources, ht.args.priority, - ht.is_async, ) })) .chain(app.hardware_tasks.iter().map(|(name, ht)| { @@ -65,7 +55,6 @@ pub(crate) fn app(app: &App) -> Result { .collect::>(), &ht.args.local_resources, ht.args.priority, - false, ) })) .collect(); @@ -84,21 +73,20 @@ pub(crate) fn app(app: &App) -> Result { // Check that lock_free resources are correct for lf_res in lock_free.iter() { - for (task, tr, _, priority, is_async) in task_resources_list.iter() { + for (task, tr, _, priority) in task_resources_list.iter() { for r in tr { // Get all uses of resources annotated lock_free if lf_res == r { // lock_free resources are not allowed in async tasks - if *is_async { - error.push(syn::Error::new( + error.push(syn::Error::new( r.span(), format!( "Lock free shared resource {:?} is used by an async tasks, which is forbidden", r.to_string(), ), )); - } + // TODO: Should this be removed? // HashMap returns the previous existing object if old.key == new.key if let Some(lf_res) = lf_hash.insert(r.to_string(), (task, r, priority)) { // Check if priority differ, if it does, append to @@ -150,7 +138,7 @@ pub(crate) fn app(app: &App) -> Result { // Check that local resources are not shared for lr in local { - for (task, _, local_resources, _, _) in task_resources_list.iter() { + for (task, _, local_resources, _) in task_resources_list.iter() { for (name, res) in local_resources.iter() { // Get all uses of resources annotated lock_free if lr == name { @@ -193,18 +181,7 @@ pub(crate) fn app(app: &App) -> Result { error.push(syn::Error::new( name.span(), format!( - "Software task {:?} has priority 0, but `#[idle]` is defined. 0-priority software tasks are only allowed if there is no `#[idle]`.", - name.to_string(), - ) - )); - } - - // 0-priority tasks must be async - if !task.is_async { - error.push(syn::Error::new( - name.span(), - format!( - "Software task {:?} has priority 0, but is not `async`. 0-priority software tasks must be `async`.", + "Async task {:?} has priority 0, but `#[idle]` is defined. 0-priority async tasks are only allowed if there is no `#[idle]`.", name.to_string(), ) )); @@ -263,7 +240,7 @@ pub(crate) fn app(app: &App) -> Result { // Create the list of used local resource Idents let mut used_local_resource = IndexSet::new(); - for (_, _, locals, _, _) in task_resources_list { + for (_, _, locals, _) in task_resources_list { for (local, _) in locals { used_local_resource.insert(local.clone()); } @@ -307,27 +284,11 @@ pub(crate) fn app(app: &App) -> Result { let channel = channels.entry(spawnee_prio).or_default(); channel.tasks.insert(name.clone()); - - if !spawnee.args.only_same_priority_spawn { - // Require `Send` if the task can be spawned from other priorities - spawnee.inputs.iter().for_each(|input| { - send_types.insert(input.ty.clone()); - }); - } } // No channel should ever be empty debug_assert!(channels.values().all(|channel| !channel.tasks.is_empty())); - // Compute channel capacities - for channel in channels.values_mut() { - channel.capacity = channel - .tasks - .iter() - .map(|name| app.software_tasks[name].args.capacity) - .sum(); - } - Ok(Analysis { channels, shared_resources: used_shared_resource, diff --git a/macros/src/syntax/ast.rs b/macros/src/syntax/ast.rs index 0f2e36f..ea6e402 100644 --- a/macros/src/syntax/ast.rs +++ b/macros/src/syntax/ast.rs @@ -1,6 +1,6 @@ //! Abstract Syntax Tree -use syn::{Attribute, Expr, Ident, Item, ItemUse, Pat, PatType, Path, Stmt, Type}; +use syn::{Attribute, Expr, Ident, Item, ItemUse, Pat, Path, Stmt, Type}; use crate::syntax::Map; @@ -20,9 +20,6 @@ pub struct App { /// The `#[idle]` function pub idle: Option, - /// Monotonic clocks - pub monotonics: Map, - /// Resources shared between tasks defined in `#[shared]` pub shared_resources: Map, @@ -38,7 +35,7 @@ pub struct App { /// Hardware tasks: `#[task(binds = ..)]`s pub hardware_tasks: Map, - /// Software tasks: `#[task]` + /// Async software tasks: `#[task]` pub software_tasks: Map, } @@ -192,38 +189,7 @@ pub struct LocalResource { pub ty: Box, } -/// Monotonic -#[derive(Debug)] -#[non_exhaustive] -pub struct Monotonic { - /// `#[cfg]` attributes like `#[cfg(debug_assertions)]` - pub cfgs: Vec, - - /// The identifier of the monotonic - pub ident: Ident, - - /// The type of this monotonic - pub ty: Box, - - /// Monotonic args - pub args: MonotonicArgs, -} - -/// Monotonic metadata -#[derive(Debug)] -#[non_exhaustive] -pub struct MonotonicArgs { - /// The interrupt or exception that this monotonic is bound to - pub binds: Ident, - - /// The priority of this monotonic - pub priority: Option, - - /// If this is the default monotonic - pub default: bool, -} - -/// A software task +/// An async software task #[derive(Debug)] #[non_exhaustive] pub struct SoftwareTask { @@ -239,26 +205,17 @@ pub struct SoftwareTask { /// The context argument pub context: Box, - /// The inputs of this software task - pub inputs: Vec, - /// The statements that make up the task handler pub stmts: Vec, /// The task is declared externally pub is_extern: bool, - - /// If the task is marked as `async` - pub is_async: bool, } /// Software task metadata #[derive(Debug)] #[non_exhaustive] pub struct SoftwareTaskArgs { - /// The task capacity: the maximum number of pending messages that can be queued - pub capacity: u8, - /// The priority of this task pub priority: u8, @@ -275,7 +232,6 @@ pub struct SoftwareTaskArgs { impl Default for SoftwareTaskArgs { fn default() -> Self { Self { - capacity: 1, priority: 1, local_resources: LocalResources::new(), shared_resources: SharedResources::new(), diff --git a/macros/src/syntax/parse.rs b/macros/src/syntax/parse.rs index ceedaa9..abdd677 100644 --- a/macros/src/syntax/parse.rs +++ b/macros/src/syntax/parse.rs @@ -2,7 +2,6 @@ mod app; mod hardware_task; mod idle; mod init; -mod monotonic; mod resource; mod software_task; mod util; @@ -11,15 +10,12 @@ use proc_macro2::TokenStream as TokenStream2; use syn::{ braced, parenthesized, parse::{self, Parse, ParseStream, Parser}, - token::{self, Brace}, - Ident, Item, LitBool, LitInt, Path, Token, + token::Brace, + Ident, Item, LitInt, Token, }; use crate::syntax::{ - ast::{ - App, AppArgs, HardwareTaskArgs, IdleArgs, InitArgs, MonotonicArgs, SoftwareTaskArgs, - TaskLocal, - }, + ast::{App, AppArgs, HardwareTaskArgs, IdleArgs, InitArgs, SoftwareTaskArgs, TaskLocal}, Either, }; @@ -388,7 +384,6 @@ fn task_args(tokens: TokenStream2) -> parse::Result parse::Result parse::Result { - (|input: ParseStream<'_>| -> parse::Result { - let mut binds = None; - let mut priority = None; - let mut default = None; - - if !input.peek(token::Paren) { - return Err(parse::Error::new( - path.segments.first().unwrap().ident.span(), - "expected opening ( in #[monotonic( ... )]", - )); - } - - let content; - parenthesized!(content in input); - - if !content.is_empty() { - loop { - // Parse identifier name - let ident: Ident = content.parse()?; - // Handle equal sign - let _: Token![=] = content.parse()?; - - match &*ident.to_string() { - "binds" => { - if binds.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - // Parse identifier name - let ident = content.parse()?; - - binds = Some(ident); - } - - "priority" => { - if priority.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - // #lit - let lit: LitInt = content.parse()?; - - if !lit.suffix().is_empty() { - return Err(parse::Error::new( - lit.span(), - "this literal must be unsuffixed", - )); - } - - let value = lit.base10_parse::().ok(); - if value.is_none() || value == Some(0) { - return Err(parse::Error::new( - lit.span(), - "this literal must be in the range 1...255", - )); - } - - priority = Some(value.unwrap()); - } - - "default" => { - if default.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - let lit: LitBool = content.parse()?; - default = Some(lit.value); - } - - _ => { - return Err(parse::Error::new(ident.span(), "unexpected argument")); - } - } - if content.is_empty() { - break; - } - - // Handle comma: , - let _: Token![,] = content.parse()?; - } - } - - let binds = if let Some(r) = binds { - r - } else { - return Err(parse::Error::new( - content.span(), - "`binds = ...` is missing", - )); - }; - let default = default.unwrap_or(false); - - Ok(MonotonicArgs { - binds, - priority, - default, - }) - }) - .parse2(tokens) -} diff --git a/macros/src/syntax/parse/app.rs b/macros/src/syntax/parse/app.rs index dd7c399..8a9242e 100644 --- a/macros/src/syntax/parse/app.rs +++ b/macros/src/syntax/parse/app.rs @@ -5,14 +5,14 @@ use proc_macro2::TokenStream as TokenStream2; use syn::{ parse::{self, ParseStream, Parser}, spanned::Spanned, - Expr, ExprArray, Fields, ForeignItem, Ident, Item, LitBool, Path, Token, Type, Visibility, + Expr, ExprArray, Fields, ForeignItem, Ident, Item, LitBool, Path, Token, Visibility, }; use super::Input; use crate::syntax::{ ast::{ App, AppArgs, Dispatcher, Dispatchers, HardwareTask, Idle, IdleArgs, Init, InitArgs, - LocalResource, Monotonic, MonotonicArgs, SharedResource, SoftwareTask, + LocalResource, SharedResource, SoftwareTask, }, parse::{self as syntax_parse, util}, Either, Map, Set, @@ -150,7 +150,6 @@ impl App { let mut shared_resources = Map::new(); let mut local_resources_ident = None; let mut local_resources = Map::new(); - let mut monotonics = Map::new(); let mut hardware_tasks = Map::new(); let mut software_tasks = Map::new(); let mut user_imports = vec![]; @@ -158,7 +157,6 @@ impl App { let mut seen_idents = HashSet::::new(); let mut bindings = HashSet::::new(); - let mut monotonic_types = HashSet::::new(); let mut check_binding = |ident: &Ident| { if bindings.contains(ident) { @@ -186,19 +184,6 @@ impl App { Ok(()) }; - let mut check_monotonic = |ty: &Type| { - if monotonic_types.contains(ty) { - return Err(parse::Error::new( - ty.span(), - "this type is already used by another monotonic", - )); - } else { - monotonic_types.insert(ty.clone()); - } - - Ok(()) - }; - for mut item in input.items { match item { Item::Fn(mut item) => { @@ -448,44 +433,6 @@ impl App { // Store the user provided use-statements user_imports.push(itemuse_.clone()); } - Item::Type(ref mut type_item) => { - // Match types with the attribute #[monotonic] - if let Some(pos) = type_item - .attrs - .iter() - .position(|attr| util::attr_eq(attr, "monotonic")) - { - let span = type_item.ident.span(); - - if monotonics.contains_key(&type_item.ident) { - return Err(parse::Error::new( - span, - "`#[monotonic(...)]` on a specific type must appear at most once", - )); - } - - if type_item.vis != Visibility::Inherited { - return Err(parse::Error::new( - type_item.span(), - "this item must have inherited / private visibility", - )); - } - - check_monotonic(&*type_item.ty)?; - - let m = type_item.attrs.remove(pos); - let args = MonotonicArgs::parse(m)?; - - check_binding(&args.binds)?; - - let monotonic = Monotonic::parse(args, type_item, span)?; - - monotonics.insert(type_item.ident.clone(), monotonic); - } - - // All types are passed on - user_code.push(item.clone()); - } _ => { // Anything else within the module should not make any difference user_code.push(item.clone()); @@ -524,7 +471,6 @@ impl App { name: input.ident, init, idle, - monotonics, shared_resources, local_resources, user_imports, diff --git a/macros/src/syntax/parse/hardware_task.rs b/macros/src/syntax/parse/hardware_task.rs index 304bfcd..ff94bc5 100644 --- a/macros/src/syntax/parse/hardware_task.rs +++ b/macros/src/syntax/parse/hardware_task.rs @@ -23,19 +23,17 @@ impl HardwareTask { } if valid_signature { - if let Some((context, Ok(rest))) = util::parse_inputs(item.sig.inputs, &name) { - if rest.is_empty() { - let FilterAttrs { cfgs, attrs, .. } = util::filter_attributes(item.attrs); + if let Some(context) = util::parse_inputs(item.sig.inputs, &name) { + let FilterAttrs { cfgs, attrs, .. } = util::filter_attributes(item.attrs); - return Ok(HardwareTask { - args, - cfgs, - attrs, - context, - stmts: item.block.stmts, - is_extern: false, - }); - } + return Ok(HardwareTask { + args, + cfgs, + attrs, + context, + stmts: item.block.stmts, + is_extern: false, + }); } } @@ -69,19 +67,17 @@ impl HardwareTask { } if valid_signature { - if let Some((context, Ok(rest))) = util::parse_inputs(item.sig.inputs, &name) { - if rest.is_empty() { - let FilterAttrs { cfgs, attrs, .. } = util::filter_attributes(item.attrs); + if let Some(context) = util::parse_inputs(item.sig.inputs, &name) { + let FilterAttrs { cfgs, attrs, .. } = util::filter_attributes(item.attrs); - return Ok(HardwareTask { - args, - cfgs, - attrs, - context, - stmts: Vec::::new(), - is_extern: true, - }); - } + return Ok(HardwareTask { + args, + cfgs, + attrs, + context, + stmts: Vec::::new(), + is_extern: true, + }); } } diff --git a/macros/src/syntax/parse/idle.rs b/macros/src/syntax/parse/idle.rs index d9f3a99..ffec358 100644 --- a/macros/src/syntax/parse/idle.rs +++ b/macros/src/syntax/parse/idle.rs @@ -21,16 +21,14 @@ impl Idle { let name = item.sig.ident.to_string(); if valid_signature { - if let Some((context, Ok(rest))) = util::parse_inputs(item.sig.inputs, &name) { - if rest.is_empty() { - return Ok(Idle { - args, - attrs: item.attrs, - context, - name: item.sig.ident, - stmts: item.block.stmts, - }); - } + if let Some(context) = util::parse_inputs(item.sig.inputs, &name) { + return Ok(Idle { + args, + attrs: item.attrs, + context, + name: item.sig.ident, + stmts: item.block.stmts, + }); } } diff --git a/macros/src/syntax/parse/init.rs b/macros/src/syntax/parse/init.rs index 727ee20..5ec1aba 100644 --- a/macros/src/syntax/parse/init.rs +++ b/macros/src/syntax/parse/init.rs @@ -25,18 +25,16 @@ impl Init { if let Ok((user_shared_struct, user_local_struct)) = util::type_is_init_return(&item.sig.output, &name) { - if let Some((context, Ok(rest))) = util::parse_inputs(item.sig.inputs, &name) { - if rest.is_empty() { - return Ok(Init { - args, - attrs: item.attrs, - context, - name: item.sig.ident, - stmts: item.block.stmts, - user_shared_struct, - user_local_struct, - }); - } + if let Some(context) = util::parse_inputs(item.sig.inputs, &name) { + return Ok(Init { + args, + attrs: item.attrs, + context, + name: item.sig.ident, + stmts: item.block.stmts, + user_shared_struct, + user_local_struct, + }); } } } diff --git a/macros/src/syntax/parse/software_task.rs b/macros/src/syntax/parse/software_task.rs index 2b1ac4a..6be597e 100644 --- a/macros/src/syntax/parse/software_task.rs +++ b/macros/src/syntax/parse/software_task.rs @@ -8,17 +8,16 @@ use crate::syntax::{ impl SoftwareTask { pub(crate) fn parse(args: SoftwareTaskArgs, item: ItemFn) -> parse::Result { - let valid_signature = - util::check_fn_signature(&item, true) && util::type_is_unit(&item.sig.output); + let valid_signature = util::check_fn_signature(&item, true) + && util::type_is_unit(&item.sig.output) + && item.sig.asyncness.is_some(); let span = item.sig.ident.span(); let name = item.sig.ident.to_string(); - let is_async = item.sig.asyncness.is_some(); - if valid_signature { - if let Some((context, Ok(inputs))) = util::parse_inputs(item.sig.inputs, &name) { + if let Some(context) = util::parse_inputs(item.sig.inputs, &name) { let FilterAttrs { cfgs, attrs, .. } = util::filter_attributes(item.attrs); return Ok(SoftwareTask { @@ -26,10 +25,8 @@ impl SoftwareTask { attrs, cfgs, context, - inputs, stmts: item.block.stmts, is_extern: false, - is_async, }); } } @@ -37,7 +34,7 @@ impl SoftwareTask { Err(parse::Error::new( span, &format!( - "this task handler must have type signature `(async) fn({}::Context, ..)`", + "this task handler must have type signature `async fn({}::Context)`", name ), )) @@ -49,17 +46,16 @@ impl SoftwareTask { args: SoftwareTaskArgs, item: ForeignItemFn, ) -> parse::Result { - let valid_signature = - util::check_foreign_fn_signature(&item, true) && util::type_is_unit(&item.sig.output); + let valid_signature = util::check_foreign_fn_signature(&item, true) + && util::type_is_unit(&item.sig.output) + && item.sig.asyncness.is_some(); let span = item.sig.ident.span(); let name = item.sig.ident.to_string(); - let is_async = item.sig.asyncness.is_some(); - if valid_signature { - if let Some((context, Ok(inputs))) = util::parse_inputs(item.sig.inputs, &name) { + if let Some(context) = util::parse_inputs(item.sig.inputs, &name) { let FilterAttrs { cfgs, attrs, .. } = util::filter_attributes(item.attrs); return Ok(SoftwareTask { @@ -67,10 +63,8 @@ impl SoftwareTask { attrs, cfgs, context, - inputs, stmts: Vec::::new(), is_extern: true, - is_async, }); } } @@ -78,7 +72,7 @@ impl SoftwareTask { Err(parse::Error::new( span, &format!( - "this task handler must have type signature `(async) fn({}::Context, ..)`", + "this task handler must have type signature `async fn({}::Context)`", name ), )) diff --git a/macros/src/syntax/parse/util.rs b/macros/src/syntax/parse/util.rs index 3fa51ef..119129c 100644 --- a/macros/src/syntax/parse/util.rs +++ b/macros/src/syntax/parse/util.rs @@ -3,8 +3,8 @@ use syn::{ parse::{self, ParseStream}, punctuated::Punctuated, spanned::Spanned, - Abi, AttrStyle, Attribute, Expr, FnArg, ForeignItemFn, Ident, ItemFn, Pat, PatType, Path, - PathArguments, ReturnType, Token, Type, Visibility, + Abi, AttrStyle, Attribute, Expr, FnArg, ForeignItemFn, Ident, ItemFn, Pat, Path, PathArguments, + ReturnType, Token, Type, Visibility, }; use crate::syntax::{ @@ -231,29 +231,23 @@ pub fn parse_local_resources(content: ParseStream<'_>) -> parse::Result, Result, FnArg>)>; - -pub fn parse_inputs(inputs: Punctuated, name: &str) -> ParseInputResult { +pub fn parse_inputs(inputs: Punctuated, name: &str) -> Option> { let mut inputs = inputs.into_iter(); match inputs.next() { Some(FnArg::Typed(first)) => { if type_is_path(&first.ty, &[name, "Context"]) { - let rest = inputs - .map(|arg| match arg { - FnArg::Typed(arg) => Ok(arg), - _ => Err(arg), - }) - .collect::, _>>(); - - Some((first.pat, rest)) - } else { - None + // No more inputs + if inputs.next().is_none() { + return Some(first.pat); + } } } - _ => None, + _ => {} } + + None } pub fn type_is_bottom(ty: &ReturnType) -> bool { -- cgit v1.2.3 From d27d0fe33fdb54e6a11a1e9d09a7916f19e5c9ec Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Wed, 4 Jan 2023 20:01:05 +0100 Subject: Added software task codegen back --- macros/src/codegen.rs | 11 +++- macros/src/codegen/software_tasks.rs | 101 +++++++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 macros/src/codegen/software_tasks.rs diff --git a/macros/src/codegen.rs b/macros/src/codegen.rs index 618d9f3..6460afe 100644 --- a/macros/src/codegen.rs +++ b/macros/src/codegen.rs @@ -18,7 +18,7 @@ mod post_init; mod pre_init; mod shared_resources; mod shared_resources_struct; -// mod software_tasks; +mod software_tasks; // mod timer_queue; mod util; @@ -92,6 +92,9 @@ pub fn app(app: &App, analysis: &Analysis) -> TokenStream2 { let (mod_app_hardware_tasks, root_hardware_tasks, user_hardware_tasks) = hardware_tasks::codegen(app, analysis); + let (mod_app_software_tasks, root_software_tasks, user_software_tasks) = + software_tasks::codegen(app, analysis); + let mod_app_async_dispatchers = async_dispatchers::codegen(app, analysis); let user_imports = &app.user_imports; let user_code = &app.user_code; @@ -116,6 +119,8 @@ pub fn app(app: &App, analysis: &Analysis) -> TokenStream2 { #(#user_hardware_tasks)* + #(#user_software_tasks)* + #(#root)* #mod_shared_resources @@ -124,6 +129,8 @@ pub fn app(app: &App, analysis: &Analysis) -> TokenStream2 { #(#root_hardware_tasks)* + #(#root_software_tasks)* + /// app module #(#mod_app)* @@ -133,6 +140,8 @@ pub fn app(app: &App, analysis: &Analysis) -> TokenStream2 { #(#mod_app_hardware_tasks)* + #(#mod_app_software_tasks)* + #(#mod_app_async_dispatchers)* #(#mains)* diff --git a/macros/src/codegen/software_tasks.rs b/macros/src/codegen/software_tasks.rs new file mode 100644 index 0000000..b2b468c --- /dev/null +++ b/macros/src/codegen/software_tasks.rs @@ -0,0 +1,101 @@ +use crate::syntax::{ast::App, Context}; +use crate::{ + analyze::Analysis, + codegen::{local_resources_struct, module, shared_resources_struct, util}, +}; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +pub fn codegen( + app: &App, + analysis: &Analysis, +) -> ( + // mod_app_software_tasks -- free queues, buffers and `${task}Resources` constructors + Vec, + // root_software_tasks -- items that must be placed in the root of the crate: + // - `${task}Locals` structs + // - `${task}Resources` structs + // - `${task}` modules + Vec, + // user_software_tasks -- the `#[task]` functions written by the user + Vec, +) { + let mut mod_app = vec![]; + let mut root = vec![]; + let mut user_tasks = vec![]; + + // Any task + for (name, task) in app.software_tasks.iter() { + let executor_ident = util::executor_run_ident(name); + mod_app.push(quote!( + #[allow(non_camel_case_types)] + #[allow(non_upper_case_globals)] + #[doc(hidden)] + static #executor_ident: core::sync::atomic::AtomicBool = + core::sync::atomic::AtomicBool::new(false); + )); + + // `${task}Resources` + let mut shared_needs_lt = false; + let mut local_needs_lt = false; + + // `${task}Locals` + if !task.args.local_resources.is_empty() { + let (item, constructor) = local_resources_struct::codegen( + Context::SoftwareTask(name), + &mut local_needs_lt, + app, + ); + + root.push(item); + + mod_app.push(constructor); + } + + if !task.args.shared_resources.is_empty() { + let (item, constructor) = shared_resources_struct::codegen( + Context::SoftwareTask(name), + &mut shared_needs_lt, + app, + ); + + root.push(item); + + mod_app.push(constructor); + } + + if !&task.is_extern { + let context = &task.context; + let attrs = &task.attrs; + let cfgs = &task.cfgs; + let stmts = &task.stmts; + let context_lifetime = if shared_needs_lt || local_needs_lt { + quote!(<'static>) + } else { + quote!() + }; + + user_tasks.push(quote!( + #(#attrs)* + #(#cfgs)* + #[allow(non_snake_case)] + async fn #name(#context: #name::Context #context_lifetime) { + use rtic::Mutex as _; + use rtic::mutex::prelude::*; + + #(#stmts)* + } + )); + } + + root.push(module::codegen( + Context::SoftwareTask(name), + shared_needs_lt, + local_needs_lt, + app, + analysis, + )); + } + + (mod_app, root, user_tasks) +} -- cgit v1.2.3 From 5c3cedf69ad07cb8522a0b040bdbf1f9ca4cf37b Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Wed, 4 Jan 2023 20:29:16 +0100 Subject: Fix fences --- macros/src/codegen/async_dispatchers.rs | 5 +++++ macros/src/codegen/module.rs | 10 ++-------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/macros/src/codegen/async_dispatchers.rs b/macros/src/codegen/async_dispatchers.rs index aa854d7..c811665 100644 --- a/macros/src/codegen/async_dispatchers.rs +++ b/macros/src/codegen/async_dispatchers.rs @@ -101,7 +101,12 @@ pub fn codegen(app: &App, analysis: &Analysis) -> Vec { const PRIORITY: u8 = #level; rtic::export::run(PRIORITY, || { + // Have the acquire/release semantics outside the checks to no overdo it + core::sync::atomic::fence(core::sync::atomic::Ordering::Acquire); + #(#stmts)* + + core::sync::atomic::fence(core::sync::atomic::Ordering::Release); }); } )); diff --git a/macros/src/codegen/module.rs b/macros/src/codegen/module.rs index eb0cb65..b4ad68a 100644 --- a/macros/src/codegen/module.rs +++ b/macros/src/codegen/module.rs @@ -111,14 +111,8 @@ pub fn codegen( let v = Vec::new(); let cfgs = match ctxt { - Context::HardwareTask(t) => { - &app.hardware_tasks[t].cfgs - // ... - } - Context::SoftwareTask(t) => { - &app.software_tasks[t].cfgs - // ... - } + Context::HardwareTask(t) => &app.hardware_tasks[t].cfgs, + Context::SoftwareTask(t) => &app.software_tasks[t].cfgs, _ => &v, }; -- cgit v1.2.3 From 858320cbfc391a74bff6b9c8a0b3c7696a232b76 Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Wed, 4 Jan 2023 21:08:44 +0100 Subject: Even more cleanup --- macros/src/codegen.rs | 3 -- macros/src/codegen/init.rs | 5 ++- macros/src/syntax/parse/init.rs | 4 +-- macros/src/syntax/parse/monotonic.rs | 42 ---------------------- macros/src/syntax/parse/util.rs | 6 ++-- src/export.rs | 70 ------------------------------------ 6 files changed, 7 insertions(+), 123 deletions(-) delete mode 100644 macros/src/syntax/parse/monotonic.rs diff --git a/macros/src/codegen.rs b/macros/src/codegen.rs index 6460afe..0f68c34 100644 --- a/macros/src/codegen.rs +++ b/macros/src/codegen.rs @@ -6,20 +6,17 @@ use crate::syntax::ast::App; mod assertions; mod async_dispatchers; -// mod dispatchers; mod hardware_tasks; mod idle; mod init; mod local_resources; mod local_resources_struct; mod module; -// mod monotonic; mod post_init; mod pre_init; mod shared_resources; mod shared_resources_struct; mod software_tasks; -// mod timer_queue; mod util; #[allow(clippy::too_many_lines)] diff --git a/macros/src/codegen/init.rs b/macros/src/codegen/init.rs index 9a6fe2d..c7b8712 100644 --- a/macros/src/codegen/init.rs +++ b/macros/src/codegen/init.rs @@ -76,7 +76,7 @@ pub fn codegen(app: &App, analysis: &Analysis) -> CodegenResult { // let locals_pat = locals_pat.iter(); - let user_init_return = quote! {#shared, #local, #name::Monotonics}; + let user_init_return = quote! {#shared, #local}; let user_init = quote!( #(#attrs)* @@ -99,9 +99,8 @@ pub fn codegen(app: &App, analysis: &Analysis) -> CodegenResult { mod_app = Some(constructor); } - // let locals_new = locals_new.iter(); let call_init = quote! { - let (shared_resources, local_resources, mut monotonics) = #name(#name::Context::new(core.into())); + let (shared_resources, local_resources) = #name(#name::Context::new(core.into())); }; root_init.push(module::codegen( diff --git a/macros/src/syntax/parse/init.rs b/macros/src/syntax/parse/init.rs index 5ec1aba..61d3539 100644 --- a/macros/src/syntax/parse/init.rs +++ b/macros/src/syntax/parse/init.rs @@ -23,7 +23,7 @@ impl Init { if valid_signature { if let Ok((user_shared_struct, user_local_struct)) = - util::type_is_init_return(&item.sig.output, &name) + util::type_is_init_return(&item.sig.output) { if let Some(context) = util::parse_inputs(item.sig.inputs, &name) { return Ok(Init { @@ -42,7 +42,7 @@ impl Init { Err(parse::Error::new( span, &format!( - "the `#[init]` function must have signature `fn({}::Context) -> (Shared resources struct, Local resources struct, {0}::Monotonics)`", + "the `#[init]` function must have signature `fn({}::Context) -> (Shared resources struct, Local resources struct)`", name ), )) diff --git a/macros/src/syntax/parse/monotonic.rs b/macros/src/syntax/parse/monotonic.rs deleted file mode 100644 index 0583233..0000000 --- a/macros/src/syntax/parse/monotonic.rs +++ /dev/null @@ -1,42 +0,0 @@ -use proc_macro2::Span; -use syn::Attribute; -use syn::{parse, spanned::Spanned, ItemType, Visibility}; - -use crate::syntax::parse::util::FilterAttrs; -use crate::syntax::{ - ast::{Monotonic, MonotonicArgs}, - parse::util, -}; - -impl MonotonicArgs { - pub(crate) fn parse(attr: Attribute) -> parse::Result { - crate::syntax::parse::monotonic_args(attr.path, attr.tokens) - } -} - -impl Monotonic { - pub(crate) fn parse(args: MonotonicArgs, item: &ItemType, span: Span) -> parse::Result { - if item.vis != Visibility::Inherited { - return Err(parse::Error::new( - span, - "this field must have inherited / private visibility", - )); - } - - let FilterAttrs { cfgs, attrs, .. } = util::filter_attributes(item.attrs.clone()); - - if !attrs.is_empty() { - return Err(parse::Error::new( - attrs[0].path.span(), - "Monotonic does not support attributes other than `#[cfg]`", - )); - } - - Ok(Monotonic { - cfgs, - ident: item.ident.clone(), - ty: item.ty.clone(), - args, - }) - } -} diff --git a/macros/src/syntax/parse/util.rs b/macros/src/syntax/parse/util.rs index 119129c..28c3eac 100644 --- a/macros/src/syntax/parse/util.rs +++ b/macros/src/syntax/parse/util.rs @@ -277,18 +277,18 @@ fn extract_init_resource_name_ident(ty: Type) -> Result { } /// Checks Init's return type, return the user provided types for analysis -pub fn type_is_init_return(ty: &ReturnType, name: &str) -> Result<(Ident, Ident), ()> { +pub fn type_is_init_return(ty: &ReturnType) -> Result<(Ident, Ident), ()> { match ty { ReturnType::Default => Err(()), ReturnType::Type(_, ty) => match &**ty { Type::Tuple(t) => { // return should be: - // fn -> (User's #[shared] struct, User's #[local] struct, {name}::Monotonics) + // fn -> (User's #[shared] struct, User's #[local] struct) // // We check the length and the last one here, analysis checks that the user // provided structs are correct. - if t.elems.len() == 3 && type_is_path(&t.elems[2], &[name, "Monotonics"]) { + if t.elems.len() == 2 { return Ok(( extract_init_resource_name_ident(t.elems[0].clone())?, extract_init_resource_name_ident(t.elems[1].clone())?, diff --git a/src/export.rs b/src/export.rs index da4a691..82320fb 100644 --- a/src/export.rs +++ b/src/export.rs @@ -15,65 +15,6 @@ pub use cortex_m::{ peripheral::{scb::SystemHandler, DWT, NVIC, SCB, SYST}, Peripherals, }; -pub use heapless::sorted_linked_list::SortedLinkedList; -pub use heapless::spsc::Queue; -pub use heapless::BinaryHeap; -pub use heapless::Vec; -pub use rtic_monotonic as monotonic; - -pub mod idle_executor { - use core::{ - future::Future, - pin::Pin, - task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, - }; - - fn no_op(_: *const ()) {} - fn no_op_clone(_: *const ()) -> RawWaker { - noop_raw_waker() - } - - static IDLE_WAKER_TABLE: RawWakerVTable = RawWakerVTable::new(no_op_clone, no_op, no_op, no_op); - - #[inline] - fn noop_raw_waker() -> RawWaker { - RawWaker::new(core::ptr::null(), &IDLE_WAKER_TABLE) - } - - pub struct IdleExecutor - where - T: Future, - { - idle: T, - } - - impl IdleExecutor - where - T: Future, - { - #[inline(always)] - pub fn new(idle: T) -> Self { - Self { idle } - } - - #[inline(always)] - pub fn run(&mut self) -> ! { - let w = unsafe { Waker::from_raw(noop_raw_waker()) }; - let mut ctxt = Context::from_waker(&w); - loop { - match unsafe { Pin::new_unchecked(&mut self.idle) }.poll(&mut ctxt) { - Poll::Pending => { - // All ok! - } - Poll::Ready(_) => { - // The idle executor will never return - unreachable!() - } - } - } - } - } -} pub mod executor { use core::{ @@ -143,10 +84,6 @@ pub mod executor { } } -pub type SCFQ = Queue; -pub type SCRQ = Queue<(T, u8), N>; -pub type ASYNCRQ = Queue; - /// Mask is used to store interrupt masks on systems without a BASEPRI register (M0, M0+, M23). /// It needs to be large enough to cover all the relevant interrupts in use. /// For M0/M0+ there are only 32 interrupts so we only need one u32 value. @@ -290,13 +227,6 @@ where { } -#[inline(always)] -pub fn assert_monotonic() -where - T: monotonic::Monotonic, -{ -} - /// Lock implementation using BASEPRI and global Critical Section (CS) /// /// # Safety -- cgit v1.2.3 From 3b97531a5c40293e265999db543acec365c629df Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Wed, 4 Jan 2023 21:33:41 +0100 Subject: First example builds again --- examples/async-task.rs | 20 +++----------------- macros/src/codegen/async_dispatchers.rs | 15 ++++++--------- macros/src/codegen/module.rs | 11 ++++++----- macros/src/codegen/util.rs | 2 +- rust-toolchain.toml | 4 ++++ 5 files changed, 20 insertions(+), 32 deletions(-) create mode 100644 rust-toolchain.toml diff --git a/examples/async-task.rs b/examples/async-task.rs index 4d25ec4..7d0ee86 100644 --- a/examples/async-task.rs +++ b/examples/async-task.rs @@ -13,7 +13,6 @@ use panic_semihosting as _; #[rtic::app(device = lm3s6965, dispatchers = [SSI0, UART0], peripherals = true)] mod app { use cortex_m_semihosting::{debug, hprintln}; - use systick_monotonic::*; #[shared] struct Shared {} @@ -21,21 +20,13 @@ mod app { #[local] struct Local {} - #[monotonic(binds = SysTick, default = true)] - type MyMono = Systick<100>; - #[init] - fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { + fn init(cx: init::Context) -> (Shared, Local) { hprintln!("init").unwrap(); - normal_task::spawn().ok(); - async_task::spawn().ok(); + async_task::spawn().unwrap(); - ( - Shared {}, - Local {}, - init::Monotonics(Systick::new(cx.core.SYST, 12_000_000)), - ) + (Shared {}, Local {}) } #[idle] @@ -47,11 +38,6 @@ mod app { } } - #[task] - fn normal_task(_cx: normal_task::Context) { - hprintln!("hello from normal").ok(); - } - #[task] async fn async_task(_cx: async_task::Context) { hprintln!("hello from async").ok(); diff --git a/macros/src/codegen/async_dispatchers.rs b/macros/src/codegen/async_dispatchers.rs index c811665..f428cef 100644 --- a/macros/src/codegen/async_dispatchers.rs +++ b/macros/src/codegen/async_dispatchers.rs @@ -45,23 +45,20 @@ pub fn codegen(app: &App, analysis: &Analysis) -> Vec { let executor_run_ident = util::executor_run_ident(name); let rq = util::rq_async_ident(name); - let (rq_ty, rq_expr) = { - ( - quote!(rtic::export::ASYNCRQ<(), 2>), // TODO: This needs updating to a counter instead of a queue - quote!(rtic::export::Queue::new()), - ) - }; items.push(quote!( #[doc(hidden)] #[allow(non_camel_case_types)] #[allow(non_upper_case_globals)] - static #rq: rtic::RacyCell<#rq_ty> = rtic::RacyCell::new(#rq_expr); + static #rq: core::sync::atomic::AtomicBool = core::sync::atomic::AtomicBool::new(false); )); stmts.push(quote!( if !(&*#exec_name.get()).is_running() { - if let Some(()) = rtic::export::interrupt::free(|_| (&mut *#rq.get_mut()).dequeue()) { + // TODO Fix this to be compare and swap + if #rq.load(core::sync::atomic::Ordering::Relaxed) { + #rq.store(false, core::sync::atomic::Ordering::Relaxed); + // The async executor needs a static priority #prio_name.get_mut().write(rtic::export::Priority::new(PRIORITY)); @@ -77,7 +74,7 @@ pub fn codegen(app: &App, analysis: &Analysis) -> Vec { if (&mut *#exec_name.get_mut()).poll(|| { #executor_run_ident.store(true, core::sync::atomic::Ordering::Release); rtic::pend(#device::#enum_::#interrupt); - }) && !rtic::export::interrupt::free(|_| (&*#rq.get_mut()).is_empty()) { + }) && #rq.load(core::sync::atomic::Ordering::Relaxed) { // If the ready queue is not empty and the executor finished, restart this // dispatch to check if the executor should be restarted. rtic::pend(#device::#enum_::#interrupt); diff --git a/macros/src/codegen/module.rs b/macros/src/codegen/module.rs index b4ad68a..7bbfdf3 100644 --- a/macros/src/codegen/module.rs +++ b/macros/src/codegen/module.rs @@ -183,13 +183,14 @@ pub fn codegen( #[doc(hidden)] pub fn #internal_spawn_ident() -> Result<(), ()> { unsafe { - let r = rtic::export::interrupt::free(|_| (&mut *#rq.get_mut()).enqueue(())); - - if r.is_ok() { + // TODO: Fix this to be compare and swap + if #rq.load(core::sync::atomic::Ordering::Acquire) { + Err(()) + } else { + #rq.store(true, core::sync::atomic::Ordering::Release); rtic::pend(#device::#enum_::#interrupt); + Ok(()) } - - r } })); diff --git a/macros/src/codegen/util.rs b/macros/src/codegen/util.rs index 61bde98..aa720c0 100644 --- a/macros/src/codegen/util.rs +++ b/macros/src/codegen/util.rs @@ -154,7 +154,7 @@ pub fn local_resources_ident(ctxt: Context, app: &App) -> Ident { /// Generates an identifier for a ready queue, async task version pub fn rq_async_ident(async_task_name: &Ident) -> Ident { - mark_internal_name(&format!("ASYNC_TACK_{}_RQ", async_task_name)) + mark_internal_name(&format!("ASYNC_TASK_{}_RQ", async_task_name)) } /// Suffixed identifier diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..bbd57bc --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "nightly" +components = [ "rust-src", "rustfmt", "llvm-tools-preview" ] +targets = [ "thumbv7em-none-eabihf" ] -- cgit v1.2.3 From 53f3d397e76383deabbe9579a3522174c422a958 Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Wed, 4 Jan 2023 21:36:43 +0100 Subject: More removal --- src/export.rs | 5 - src/lib.rs | 4 - src/sll.rs | 421 ---------------------------------------------------------- src/tq.rs | 328 --------------------------------------------- 4 files changed, 758 deletions(-) delete mode 100644 src/sll.rs delete mode 100644 src/tq.rs diff --git a/src/export.rs b/src/export.rs index 82320fb..2cc031e 100644 --- a/src/export.rs +++ b/src/export.rs @@ -1,8 +1,3 @@ -#![allow(clippy::inline_always)] -pub use crate::{ - sll::{IntrusiveSortedLinkedList, Node as IntrusiveNode}, - tq::{TaskNotReady, TimerQueue, WakerNotReady}, -}; pub use bare_metal::CriticalSection; use core::{ cell::Cell, diff --git a/src/lib.rs b/src/lib.rs index da556a5..e8b8140 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -51,10 +51,6 @@ pub mod mutex { #[doc(hidden)] pub mod export; -#[doc(hidden)] -pub mod sll; -#[doc(hidden)] -mod tq; /// Sets the given `interrupt` as pending /// diff --git a/src/sll.rs b/src/sll.rs deleted file mode 100644 index 43b53c1..0000000 --- a/src/sll.rs +++ /dev/null @@ -1,421 +0,0 @@ -//! An intrusive sorted priority linked list, designed for use in `Future`s in RTIC. -use core::cmp::Ordering; -use core::fmt; -use core::marker::PhantomData; -use core::ops::{Deref, DerefMut}; -use core::ptr::NonNull; - -/// Marker for Min sorted [`IntrusiveSortedLinkedList`]. -pub struct Min; - -/// Marker for Max sorted [`IntrusiveSortedLinkedList`]. -pub struct Max; - -/// The linked list kind: min-list or max-list -pub trait Kind: private::Sealed { - #[doc(hidden)] - fn ordering() -> Ordering; -} - -impl Kind for Min { - fn ordering() -> Ordering { - Ordering::Less - } -} - -impl Kind for Max { - fn ordering() -> Ordering { - Ordering::Greater - } -} - -/// Sealed traits -mod private { - pub trait Sealed {} -} - -impl private::Sealed for Max {} -impl private::Sealed for Min {} - -/// A node in the [`IntrusiveSortedLinkedList`]. -pub struct Node { - pub val: T, - next: Option>>, -} - -impl Node { - pub fn new(val: T) -> Self { - Self { val, next: None } - } -} - -/// The linked list. -pub struct IntrusiveSortedLinkedList<'a, T, K> { - head: Option>>, - _kind: PhantomData, - _lt: PhantomData<&'a ()>, -} - -impl<'a, T, K> fmt::Debug for IntrusiveSortedLinkedList<'a, T, K> -where - T: Ord + core::fmt::Debug, - K: Kind, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut l = f.debug_list(); - let mut current = self.head; - - while let Some(head) = current { - let head = unsafe { head.as_ref() }; - current = head.next; - - l.entry(&head.val); - } - - l.finish() - } -} - -impl<'a, T, K> IntrusiveSortedLinkedList<'a, T, K> -where - T: Ord, - K: Kind, -{ - pub const fn new() -> Self { - Self { - head: None, - _kind: PhantomData, - _lt: PhantomData, - } - } - - // Push to the list. - pub fn push(&mut self, new: &'a mut Node) { - unsafe { - if let Some(head) = self.head { - if head.as_ref().val.cmp(&new.val) != K::ordering() { - // This is newer than head, replace head - new.next = self.head; - self.head = Some(NonNull::new_unchecked(new)); - } else { - // It's not head, search the list for the correct placement - let mut current = head; - - while let Some(next) = current.as_ref().next { - if next.as_ref().val.cmp(&new.val) != K::ordering() { - break; - } - - current = next; - } - - new.next = current.as_ref().next; - current.as_mut().next = Some(NonNull::new_unchecked(new)); - } - } else { - // List is empty, place at head - self.head = Some(NonNull::new_unchecked(new)) - } - } - } - - /// Get an iterator over the sorted list. - pub fn iter(&self) -> Iter<'_, T, K> { - Iter { - _list: self, - index: self.head, - } - } - - /// Find an element in the list that can be changed and resorted. - pub fn find_mut(&mut self, mut f: F) -> Option> - where - F: FnMut(&T) -> bool, - { - let head = self.head?; - - // Special-case, first element - if f(&unsafe { head.as_ref() }.val) { - return Some(FindMut { - is_head: true, - prev_index: None, - index: self.head, - list: self, - maybe_changed: false, - }); - } - - let mut current = head; - - while let Some(next) = unsafe { current.as_ref() }.next { - if f(&unsafe { next.as_ref() }.val) { - return Some(FindMut { - is_head: false, - prev_index: Some(current), - index: Some(next), - list: self, - maybe_changed: false, - }); - } - - current = next; - } - - None - } - - /// Peek at the first element. - pub fn peek(&self) -> Option<&T> { - self.head.map(|head| unsafe { &head.as_ref().val }) - } - - /// Pops the first element in the list. - /// - /// Complexity is worst-case `O(1)`. - pub fn pop(&mut self) -> Option<&'a Node> { - if let Some(head) = self.head { - let v = unsafe { head.as_ref() }; - self.head = v.next; - Some(v) - } else { - None - } - } - - /// Checks if the linked list is empty. - #[inline] - pub fn is_empty(&self) -> bool { - self.head.is_none() - } -} - -/// Iterator for the linked list. -pub struct Iter<'a, T, K> -where - T: Ord, - K: Kind, -{ - _list: &'a IntrusiveSortedLinkedList<'a, T, K>, - index: Option>>, -} - -impl<'a, T, K> Iterator for Iter<'a, T, K> -where - T: Ord, - K: Kind, -{ - type Item = &'a T; - - fn next(&mut self) -> Option { - let index = self.index?; - - let node = unsafe { index.as_ref() }; - self.index = node.next; - - Some(&node.val) - } -} - -/// Comes from [`IntrusiveSortedLinkedList::find_mut`]. -pub struct FindMut<'a, 'b, T, K> -where - T: Ord + 'b, - K: Kind, -{ - list: &'a mut IntrusiveSortedLinkedList<'b, T, K>, - is_head: bool, - prev_index: Option>>, - index: Option>>, - maybe_changed: bool, -} - -impl<'a, 'b, T, K> FindMut<'a, 'b, T, K> -where - T: Ord, - K: Kind, -{ - unsafe fn pop_internal(&mut self) -> &'b mut Node { - if self.is_head { - // If it is the head element, we can do a normal pop - let mut head = self.list.head.unwrap_unchecked(); - let v = head.as_mut(); - self.list.head = v.next; - v - } else { - // Somewhere in the list - let mut prev = self.prev_index.unwrap_unchecked(); - let mut curr = self.index.unwrap_unchecked(); - - // Re-point the previous index - prev.as_mut().next = curr.as_ref().next; - - curr.as_mut() - } - } - - /// This will pop the element from the list. - /// - /// Complexity is worst-case `O(1)`. - #[inline] - pub fn pop(mut self) -> &'b mut Node { - unsafe { self.pop_internal() } - } - - /// This will resort the element into the correct position in the list if needed. The resorting - /// will only happen if the element has been accessed mutably. - /// - /// Same as calling `drop`. - /// - /// Complexity is worst-case `O(N)`. - #[inline] - pub fn finish(self) { - drop(self) - } -} - -impl<'b, T, K> Drop for FindMut<'_, 'b, T, K> -where - T: Ord + 'b, - K: Kind, -{ - fn drop(&mut self) { - // Only resort the list if the element has changed - if self.maybe_changed { - unsafe { - let val = self.pop_internal(); - self.list.push(val); - } - } - } -} - -impl Deref for FindMut<'_, '_, T, K> -where - T: Ord, - K: Kind, -{ - type Target = T; - - fn deref(&self) -> &Self::Target { - unsafe { &self.index.unwrap_unchecked().as_ref().val } - } -} - -impl DerefMut for FindMut<'_, '_, T, K> -where - T: Ord, - K: Kind, -{ - fn deref_mut(&mut self) -> &mut Self::Target { - self.maybe_changed = true; - unsafe { &mut self.index.unwrap_unchecked().as_mut().val } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn const_new() { - static mut _V1: IntrusiveSortedLinkedList = IntrusiveSortedLinkedList::new(); - } - - #[test] - fn test_peek() { - let mut ll: IntrusiveSortedLinkedList = IntrusiveSortedLinkedList::new(); - - let mut a = Node { val: 1, next: None }; - ll.push(&mut a); - assert_eq!(ll.peek().unwrap(), &1); - - let mut a = Node { val: 2, next: None }; - ll.push(&mut a); - assert_eq!(ll.peek().unwrap(), &2); - - let mut a = Node { val: 3, next: None }; - ll.push(&mut a); - assert_eq!(ll.peek().unwrap(), &3); - - let mut ll: IntrusiveSortedLinkedList = IntrusiveSortedLinkedList::new(); - - let mut a = Node { val: 2, next: None }; - ll.push(&mut a); - assert_eq!(ll.peek().unwrap(), &2); - - let mut a = Node { val: 1, next: None }; - ll.push(&mut a); - assert_eq!(ll.peek().unwrap(), &1); - - let mut a = Node { val: 3, next: None }; - ll.push(&mut a); - assert_eq!(ll.peek().unwrap(), &1); - } - - #[test] - fn test_empty() { - let ll: IntrusiveSortedLinkedList = IntrusiveSortedLinkedList::new(); - - assert!(ll.is_empty()) - } - - #[test] - fn test_updating() { - let mut ll: IntrusiveSortedLinkedList = IntrusiveSortedLinkedList::new(); - - let mut a = Node { val: 1, next: None }; - ll.push(&mut a); - - let mut a = Node { val: 2, next: None }; - ll.push(&mut a); - - let mut a = Node { val: 3, next: None }; - ll.push(&mut a); - - let mut find = ll.find_mut(|v| *v == 2).unwrap(); - - *find += 1000; - find.finish(); - - assert_eq!(ll.peek().unwrap(), &1002); - - let mut find = ll.find_mut(|v| *v == 3).unwrap(); - - *find += 1000; - find.finish(); - - assert_eq!(ll.peek().unwrap(), &1003); - - // Remove largest element - ll.find_mut(|v| *v == 1003).unwrap().pop(); - - assert_eq!(ll.peek().unwrap(), &1002); - } - - #[test] - fn test_updating_1() { - let mut ll: IntrusiveSortedLinkedList = IntrusiveSortedLinkedList::new(); - - let mut a = Node { val: 1, next: None }; - ll.push(&mut a); - - let v = ll.pop().unwrap(); - - assert_eq!(v.val, 1); - } - - #[test] - fn test_updating_2() { - let mut ll: IntrusiveSortedLinkedList = IntrusiveSortedLinkedList::new(); - - let mut a = Node { val: 1, next: None }; - ll.push(&mut a); - - let mut find = ll.find_mut(|v| *v == 1).unwrap(); - - *find += 1000; - find.finish(); - - assert_eq!(ll.peek().unwrap(), &1001); - } -} diff --git a/src/tq.rs b/src/tq.rs deleted file mode 100644 index daa91c8..0000000 --- a/src/tq.rs +++ /dev/null @@ -1,328 +0,0 @@ -use crate::{ - sll::{IntrusiveSortedLinkedList, Min as IsslMin, Node as IntrusiveNode}, - Monotonic, -}; -use core::cmp::Ordering; -use core::task::Waker; -use heapless::sorted_linked_list::{LinkedIndexU16, Min as SllMin, SortedLinkedList}; - -pub struct TimerQueue<'a, Mono, Task, const N_TASK: usize> -where - Mono: Monotonic, - Task: Copy, -{ - pub task_queue: SortedLinkedList, LinkedIndexU16, SllMin, N_TASK>, - pub waker_queue: IntrusiveSortedLinkedList<'a, WakerNotReady, IsslMin>, -} - -impl<'a, Mono, Task, const N_TASK: usize> TimerQueue<'a, Mono, Task, N_TASK> -where - Mono: Monotonic + 'a, - Task: Copy, -{ - fn check_if_enable( - &self, - instant: Mono::Instant, - enable_interrupt: F1, - pend_handler: F2, - mono: Option<&mut Mono>, - ) where - F1: FnOnce(), - F2: FnOnce(), - { - // Check if the top contains a non-empty element and if that element is - // greater than nr - let if_task_heap_max_greater_than_nr = self - .task_queue - .peek() - .map_or(true, |head| instant < head.instant); - let if_waker_heap_max_greater_than_nr = self - .waker_queue - .peek() - .map_or(true, |head| instant < head.instant); - - if if_task_heap_max_greater_than_nr || if_waker_heap_max_greater_than_nr { - if Mono::DISABLE_INTERRUPT_ON_EMPTY_QUEUE && self.is_empty() { - if let Some(mono) = mono { - mono.enable_timer(); - } - enable_interrupt(); - } - - pend_handler(); - } - } - - /// Enqueue a task without checking if it is full - #[inline] - pub unsafe fn enqueue_task_unchecked( - &mut self, - nr: TaskNotReady, - enable_interrupt: F1, - pend_handler: F2, - mono: Option<&mut Mono>, - ) where - F1: FnOnce(), - F2: FnOnce(), - { - self.check_if_enable(nr.instant, enable_interrupt, pend_handler, mono); - self.task_queue.push_unchecked(nr); - } - - /// Enqueue a waker - #[inline] - pub fn enqueue_waker( - &mut self, - nr: &'a mut IntrusiveNode>, - enable_interrupt: F1, - pend_handler: F2, - mono: Option<&mut Mono>, - ) where - F1: FnOnce(), - F2: FnOnce(), - { - self.check_if_enable(nr.val.instant, enable_interrupt, pend_handler, mono); - self.waker_queue.push(nr); - } - - /// Check if all the timer queue is empty. - #[inline] - pub fn is_empty(&self) -> bool { - self.task_queue.is_empty() && self.waker_queue.is_empty() - } - - /// Cancel the marker value for a task - pub fn cancel_task_marker(&mut self, marker: u32) -> Option<(Task, u8)> { - if let Some(val) = self.task_queue.find_mut(|nr| nr.marker == marker) { - let nr = val.pop(); - - Some((nr.task, nr.index)) - } else { - None - } - } - - /// Cancel the marker value for a waker - pub fn cancel_waker_marker(&mut self, marker: u32) { - if let Some(val) = self.waker_queue.find_mut(|nr| nr.marker == marker) { - let _ = val.pop(); - } - } - - /// Update the instant at an marker value for a task to a new instant - #[allow(clippy::result_unit_err)] - pub fn update_task_marker( - &mut self, - marker: u32, - new_marker: u32, - instant: Mono::Instant, - pend_handler: F, - ) -> Result<(), ()> { - if let Some(mut val) = self.task_queue.find_mut(|nr| nr.marker == marker) { - val.instant = instant; - val.marker = new_marker; - - // On update pend the handler to reconfigure the next compare match - pend_handler(); - - Ok(()) - } else { - Err(()) - } - } - - fn dequeue_task_queue( - &mut self, - instant: Mono::Instant, - mono: &mut Mono, - ) -> Option<(Task, u8)> { - if instant <= mono.now() { - // task became ready - let nr = unsafe { self.task_queue.pop_unchecked() }; - Some((nr.task, nr.index)) - } else { - // Set compare - mono.set_compare(instant); - - // Double check that the instant we set is really in the future, else - // dequeue. If the monotonic is fast enough it can happen that from the - // read of now to the set of the compare, the time can overflow. This is to - // guard against this. - if instant <= mono.now() { - let nr = unsafe { self.task_queue.pop_unchecked() }; - Some((nr.task, nr.index)) - } else { - None - } - } - } - - fn dequeue_waker_queue(&mut self, instant: Mono::Instant, mono: &mut Mono) -> bool { - let mut did_wake = false; - - if instant <= mono.now() { - // Task became ready, wake the waker - if let Some(v) = self.waker_queue.pop() { - v.val.waker.wake_by_ref(); - - did_wake = true; - } - } else { - // Set compare - mono.set_compare(instant); - - // Double check that the instant we set is really in the future, else - // dequeue. If the monotonic is fast enough it can happen that from the - // read of now to the set of the compare, the time can overflow. This is to - // guard against this. - if instant <= mono.now() { - if let Some(v) = self.waker_queue.pop() { - v.val.waker.wake_by_ref(); - - did_wake = true; - } - } - } - - did_wake - } - - /// Dequeue a task from the ``TimerQueue`` - pub fn dequeue(&mut self, disable_interrupt: F, mono: &mut Mono) -> Option<(Task, u8)> - where - F: FnOnce(), - { - mono.clear_compare_flag(); - - loop { - let tq = self.task_queue.peek().map(|p| p.instant); - let wq = self.waker_queue.peek().map(|p| p.instant); - - let dequeue_task; - let instant; - - match (tq, wq) { - (Some(tq_instant), Some(wq_instant)) => { - if tq_instant <= wq_instant { - dequeue_task = true; - instant = tq_instant; - } else { - dequeue_task = false; - instant = wq_instant; - } - } - (Some(tq_instant), None) => { - dequeue_task = true; - instant = tq_instant; - } - (None, Some(wq_instant)) => { - dequeue_task = false; - instant = wq_instant; - } - (None, None) => { - // The queue is empty, disable the interrupt. - if Mono::DISABLE_INTERRUPT_ON_EMPTY_QUEUE { - disable_interrupt(); - mono.disable_timer(); - } - - return None; - } - } - - if dequeue_task { - return self.dequeue_task_queue(instant, mono); - } else if !self.dequeue_waker_queue(instant, mono) { - return None; - } else { - // Run the dequeue again - } - } - } -} - -pub struct TaskNotReady -where - Task: Copy, - Mono: Monotonic, -{ - pub task: Task, - pub index: u8, - pub instant: Mono::Instant, - pub marker: u32, -} - -impl Eq for TaskNotReady -where - Task: Copy, - Mono: Monotonic, -{ -} - -impl Ord for TaskNotReady -where - Task: Copy, - Mono: Monotonic, -{ - fn cmp(&self, other: &Self) -> Ordering { - self.instant.cmp(&other.instant) - } -} - -impl PartialEq for TaskNotReady -where - Task: Copy, - Mono: Monotonic, -{ - fn eq(&self, other: &Self) -> bool { - self.instant == other.instant - } -} - -impl PartialOrd for TaskNotReady -where - Task: Copy, - Mono: Monotonic, -{ - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -pub struct WakerNotReady -where - Mono: Monotonic, -{ - pub waker: Waker, - pub instant: Mono::Instant, - pub marker: u32, -} - -impl Eq for WakerNotReady where Mono: Monotonic {} - -impl Ord for WakerNotReady -where - Mono: Monotonic, -{ - fn cmp(&self, other: &Self) -> Ordering { - self.instant.cmp(&other.instant) - } -} - -impl PartialEq for WakerNotReady -where - Mono: Monotonic, -{ - fn eq(&self, other: &Self) -> bool { - self.instant == other.instant - } -} - -impl PartialOrd for WakerNotReady -where - Mono: Monotonic, -{ - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} -- cgit v1.2.3 From 714020a624ca93c42d5da7ebe612e7fc668e1471 Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Sat, 7 Jan 2023 11:24:13 +0100 Subject: Removed Priority, simplified lifetime handling --- examples/async-task.rs | 26 +++++-- macros/src/bindings.rs | 1 + macros/src/codegen.rs | 4 + macros/src/codegen/async_dispatchers.rs | 17 +---- macros/src/codegen/hardware_tasks.rs | 31 +++----- macros/src/codegen/idle.rs | 18 +---- macros/src/codegen/init.rs | 11 +-- macros/src/codegen/local_resources_struct.rs | 8 +- macros/src/codegen/module.rs | 50 +++---------- macros/src/codegen/shared_resources.rs | 13 +--- macros/src/codegen/shared_resources_struct.rs | 13 +--- macros/src/codegen/software_tasks.rs | 31 ++------ macros/src/codegen/util.rs | 7 +- rust-toolchain.toml | 2 +- src/export.rs | 103 +++++++------------------- 15 files changed, 99 insertions(+), 236 deletions(-) diff --git a/examples/async-task.rs b/examples/async-task.rs index 7d0ee86..d058fe5 100644 --- a/examples/async-task.rs +++ b/examples/async-task.rs @@ -15,7 +15,9 @@ mod app { use cortex_m_semihosting::{debug, hprintln}; #[shared] - struct Shared {} + struct Shared { + a: u32, + } #[local] struct Local {} @@ -25,11 +27,12 @@ mod app { hprintln!("init").unwrap(); async_task::spawn().unwrap(); + async_task2::spawn().unwrap(); - (Shared {}, Local {}) + (Shared { a: 0 }, Local {}) } - #[idle] + #[idle(shared = [a])] fn idle(_: idle::Context) -> ! { // debug::exit(debug::EXIT_SUCCESS); loop { @@ -38,10 +41,23 @@ mod app { } } - #[task] - async fn async_task(_cx: async_task::Context) { + #[task(binds = UART1, shared = [a])] + fn hw_task(cx: hw_task::Context) { + let hw_task::SharedResources { a } = cx.shared; + hprintln!("hello from hw").ok(); + } + + #[task(shared = [a])] + async fn async_task(cx: async_task::Context) { + let async_task::SharedResources { a } = cx.shared; hprintln!("hello from async").ok(); debug::exit(debug::EXIT_SUCCESS); } + + #[task(priority = 2, shared = [a])] + async fn async_task2(cx: async_task2::Context) { + let async_task2::SharedResources { a } = cx.shared; + hprintln!("hello from async2").ok(); + } } diff --git a/macros/src/bindings.rs b/macros/src/bindings.rs index e69de29..8b13789 100644 --- a/macros/src/bindings.rs +++ b/macros/src/bindings.rs @@ -0,0 +1 @@ + diff --git a/macros/src/codegen.rs b/macros/src/codegen.rs index 0f68c34..b490d7a 100644 --- a/macros/src/codegen.rs +++ b/macros/src/codegen.rs @@ -19,6 +19,10 @@ mod shared_resources_struct; mod software_tasks; mod util; +// TODO: organize codegen to actual parts of code +// so `main::codegen` generates ALL the code for `fn main`, +// `software_tasks::codegen` generates ALL the code for software tasks etc... + #[allow(clippy::too_many_lines)] pub fn app(app: &App, analysis: &Analysis) -> TokenStream2 { let mut mod_app = vec![]; diff --git a/macros/src/codegen/async_dispatchers.rs b/macros/src/codegen/async_dispatchers.rs index f428cef..d53d7b5 100644 --- a/macros/src/codegen/async_dispatchers.rs +++ b/macros/src/codegen/async_dispatchers.rs @@ -13,7 +13,6 @@ pub fn codegen(app: &App, analysis: &Analysis) -> Vec { for (name, _) in app.software_tasks.iter() { let type_name = util::internal_task_ident(name, "F"); let exec_name = util::internal_task_ident(name, "EXEC"); - let prio_name = util::internal_task_ident(name, "PRIORITY"); items.push(quote!( #[allow(non_camel_case_types)] @@ -22,12 +21,6 @@ pub fn codegen(app: &App, analysis: &Analysis) -> Vec { static #exec_name: rtic::RacyCell> = rtic::RacyCell::new(rtic::export::executor::AsyncTaskExecutor::new()); - - // The executors priority, this can be any value - we will overwrite it when we - // start a task - #[allow(non_upper_case_globals)] - static #prio_name: rtic::RacyCell = - unsafe { rtic::RacyCell::new(rtic::export::Priority::new(0)) }; )); } @@ -39,7 +32,6 @@ pub fn codegen(app: &App, analysis: &Analysis) -> Vec { for name in channel.tasks.iter() { let exec_name = util::internal_task_ident(name, "EXEC"); - let prio_name = util::internal_task_ident(name, "PRIORITY"); // let task = &app.software_tasks[name]; // let cfgs = &task.cfgs; let executor_run_ident = util::executor_run_ident(name); @@ -57,14 +49,9 @@ pub fn codegen(app: &App, analysis: &Analysis) -> Vec { if !(&*#exec_name.get()).is_running() { // TODO Fix this to be compare and swap if #rq.load(core::sync::atomic::Ordering::Relaxed) { - #rq.store(false, core::sync::atomic::Ordering::Relaxed); - - - // The async executor needs a static priority - #prio_name.get_mut().write(rtic::export::Priority::new(PRIORITY)); - let priority: &'static _ = &*#prio_name.get(); + #rq.store(false, core::sync::atomic::Ordering::Relaxed); - (&mut *#exec_name.get_mut()).spawn(#name(#name::Context::new(priority))); + (&mut *#exec_name.get_mut()).spawn(#name(#name::Context::new())); #executor_run_ident.store(true, core::sync::atomic::Ordering::Relaxed); } } diff --git a/macros/src/codegen/hardware_tasks.rs b/macros/src/codegen/hardware_tasks.rs index 2a81d9a..9ea5825 100644 --- a/macros/src/codegen/hardware_tasks.rs +++ b/macros/src/codegen/hardware_tasks.rs @@ -41,22 +41,16 @@ pub fn codegen( rtic::export::run(PRIORITY, || { #name( - #name::Context::new(&rtic::export::Priority::new(PRIORITY)) + #name::Context::new() ) }); } )); - let mut shared_needs_lt = false; - let mut local_needs_lt = false; - // `${task}Locals` if !task.args.local_resources.is_empty() { - let (item, constructor) = local_resources_struct::codegen( - Context::HardwareTask(name), - &mut local_needs_lt, - app, - ); + let (item, constructor) = + local_resources_struct::codegen(Context::HardwareTask(name), app); root.push(item); @@ -65,24 +59,19 @@ pub fn codegen( // `${task}Resources` if !task.args.shared_resources.is_empty() { - let (item, constructor) = shared_resources_struct::codegen( - Context::HardwareTask(name), - &mut shared_needs_lt, - app, - ); + let (item, constructor) = + shared_resources_struct::codegen(Context::HardwareTask(name), app); root.push(item); mod_app.push(constructor); } - root.push(module::codegen( - Context::HardwareTask(name), - shared_needs_lt, - local_needs_lt, - app, - analysis, - )); + // Module generation... + + root.push(module::codegen(Context::HardwareTask(name), app, analysis)); + + // End module generation if !task.is_extern { let attrs = &task.attrs; diff --git a/macros/src/codegen/idle.rs b/macros/src/codegen/idle.rs index 9867939..a4f6325 100644 --- a/macros/src/codegen/idle.rs +++ b/macros/src/codegen/idle.rs @@ -24,37 +24,27 @@ pub fn codegen( TokenStream2, ) { if let Some(idle) = &app.idle { - let mut shared_needs_lt = false; - let mut local_needs_lt = false; let mut mod_app = vec![]; let mut root_idle = vec![]; let name = &idle.name; if !idle.args.shared_resources.is_empty() { - let (item, constructor) = - shared_resources_struct::codegen(Context::Idle, &mut shared_needs_lt, app); + let (item, constructor) = shared_resources_struct::codegen(Context::Idle, app); root_idle.push(item); mod_app.push(constructor); } if !idle.args.local_resources.is_empty() { - let (item, constructor) = - local_resources_struct::codegen(Context::Idle, &mut local_needs_lt, app); + let (item, constructor) = local_resources_struct::codegen(Context::Idle, app); root_idle.push(item); mod_app.push(constructor); } - root_idle.push(module::codegen( - Context::Idle, - shared_needs_lt, - local_needs_lt, - app, - analysis, - )); + root_idle.push(module::codegen(Context::Idle, app, analysis)); let attrs = &idle.attrs; let context = &idle.context; @@ -71,7 +61,7 @@ pub fn codegen( )); let call_idle = quote!(#name( - #name::Context::new(&rtic::export::Priority::new(0)) + #name::Context::new() )); (mod_app, root_idle, user_idle, call_idle) diff --git a/macros/src/codegen/init.rs b/macros/src/codegen/init.rs index c7b8712..bbde4f2 100644 --- a/macros/src/codegen/init.rs +++ b/macros/src/codegen/init.rs @@ -91,8 +91,7 @@ pub fn codegen(app: &App, analysis: &Analysis) -> CodegenResult { // `${task}Locals` if !init.args.local_resources.is_empty() { - let (item, constructor) = - local_resources_struct::codegen(Context::Init, &mut local_needs_lt, app); + let (item, constructor) = local_resources_struct::codegen(Context::Init, app); root_init.push(item); @@ -103,13 +102,7 @@ pub fn codegen(app: &App, analysis: &Analysis) -> CodegenResult { let (shared_resources, local_resources) = #name(#name::Context::new(core.into())); }; - root_init.push(module::codegen( - Context::Init, - false, - local_needs_lt, - app, - analysis, - )); + root_init.push(module::codegen(Context::Init, app, analysis)); (mod_app, root_init, user_init, call_init) } diff --git a/macros/src/codegen/local_resources_struct.rs b/macros/src/codegen/local_resources_struct.rs index 6bcf4fa..a0413f9 100644 --- a/macros/src/codegen/local_resources_struct.rs +++ b/macros/src/codegen/local_resources_struct.rs @@ -8,7 +8,7 @@ use quote::quote; use crate::codegen::util; /// Generates local resources structs -pub fn codegen(ctxt: Context, needs_lt: &mut bool, app: &App) -> (TokenStream2, TokenStream2) { +pub fn codegen(ctxt: Context, app: &App) -> (TokenStream2, TokenStream2) { let mut lt = None; let resources = match ctxt { @@ -74,16 +74,14 @@ pub fn codegen(ctxt: Context, needs_lt: &mut bool, app: &App) -> (TokenStream2, } if lt.is_some() { - *needs_lt = true; - // The struct could end up empty due to `cfg`s leading to an error due to `'a` being unused if has_cfgs { fields.push(quote!( #[doc(hidden)] - pub __marker__: core::marker::PhantomData<&'a ()> + pub __rtic_internal_marker: ::core::marker::PhantomData<&'a ()> )); - values.push(quote!(__marker__: core::marker::PhantomData)); + values.push(quote!(__rtic_internal_marker: ::core::marker::PhantomData)); } } diff --git a/macros/src/codegen/module.rs b/macros/src/codegen/module.rs index 7bbfdf3..a64abd8 100644 --- a/macros/src/codegen/module.rs +++ b/macros/src/codegen/module.rs @@ -4,13 +4,7 @@ use proc_macro2::TokenStream as TokenStream2; use quote::quote; #[allow(clippy::too_many_lines)] -pub fn codegen( - ctxt: Context, - shared_resources_tick: bool, - local_resources_tick: bool, - app: &App, - analysis: &Analysis, -) -> TokenStream2 { +pub fn codegen(ctxt: Context, app: &App, analysis: &Analysis) -> TokenStream2 { let mut items = vec![]; let mut module_items = vec![]; let mut fields = vec![]; @@ -20,7 +14,6 @@ pub fn codegen( let name = ctxt.ident(app); - let mut lt = None; match ctxt { Context::Init => { fields.push(quote!( @@ -39,10 +32,9 @@ pub fn codegen( values.push(quote!(device: #device::Peripherals::steal())); } - lt = Some(quote!('a)); fields.push(quote!( /// Critical section token for init - pub cs: rtic::export::CriticalSection<#lt> + pub cs: rtic::export::CriticalSection<'a> )); values.push(quote!(cs: rtic::export::CriticalSection::new())); @@ -55,12 +47,6 @@ pub fn codegen( if ctxt.has_local_resources(app) { let ident = util::local_resources_ident(ctxt, app); - let lt = if local_resources_tick { - lt = Some(quote!('a)); - Some(quote!('a)) - } else { - None - }; module_items.push(quote!( #[doc(inline)] @@ -69,7 +55,7 @@ pub fn codegen( fields.push(quote!( /// Local Resources this task has access to - pub local: #name::LocalResources<#lt> + pub local: #name::LocalResources<'a> )); values.push(quote!(local: #name::LocalResources::new())); @@ -77,12 +63,6 @@ pub fn codegen( if ctxt.has_shared_resources(app) { let ident = util::shared_resources_ident(ctxt, app); - let lt = if shared_resources_tick { - lt = Some(quote!('a)); - Some(quote!('a)) - } else { - None - }; module_items.push(quote!( #[doc(inline)] @@ -91,15 +71,10 @@ pub fn codegen( fields.push(quote!( /// Shared Resources this task has access to - pub shared: #name::SharedResources<#lt> + pub shared: #name::SharedResources<'a> )); - let priority = if ctxt.is_init() { - None - } else { - Some(quote!(priority)) - }; - values.push(quote!(shared: #name::SharedResources::new(#priority))); + values.push(quote!(shared: #name::SharedResources::new())); } let doc = match ctxt { @@ -122,12 +97,6 @@ pub fn codegen( None }; - let priority = if ctxt.is_init() { - None - } else { - Some(quote!(priority: &#lt rtic::export::Priority)) - }; - let internal_context_name = util::internal_task_ident(name, "Context"); items.push(quote!( @@ -135,15 +104,18 @@ pub fn codegen( /// Execution context #[allow(non_snake_case)] #[allow(non_camel_case_types)] - pub struct #internal_context_name<#lt> { + pub struct #internal_context_name<'a> { + #[doc(hidden)] + __rtic_internal_p: ::core::marker::PhantomData<&'a ()>, #(#fields,)* } #(#cfgs)* - impl<#lt> #internal_context_name<#lt> { + impl<'a> #internal_context_name<'a> { #[inline(always)] - pub unsafe fn new(#core #priority) -> Self { + pub unsafe fn new(#core) -> Self { #internal_context_name { + __rtic_internal_p: ::core::marker::PhantomData, #(#values,)* } } diff --git a/macros/src/codegen/shared_resources.rs b/macros/src/codegen/shared_resources.rs index b63e743..5c54fb9 100644 --- a/macros/src/codegen/shared_resources.rs +++ b/macros/src/codegen/shared_resources.rs @@ -57,19 +57,14 @@ pub fn codegen( #[allow(non_camel_case_types)] #(#cfgs)* pub struct #shared_name<'a> { - priority: &'a Priority, + __rtic_internal_p: ::core::marker::PhantomData<&'a ()>, } #(#cfgs)* impl<'a> #shared_name<'a> { #[inline(always)] - pub unsafe fn new(priority: &'a Priority) -> Self { - #shared_name { priority } - } - - #[inline(always)] - pub unsafe fn priority(&self) -> &Priority { - self.priority + pub unsafe fn new() -> Self { + #shared_name { __rtic_internal_p: ::core::marker::PhantomData } } } )); @@ -104,8 +99,6 @@ pub fn codegen( quote!() } else { quote!(mod shared_resources { - use rtic::export::Priority; - #(#mod_resources)* }) }; diff --git a/macros/src/codegen/shared_resources_struct.rs b/macros/src/codegen/shared_resources_struct.rs index 1d46aa4..de597ca 100644 --- a/macros/src/codegen/shared_resources_struct.rs +++ b/macros/src/codegen/shared_resources_struct.rs @@ -5,7 +5,7 @@ use quote::quote; use crate::codegen::util; /// Generate shared resources structs -pub fn codegen(ctxt: Context, needs_lt: &mut bool, app: &App) -> (TokenStream2, TokenStream2) { +pub fn codegen(ctxt: Context, app: &App) -> (TokenStream2, TokenStream2) { let mut lt = None; let resources = match ctxt { @@ -72,7 +72,7 @@ pub fn codegen(ctxt: Context, needs_lt: &mut bool, app: &App) -> (TokenStream2, values.push(quote!( #(#cfgs)* - #name: shared_resources::#shared_name::new(priority) + #name: shared_resources::#shared_name::new() )); @@ -93,8 +93,6 @@ pub fn codegen(ctxt: Context, needs_lt: &mut bool, app: &App) -> (TokenStream2, } if lt.is_some() { - *needs_lt = true; - // The struct could end up empty due to `cfg`s leading to an error due to `'a` being unused if has_cfgs { fields.push(quote!( @@ -117,15 +115,10 @@ pub fn codegen(ctxt: Context, needs_lt: &mut bool, app: &App) -> (TokenStream2, } ); - let arg = if ctxt.is_init() { - None - } else { - Some(quote!(priority: &#lt rtic::export::Priority)) - }; let constructor = quote!( impl<#lt> #ident<#lt> { #[inline(always)] - pub unsafe fn new(#arg) -> Self { + pub unsafe fn new() -> Self { #ident { #(#values,)* } diff --git a/macros/src/codegen/software_tasks.rs b/macros/src/codegen/software_tasks.rs index b2b468c..350c1e6 100644 --- a/macros/src/codegen/software_tasks.rs +++ b/macros/src/codegen/software_tasks.rs @@ -36,16 +36,11 @@ pub fn codegen( )); // `${task}Resources` - let mut shared_needs_lt = false; - let mut local_needs_lt = false; // `${task}Locals` if !task.args.local_resources.is_empty() { - let (item, constructor) = local_resources_struct::codegen( - Context::SoftwareTask(name), - &mut local_needs_lt, - app, - ); + let (item, constructor) = + local_resources_struct::codegen(Context::SoftwareTask(name), app); root.push(item); @@ -53,11 +48,8 @@ pub fn codegen( } if !task.args.shared_resources.is_empty() { - let (item, constructor) = shared_resources_struct::codegen( - Context::SoftwareTask(name), - &mut shared_needs_lt, - app, - ); + let (item, constructor) = + shared_resources_struct::codegen(Context::SoftwareTask(name), app); root.push(item); @@ -69,17 +61,12 @@ pub fn codegen( let attrs = &task.attrs; let cfgs = &task.cfgs; let stmts = &task.stmts; - let context_lifetime = if shared_needs_lt || local_needs_lt { - quote!(<'static>) - } else { - quote!() - }; user_tasks.push(quote!( #(#attrs)* #(#cfgs)* #[allow(non_snake_case)] - async fn #name(#context: #name::Context #context_lifetime) { + async fn #name(#context: #name::Context<'static>) { use rtic::Mutex as _; use rtic::mutex::prelude::*; @@ -88,13 +75,7 @@ pub fn codegen( )); } - root.push(module::codegen( - Context::SoftwareTask(name), - shared_needs_lt, - local_needs_lt, - app, - analysis, - )); + root.push(module::codegen(Context::SoftwareTask(name), app, analysis)); } (mod_app, root, user_tasks) diff --git a/macros/src/codegen/util.rs b/macros/src/codegen/util.rs index aa720c0..a071ca2 100644 --- a/macros/src/codegen/util.rs +++ b/macros/src/codegen/util.rs @@ -17,10 +17,10 @@ pub fn impl_mutex( ceiling: u8, ptr: &TokenStream2, ) -> TokenStream2 { - let (path, priority) = if resources_prefix { - (quote!(shared_resources::#name), quote!(self.priority())) + let path = if resources_prefix { + quote!(shared_resources::#name) } else { - (quote!(#name), quote!(self.priority)) + quote!(#name) }; let device = &app.args.device; @@ -38,7 +38,6 @@ pub fn impl_mutex( unsafe { rtic::export::lock( #ptr, - #priority, CEILING, #device::NVIC_PRIO_BITS, &#masks_name, diff --git a/rust-toolchain.toml b/rust-toolchain.toml index bbd57bc..e28b55d 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] channel = "nightly" components = [ "rust-src", "rustfmt", "llvm-tools-preview" ] -targets = [ "thumbv7em-none-eabihf" ] +targets = [ "thumbv6m-none-eabi", "thumbv7m-none-eabi" ] diff --git a/src/export.rs b/src/export.rs index 2cc031e..49ebd87 100644 --- a/src/export.rs +++ b/src/export.rs @@ -163,38 +163,6 @@ impl Barrier { } } -// Newtype over `Cell` that forbids mutation through a shared reference -pub struct Priority { - inner: Cell, -} - -impl Priority { - /// Create a new Priority - /// - /// # Safety - /// - /// Will overwrite the current Priority - #[inline(always)] - pub const unsafe fn new(value: u8) -> Self { - Priority { - inner: Cell::new(value), - } - } - - /// Change the current priority to `value` - // These two methods are used by `lock` (see below) but can't be used from the RTIC application - #[inline(always)] - fn set(&self, value: u8) { - self.inner.set(value); - } - - /// Get the current priority - #[inline(always)] - fn get(&self) -> u8 { - self.inner.get() - } -} - /// Const helper to check architecture pub const fn have_basepri() -> bool { #[cfg(have_basepri)] @@ -260,30 +228,20 @@ where #[inline(always)] pub unsafe fn lock( ptr: *mut T, - priority: &Priority, ceiling: u8, nvic_prio_bits: u8, _mask: &[Mask; 3], f: impl FnOnce(&mut T) -> R, ) -> R { - let current = priority.get(); - - if current < ceiling { - if ceiling == (1 << nvic_prio_bits) { - priority.set(u8::max_value()); - let r = interrupt::free(|_| f(&mut *ptr)); - priority.set(current); - r - } else { - priority.set(ceiling); - basepri::write(logical2hw(ceiling, nvic_prio_bits)); - let r = f(&mut *ptr); - basepri::write(logical2hw(current, nvic_prio_bits)); - priority.set(current); - r - } + if ceiling == (1 << nvic_prio_bits) { + let r = interrupt::free(|_| f(&mut *ptr)); + r } else { - f(&mut *ptr) + let current = basepri::read(); + basepri::write(logical2hw(ceiling, nvic_prio_bits)); + let r = f(&mut *ptr); + basepri::write(logical2hw(current, nvic_prio_bits)); + r } } @@ -335,40 +293,29 @@ pub unsafe fn lock( #[inline(always)] pub unsafe fn lock( ptr: *mut T, - priority: &Priority, ceiling: u8, _nvic_prio_bits: u8, masks: &[Mask; 3], f: impl FnOnce(&mut T) -> R, ) -> R { - let current = priority.get(); - if current < ceiling { - if ceiling >= 4 { - // safe to manipulate outside critical section - priority.set(ceiling); - // execute closure under protection of raised system ceiling - let r = interrupt::free(|_| f(&mut *ptr)); - // safe to manipulate outside critical section - priority.set(current); - r - } else { - // safe to manipulate outside critical section - priority.set(ceiling); - let mask = compute_mask(current, ceiling, masks); - clear_enable_mask(mask); - - // execute closure under protection of raised system ceiling - let r = f(&mut *ptr); - - set_enable_mask(mask); - - // safe to manipulate outside critical section - priority.set(current); - r - } + if ceiling >= 4 { + // safe to manipulate outside critical section + // execute closure under protection of raised system ceiling + let r = interrupt::free(|_| f(&mut *ptr)); + // safe to manipulate outside critical section + r } else { - // execute closure without raising system ceiling - f(&mut *ptr) + // safe to manipulate outside critical section + let mask = compute_mask(0, ceiling, masks); + clear_enable_mask(mask); + + // execute closure under protection of raised system ceiling + let r = f(&mut *ptr); + + set_enable_mask(mask); + + // safe to manipulate outside critical section + r } } -- cgit v1.2.3 From 511ff675b558812d65048ae737b6f1c53133e211 Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Sat, 7 Jan 2023 11:36:32 +0100 Subject: Break codegen for 0-prio async --- macros/src/codegen/idle.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/macros/src/codegen/idle.rs b/macros/src/codegen/idle.rs index a4f6325..c0eecbc 100644 --- a/macros/src/codegen/idle.rs +++ b/macros/src/codegen/idle.rs @@ -67,15 +67,15 @@ pub fn codegen( (mod_app, root_idle, user_idle, call_idle) } else { // TODO: No idle defined, check for 0-priority tasks and generate an executor if needed + unimplemented!(); - // - ( - vec![], - vec![], - None, - quote!(loop { - rtic::export::nop() - }), - ) + // ( + // vec![], + // vec![], + // None, + // quote!(loop { + // rtic::export::nop() + // }), + // ) } } -- cgit v1.2.3 From 2ad36a6efed5028e0e6bd991b82a50c045f825a8 Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Sat, 7 Jan 2023 13:18:43 +0100 Subject: Lifetime cleanup --- examples/async-task.rs | 6 +++--- macros/src/codegen/local_resources_struct.rs | 25 +++++++---------------- macros/src/codegen/shared_resources_struct.rs | 29 +++++++-------------------- 3 files changed, 17 insertions(+), 43 deletions(-) diff --git a/examples/async-task.rs b/examples/async-task.rs index d058fe5..45d44fd 100644 --- a/examples/async-task.rs +++ b/examples/async-task.rs @@ -43,13 +43,13 @@ mod app { #[task(binds = UART1, shared = [a])] fn hw_task(cx: hw_task::Context) { - let hw_task::SharedResources { a } = cx.shared; + let hw_task::SharedResources { a, .. } = cx.shared; hprintln!("hello from hw").ok(); } #[task(shared = [a])] async fn async_task(cx: async_task::Context) { - let async_task::SharedResources { a } = cx.shared; + let async_task::SharedResources { a, .. } = cx.shared; hprintln!("hello from async").ok(); debug::exit(debug::EXIT_SUCCESS); @@ -57,7 +57,7 @@ mod app { #[task(priority = 2, shared = [a])] async fn async_task2(cx: async_task2::Context) { - let async_task2::SharedResources { a } = cx.shared; + let async_task2::SharedResources { a, .. } = cx.shared; hprintln!("hello from async2").ok(); } } diff --git a/macros/src/codegen/local_resources_struct.rs b/macros/src/codegen/local_resources_struct.rs index a0413f9..e268508 100644 --- a/macros/src/codegen/local_resources_struct.rs +++ b/macros/src/codegen/local_resources_struct.rs @@ -9,8 +9,6 @@ use crate::codegen::util; /// Generates local resources structs pub fn codegen(ctxt: Context, app: &App) -> (TokenStream2, TokenStream2) { - let mut lt = None; - let resources = match ctxt { Context::Init => &app.init.args.local_resources, Context::Idle => { @@ -28,7 +26,6 @@ pub fn codegen(ctxt: Context, app: &App) -> (TokenStream2, TokenStream2) { let mut fields = vec![]; let mut values = vec![]; - let mut has_cfgs = false; for (name, task_local) in resources { let (cfgs, ty, is_declared) = match task_local { @@ -39,12 +36,9 @@ pub fn codegen(ctxt: Context, app: &App) -> (TokenStream2, TokenStream2) { TaskLocal::Declared(r) => (&r.cfgs, &r.ty, true), }; - has_cfgs |= !cfgs.is_empty(); - let lt = if ctxt.runs_once() { quote!('static) } else { - lt = Some(quote!('a)); quote!('a) }; @@ -73,17 +67,12 @@ pub fn codegen(ctxt: Context, app: &App) -> (TokenStream2, TokenStream2) { )); } - if lt.is_some() { - // The struct could end up empty due to `cfg`s leading to an error due to `'a` being unused - if has_cfgs { - fields.push(quote!( - #[doc(hidden)] - pub __rtic_internal_marker: ::core::marker::PhantomData<&'a ()> - )); + fields.push(quote!( + #[doc(hidden)] + pub __rtic_internal_marker: ::core::marker::PhantomData<&'a ()> + )); - values.push(quote!(__rtic_internal_marker: ::core::marker::PhantomData)); - } - } + values.push(quote!(__rtic_internal_marker: ::core::marker::PhantomData)); let doc = format!("Local resources `{}` has access to", ctxt.ident(app)); let ident = util::local_resources_ident(ctxt, app); @@ -91,13 +80,13 @@ pub fn codegen(ctxt: Context, app: &App) -> (TokenStream2, TokenStream2) { #[allow(non_snake_case)] #[allow(non_camel_case_types)] #[doc = #doc] - pub struct #ident<#lt> { + pub struct #ident<'a> { #(#fields,)* } ); let constructor = quote!( - impl<#lt> #ident<#lt> { + impl<'a> #ident<'a> { #[inline(always)] pub unsafe fn new() -> Self { #ident { diff --git a/macros/src/codegen/shared_resources_struct.rs b/macros/src/codegen/shared_resources_struct.rs index de597ca..24c93de 100644 --- a/macros/src/codegen/shared_resources_struct.rs +++ b/macros/src/codegen/shared_resources_struct.rs @@ -6,8 +6,6 @@ use crate::codegen::util; /// Generate shared resources structs pub fn codegen(ctxt: Context, app: &App) -> (TokenStream2, TokenStream2) { - let mut lt = None; - let resources = match ctxt { Context::Init => unreachable!("Tried to generate shared resources struct for init"), Context::Idle => { @@ -23,13 +21,11 @@ pub fn codegen(ctxt: Context, app: &App) -> (TokenStream2, TokenStream2) { let mut fields = vec![]; let mut values = vec![]; - let mut has_cfgs = false; for (name, access) in resources { let res = app.shared_resources.get(name).expect("UNREACHABLE"); let cfgs = &res.cfgs; - has_cfgs |= !cfgs.is_empty(); // access hold if the resource is [x] (exclusive) or [&x] (shared) let mut_ = if access.is_exclusive() { @@ -46,7 +42,6 @@ pub fn codegen(ctxt: Context, app: &App) -> (TokenStream2, TokenStream2) { let lt = if ctxt.runs_once() { quote!('static) } else { - lt = Some(quote!('a)); quote!('a) }; @@ -55,16 +50,11 @@ pub fn codegen(ctxt: Context, app: &App) -> (TokenStream2, TokenStream2) { pub #name: &#lt #mut_ #ty )); } else if access.is_shared() { - lt = Some(quote!('a)); - fields.push(quote!( #(#cfgs)* pub #name: &'a #ty )); } else { - // Resource proxy - lt = Some(quote!('a)); - fields.push(quote!( #(#cfgs)* pub #name: shared_resources::#shared_name<'a> @@ -92,17 +82,12 @@ pub fn codegen(ctxt: Context, app: &App) -> (TokenStream2, TokenStream2) { )); } - if lt.is_some() { - // The struct could end up empty due to `cfg`s leading to an error due to `'a` being unused - if has_cfgs { - fields.push(quote!( - #[doc(hidden)] - pub __marker__: core::marker::PhantomData<&'a ()> - )); + fields.push(quote!( + #[doc(hidden)] + pub __rtic_internal_marker: core::marker::PhantomData<&'a ()> + )); - values.push(quote!(__marker__: core::marker::PhantomData)); - } - } + values.push(quote!(__rtic_internal_marker: core::marker::PhantomData)); let doc = format!("Shared resources `{}` has access to", ctxt.ident(app)); let ident = util::shared_resources_ident(ctxt, app); @@ -110,13 +95,13 @@ pub fn codegen(ctxt: Context, app: &App) -> (TokenStream2, TokenStream2) { #[allow(non_snake_case)] #[allow(non_camel_case_types)] #[doc = #doc] - pub struct #ident<#lt> { + pub struct #ident<'a> { #(#fields,)* } ); let constructor = quote!( - impl<#lt> #ident<#lt> { + impl<'a> #ident<'a> { #[inline(always)] pub unsafe fn new() -> Self { #ident { -- cgit v1.2.3 From fe2b5cc52ee634346bc8aecf5041b6af9fdea529 Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Sat, 7 Jan 2023 13:23:20 +0100 Subject: Removed same prio spawn --- macros/src/codegen/init.rs | 1 - macros/src/syntax/ast.rs | 4 ---- macros/src/syntax/parse.rs | 32 ------------------------- macros/ui/task-interrupt-same-prio-spawn.rs | 7 ------ macros/ui/task-interrupt-same-prio-spawn.stderr | 5 ---- 5 files changed, 49 deletions(-) delete mode 100644 macros/ui/task-interrupt-same-prio-spawn.rs delete mode 100644 macros/ui/task-interrupt-same-prio-spawn.stderr diff --git a/macros/src/codegen/init.rs b/macros/src/codegen/init.rs index bbde4f2..2aa8fb3 100644 --- a/macros/src/codegen/init.rs +++ b/macros/src/codegen/init.rs @@ -25,7 +25,6 @@ type CodegenResult = ( /// Generates support code for `#[init]` functions pub fn codegen(app: &App, analysis: &Analysis) -> CodegenResult { let init = &app.init; - let mut local_needs_lt = false; let name = &init.name; let mut root_init = vec![]; diff --git a/macros/src/syntax/ast.rs b/macros/src/syntax/ast.rs index ea6e402..da6016a 100644 --- a/macros/src/syntax/ast.rs +++ b/macros/src/syntax/ast.rs @@ -224,9 +224,6 @@ pub struct SoftwareTaskArgs { /// Shared resources that can be accessed from this context pub shared_resources: SharedResources, - - /// Only same priority tasks can spawn this task - pub only_same_priority_spawn: bool, } impl Default for SoftwareTaskArgs { @@ -235,7 +232,6 @@ impl Default for SoftwareTaskArgs { priority: 1, local_resources: LocalResources::new(), shared_resources: SharedResources::new(), - only_same_priority_spawn: false, } } } diff --git a/macros/src/syntax/parse.rs b/macros/src/syntax/parse.rs index abdd677..c78453a 100644 --- a/macros/src/syntax/parse.rs +++ b/macros/src/syntax/parse.rs @@ -196,8 +196,6 @@ fn task_args(tokens: TokenStream2) -> parse::Result parse::Result parse::Result { return Err(parse::Error::new(ident.span(), "unexpected argument")); } @@ -369,13 +345,6 @@ fn task_args(tokens: TokenStream2) -> parse::Result parse::Result ui/task-interrupt-same-prio-spawn.rs:5:29 - | -5 | #[task(binds = SysTick, only_same_priority_spawn_please_fix_me)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -- cgit v1.2.3 From 9247252cc74fab4a421f8e8370b29c37ca2fa48a Mon Sep 17 00:00:00 2001 From: Per Lindgren Date: Sat, 7 Jan 2023 13:58:15 +0100 Subject: examples/async-task fixup --- examples/async-task.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/async-task.rs b/examples/async-task.rs index 45d44fd..012e95e 100644 --- a/examples/async-task.rs +++ b/examples/async-task.rs @@ -6,7 +6,7 @@ use panic_semihosting as _; // NOTES: // -// - Async tasks cannot have `#[lock_free]` resources, as they can interleve and each async +// - Async tasks cannot have `#[lock_free]` resources, as they can interleave and each async // task can have a mutable reference stored. // - Spawning an async task equates to it being polled once. @@ -23,7 +23,7 @@ mod app { struct Local {} #[init] - fn init(cx: init::Context) -> (Shared, Local) { + fn init(_cx: init::Context) -> (Shared, Local) { hprintln!("init").unwrap(); async_task::spawn().unwrap(); @@ -43,13 +43,13 @@ mod app { #[task(binds = UART1, shared = [a])] fn hw_task(cx: hw_task::Context) { - let hw_task::SharedResources { a, .. } = cx.shared; + let hw_task::SharedResources { a: _, .. } = cx.shared; hprintln!("hello from hw").ok(); } #[task(shared = [a])] async fn async_task(cx: async_task::Context) { - let async_task::SharedResources { a, .. } = cx.shared; + let async_task::SharedResources { a: _, .. } = cx.shared; hprintln!("hello from async").ok(); debug::exit(debug::EXIT_SUCCESS); @@ -57,7 +57,7 @@ mod app { #[task(priority = 2, shared = [a])] async fn async_task2(cx: async_task2::Context) { - let async_task2::SharedResources { a, .. } = cx.shared; + let async_task2::SharedResources { a: _, .. } = cx.shared; hprintln!("hello from async2").ok(); } } -- cgit v1.2.3 From 29228c47239f12794feb91ae5a81d748530c40dc Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Sat, 7 Jan 2023 14:06:11 +0100 Subject: Main in main codegen --- macros/src/codegen.rs | 43 +++++----------------------------- macros/src/codegen/idle.rs | 18 ++------------- macros/src/codegen/init.rs | 9 ++------ macros/src/codegen/main.rs | 51 +++++++++++++++++++++++++++++++++++++++++ macros/src/codegen/post_init.rs | 4 +--- 5 files changed, 62 insertions(+), 63 deletions(-) create mode 100644 macros/src/codegen/main.rs diff --git a/macros/src/codegen.rs b/macros/src/codegen.rs index b490d7a..839b1cd 100644 --- a/macros/src/codegen.rs +++ b/macros/src/codegen.rs @@ -19,6 +19,8 @@ mod shared_resources_struct; mod software_tasks; mod util; +mod main; + // TODO: organize codegen to actual parts of code // so `main::codegen` generates ALL the code for `fn main`, // `software_tasks::codegen` generates ALL the code for software tasks etc... @@ -26,20 +28,15 @@ mod util; #[allow(clippy::too_many_lines)] pub fn app(app: &App, analysis: &Analysis) -> TokenStream2 { let mut mod_app = vec![]; - let mut mains = vec![]; let mut root = vec![]; let mut user = vec![]; // Generate the `main` function - let assertion_stmts = assertions::codegen(app, analysis); - - let pre_init_stmts = pre_init::codegen(app, analysis); - - let (mod_app_init, root_init, user_init, call_init) = init::codegen(app, analysis); + let main = main::codegen(app, analysis); - let post_init_stmts = post_init::codegen(app, analysis); + let (mod_app_init, root_init, user_init) = init::codegen(app, analysis); - let (mod_app_idle, root_idle, user_idle, call_idle) = idle::codegen(app, analysis); + let (mod_app_idle, root_idle, user_idle) = idle::codegen(app, analysis); user.push(quote!( #user_init @@ -59,34 +56,6 @@ pub fn app(app: &App, analysis: &Analysis) -> TokenStream2 { #(#mod_app_idle)* )); - let main = util::suffixed("main"); - mains.push(quote!( - #[doc(hidden)] - mod rtic_ext { - use super::*; - #[no_mangle] - unsafe extern "C" fn #main() -> ! { - #(#assertion_stmts)* - - #(#pre_init_stmts)* - - #[inline(never)] - fn __rtic_init_resources(f: F) where F: FnOnce() { - f(); - } - - // Wrap late_init_stmts in a function to ensure that stack space is reclaimed. - __rtic_init_resources(||{ - #call_init - - #(#post_init_stmts)* - }); - - #call_idle - } - } - )); - let (mod_app_shared_resources, mod_shared_resources) = shared_resources::codegen(app, analysis); let (mod_app_local_resources, mod_local_resources) = local_resources::codegen(app, analysis); @@ -145,7 +114,7 @@ pub fn app(app: &App, analysis: &Analysis) -> TokenStream2 { #(#mod_app_async_dispatchers)* - #(#mains)* + #main } ) } diff --git a/macros/src/codegen/idle.rs b/macros/src/codegen/idle.rs index c0eecbc..1f05d12 100644 --- a/macros/src/codegen/idle.rs +++ b/macros/src/codegen/idle.rs @@ -20,8 +20,6 @@ pub fn codegen( Vec, // user_idle Option, - // call_idle - TokenStream2, ) { if let Some(idle) = &app.idle { let mut mod_app = vec![]; @@ -60,22 +58,10 @@ pub fn codegen( } )); - let call_idle = quote!(#name( - #name::Context::new() - )); - - (mod_app, root_idle, user_idle, call_idle) + (mod_app, root_idle, user_idle) } else { // TODO: No idle defined, check for 0-priority tasks and generate an executor if needed - unimplemented!(); - // ( - // vec![], - // vec![], - // None, - // quote!(loop { - // rtic::export::nop() - // }), - // ) + (vec![], vec![], None) } } diff --git a/macros/src/codegen/init.rs b/macros/src/codegen/init.rs index 2aa8fb3..3b2bcd4 100644 --- a/macros/src/codegen/init.rs +++ b/macros/src/codegen/init.rs @@ -18,8 +18,6 @@ type CodegenResult = ( Vec, // user_init -- the `#[init]` function written by the user TokenStream2, - // call_init -- the call to the user `#[init]` - TokenStream2, ); /// Generates support code for `#[init]` functions @@ -63,6 +61,7 @@ pub fn codegen(app: &App, analysis: &Analysis) -> CodegenResult { ) }) .collect(); + root_init.push(quote! { struct #shared { #(#shared_resources)* @@ -97,11 +96,7 @@ pub fn codegen(app: &App, analysis: &Analysis) -> CodegenResult { mod_app = Some(constructor); } - let call_init = quote! { - let (shared_resources, local_resources) = #name(#name::Context::new(core.into())); - }; - root_init.push(module::codegen(Context::Init, app, analysis)); - (mod_app, root_init, user_init, call_init) + (mod_app, root_init, user_init) } diff --git a/macros/src/codegen/main.rs b/macros/src/codegen/main.rs new file mode 100644 index 0000000..90f09ae --- /dev/null +++ b/macros/src/codegen/main.rs @@ -0,0 +1,51 @@ +use crate::{analyze::Analysis, codegen::util, syntax::ast::App}; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +use super::{assertions, post_init, pre_init}; + +/// Generates code for `fn main` +pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { + let assertion_stmts = assertions::codegen(app, analysis); + + let pre_init_stmts = pre_init::codegen(app, analysis); + + let post_init_stmts = post_init::codegen(app, analysis); + + let call_idle = if let Some(idle) = &app.idle { + let name = &idle.name; + quote!(#name(#name::Context::new())) + } else { + // TODO: No idle defined, check for 0-priority tasks and generate an executor if needed + + quote!(loop { + rtic::export::nop() + }) + }; + + let main = util::suffixed("main"); + let init_name = &app.init.name; + quote!( + #[doc(hidden)] + #[no_mangle] + unsafe extern "C" fn #main() -> ! { + #(#assertion_stmts)* + + #(#pre_init_stmts)* + + #[inline(never)] + fn __rtic_init_resources(f: F) where F: FnOnce() { + f(); + } + + // Wrap late_init_stmts in a function to ensure that stack space is reclaimed. + __rtic_init_resources(||{ + let (shared_resources, local_resources) = #init_name(#init_name::Context::new(core.into())); + + #(#post_init_stmts)* + }); + + #call_idle + } + ) +} diff --git a/macros/src/codegen/post_init.rs b/macros/src/codegen/post_init.rs index e8183b9..c4e5383 100644 --- a/macros/src/codegen/post_init.rs +++ b/macros/src/codegen/post_init.rs @@ -1,9 +1,7 @@ -use crate::syntax::ast::App; +use crate::{analyze::Analysis, codegen::util, syntax::ast::App}; use proc_macro2::TokenStream as TokenStream2; use quote::quote; -use crate::{analyze::Analysis, codegen::util}; - /// Generates code that runs after `#[init]` returns pub fn codegen(app: &App, analysis: &Analysis) -> Vec { let mut stmts = vec![]; -- cgit v1.2.3 From 6dc2d29cd994a27fa59e23f9fb0bece677c83ffa Mon Sep 17 00:00:00 2001 From: Per Lindgren Date: Sat, 7 Jan 2023 14:07:50 +0100 Subject: export Cell removed, expmples updated --- examples/idle.rs | 4 ++-- examples/init.rs | 4 ++-- src/export.rs | 5 +---- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/examples/idle.rs b/examples/idle.rs index 55d6b15..9a7a727 100644 --- a/examples/idle.rs +++ b/examples/idle.rs @@ -18,10 +18,10 @@ mod app { struct Local {} #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { + fn init(_: init::Context) -> (Shared, Local) { hprintln!("init").unwrap(); - (Shared {}, Local {}, init::Monotonics()) + (Shared {}, Local {}) } #[idle(local = [x: u32 = 0])] diff --git a/examples/init.rs b/examples/init.rs index b8a5bc5..c807a3c 100644 --- a/examples/init.rs +++ b/examples/init.rs @@ -18,7 +18,7 @@ mod app { struct Local {} #[init(local = [x: u32 = 0])] - fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { + fn init(cx: init::Context) -> (Shared, Local) { // Cortex-M peripherals let _core: cortex_m::Peripherals = cx.core; @@ -36,6 +36,6 @@ mod app { debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - (Shared {}, Local {}, init::Monotonics()) + (Shared {}, Local {}) } } diff --git a/src/export.rs b/src/export.rs index 49ebd87..d6b2fc0 100644 --- a/src/export.rs +++ b/src/export.rs @@ -1,8 +1,5 @@ pub use bare_metal::CriticalSection; -use core::{ - cell::Cell, - sync::atomic::{AtomicBool, Ordering}, -}; +use core::sync::atomic::{AtomicBool, Ordering}; pub use cortex_m::{ asm::nop, asm::wfi, -- cgit v1.2.3 From 4337e3980c52116e1606c60ff12eaea4a9971ece Mon Sep 17 00:00:00 2001 From: Per Lindgren Date: Sat, 7 Jan 2023 14:09:31 +0100 Subject: examples/idle-wfi fixed --- examples/idle-wfi.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/idle-wfi.rs b/examples/idle-wfi.rs index 4a8a8de..dd2d43f 100644 --- a/examples/idle-wfi.rs +++ b/examples/idle-wfi.rs @@ -18,14 +18,14 @@ mod app { struct Local {} #[init] - fn init(mut cx: init::Context) -> (Shared, Local, init::Monotonics) { + fn init(mut cx: init::Context) -> (Shared, Local) { hprintln!("init").unwrap(); // Set the ARM SLEEPONEXIT bit to go to sleep after handling interrupts // See https://developer.arm.com/docs/100737/0100/power-management/sleep-mode/sleep-on-exit-bit cx.core.SCB.set_sleepdeep(); - (Shared {}, Local {}, init::Monotonics()) + (Shared {}, Local {}) } #[idle(local = [x: u32 = 0])] -- cgit v1.2.3 From b9b3ded5e21c40256163cf85f4fba2991c03a45c Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Sat, 7 Jan 2023 14:13:18 +0100 Subject: Cleanup weird locals in codegen --- macros/src/codegen.rs | 39 +++++++++++---------------------------- 1 file changed, 11 insertions(+), 28 deletions(-) diff --git a/macros/src/codegen.rs b/macros/src/codegen.rs index 839b1cd..bb1028f 100644 --- a/macros/src/codegen.rs +++ b/macros/src/codegen.rs @@ -27,10 +27,6 @@ mod main; #[allow(clippy::too_many_lines)] pub fn app(app: &App, analysis: &Analysis) -> TokenStream2 { - let mut mod_app = vec![]; - let mut root = vec![]; - let mut user = vec![]; - // Generate the `main` function let main = main::codegen(app, analysis); @@ -38,24 +34,6 @@ pub fn app(app: &App, analysis: &Analysis) -> TokenStream2 { let (mod_app_idle, root_idle, user_idle) = idle::codegen(app, analysis); - user.push(quote!( - #user_init - - #user_idle - )); - - root.push(quote!( - #(#root_init)* - - #(#root_idle)* - )); - - mod_app.push(quote!( - #mod_app_init - - #(#mod_app_idle)* - )); - let (mod_app_shared_resources, mod_shared_resources) = shared_resources::codegen(app, analysis); let (mod_app_local_resources, mod_local_resources) = local_resources::codegen(app, analysis); @@ -85,13 +63,21 @@ pub fn app(app: &App, analysis: &Analysis) -> TokenStream2 { #(#user_code)* /// User code end - #(#user)* - #(#user_hardware_tasks)* #(#user_software_tasks)* - #(#root)* + #mod_app_init + + #(#root_init)* + + #user_init + + #(#mod_app_idle)* + + #(#root_idle)* + + #user_idle #mod_shared_resources @@ -101,9 +87,6 @@ pub fn app(app: &App, analysis: &Analysis) -> TokenStream2 { #(#root_software_tasks)* - /// app module - #(#mod_app)* - #(#mod_app_shared_resources)* #(#mod_app_local_resources)* -- cgit v1.2.3 From bd20d0d89e3ea477c9970f06f0ec750cee71690b Mon Sep 17 00:00:00 2001 From: Per Lindgren Date: Sat, 7 Jan 2023 14:16:24 +0100 Subject: examples/locals fixed --- examples/locals.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/locals.rs b/examples/locals.rs index aa5d0fe..a35b4c9 100644 --- a/examples/locals.rs +++ b/examples/locals.rs @@ -1,5 +1,6 @@ //! examples/locals.rs +#![feature(type_alias_impl_trait)] #![deny(unsafe_code)] #![deny(warnings)] #![no_main] @@ -23,7 +24,7 @@ mod app { // `#[init]` cannot access locals from the `#[local]` struct as they are initialized here. #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { + fn init(_: init::Context) -> (Shared, Local) { foo::spawn().unwrap(); bar::spawn().unwrap(); @@ -35,7 +36,6 @@ mod app { local_to_bar: 0, local_to_idle: 0, }, - init::Monotonics(), ) } @@ -62,7 +62,7 @@ mod app { // `local_to_foo` can only be accessed from this context #[task(local = [local_to_foo])] - fn foo(cx: foo::Context) { + async fn foo(cx: foo::Context) { let local_to_foo = cx.local.local_to_foo; *local_to_foo += 1; @@ -74,7 +74,7 @@ mod app { // `local_to_bar` can only be accessed from this context #[task(local = [local_to_bar])] - fn bar(cx: bar::Context) { + async fn bar(cx: bar::Context) { let local_to_bar = cx.local.local_to_bar; *local_to_bar += 1; -- cgit v1.2.3 From b054e871d486e8eb35e3c98a73652640238c5e7d Mon Sep 17 00:00:00 2001 From: Per Lindgren Date: Sat, 7 Jan 2023 14:23:32 +0100 Subject: examples/lock fixed --- examples/lock-free.no_rs | 50 ++++++++++++++++++++++++++++++++++++++++++++++++ examples/lock-free.rs | 49 ----------------------------------------------- examples/lock.rs | 11 ++++++----- 3 files changed, 56 insertions(+), 54 deletions(-) create mode 100644 examples/lock-free.no_rs delete mode 100644 examples/lock-free.rs diff --git a/examples/lock-free.no_rs b/examples/lock-free.no_rs new file mode 100644 index 0000000..053307c --- /dev/null +++ b/examples/lock-free.no_rs @@ -0,0 +1,50 @@ +//! examples/lock-free.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [GPIOA])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + + #[shared] + struct Shared { + #[lock_free] // <- lock-free shared resource + counter: u64, + } + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + foo::spawn().unwrap(); + + (Shared { counter: 0 }, Local {}) + } + + #[task(shared = [counter])] // <- same priority + async fn foo(c: foo::Context) { + bar::spawn().unwrap(); + + *c.shared.counter += 1; // <- no lock API required + let counter = *c.shared.counter; + hprintln!(" foo = {}", counter).unwrap(); + } + + #[task(shared = [counter])] // <- same priority + async fn bar(c: bar::Context) { + foo::spawn().unwrap(); + + *c.shared.counter += 1; // <- no lock API required + let counter = *c.shared.counter; + hprintln!(" bar = {}", counter).unwrap(); + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } +} diff --git a/examples/lock-free.rs b/examples/lock-free.rs deleted file mode 100644 index ea6ff1b..0000000 --- a/examples/lock-free.rs +++ /dev/null @@ -1,49 +0,0 @@ -//! examples/lock-free.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [GPIOA])] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - - #[shared] - struct Shared { - #[lock_free] // <- lock-free shared resource - counter: u64, - } - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { - foo::spawn().unwrap(); - - (Shared { counter: 0 }, Local {}, init::Monotonics()) - } - - #[task(shared = [counter])] // <- same priority - fn foo(c: foo::Context) { - bar::spawn().unwrap(); - - *c.shared.counter += 1; // <- no lock API required - let counter = *c.shared.counter; - hprintln!(" foo = {}", counter).unwrap(); - } - - #[task(shared = [counter])] // <- same priority - fn bar(c: bar::Context) { - foo::spawn().unwrap(); - - *c.shared.counter += 1; // <- no lock API required - let counter = *c.shared.counter; - hprintln!(" bar = {}", counter).unwrap(); - - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } -} diff --git a/examples/lock.rs b/examples/lock.rs index f1a1696..50b6aaa 100644 --- a/examples/lock.rs +++ b/examples/lock.rs @@ -4,6 +4,7 @@ #![deny(warnings)] #![no_main] #![no_std] +#![feature(type_alias_impl_trait)] use panic_semihosting as _; @@ -20,15 +21,15 @@ mod app { struct Local {} #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { + fn init(_: init::Context) -> (Shared, Local) { foo::spawn().unwrap(); - (Shared { shared: 0 }, Local {}, init::Monotonics()) + (Shared { shared: 0 }, Local {}) } // when omitted priority is assumed to be `1` #[task(shared = [shared])] - fn foo(mut c: foo::Context) { + async fn foo(mut c: foo::Context) { hprintln!("A").unwrap(); // the lower priority task requires a critical section to access the data @@ -53,7 +54,7 @@ mod app { } #[task(priority = 2, shared = [shared])] - fn bar(mut c: bar::Context) { + async fn bar(mut c: bar::Context) { // the higher priority task does still need a critical section let shared = c.shared.shared.lock(|shared| { *shared += 1; @@ -65,7 +66,7 @@ mod app { } #[task(priority = 3)] - fn baz(_: baz::Context) { + async fn baz(_: baz::Context) { hprintln!("C").unwrap(); } } -- cgit v1.2.3 From 76595b7aedd2a14aea8569b75fabe62120f93230 Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Sat, 7 Jan 2023 14:26:55 +0100 Subject: All codegen is now explicit --- macros/src/codegen.rs | 56 +++++++++------------------------ macros/src/codegen/async_dispatchers.rs | 4 +-- macros/src/codegen/hardware_tasks.rs | 23 +++++--------- macros/src/codegen/idle.rs | 27 ++++++---------- macros/src/codegen/init.rs | 23 +++++--------- macros/src/codegen/local_resources.rs | 13 ++------ macros/src/codegen/shared_resources.rs | 16 ++++------ macros/src/codegen/software_tasks.rs | 23 +++++--------- 8 files changed, 57 insertions(+), 128 deletions(-) diff --git a/macros/src/codegen.rs b/macros/src/codegen.rs index bb1028f..24e98ce 100644 --- a/macros/src/codegen.rs +++ b/macros/src/codegen.rs @@ -29,21 +29,14 @@ mod main; pub fn app(app: &App, analysis: &Analysis) -> TokenStream2 { // Generate the `main` function let main = main::codegen(app, analysis); + let init_codegen = init::codegen(app, analysis); + let idle_codegen = idle::codegen(app, analysis); + let shared_resources_codegen = shared_resources::codegen(app, analysis); + let local_resources_codegen = local_resources::codegen(app, analysis); + let hardware_tasks_codegen = hardware_tasks::codegen(app, analysis); + let software_tasks_codegen = software_tasks::codegen(app, analysis); + let async_dispatchers_codegen = async_dispatchers::codegen(app, analysis); - let (mod_app_init, root_init, user_init) = init::codegen(app, analysis); - - let (mod_app_idle, root_idle, user_idle) = idle::codegen(app, analysis); - - let (mod_app_shared_resources, mod_shared_resources) = shared_resources::codegen(app, analysis); - let (mod_app_local_resources, mod_local_resources) = local_resources::codegen(app, analysis); - - let (mod_app_hardware_tasks, root_hardware_tasks, user_hardware_tasks) = - hardware_tasks::codegen(app, analysis); - - let (mod_app_software_tasks, root_software_tasks, user_software_tasks) = - software_tasks::codegen(app, analysis); - - let mod_app_async_dispatchers = async_dispatchers::codegen(app, analysis); let user_imports = &app.user_imports; let user_code = &app.user_code; let name = &app.name; @@ -59,43 +52,22 @@ pub fn app(app: &App, analysis: &Analysis) -> TokenStream2 { #(#user_imports)* - /// User code from within the module #(#user_code)* /// User code end - #(#user_hardware_tasks)* - - #(#user_software_tasks)* - - #mod_app_init - - #(#root_init)* - - #user_init - - #(#mod_app_idle)* - - #(#root_idle)* - - #user_idle - - #mod_shared_resources - - #mod_local_resources - - #(#root_hardware_tasks)* + #init_codegen - #(#root_software_tasks)* + #idle_codegen - #(#mod_app_shared_resources)* + #hardware_tasks_codegen - #(#mod_app_local_resources)* + #software_tasks_codegen - #(#mod_app_hardware_tasks)* + #shared_resources_codegen - #(#mod_app_software_tasks)* + #local_resources_codegen - #(#mod_app_async_dispatchers)* + #async_dispatchers_codegen #main } diff --git a/macros/src/codegen/async_dispatchers.rs b/macros/src/codegen/async_dispatchers.rs index d53d7b5..62b17fe 100644 --- a/macros/src/codegen/async_dispatchers.rs +++ b/macros/src/codegen/async_dispatchers.rs @@ -4,7 +4,7 @@ use proc_macro2::TokenStream as TokenStream2; use quote::quote; /// Generates task dispatchers -pub fn codegen(app: &App, analysis: &Analysis) -> Vec { +pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { let mut items = vec![]; let interrupts = &analysis.interrupts; @@ -96,5 +96,5 @@ pub fn codegen(app: &App, analysis: &Analysis) -> Vec { )); } - items + quote!(#(#items)*) } diff --git a/macros/src/codegen/hardware_tasks.rs b/macros/src/codegen/hardware_tasks.rs index 9ea5825..8a5a8f6 100644 --- a/macros/src/codegen/hardware_tasks.rs +++ b/macros/src/codegen/hardware_tasks.rs @@ -7,20 +7,7 @@ use proc_macro2::TokenStream as TokenStream2; use quote::quote; /// Generate support code for hardware tasks (`#[exception]`s and `#[interrupt]`s) -pub fn codegen( - app: &App, - analysis: &Analysis, -) -> ( - // mod_app_hardware_tasks -- interrupt handlers and `${task}Resources` constructors - Vec, - // root_hardware_tasks -- items that must be placed in the root of the crate: - // - `${task}Locals` structs - // - `${task}Resources` structs - // - `${task}` modules - Vec, - // user_hardware_tasks -- the `#[task]` functions written by the user - Vec, -) { +pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { let mut mod_app = vec![]; let mut root = vec![]; let mut user_tasks = vec![]; @@ -90,5 +77,11 @@ pub fn codegen( } } - (mod_app, root, user_tasks) + quote!( + #(#mod_app)* + + #(#root)* + + #(#user_tasks)* + ) } diff --git a/macros/src/codegen/idle.rs b/macros/src/codegen/idle.rs index 1f05d12..0c833ef 100644 --- a/macros/src/codegen/idle.rs +++ b/macros/src/codegen/idle.rs @@ -7,20 +7,7 @@ use proc_macro2::TokenStream as TokenStream2; use quote::quote; /// Generates support code for `#[idle]` functions -pub fn codegen( - app: &App, - analysis: &Analysis, -) -> ( - // mod_app_idle -- the `${idle}Resources` constructor - Vec, - // root_idle -- items that must be placed in the root of the crate: - // - the `${idle}Locals` struct - // - the `${idle}Resources` struct - // - the `${idle}` module, which contains types like `${idle}::Context` - Vec, - // user_idle - Option, -) { +pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { if let Some(idle) = &app.idle { let mut mod_app = vec![]; let mut root_idle = vec![]; @@ -58,10 +45,14 @@ pub fn codegen( } )); - (mod_app, root_idle, user_idle) - } else { - // TODO: No idle defined, check for 0-priority tasks and generate an executor if needed + quote!( + #(#mod_app)* + + #(#root_idle)* - (vec![], vec![], None) + #user_idle + ) + } else { + quote!() } } diff --git a/macros/src/codegen/init.rs b/macros/src/codegen/init.rs index 3b2bcd4..6e1059f 100644 --- a/macros/src/codegen/init.rs +++ b/macros/src/codegen/init.rs @@ -7,21 +7,8 @@ use crate::{ syntax::{ast::App, Context}, }; -type CodegenResult = ( - // mod_app_idle -- the `${init}Resources` constructor - Option, - // root_init -- items that must be placed in the root of the crate: - // - the `${init}Locals` struct - // - the `${init}Resources` struct - // - the `${init}LateResources` struct - // - the `${init}` module, which contains types like `${init}::Context` - Vec, - // user_init -- the `#[init]` function written by the user - TokenStream2, -); - /// Generates support code for `#[init]` functions -pub fn codegen(app: &App, analysis: &Analysis) -> CodegenResult { +pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { let init = &app.init; let name = &init.name; @@ -98,5 +85,11 @@ pub fn codegen(app: &App, analysis: &Analysis) -> CodegenResult { root_init.push(module::codegen(Context::Init, app, analysis)); - (mod_app, root_init, user_init) + quote!( + #mod_app + + #(#root_init)* + + #user_init + ) } diff --git a/macros/src/codegen/local_resources.rs b/macros/src/codegen/local_resources.rs index 6fc63cd..e6d1553 100644 --- a/macros/src/codegen/local_resources.rs +++ b/macros/src/codegen/local_resources.rs @@ -6,17 +6,8 @@ use quote::quote; /// Generates `local` variables and local resource proxies /// /// I.e. the `static` variables and theirs proxies. -pub fn codegen( - app: &App, - _analysis: &Analysis, -) -> ( - // mod_app -- the `static` variables behind the proxies - Vec, - // mod_resources -- the `resources` module - TokenStream2, -) { +pub fn codegen(app: &App, _analysis: &Analysis) -> TokenStream2 { let mut mod_app = vec![]; - // let mut mod_resources: _ = vec![]; // All local resources declared in the `#[local]' struct for (name, res) in &app.local_resources { @@ -70,5 +61,5 @@ pub fn codegen( )); } - (mod_app, TokenStream2::new()) + quote!(#(#mod_app)*) } diff --git a/macros/src/codegen/shared_resources.rs b/macros/src/codegen/shared_resources.rs index 5c54fb9..19fd13f 100644 --- a/macros/src/codegen/shared_resources.rs +++ b/macros/src/codegen/shared_resources.rs @@ -5,15 +5,7 @@ use quote::quote; use std::collections::HashMap; /// Generates `static` variables and shared resource proxies -pub fn codegen( - app: &App, - analysis: &Analysis, -) -> ( - // mod_app -- the `static` variables behind the proxies - Vec, - // mod_resources -- the `resources` module - TokenStream2, -) { +pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { let mut mod_app = vec![]; let mut mod_resources = vec![]; @@ -183,5 +175,9 @@ pub fn codegen( )); } - (mod_app, mod_resources) + quote!( + #(#mod_app)* + + #mod_resources + ) } diff --git a/macros/src/codegen/software_tasks.rs b/macros/src/codegen/software_tasks.rs index 350c1e6..4cb1fa9 100644 --- a/macros/src/codegen/software_tasks.rs +++ b/macros/src/codegen/software_tasks.rs @@ -6,20 +6,7 @@ use crate::{ use proc_macro2::TokenStream as TokenStream2; use quote::quote; -pub fn codegen( - app: &App, - analysis: &Analysis, -) -> ( - // mod_app_software_tasks -- free queues, buffers and `${task}Resources` constructors - Vec, - // root_software_tasks -- items that must be placed in the root of the crate: - // - `${task}Locals` structs - // - `${task}Resources` structs - // - `${task}` modules - Vec, - // user_software_tasks -- the `#[task]` functions written by the user - Vec, -) { +pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { let mut mod_app = vec![]; let mut root = vec![]; let mut user_tasks = vec![]; @@ -78,5 +65,11 @@ pub fn codegen( root.push(module::codegen(Context::SoftwareTask(name), app, analysis)); } - (mod_app, root, user_tasks) + quote!( + #(#mod_app)* + + #(#root)* + + #(#user_tasks)* + ) } -- cgit v1.2.3 From 569a761122f15a92671b299603a5dc7fe6e5324b Mon Sep 17 00:00:00 2001 From: Per Lindgren Date: Sat, 7 Jan 2023 14:27:57 +0100 Subject: examples/multiloc fixed --- examples/message.no_rs | 52 ++++++++++++++++++++++++++++++++++++++++++ examples/message.rs | 52 ------------------------------------------ examples/message_passing.no_rs | 37 ++++++++++++++++++++++++++++++ examples/message_passing.rs | 37 ------------------------------ examples/multilock.rs | 6 ++--- 5 files changed, 92 insertions(+), 92 deletions(-) create mode 100644 examples/message.no_rs delete mode 100644 examples/message.rs create mode 100644 examples/message_passing.no_rs delete mode 100644 examples/message_passing.rs diff --git a/examples/message.no_rs b/examples/message.no_rs new file mode 100644 index 0000000..76c5675 --- /dev/null +++ b/examples/message.no_rs @@ -0,0 +1,52 @@ +//! examples/message.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { + foo::spawn(/* no message */).unwrap(); + + (Shared {}, Local {}, init::Monotonics()) + } + + #[task(local = [count: u32 = 0])] + fn foo(cx: foo::Context) { + hprintln!("foo").unwrap(); + + bar::spawn(*cx.local.count).unwrap(); + *cx.local.count += 1; + } + + #[task] + fn bar(_: bar::Context, x: u32) { + hprintln!("bar({})", x).unwrap(); + + baz::spawn(x + 1, x + 2).unwrap(); + } + + #[task] + fn baz(_: baz::Context, x: u32, y: u32) { + hprintln!("baz({}, {})", x, y).unwrap(); + + if x + y > 4 { + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } + + foo::spawn().unwrap(); + } +} diff --git a/examples/message.rs b/examples/message.rs deleted file mode 100644 index 76c5675..0000000 --- a/examples/message.rs +++ /dev/null @@ -1,52 +0,0 @@ -//! examples/message.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { - foo::spawn(/* no message */).unwrap(); - - (Shared {}, Local {}, init::Monotonics()) - } - - #[task(local = [count: u32 = 0])] - fn foo(cx: foo::Context) { - hprintln!("foo").unwrap(); - - bar::spawn(*cx.local.count).unwrap(); - *cx.local.count += 1; - } - - #[task] - fn bar(_: bar::Context, x: u32) { - hprintln!("bar({})", x).unwrap(); - - baz::spawn(x + 1, x + 2).unwrap(); - } - - #[task] - fn baz(_: baz::Context, x: u32, y: u32) { - hprintln!("baz({}, {})", x, y).unwrap(); - - if x + y > 4 { - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } - - foo::spawn().unwrap(); - } -} diff --git a/examples/message_passing.no_rs b/examples/message_passing.no_rs new file mode 100644 index 0000000..ffa9537 --- /dev/null +++ b/examples/message_passing.no_rs @@ -0,0 +1,37 @@ +//! examples/message_passing.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { + foo::spawn(1, 1).unwrap(); + foo::spawn(1, 2).unwrap(); + foo::spawn(2, 3).unwrap(); + assert!(foo::spawn(1, 4).is_err()); // The capacity of `foo` is reached + + (Shared {}, Local {}, init::Monotonics()) + } + + #[task(capacity = 3)] + fn foo(_c: foo::Context, x: i32, y: u32) { + hprintln!("foo {}, {}", x, y).unwrap(); + if x == 2 { + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } + } +} diff --git a/examples/message_passing.rs b/examples/message_passing.rs deleted file mode 100644 index ffa9537..0000000 --- a/examples/message_passing.rs +++ /dev/null @@ -1,37 +0,0 @@ -//! examples/message_passing.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { - foo::spawn(1, 1).unwrap(); - foo::spawn(1, 2).unwrap(); - foo::spawn(2, 3).unwrap(); - assert!(foo::spawn(1, 4).is_err()); // The capacity of `foo` is reached - - (Shared {}, Local {}, init::Monotonics()) - } - - #[task(capacity = 3)] - fn foo(_c: foo::Context, x: i32, y: u32) { - hprintln!("foo {}, {}", x, y).unwrap(); - if x == 2 { - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } - } -} diff --git a/examples/multilock.rs b/examples/multilock.rs index d99bae6..7bea2d3 100644 --- a/examples/multilock.rs +++ b/examples/multilock.rs @@ -4,6 +4,7 @@ #![deny(warnings)] #![no_main] #![no_std] +#![feature(type_alias_impl_trait)] use panic_semihosting as _; @@ -22,7 +23,7 @@ mod app { struct Local {} #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { + fn init(_: init::Context) -> (Shared, Local) { locks::spawn().unwrap(); ( @@ -32,13 +33,12 @@ mod app { shared3: 0, }, Local {}, - init::Monotonics(), ) } // when omitted priority is assumed to be `1` #[task(shared = [shared1, shared2, shared3])] - fn locks(c: locks::Context) { + async fn locks(c: locks::Context) { let s1 = c.shared.shared1; let s2 = c.shared.shared2; let s3 = c.shared.shared3; -- cgit v1.2.3 From 5606ba3cf38c80be5d3e9c88ad4da9982b114851 Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Sat, 7 Jan 2023 14:38:04 +0100 Subject: Fix locks, basepri writeback error --- src/export.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/export.rs b/src/export.rs index d6b2fc0..bfd0f6d 100644 --- a/src/export.rs +++ b/src/export.rs @@ -237,7 +237,7 @@ pub unsafe fn lock( let current = basepri::read(); basepri::write(logical2hw(ceiling, nvic_prio_bits)); let r = f(&mut *ptr); - basepri::write(logical2hw(current, nvic_prio_bits)); + basepri::write(current); r } } -- cgit v1.2.3 From 9a4f97ca5ebf19e6612115db5c763d0d61dd28a1 Mon Sep 17 00:00:00 2001 From: Per Lindgren Date: Sat, 7 Jan 2023 17:59:39 +0100 Subject: more examples --- Cargo.toml | 11 +-- examples/async-delay.no_rs | 63 ++++++++++++++++ examples/async-delay.rs | 67 ----------------- examples/async-infinite-loop.no_rs | 53 +++++++++++++ examples/async-infinite-loop.rs | 57 -------------- examples/async-task-multiple-prios.rs | 73 +++++++++++------- examples/async-task.rs | 6 +- examples/async-timeout.no_rs | 87 ++++++++++++++++++++++ examples/async-timeout.rs | 87 ---------------------- examples/big-struct-opt.rs | 34 +++++++-- examples/binds.rs | 4 +- examples/cancel-reschedule.no_rs | 73 ++++++++++++++++++ examples/cancel-reschedule.rs | 73 ------------------ examples/capacity.no_rs | 49 ++++++++++++ examples/capacity.rs | 49 ------------ examples/cfg-whole-task.no_rs | 94 +++++++++++++++++++++++ examples/cfg-whole-task.rs | 94 ----------------------- examples/common.no_rs | 102 +++++++++++++++++++++++++ examples/common.rs | 102 ------------------------- examples/complex.rs | 3 +- examples/declared_locals.rs | 8 +- examples/destructure.rs | 11 +-- examples/extern_binds.rs | 4 +- examples/extern_spawn.rs | 19 +++-- examples/generics.rs | 4 +- examples/hardware.rs | 4 +- examples/not-sync.rs | 32 +++++--- examples/only-shared-access.rs | 9 ++- examples/periodic-at.no_rs | 49 ++++++++++++ examples/periodic-at.rs | 49 ------------ examples/periodic-at2.no_rs | 61 +++++++++++++++ examples/periodic-at2.rs | 61 --------------- examples/periodic.no_rs | 48 ++++++++++++ examples/periodic.rs | 48 ------------ examples/peripherals-taken.rs | 4 +- examples/pool.no_rs | 70 +++++++++++++++++ examples/pool.rs | 70 ----------------- examples/preempt.rs | 11 +-- examples/ramfunc.rs | 10 +-- examples/resource-user-struct.rs | 4 +- examples/schedule.no_rs | 64 ++++++++++++++++ examples/schedule.rs | 64 ---------------- examples/shared.rs | 4 +- examples/smallest.rs | 4 +- examples/spawn.rs | 7 +- examples/static.rs | 7 +- examples/t-binds.rs | 4 +- examples/t-cfg-resources.rs | 3 +- examples/t-htask-main.rs | 4 +- examples/t-idle-main.rs | 4 +- examples/t-late-not-send.rs | 3 +- examples/t-schedule.no_rs | 136 ++++++++++++++++++++++++++++++++++ examples/t-schedule.rs | 136 ---------------------------------- examples/t-spawn.no_rs | 69 +++++++++++++++++ examples/t-spawn.rs | 68 ----------------- examples/task.rs | 11 +-- 56 files changed, 1193 insertions(+), 1152 deletions(-) create mode 100644 examples/async-delay.no_rs delete mode 100644 examples/async-delay.rs create mode 100644 examples/async-infinite-loop.no_rs delete mode 100644 examples/async-infinite-loop.rs create mode 100644 examples/async-timeout.no_rs delete mode 100644 examples/async-timeout.rs create mode 100644 examples/cancel-reschedule.no_rs delete mode 100644 examples/cancel-reschedule.rs create mode 100644 examples/capacity.no_rs delete mode 100644 examples/capacity.rs create mode 100644 examples/cfg-whole-task.no_rs delete mode 100644 examples/cfg-whole-task.rs create mode 100644 examples/common.no_rs delete mode 100644 examples/common.rs create mode 100644 examples/periodic-at.no_rs delete mode 100644 examples/periodic-at.rs create mode 100644 examples/periodic-at2.no_rs delete mode 100644 examples/periodic-at2.rs create mode 100644 examples/periodic.no_rs delete mode 100644 examples/periodic.rs create mode 100644 examples/pool.no_rs delete mode 100644 examples/pool.rs create mode 100644 examples/schedule.no_rs delete mode 100644 examples/schedule.rs create mode 100644 examples/t-schedule.no_rs delete mode 100644 examples/t-schedule.rs create mode 100644 examples/t-spawn.no_rs delete mode 100644 examples/t-spawn.rs diff --git a/Cargo.toml b/Cargo.toml index d995de4..cad9291 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,10 +49,7 @@ codegen-units = 1 lto = true [workspace] -members = [ - "macros", - "xtask", -] +members = ["macros", "xtask"] # do not optimize proc-macro deps or build scripts [profile.dev.build-override] @@ -76,6 +73,6 @@ lm3s6965 = { git = "https://github.com/japaric/lm3s6965" } [features] test-critical-section = ["cortex-m/critical-section-single-core"] -[[example]] -name = "pool" -required-features = ["test-critical-section"] +# [[example]] +# name = "pool" +# required-features = ["test-critical-section"] diff --git a/examples/async-delay.no_rs b/examples/async-delay.no_rs new file mode 100644 index 0000000..fb478c3 --- /dev/null +++ b/examples/async-delay.no_rs @@ -0,0 +1,63 @@ +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0, UART0], peripherals = true)] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use systick_monotonic::*; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[monotonic(binds = SysTick, default = true)] + type MyMono = Systick<100>; + + #[init] + fn init(cx: init::Context) -> (Shared, Local) { + hprintln!("init").unwrap(); + + foo::spawn().ok(); + bar::spawn().ok(); + baz::spawn().ok(); + + (Shared {}, Local {}) + } + + #[idle] + fn idle(_: idle::Context) -> ! { + // debug::exit(debug::EXIT_SUCCESS); + loop { + // hprintln!("idle"); + cortex_m::asm::wfi(); // put the MCU in sleep mode until interrupt occurs + } + } + + #[task] + async fn foo(_cx: foo::Context) { + hprintln!("hello from foo").ok(); + monotonics::delay(100.millis()).await; + hprintln!("bye from foo").ok(); + } + + #[task] + async fn bar(_cx: bar::Context) { + hprintln!("hello from bar").ok(); + monotonics::delay(200.millis()).await; + hprintln!("bye from bar").ok(); + } + + #[task] + async fn baz(_cx: baz::Context) { + hprintln!("hello from baz").ok(); + monotonics::delay(300.millis()).await; + hprintln!("bye from baz").ok(); + + debug::exit(debug::EXIT_SUCCESS); + } +} diff --git a/examples/async-delay.rs b/examples/async-delay.rs deleted file mode 100644 index 7802bda..0000000 --- a/examples/async-delay.rs +++ /dev/null @@ -1,67 +0,0 @@ -#![no_main] -#![no_std] -#![feature(type_alias_impl_trait)] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0, UART0], peripherals = true)] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - use systick_monotonic::*; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[monotonic(binds = SysTick, default = true)] - type MyMono = Systick<100>; - - #[init] - fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { - hprintln!("init").unwrap(); - - foo::spawn().ok(); - bar::spawn().ok(); - baz::spawn().ok(); - - ( - Shared {}, - Local {}, - init::Monotonics(Systick::new(cx.core.SYST, 12_000_000)), - ) - } - - #[idle] - fn idle(_: idle::Context) -> ! { - // debug::exit(debug::EXIT_SUCCESS); - loop { - // hprintln!("idle"); - cortex_m::asm::wfi(); // put the MCU in sleep mode until interrupt occurs - } - } - - #[task] - async fn foo(_cx: foo::Context) { - hprintln!("hello from foo").ok(); - monotonics::delay(100.millis()).await; - hprintln!("bye from foo").ok(); - } - - #[task] - async fn bar(_cx: bar::Context) { - hprintln!("hello from bar").ok(); - monotonics::delay(200.millis()).await; - hprintln!("bye from bar").ok(); - } - - #[task] - async fn baz(_cx: baz::Context) { - hprintln!("hello from baz").ok(); - monotonics::delay(300.millis()).await; - hprintln!("bye from baz").ok(); - - debug::exit(debug::EXIT_SUCCESS); - } -} diff --git a/examples/async-infinite-loop.no_rs b/examples/async-infinite-loop.no_rs new file mode 100644 index 0000000..a95f998 --- /dev/null +++ b/examples/async-infinite-loop.no_rs @@ -0,0 +1,53 @@ +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0, UART0], peripherals = true)] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use systick_monotonic::*; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[monotonic(binds = SysTick, default = true)] + type MyMono = Systick<100>; + + #[init] + fn init(cx: init::Context) -> (Shared, Local) { + hprintln!("init").unwrap(); + + foo::spawn().ok(); + + (Shared {}, Local {}) + } + + #[idle] + fn idle(_: idle::Context) -> ! { + loop { + cortex_m::asm::wfi(); // put the MCU in sleep mode until interrupt occurs + } + } + + // Infinite loops are not allowed in RTIC, however in async tasks they are - if there is an + // await inside the loop. + #[task] + async fn foo(_cx: foo::Context) { + let mut i = 0; + loop { + if i == 5 { + debug::exit(debug::EXIT_SUCCESS); + } + + hprintln!("hello from async {}", i).ok(); + monotonics::delay(100.millis()).await; // This makes it okey! + + i += 1; + } + } +} diff --git a/examples/async-infinite-loop.rs b/examples/async-infinite-loop.rs deleted file mode 100644 index 7615818..0000000 --- a/examples/async-infinite-loop.rs +++ /dev/null @@ -1,57 +0,0 @@ -#![no_main] -#![no_std] -#![feature(type_alias_impl_trait)] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0, UART0], peripherals = true)] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - use systick_monotonic::*; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[monotonic(binds = SysTick, default = true)] - type MyMono = Systick<100>; - - #[init] - fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { - hprintln!("init").unwrap(); - - foo::spawn().ok(); - - ( - Shared {}, - Local {}, - init::Monotonics(Systick::new(cx.core.SYST, 12_000_000)), - ) - } - - #[idle] - fn idle(_: idle::Context) -> ! { - loop { - cortex_m::asm::wfi(); // put the MCU in sleep mode until interrupt occurs - } - } - - // Infinite loops are not allowed in RTIC, however in async tasks they are - if there is an - // await inside the loop. - #[task] - async fn foo(_cx: foo::Context) { - let mut i = 0; - loop { - if i == 5 { - debug::exit(debug::EXIT_SUCCESS); - } - - hprintln!("hello from async {}", i).ok(); - monotonics::delay(100.millis()).await; // This makes it okey! - - i += 1; - } - } -} diff --git a/examples/async-task-multiple-prios.rs b/examples/async-task-multiple-prios.rs index 3e19798..2f3a0f7 100644 --- a/examples/async-task-multiple-prios.rs +++ b/examples/async-task-multiple-prios.rs @@ -6,14 +6,13 @@ use panic_semihosting as _; // NOTES: // -// - Async tasks cannot have `#[lock_free]` resources, as they can interleve and each async +// - Async tasks cannot have `#[lock_free]` resources, as they can interleave and each async // task can have a mutable reference stored. // - Spawning an async task equates to it being polled once. -#[rtic::app(device = lm3s6965, dispatchers = [SSI0, QEI0, UART0, UART1], peripherals = true)] +#[rtic::app(device = lm3s6965, dispatchers = [SSI0, QEI0])] mod app { use cortex_m_semihosting::{debug, hprintln}; - use systick_monotonic::*; #[shared] struct Shared { @@ -24,53 +23,71 @@ mod app { #[local] struct Local {} - #[monotonic(binds = SysTick, default = true)] - type MyMono = Systick<100>; - #[init] - fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { + fn init(cx: init::Context) -> (Shared, Local) { hprintln!("init").unwrap(); - normal_task::spawn().ok(); - async_task::spawn().ok(); - normal_task2::spawn().ok(); + async_task1::spawn().ok(); async_task2::spawn().ok(); + async_task3::spawn().ok(); + async_task4::spawn().ok(); - ( - Shared { a: 0, b: 0 }, - Local {}, - init::Monotonics(Systick::new(cx.core.SYST, 12_000_000)), - ) + (Shared { a: 0, b: 0 }, Local {}) } #[idle] fn idle(_: idle::Context) -> ! { - // debug::exit(debug::EXIT_SUCCESS); loop { - // hprintln!("idle"); - cortex_m::asm::wfi(); // put the MCU in sleep mode until interrupt occurs + hprintln!("idle"); + debug::exit(debug::EXIT_SUCCESS); } } #[task(priority = 1, shared = [a, b])] - fn normal_task(_cx: normal_task::Context) { - hprintln!("hello from normal 1").ok(); + async fn async_task1(mut cx: async_task1::Context) { + hprintln!( + "hello from async 1 a {}", + cx.shared.a.lock(|a| { + *a += 1; + *a + }) + ) + .ok(); } #[task(priority = 1, shared = [a, b])] - async fn async_task(_cx: async_task::Context) { - hprintln!("hello from async 1").ok(); - - debug::exit(debug::EXIT_SUCCESS); + async fn async_task2(mut cx: async_task2::Context) { + hprintln!( + "hello from async 2 a {}", + cx.shared.a.lock(|a| { + *a += 1; + *a + }) + ) + .ok(); } #[task(priority = 2, shared = [a, b])] - fn normal_task2(_cx: normal_task2::Context) { - hprintln!("hello from normal 2").ok(); + async fn async_task3(mut cx: async_task3::Context) { + hprintln!( + "hello from async 3 a {}", + cx.shared.a.lock(|a| { + *a += 1; + *a + }) + ) + .ok(); } #[task(priority = 2, shared = [a, b])] - async fn async_task2(_cx: async_task2::Context) { - hprintln!("hello from async 2").ok(); + async fn async_task4(mut cx: async_task4::Context) { + hprintln!( + "hello from async 4 a {}", + cx.shared.a.lock(|a| { + *a += 1; + *a + }) + ) + .ok(); } } diff --git a/examples/async-task.rs b/examples/async-task.rs index 012e95e..210a865 100644 --- a/examples/async-task.rs +++ b/examples/async-task.rs @@ -34,9 +34,9 @@ mod app { #[idle(shared = [a])] fn idle(_: idle::Context) -> ! { - // debug::exit(debug::EXIT_SUCCESS); loop { - // hprintln!("idle"); + hprintln!("idle"); + debug::exit(debug::EXIT_SUCCESS); cortex_m::asm::wfi(); // put the MCU in sleep mode until interrupt occurs } } @@ -51,8 +51,6 @@ mod app { async fn async_task(cx: async_task::Context) { let async_task::SharedResources { a: _, .. } = cx.shared; hprintln!("hello from async").ok(); - - debug::exit(debug::EXIT_SUCCESS); } #[task(priority = 2, shared = [a])] diff --git a/examples/async-timeout.no_rs b/examples/async-timeout.no_rs new file mode 100644 index 0000000..3f68df7 --- /dev/null +++ b/examples/async-timeout.no_rs @@ -0,0 +1,87 @@ +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use panic_semihosting as _; + +// NOTES: +// +// - Async tasks cannot have `#[lock_free]` resources, as they can interleve and each async +// task can have a mutable reference stored. +// - Spawning an async task equates to it being polled once. + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0, UART0], peripherals = true)] +mod app { + use core::{ + future::Future, + pin::Pin, + task::{Context, Poll}, + }; + use cortex_m_semihosting::{debug, hprintln}; + use systick_monotonic::*; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[monotonic(binds = SysTick, default = true)] + type MyMono = Systick<100>; + + #[init] + fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { + hprintln!("init").unwrap(); + + foo::spawn().ok(); + bar::spawn().ok(); + + ( + Shared {}, + Local {}, + init::Monotonics(Systick::new(cx.core.SYST, 12_000_000)), + ) + } + + #[idle] + fn idle(_: idle::Context) -> ! { + loop { + cortex_m::asm::wfi(); // put the MCU in sleep mode until interrupt occurs + } + } + + #[task] + async fn foo(_cx: foo::Context) { + hprintln!("hello from foo").ok(); + + // This will not timeout + match monotonics::timeout_after(monotonics::delay(100.millis()), 200.millis()).await { + Ok(_) => hprintln!("foo no timeout").ok(), + Err(_) => hprintln!("foo timeout").ok(), + }; + } + + #[task] + async fn bar(_cx: bar::Context) { + hprintln!("hello from bar").ok(); + + // This will timeout + match monotonics::timeout_after(NeverEndingFuture {}, 300.millis()).await { + Ok(_) => hprintln!("bar no timeout").ok(), + Err(_) => hprintln!("bar timeout").ok(), + }; + + debug::exit(debug::EXIT_SUCCESS); + } + + pub struct NeverEndingFuture {} + + impl Future for NeverEndingFuture { + type Output = (); + + fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll { + // Never finish + Poll::Pending + } + } +} diff --git a/examples/async-timeout.rs b/examples/async-timeout.rs deleted file mode 100644 index 3f68df7..0000000 --- a/examples/async-timeout.rs +++ /dev/null @@ -1,87 +0,0 @@ -#![no_main] -#![no_std] -#![feature(type_alias_impl_trait)] - -use panic_semihosting as _; - -// NOTES: -// -// - Async tasks cannot have `#[lock_free]` resources, as they can interleve and each async -// task can have a mutable reference stored. -// - Spawning an async task equates to it being polled once. - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0, UART0], peripherals = true)] -mod app { - use core::{ - future::Future, - pin::Pin, - task::{Context, Poll}, - }; - use cortex_m_semihosting::{debug, hprintln}; - use systick_monotonic::*; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[monotonic(binds = SysTick, default = true)] - type MyMono = Systick<100>; - - #[init] - fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { - hprintln!("init").unwrap(); - - foo::spawn().ok(); - bar::spawn().ok(); - - ( - Shared {}, - Local {}, - init::Monotonics(Systick::new(cx.core.SYST, 12_000_000)), - ) - } - - #[idle] - fn idle(_: idle::Context) -> ! { - loop { - cortex_m::asm::wfi(); // put the MCU in sleep mode until interrupt occurs - } - } - - #[task] - async fn foo(_cx: foo::Context) { - hprintln!("hello from foo").ok(); - - // This will not timeout - match monotonics::timeout_after(monotonics::delay(100.millis()), 200.millis()).await { - Ok(_) => hprintln!("foo no timeout").ok(), - Err(_) => hprintln!("foo timeout").ok(), - }; - } - - #[task] - async fn bar(_cx: bar::Context) { - hprintln!("hello from bar").ok(); - - // This will timeout - match monotonics::timeout_after(NeverEndingFuture {}, 300.millis()).await { - Ok(_) => hprintln!("bar no timeout").ok(), - Err(_) => hprintln!("bar timeout").ok(), - }; - - debug::exit(debug::EXIT_SUCCESS); - } - - pub struct NeverEndingFuture {} - - impl Future for NeverEndingFuture { - type Output = (); - - fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll { - // Never finish - Poll::Pending - } - } -} diff --git a/examples/big-struct-opt.rs b/examples/big-struct-opt.rs index bbc2535..4bf93b2 100644 --- a/examples/big-struct-opt.rs +++ b/examples/big-struct-opt.rs @@ -5,6 +5,7 @@ #![no_main] #![no_std] +#![feature(type_alias_impl_trait)] use panic_semihosting as _; @@ -20,11 +21,12 @@ impl BigStruct { } } -#[rtic::app(device = lm3s6965)] +#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] mod app { use super::BigStruct; use core::mem::MaybeUninit; - use cortex_m_semihosting::debug; + use cortex_m_semihosting::{debug, hprintln}; + use lm3s6965::Interrupt; #[shared] struct Shared { @@ -35,25 +37,43 @@ mod app { struct Local {} #[init(local = [bs: MaybeUninit = MaybeUninit::uninit()])] - fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { + fn init(cx: init::Context) -> (Shared, Local) { let big_struct = unsafe { // write directly into the static storage cx.local.bs.as_mut_ptr().write(BigStruct::new()); &mut *cx.local.bs.as_mut_ptr() }; - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - + rtic::pend(Interrupt::UART0); + async_task::spawn().unwrap(); ( Shared { // assign the reference so we can use the resource big_struct, }, Local {}, - init::Monotonics(), ) } + #[idle] + fn idle(_: idle::Context) -> ! { + loop { + hprintln!("idle"); + debug::exit(debug::EXIT_SUCCESS); + } + } + #[task(binds = UART0, shared = [big_struct])] - fn task(_: task::Context) {} + fn uart0(mut cx: uart0::Context) { + cx.shared + .big_struct + .lock(|b| hprintln!("uart0 data:{:?}", &b.data[0..5]).unwrap()); + } + + #[task(shared = [big_struct], priority = 2)] + async fn async_task(mut cx: async_task::Context) { + cx.shared + .big_struct + .lock(|b| hprintln!("async_task data:{:?}", &b.data[0..5]).unwrap()); + } } diff --git a/examples/binds.rs b/examples/binds.rs index 56565cb..ec25ccc 100644 --- a/examples/binds.rs +++ b/examples/binds.rs @@ -20,12 +20,12 @@ mod app { struct Local {} #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { + fn init(_: init::Context) -> (Shared, Local) { rtic::pend(Interrupt::UART0); hprintln!("init").unwrap(); - (Shared {}, Local {}, init::Monotonics()) + (Shared {}, Local {}) } #[idle] diff --git a/examples/cancel-reschedule.no_rs b/examples/cancel-reschedule.no_rs new file mode 100644 index 0000000..a38a9c4 --- /dev/null +++ b/examples/cancel-reschedule.no_rs @@ -0,0 +1,73 @@ +//! examples/cancel-reschedule.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use systick_monotonic::*; + + #[monotonic(binds = SysTick, default = true)] + type MyMono = Systick<100>; // 100 Hz / 10 ms granularity + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { + let systick = cx.core.SYST; + + // Initialize the monotonic (SysTick rate in QEMU is 12 MHz) + let mono = Systick::new(systick, 12_000_000); + + hprintln!("init").ok(); + + // Schedule `foo` to run 1 second in the future + foo::spawn_after(1.secs()).unwrap(); + + ( + Shared {}, + Local {}, + init::Monotonics(mono), // Give the monotonic to RTIC + ) + } + + #[task] + fn foo(_: foo::Context) { + hprintln!("foo").ok(); + + // Schedule `bar` to run 2 seconds in the future (1 second after foo runs) + let spawn_handle = baz::spawn_after(2.secs()).unwrap(); + bar::spawn_after(1.secs(), spawn_handle, false).unwrap(); // Change to true + } + + #[task] + fn bar(_: bar::Context, baz_handle: baz::SpawnHandle, do_reschedule: bool) { + hprintln!("bar").ok(); + + if do_reschedule { + // Reschedule baz 2 seconds from now, instead of the original 1 second + // from now. + baz_handle.reschedule_after(2.secs()).unwrap(); + // Or baz_handle.reschedule_at(/* time */) + } else { + // Or cancel it + baz_handle.cancel().unwrap(); + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } + } + + #[task] + fn baz(_: baz::Context) { + hprintln!("baz").ok(); + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } +} diff --git a/examples/cancel-reschedule.rs b/examples/cancel-reschedule.rs deleted file mode 100644 index a38a9c4..0000000 --- a/examples/cancel-reschedule.rs +++ /dev/null @@ -1,73 +0,0 @@ -//! examples/cancel-reschedule.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - use systick_monotonic::*; - - #[monotonic(binds = SysTick, default = true)] - type MyMono = Systick<100>; // 100 Hz / 10 ms granularity - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { - let systick = cx.core.SYST; - - // Initialize the monotonic (SysTick rate in QEMU is 12 MHz) - let mono = Systick::new(systick, 12_000_000); - - hprintln!("init").ok(); - - // Schedule `foo` to run 1 second in the future - foo::spawn_after(1.secs()).unwrap(); - - ( - Shared {}, - Local {}, - init::Monotonics(mono), // Give the monotonic to RTIC - ) - } - - #[task] - fn foo(_: foo::Context) { - hprintln!("foo").ok(); - - // Schedule `bar` to run 2 seconds in the future (1 second after foo runs) - let spawn_handle = baz::spawn_after(2.secs()).unwrap(); - bar::spawn_after(1.secs(), spawn_handle, false).unwrap(); // Change to true - } - - #[task] - fn bar(_: bar::Context, baz_handle: baz::SpawnHandle, do_reschedule: bool) { - hprintln!("bar").ok(); - - if do_reschedule { - // Reschedule baz 2 seconds from now, instead of the original 1 second - // from now. - baz_handle.reschedule_after(2.secs()).unwrap(); - // Or baz_handle.reschedule_at(/* time */) - } else { - // Or cancel it - baz_handle.cancel().unwrap(); - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } - } - - #[task] - fn baz(_: baz::Context) { - hprintln!("baz").ok(); - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } -} diff --git a/examples/capacity.no_rs b/examples/capacity.no_rs new file mode 100644 index 0000000..a617269 --- /dev/null +++ b/examples/capacity.no_rs @@ -0,0 +1,49 @@ +//! examples/capacity.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use lm3s6965::Interrupt; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { + rtic::pend(Interrupt::UART0); + + (Shared {}, Local {}, init::Monotonics()) + } + + #[task(binds = UART0)] + fn uart0(_: uart0::Context) { + foo::spawn(0).unwrap(); + foo::spawn(1).unwrap(); + foo::spawn(2).unwrap(); + foo::spawn(3).unwrap(); + + bar::spawn().unwrap(); + } + + #[task(capacity = 4)] + fn foo(_: foo::Context, x: u32) { + hprintln!("foo({})", x).unwrap(); + } + + #[task] + fn bar(_: bar::Context) { + hprintln!("bar").unwrap(); + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } +} diff --git a/examples/capacity.rs b/examples/capacity.rs deleted file mode 100644 index a617269..0000000 --- a/examples/capacity.rs +++ /dev/null @@ -1,49 +0,0 @@ -//! examples/capacity.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - use lm3s6965::Interrupt; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { - rtic::pend(Interrupt::UART0); - - (Shared {}, Local {}, init::Monotonics()) - } - - #[task(binds = UART0)] - fn uart0(_: uart0::Context) { - foo::spawn(0).unwrap(); - foo::spawn(1).unwrap(); - foo::spawn(2).unwrap(); - foo::spawn(3).unwrap(); - - bar::spawn().unwrap(); - } - - #[task(capacity = 4)] - fn foo(_: foo::Context, x: u32) { - hprintln!("foo({})", x).unwrap(); - } - - #[task] - fn bar(_: bar::Context) { - hprintln!("bar").unwrap(); - - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } -} diff --git a/examples/cfg-whole-task.no_rs b/examples/cfg-whole-task.no_rs new file mode 100644 index 0000000..f41866d --- /dev/null +++ b/examples/cfg-whole-task.no_rs @@ -0,0 +1,94 @@ +//! examples/cfg-whole-task.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0, QEI0])] +mod app { + use cortex_m_semihosting::debug; + #[cfg(debug_assertions)] + use cortex_m_semihosting::hprintln; + + #[shared] + struct Shared { + count: u32, + #[cfg(never)] + unused: u32, + } + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { + foo::spawn().unwrap(); + foo::spawn().unwrap(); + + ( + Shared { + count: 0, + #[cfg(never)] + unused: 1, + }, + Local {}, + init::Monotonics(), + ) + } + + #[idle] + fn idle(_: idle::Context) -> ! { + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + + loop { + cortex_m::asm::nop(); + } + } + + #[task(capacity = 2, shared = [count])] + fn foo(mut _cx: foo::Context) { + #[cfg(debug_assertions)] + { + _cx.shared.count.lock(|count| *count += 1); + + log::spawn(_cx.shared.count.lock(|count| *count)).unwrap(); + } + + // this wouldn't compile in `release` mode + // *_cx.shared.count += 1; + + // .. + } + + // The whole task should disappear, + // currently still present in the Tasks enum + #[cfg(never)] + #[task(capacity = 2, shared = [count])] + fn foo2(mut _cx: foo2::Context) { + #[cfg(debug_assertions)] + { + _cx.shared.count.lock(|count| *count += 10); + + log::spawn(_cx.shared.count.lock(|count| *count)).unwrap(); + } + + // this wouldn't compile in `release` mode + // *_cx.shared.count += 1; + + // .. + } + + #[cfg(debug_assertions)] + #[task(capacity = 2)] + fn log(_: log::Context, n: u32) { + hprintln!( + "foo has been called {} time{}", + n, + if n == 1 { "" } else { "s" } + ) + .ok(); + } +} diff --git a/examples/cfg-whole-task.rs b/examples/cfg-whole-task.rs deleted file mode 100644 index f41866d..0000000 --- a/examples/cfg-whole-task.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! examples/cfg-whole-task.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0, QEI0])] -mod app { - use cortex_m_semihosting::debug; - #[cfg(debug_assertions)] - use cortex_m_semihosting::hprintln; - - #[shared] - struct Shared { - count: u32, - #[cfg(never)] - unused: u32, - } - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { - foo::spawn().unwrap(); - foo::spawn().unwrap(); - - ( - Shared { - count: 0, - #[cfg(never)] - unused: 1, - }, - Local {}, - init::Monotonics(), - ) - } - - #[idle] - fn idle(_: idle::Context) -> ! { - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - - loop { - cortex_m::asm::nop(); - } - } - - #[task(capacity = 2, shared = [count])] - fn foo(mut _cx: foo::Context) { - #[cfg(debug_assertions)] - { - _cx.shared.count.lock(|count| *count += 1); - - log::spawn(_cx.shared.count.lock(|count| *count)).unwrap(); - } - - // this wouldn't compile in `release` mode - // *_cx.shared.count += 1; - - // .. - } - - // The whole task should disappear, - // currently still present in the Tasks enum - #[cfg(never)] - #[task(capacity = 2, shared = [count])] - fn foo2(mut _cx: foo2::Context) { - #[cfg(debug_assertions)] - { - _cx.shared.count.lock(|count| *count += 10); - - log::spawn(_cx.shared.count.lock(|count| *count)).unwrap(); - } - - // this wouldn't compile in `release` mode - // *_cx.shared.count += 1; - - // .. - } - - #[cfg(debug_assertions)] - #[task(capacity = 2)] - fn log(_: log::Context, n: u32) { - hprintln!( - "foo has been called {} time{}", - n, - if n == 1 { "" } else { "s" } - ) - .ok(); - } -} diff --git a/examples/common.no_rs b/examples/common.no_rs new file mode 100644 index 0000000..1fe671e --- /dev/null +++ b/examples/common.no_rs @@ -0,0 +1,102 @@ +//! examples/common.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0, QEI0])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use systick_monotonic::*; // Implements the `Monotonic` trait + + // A monotonic timer to enable scheduling in RTIC + #[monotonic(binds = SysTick, default = true)] + type MyMono = Systick<100>; // 100 Hz / 10 ms granularity + + // Resources shared between tasks + #[shared] + struct Shared { + s1: u32, + s2: i32, + } + + // Local resources to specific tasks (cannot be shared) + #[local] + struct Local { + l1: u8, + l2: i8, + } + + #[init] + fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { + let systick = cx.core.SYST; + + // Initialize the monotonic (SysTick rate in QEMU is 12 MHz) + let mono = Systick::new(systick, 12_000_000); + + // Spawn the task `foo` directly after `init` finishes + foo::spawn().unwrap(); + + // Spawn the task `bar` 1 second after `init` finishes, this is enabled + // by the `#[monotonic(..)]` above + bar::spawn_after(1.secs()).unwrap(); + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + + ( + // Initialization of shared resources + Shared { s1: 0, s2: 1 }, + // Initialization of task local resources + Local { l1: 2, l2: 3 }, + // Move the monotonic timer to the RTIC run-time, this enables + // scheduling + init::Monotonics(mono), + ) + } + + // Background task, runs whenever no other tasks are running + #[idle] + fn idle(_: idle::Context) -> ! { + loop { + continue; + } + } + + // Software task, not bound to a hardware interrupt. + // This task takes the task local resource `l1` + // The resources `s1` and `s2` are shared between all other tasks. + #[task(shared = [s1, s2], local = [l1])] + fn foo(_: foo::Context) { + // This task is only spawned once in `init`, hence this task will run + // only once + + hprintln!("foo").ok(); + } + + // Software task, also not bound to a hardware interrupt + // This task takes the task local resource `l2` + // The resources `s1` and `s2` are shared between all other tasks. + #[task(shared = [s1, s2], local = [l2])] + fn bar(_: bar::Context) { + hprintln!("bar").ok(); + + // Run `bar` once per second + bar::spawn_after(1.secs()).unwrap(); + } + + // Hardware task, bound to a hardware interrupt + // The resources `s1` and `s2` are shared between all other tasks. + #[task(binds = UART0, priority = 3, shared = [s1, s2])] + fn uart0_interrupt(_: uart0_interrupt::Context) { + // This task is bound to the interrupt `UART0` and will run + // whenever the interrupt fires + + // Note that RTIC does NOT clear the interrupt flag, this is up to the + // user + + hprintln!("UART0 interrupt!").ok(); + } +} diff --git a/examples/common.rs b/examples/common.rs deleted file mode 100644 index 1fe671e..0000000 --- a/examples/common.rs +++ /dev/null @@ -1,102 +0,0 @@ -//! examples/common.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0, QEI0])] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - use systick_monotonic::*; // Implements the `Monotonic` trait - - // A monotonic timer to enable scheduling in RTIC - #[monotonic(binds = SysTick, default = true)] - type MyMono = Systick<100>; // 100 Hz / 10 ms granularity - - // Resources shared between tasks - #[shared] - struct Shared { - s1: u32, - s2: i32, - } - - // Local resources to specific tasks (cannot be shared) - #[local] - struct Local { - l1: u8, - l2: i8, - } - - #[init] - fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { - let systick = cx.core.SYST; - - // Initialize the monotonic (SysTick rate in QEMU is 12 MHz) - let mono = Systick::new(systick, 12_000_000); - - // Spawn the task `foo` directly after `init` finishes - foo::spawn().unwrap(); - - // Spawn the task `bar` 1 second after `init` finishes, this is enabled - // by the `#[monotonic(..)]` above - bar::spawn_after(1.secs()).unwrap(); - - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - - ( - // Initialization of shared resources - Shared { s1: 0, s2: 1 }, - // Initialization of task local resources - Local { l1: 2, l2: 3 }, - // Move the monotonic timer to the RTIC run-time, this enables - // scheduling - init::Monotonics(mono), - ) - } - - // Background task, runs whenever no other tasks are running - #[idle] - fn idle(_: idle::Context) -> ! { - loop { - continue; - } - } - - // Software task, not bound to a hardware interrupt. - // This task takes the task local resource `l1` - // The resources `s1` and `s2` are shared between all other tasks. - #[task(shared = [s1, s2], local = [l1])] - fn foo(_: foo::Context) { - // This task is only spawned once in `init`, hence this task will run - // only once - - hprintln!("foo").ok(); - } - - // Software task, also not bound to a hardware interrupt - // This task takes the task local resource `l2` - // The resources `s1` and `s2` are shared between all other tasks. - #[task(shared = [s1, s2], local = [l2])] - fn bar(_: bar::Context) { - hprintln!("bar").ok(); - - // Run `bar` once per second - bar::spawn_after(1.secs()).unwrap(); - } - - // Hardware task, bound to a hardware interrupt - // The resources `s1` and `s2` are shared between all other tasks. - #[task(binds = UART0, priority = 3, shared = [s1, s2])] - fn uart0_interrupt(_: uart0_interrupt::Context) { - // This task is bound to the interrupt `UART0` and will run - // whenever the interrupt fires - - // Note that RTIC does NOT clear the interrupt flag, this is up to the - // user - - hprintln!("UART0 interrupt!").ok(); - } -} diff --git a/examples/complex.rs b/examples/complex.rs index e5cf6db..df9c862 100644 --- a/examples/complex.rs +++ b/examples/complex.rs @@ -24,7 +24,7 @@ mod app { struct Local {} #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { + fn init(_: init::Context) -> (Shared, Local) { hprintln!("init").unwrap(); ( @@ -34,7 +34,6 @@ mod app { s4: 0, }, Local {}, - init::Monotonics(), ) } diff --git a/examples/declared_locals.rs b/examples/declared_locals.rs index 52d354b..79001aa 100644 --- a/examples/declared_locals.rs +++ b/examples/declared_locals.rs @@ -7,7 +7,7 @@ use panic_semihosting as _; -#[rtic::app(device = lm3s6965, dispatchers = [UART0])] +#[rtic::app(device = lm3s6965)] mod app { use cortex_m_semihosting::debug; @@ -18,13 +18,13 @@ mod app { struct Local {} #[init(local = [a: u32 = 0])] - fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { + fn init(cx: init::Context) -> (Shared, Local) { // Locals in `#[init]` have 'static lifetime let _a: &'static mut u32 = cx.local.a; debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - (Shared {}, Local {}, init::Monotonics()) + (Shared {}, Local {}) } #[idle(local = [a: u32 = 0])] @@ -35,7 +35,7 @@ mod app { loop {} } - #[task(local = [a: u32 = 0])] + #[task(binds = UART0, local = [a: u32 = 0])] fn foo(cx: foo::Context) { // Locals in `#[task]`s have a local lifetime let _a: &mut u32 = cx.local.a; diff --git a/examples/destructure.rs b/examples/destructure.rs index 6019c22..89336bf 100644 --- a/examples/destructure.rs +++ b/examples/destructure.rs @@ -4,6 +4,7 @@ #![deny(warnings)] #![no_main] #![no_std] +#![feature(type_alias_impl_trait)] use panic_semihosting as _; @@ -22,11 +23,11 @@ mod app { struct Local {} #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { + fn init(_: init::Context) -> (Shared, Local) { foo::spawn().unwrap(); bar::spawn().unwrap(); - (Shared { a: 0, b: 0, c: 0 }, Local {}, init::Monotonics()) + (Shared { a: 0, b: 1, c: 2 }, Local {}) } #[idle] @@ -37,7 +38,7 @@ mod app { // Direct destructure #[task(shared = [&a, &b, &c])] - fn foo(cx: foo::Context) { + async fn foo(cx: foo::Context) { let a = cx.shared.a; let b = cx.shared.b; let c = cx.shared.c; @@ -47,8 +48,8 @@ mod app { // De-structure-ing syntax #[task(shared = [&a, &b, &c])] - fn bar(cx: bar::Context) { - let bar::SharedResources { a, b, c } = cx.shared; + async fn bar(cx: bar::Context) { + let bar::SharedResources { a, b, c, .. } = cx.shared; hprintln!("bar: a = {}, b = {}, c = {}", a, b, c).unwrap(); } diff --git a/examples/extern_binds.rs b/examples/extern_binds.rs index 4dc6633..c9fc108 100644 --- a/examples/extern_binds.rs +++ b/examples/extern_binds.rs @@ -26,12 +26,12 @@ mod app { struct Local {} #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { + fn init(_: init::Context) -> (Shared, Local) { rtic::pend(Interrupt::UART0); hprintln!("init").unwrap(); - (Shared {}, Local {}, init::Monotonics()) + (Shared {}, Local {}) } #[idle] diff --git a/examples/extern_spawn.rs b/examples/extern_spawn.rs index 7f9b5a5..2d9d7b6 100644 --- a/examples/extern_spawn.rs +++ b/examples/extern_spawn.rs @@ -4,17 +4,16 @@ #![deny(warnings)] #![no_main] #![no_std] +#![feature(type_alias_impl_trait)] use cortex_m_semihosting::{debug, hprintln}; use panic_semihosting as _; // Free function implementing the spawnable task `foo`. -fn foo(_c: app::foo::Context, x: i32, y: u32) { - hprintln!("foo {}, {}", x, y).unwrap(); - if x == 2 { - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } - app::foo::spawn(2, 3).unwrap(); +// Notice, you need to indicate an anonymous lifetime <'a_> +async fn foo(_c: app::foo::Context<'_>) { + hprintln!("foo").unwrap(); + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator } #[rtic::app(device = lm3s6965, dispatchers = [SSI0])] @@ -28,14 +27,14 @@ mod app { struct Local {} #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { - foo::spawn(1, 2).unwrap(); + fn init(_: init::Context) -> (Shared, Local) { + foo::spawn().unwrap(); - (Shared {}, Local {}, init::Monotonics()) + (Shared {}, Local {}) } extern "Rust" { #[task()] - fn foo(_c: foo::Context, _x: i32, _y: u32); + async fn foo(_c: foo::Context); } } diff --git a/examples/generics.rs b/examples/generics.rs index 72b861b..a73b00f 100644 --- a/examples/generics.rs +++ b/examples/generics.rs @@ -23,11 +23,11 @@ mod app { struct Local {} #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { + fn init(_: init::Context) -> (Shared, Local) { rtic::pend(Interrupt::UART0); rtic::pend(Interrupt::UART1); - (Shared { shared: 0 }, Local {}, init::Monotonics()) + (Shared { shared: 0 }, Local {}) } #[task(binds = UART0, shared = [shared], local = [state: u32 = 0])] diff --git a/examples/hardware.rs b/examples/hardware.rs index 6063224..8e4f423 100644 --- a/examples/hardware.rs +++ b/examples/hardware.rs @@ -19,14 +19,14 @@ mod app { struct Local {} #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { + fn init(_: init::Context) -> (Shared, Local) { // Pends the UART0 interrupt but its handler won't run until *after* // `init` returns because interrupts are disabled rtic::pend(Interrupt::UART0); // equivalent to NVIC::pend hprintln!("init").unwrap(); - (Shared {}, Local {}, init::Monotonics()) + (Shared {}, Local {}) } #[idle] diff --git a/examples/not-sync.rs b/examples/not-sync.rs index aa79ad5..eb5c9f8 100644 --- a/examples/not-sync.rs +++ b/examples/not-sync.rs @@ -4,12 +4,14 @@ #![deny(warnings)] #![no_main] #![no_std] +#![feature(type_alias_impl_trait)] use core::marker::PhantomData; use panic_semihosting as _; pub struct NotSync { _0: PhantomData<*const ()>, + data: u32, } unsafe impl Send for NotSync {} @@ -18,7 +20,7 @@ unsafe impl Send for NotSync {} mod app { use super::NotSync; use core::marker::PhantomData; - use cortex_m_semihosting::debug; + use cortex_m_semihosting::{debug, hprintln}; #[shared] struct Shared { @@ -29,25 +31,37 @@ mod app { struct Local {} #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + fn init(_: init::Context) -> (Shared, Local) { + hprintln!("init").unwrap(); + foo::spawn().unwrap(); + bar::spawn().unwrap(); ( Shared { - shared: NotSync { _0: PhantomData }, + shared: NotSync { + _0: PhantomData, + data: 13, + }, }, Local {}, - init::Monotonics(), ) } + #[idle] + fn idle(_: idle::Context) -> ! { + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + loop {} + } + #[task(shared = [&shared])] - fn foo(c: foo::Context) { - let _: &NotSync = c.shared.shared; + async fn foo(c: foo::Context) { + let shared: &NotSync = c.shared.shared; + hprintln!("foo a {}", shared.data).unwrap(); } #[task(shared = [&shared])] - fn bar(c: bar::Context) { - let _: &NotSync = c.shared.shared; + async fn bar(c: bar::Context) { + let shared: &NotSync = c.shared.shared; + hprintln!("foo a {}", shared.data).unwrap(); } } diff --git a/examples/only-shared-access.rs b/examples/only-shared-access.rs index 8b0a77e..b506e44 100644 --- a/examples/only-shared-access.rs +++ b/examples/only-shared-access.rs @@ -4,6 +4,7 @@ #![deny(warnings)] #![no_main] #![no_std] +#![feature(type_alias_impl_trait)] use panic_semihosting as _; @@ -20,15 +21,15 @@ mod app { struct Local {} #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { + fn init(_: init::Context) -> (Shared, Local) { foo::spawn().unwrap(); bar::spawn().unwrap(); - (Shared { key: 0xdeadbeef }, Local {}, init::Monotonics()) + (Shared { key: 0xdeadbeef }, Local {}) } #[task(shared = [&key])] - fn foo(cx: foo::Context) { + async fn foo(cx: foo::Context) { let key: &u32 = cx.shared.key; hprintln!("foo(key = {:#x})", key).unwrap(); @@ -36,7 +37,7 @@ mod app { } #[task(priority = 2, shared = [&key])] - fn bar(cx: bar::Context) { + async fn bar(cx: bar::Context) { hprintln!("bar(key = {:#x})", cx.shared.key).unwrap(); } } diff --git a/examples/periodic-at.no_rs b/examples/periodic-at.no_rs new file mode 100644 index 0000000..ca68ed5 --- /dev/null +++ b/examples/periodic-at.no_rs @@ -0,0 +1,49 @@ +//! examples/periodic-at.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use systick_monotonic::*; + + #[monotonic(binds = SysTick, default = true)] + type MyMono = Systick<100>; // 100 Hz / 10 ms granularity + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { + let systick = cx.core.SYST; + + // Initialize the monotonic (SysTick rate in QEMU is 12 MHz) + let mut mono = Systick::new(systick, 12_000_000); + + foo::spawn_after(1.secs(), mono.now()).unwrap(); + + (Shared {}, Local {}, init::Monotonics(mono)) + } + + #[task(local = [cnt: u32 = 0])] + fn foo(cx: foo::Context, instant: fugit::TimerInstantU64<100>) { + hprintln!("foo {:?}", instant).ok(); + *cx.local.cnt += 1; + + if *cx.local.cnt == 4 { + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } + + // Periodic every 100 milliseconds + let next_instant = instant + 100.millis(); + foo::spawn_at(next_instant, next_instant).unwrap(); + } +} diff --git a/examples/periodic-at.rs b/examples/periodic-at.rs deleted file mode 100644 index ca68ed5..0000000 --- a/examples/periodic-at.rs +++ /dev/null @@ -1,49 +0,0 @@ -//! examples/periodic-at.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - use systick_monotonic::*; - - #[monotonic(binds = SysTick, default = true)] - type MyMono = Systick<100>; // 100 Hz / 10 ms granularity - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { - let systick = cx.core.SYST; - - // Initialize the monotonic (SysTick rate in QEMU is 12 MHz) - let mut mono = Systick::new(systick, 12_000_000); - - foo::spawn_after(1.secs(), mono.now()).unwrap(); - - (Shared {}, Local {}, init::Monotonics(mono)) - } - - #[task(local = [cnt: u32 = 0])] - fn foo(cx: foo::Context, instant: fugit::TimerInstantU64<100>) { - hprintln!("foo {:?}", instant).ok(); - *cx.local.cnt += 1; - - if *cx.local.cnt == 4 { - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } - - // Periodic every 100 milliseconds - let next_instant = instant + 100.millis(); - foo::spawn_at(next_instant, next_instant).unwrap(); - } -} diff --git a/examples/periodic-at2.no_rs b/examples/periodic-at2.no_rs new file mode 100644 index 0000000..ec9adcc --- /dev/null +++ b/examples/periodic-at2.no_rs @@ -0,0 +1,61 @@ +//! examples/periodic-at2.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use systick_monotonic::*; + + #[monotonic(binds = SysTick, default = true)] + type MyMono = Systick<100>; // 100 Hz / 10 ms granularity + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { + let systick = cx.core.SYST; + + // Initialize the monotonic (SysTick rate in QEMU is 12 MHz) + let mut mono = Systick::new(systick, 12_000_000); + + foo::spawn_after(200.millis(), mono.now()).unwrap(); + + (Shared {}, Local {}, init::Monotonics(mono)) + } + + // Using the explicit type of the timer implementation + #[task(local = [cnt: u32 = 0])] + fn foo(cx: foo::Context, instant: fugit::TimerInstantU64<100>) { + hprintln!("foo {:?}", instant).ok(); + *cx.local.cnt += 1; + + if *cx.local.cnt == 4 { + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } + + // Spawn a new message with 100 ms offset to spawned time + let next_instant = instant + 100.millis(); + bar::spawn_at(next_instant, next_instant).unwrap(); + } + + // Using the Instant from the Monotonic trait + // This remains agnostic to the timer implementation + #[task(local = [cnt: u32 = 0])] + fn bar(_cx: bar::Context, instant: ::Instant) { + hprintln!("bar {:?}", instant).ok(); + + // Spawn a new message with 200ms offset to spawned time + let next_instant = instant + 200.millis(); + foo::spawn_at(next_instant, next_instant).unwrap(); + } +} diff --git a/examples/periodic-at2.rs b/examples/periodic-at2.rs deleted file mode 100644 index ec9adcc..0000000 --- a/examples/periodic-at2.rs +++ /dev/null @@ -1,61 +0,0 @@ -//! examples/periodic-at2.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - use systick_monotonic::*; - - #[monotonic(binds = SysTick, default = true)] - type MyMono = Systick<100>; // 100 Hz / 10 ms granularity - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { - let systick = cx.core.SYST; - - // Initialize the monotonic (SysTick rate in QEMU is 12 MHz) - let mut mono = Systick::new(systick, 12_000_000); - - foo::spawn_after(200.millis(), mono.now()).unwrap(); - - (Shared {}, Local {}, init::Monotonics(mono)) - } - - // Using the explicit type of the timer implementation - #[task(local = [cnt: u32 = 0])] - fn foo(cx: foo::Context, instant: fugit::TimerInstantU64<100>) { - hprintln!("foo {:?}", instant).ok(); - *cx.local.cnt += 1; - - if *cx.local.cnt == 4 { - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } - - // Spawn a new message with 100 ms offset to spawned time - let next_instant = instant + 100.millis(); - bar::spawn_at(next_instant, next_instant).unwrap(); - } - - // Using the Instant from the Monotonic trait - // This remains agnostic to the timer implementation - #[task(local = [cnt: u32 = 0])] - fn bar(_cx: bar::Context, instant: ::Instant) { - hprintln!("bar {:?}", instant).ok(); - - // Spawn a new message with 200ms offset to spawned time - let next_instant = instant + 200.millis(); - foo::spawn_at(next_instant, next_instant).unwrap(); - } -} diff --git a/examples/periodic.no_rs b/examples/periodic.no_rs new file mode 100644 index 0000000..2f9e8e6 --- /dev/null +++ b/examples/periodic.no_rs @@ -0,0 +1,48 @@ +//! examples/periodic.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use systick_monotonic::*; + + #[monotonic(binds = SysTick, default = true)] + type MyMono = Systick<100>; // 100 Hz / 10 ms granularity + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { + let systick = cx.core.SYST; + + // Initialize the monotonic (SysTick rate in QEMU is 12 MHz) + let mono = Systick::new(systick, 12_000_000); + + foo::spawn_after(100.millis()).unwrap(); + + (Shared {}, Local {}, init::Monotonics(mono)) + } + + #[task(local = [cnt: u32 = 0])] + fn foo(cx: foo::Context) { + hprintln!("foo").ok(); + *cx.local.cnt += 1; + + if *cx.local.cnt == 4 { + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } + + // Periodic every 100ms + foo::spawn_after(100.millis()).unwrap(); + } +} diff --git a/examples/periodic.rs b/examples/periodic.rs deleted file mode 100644 index 2f9e8e6..0000000 --- a/examples/periodic.rs +++ /dev/null @@ -1,48 +0,0 @@ -//! examples/periodic.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - use systick_monotonic::*; - - #[monotonic(binds = SysTick, default = true)] - type MyMono = Systick<100>; // 100 Hz / 10 ms granularity - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { - let systick = cx.core.SYST; - - // Initialize the monotonic (SysTick rate in QEMU is 12 MHz) - let mono = Systick::new(systick, 12_000_000); - - foo::spawn_after(100.millis()).unwrap(); - - (Shared {}, Local {}, init::Monotonics(mono)) - } - - #[task(local = [cnt: u32 = 0])] - fn foo(cx: foo::Context) { - hprintln!("foo").ok(); - *cx.local.cnt += 1; - - if *cx.local.cnt == 4 { - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } - - // Periodic every 100ms - foo::spawn_after(100.millis()).unwrap(); - } -} diff --git a/examples/peripherals-taken.rs b/examples/peripherals-taken.rs index d542c0e..9b01466 100644 --- a/examples/peripherals-taken.rs +++ b/examples/peripherals-taken.rs @@ -16,10 +16,10 @@ mod app { struct Local {} #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { + fn init(_: init::Context) -> (Shared, Local) { assert!(cortex_m::Peripherals::take().is_none()); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - (Shared {}, Local {}, init::Monotonics()) + (Shared {}, Local {}) } } diff --git a/examples/pool.no_rs b/examples/pool.no_rs new file mode 100644 index 0000000..fb8589a --- /dev/null +++ b/examples/pool.no_rs @@ -0,0 +1,70 @@ +//! examples/pool.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use heapless::{ + pool, + pool::singleton::{Box, Pool}, +}; +use panic_semihosting as _; +use rtic::app; + +// Declare a pool of 128-byte memory blocks +pool!(P: [u8; 128]); + +#[app(device = lm3s6965, dispatchers = [SSI0, QEI0])] +mod app { + use crate::{Box, Pool}; + use cortex_m_semihosting::debug; + use lm3s6965::Interrupt; + + // Import the memory pool into scope + use super::P; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init(local = [memory: [u8; 512] = [0; 512]])] + fn init(cx: init::Context) -> (Shared, Local) { + // Increase the capacity of the memory pool by ~4 + P::grow(cx.local.memory); + + rtic::pend(Interrupt::I2C0); + + (Shared {}, Local {}) + } + + #[task(binds = I2C0, priority = 2)] + async fn i2c0(_: i2c0::Context) { + // claim a memory block, initialize it and .. + let x = P::alloc().unwrap().init([0u8; 128]); + + // .. send it to the `foo` task + foo::spawn(x).ok().unwrap(); + + // send another block to the task `bar` + bar::spawn(P::alloc().unwrap().init([0u8; 128])) + .ok() + .unwrap(); + } + + #[task] + async fn foo(_: foo::Context, _x: Box

) { + // explicitly return the block to the pool + drop(_x); + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } + + #[task(priority = 2)] + async fn bar(_: bar::Context, _x: Box

) { + // this is done automatically so we can omit the call to `drop` + // drop(_x); + } +} diff --git a/examples/pool.rs b/examples/pool.rs deleted file mode 100644 index 5aadd24..0000000 --- a/examples/pool.rs +++ /dev/null @@ -1,70 +0,0 @@ -//! examples/pool.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] - -use heapless::{ - pool, - pool::singleton::{Box, Pool}, -}; -use panic_semihosting as _; -use rtic::app; - -// Declare a pool of 128-byte memory blocks -pool!(P: [u8; 128]); - -#[app(device = lm3s6965, dispatchers = [SSI0, QEI0])] -mod app { - use crate::{Box, Pool}; - use cortex_m_semihosting::debug; - use lm3s6965::Interrupt; - - // Import the memory pool into scope - use super::P; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init(local = [memory: [u8; 512] = [0; 512]])] - fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { - // Increase the capacity of the memory pool by ~4 - P::grow(cx.local.memory); - - rtic::pend(Interrupt::I2C0); - - (Shared {}, Local {}, init::Monotonics()) - } - - #[task(binds = I2C0, priority = 2)] - fn i2c0(_: i2c0::Context) { - // claim a memory block, initialize it and .. - let x = P::alloc().unwrap().init([0u8; 128]); - - // .. send it to the `foo` task - foo::spawn(x).ok().unwrap(); - - // send another block to the task `bar` - bar::spawn(P::alloc().unwrap().init([0u8; 128])) - .ok() - .unwrap(); - } - - #[task] - fn foo(_: foo::Context, _x: Box

) { - // explicitly return the block to the pool - drop(_x); - - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } - - #[task(priority = 2)] - fn bar(_: bar::Context, _x: Box

) { - // this is done automatically so we can omit the call to `drop` - // drop(x); - } -} diff --git a/examples/preempt.rs b/examples/preempt.rs index d0c8cc7..aad9125 100644 --- a/examples/preempt.rs +++ b/examples/preempt.rs @@ -2,6 +2,7 @@ #![no_main] #![no_std] +#![feature(type_alias_impl_trait)] use panic_semihosting as _; use rtic::app; @@ -17,14 +18,14 @@ mod app { struct Local {} #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { + fn init(_: init::Context) -> (Shared, Local) { foo::spawn().unwrap(); - (Shared {}, Local {}, init::Monotonics()) + (Shared {}, Local {}) } #[task(priority = 1)] - fn foo(_: foo::Context) { + async fn foo(_: foo::Context) { hprintln!("foo - start").unwrap(); baz::spawn().unwrap(); hprintln!("foo - end").unwrap(); @@ -32,12 +33,12 @@ mod app { } #[task(priority = 2)] - fn bar(_: bar::Context) { + async fn bar(_: bar::Context) { hprintln!(" bar").unwrap(); } #[task(priority = 2)] - fn baz(_: baz::Context) { + async fn baz(_: baz::Context) { hprintln!(" baz - start").unwrap(); bar::spawn().unwrap(); hprintln!(" baz - end").unwrap(); diff --git a/examples/ramfunc.rs b/examples/ramfunc.rs index b3b8012..dd1f76e 100644 --- a/examples/ramfunc.rs +++ b/examples/ramfunc.rs @@ -3,7 +3,7 @@ #![deny(warnings)] #![no_main] #![no_std] - +#![feature(type_alias_impl_trait)] use panic_semihosting as _; #[rtic::app( @@ -24,15 +24,15 @@ mod app { struct Local {} #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { + fn init(_: init::Context) -> (Shared, Local) { foo::spawn().unwrap(); - (Shared {}, Local {}, init::Monotonics()) + (Shared {}, Local {}) } #[inline(never)] #[task] - fn foo(_: foo::Context) { + async fn foo(_: foo::Context) { hprintln!("foo").unwrap(); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator @@ -42,7 +42,7 @@ mod app { #[inline(never)] #[link_section = ".data.bar"] #[task(priority = 2)] - fn bar(_: bar::Context) { + async fn bar(_: bar::Context) { foo::spawn().unwrap(); } } diff --git a/examples/resource-user-struct.rs b/examples/resource-user-struct.rs index ae1918d..a4478ce 100644 --- a/examples/resource-user-struct.rs +++ b/examples/resource-user-struct.rs @@ -29,11 +29,11 @@ mod app { struct Local {} #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { + fn init(_: init::Context) -> (Shared, Local) { rtic::pend(Interrupt::UART0); rtic::pend(Interrupt::UART1); - (Shared { shared: 0 }, Local {}, init::Monotonics()) + (Shared { shared: 0 }, Local {}) } // `shared` cannot be accessed from this context diff --git a/examples/schedule.no_rs b/examples/schedule.no_rs new file mode 100644 index 0000000..5bad5a3 --- /dev/null +++ b/examples/schedule.no_rs @@ -0,0 +1,64 @@ +//! examples/schedule.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use systick_monotonic::*; + + #[monotonic(binds = SysTick, default = true)] + type MyMono = Systick<100>; // 100 Hz / 10 ms granularity + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { + let systick = cx.core.SYST; + + // Initialize the monotonic (SysTick rate in QEMU is 12 MHz) + let mono = Systick::new(systick, 12_000_000); + + hprintln!("init").ok(); + + // Schedule `foo` to run 1 second in the future + foo::spawn_after(1.secs()).unwrap(); + + ( + Shared {}, + Local {}, + init::Monotonics(mono), // Give the monotonic to RTIC + ) + } + + #[task] + fn foo(_: foo::Context) { + hprintln!("foo").ok(); + + // Schedule `bar` to run 2 seconds in the future (1 second after foo runs) + bar::spawn_after(1.secs()).unwrap(); + } + + #[task] + fn bar(_: bar::Context) { + hprintln!("bar").ok(); + + // Schedule `baz` to run 1 seconds from now, but with a specific time instant. + baz::spawn_at(monotonics::now() + 1.secs()).unwrap(); + } + + #[task] + fn baz(_: baz::Context) { + hprintln!("baz").ok(); + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } +} diff --git a/examples/schedule.rs b/examples/schedule.rs deleted file mode 100644 index 5bad5a3..0000000 --- a/examples/schedule.rs +++ /dev/null @@ -1,64 +0,0 @@ -//! examples/schedule.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - use systick_monotonic::*; - - #[monotonic(binds = SysTick, default = true)] - type MyMono = Systick<100>; // 100 Hz / 10 ms granularity - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { - let systick = cx.core.SYST; - - // Initialize the monotonic (SysTick rate in QEMU is 12 MHz) - let mono = Systick::new(systick, 12_000_000); - - hprintln!("init").ok(); - - // Schedule `foo` to run 1 second in the future - foo::spawn_after(1.secs()).unwrap(); - - ( - Shared {}, - Local {}, - init::Monotonics(mono), // Give the monotonic to RTIC - ) - } - - #[task] - fn foo(_: foo::Context) { - hprintln!("foo").ok(); - - // Schedule `bar` to run 2 seconds in the future (1 second after foo runs) - bar::spawn_after(1.secs()).unwrap(); - } - - #[task] - fn bar(_: bar::Context) { - hprintln!("bar").ok(); - - // Schedule `baz` to run 1 seconds from now, but with a specific time instant. - baz::spawn_at(monotonics::now() + 1.secs()).unwrap(); - } - - #[task] - fn baz(_: baz::Context) { - hprintln!("baz").ok(); - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } -} diff --git a/examples/shared.rs b/examples/shared.rs index d87dca5..fdc1b1c 100644 --- a/examples/shared.rs +++ b/examples/shared.rs @@ -23,11 +23,11 @@ mod app { struct Local {} #[init(local = [q: Queue = Queue::new()])] - fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { + fn init(cx: init::Context) -> (Shared, Local) { let (p, c) = cx.local.q.split(); // Initialization of shared resources - (Shared { p, c }, Local {}, init::Monotonics()) + (Shared { p, c }, Local {}) } #[idle(shared = [c])] diff --git a/examples/smallest.rs b/examples/smallest.rs index b121fcf..5071392 100644 --- a/examples/smallest.rs +++ b/examples/smallest.rs @@ -17,8 +17,8 @@ mod app { struct Local {} #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { + fn init(_: init::Context) -> (Shared, Local) { debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - (Shared {}, Local {}, init::Monotonics()) + (Shared {}, Local {}) } } diff --git a/examples/spawn.rs b/examples/spawn.rs index 2db1ab8..266ace8 100644 --- a/examples/spawn.rs +++ b/examples/spawn.rs @@ -4,6 +4,7 @@ #![deny(warnings)] #![no_main] #![no_std] +#![feature(type_alias_impl_trait)] use panic_semihosting as _; @@ -18,15 +19,15 @@ mod app { struct Local {} #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { + fn init(_: init::Context) -> (Shared, Local) { hprintln!("init").unwrap(); foo::spawn().unwrap(); - (Shared {}, Local {}, init::Monotonics()) + (Shared {}, Local {}) } #[task] - fn foo(_: foo::Context) { + async fn foo(_: foo::Context) { hprintln!("foo").unwrap(); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator diff --git a/examples/static.rs b/examples/static.rs index c9aa604..9c981db 100644 --- a/examples/static.rs +++ b/examples/static.rs @@ -4,6 +4,7 @@ #![deny(warnings)] #![no_main] #![no_std] +#![feature(type_alias_impl_trait)] use panic_semihosting as _; @@ -22,14 +23,14 @@ mod app { } #[init(local = [q: Queue = Queue::new()])] - fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { + fn init(cx: init::Context) -> (Shared, Local) { // q has 'static life-time so after the split and return of `init` // it will continue to exist and be allocated let (p, c) = cx.local.q.split(); foo::spawn().unwrap(); - (Shared {}, Local { p, c }, init::Monotonics()) + (Shared {}, Local { p, c }) } #[idle(local = [c])] @@ -50,7 +51,7 @@ mod app { } #[task(local = [p, state: u32 = 0])] - fn foo(c: foo::Context) { + async fn foo(c: foo::Context) { *c.local.state += 1; // Lock-free access to the same underlying queue! diff --git a/examples/t-binds.rs b/examples/t-binds.rs index 12479c0..785348b 100644 --- a/examples/t-binds.rs +++ b/examples/t-binds.rs @@ -18,10 +18,10 @@ mod app { struct Local {} #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { + fn init(_: init::Context) -> (Shared, Local) { debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - (Shared {}, Local {}, init::Monotonics()) + (Shared {}, Local {}) } // Cortex-M exception diff --git a/examples/t-cfg-resources.rs b/examples/t-cfg-resources.rs index 99c97ba..0174f33 100644 --- a/examples/t-cfg-resources.rs +++ b/examples/t-cfg-resources.rs @@ -20,7 +20,7 @@ mod app { struct Local {} #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { + fn init(_: init::Context) -> (Shared, Local) { debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator ( @@ -29,7 +29,6 @@ mod app { x: 0, }, Local {}, - init::Monotonics(), ) } diff --git a/examples/t-htask-main.rs b/examples/t-htask-main.rs index 37189fa..0595e9f 100644 --- a/examples/t-htask-main.rs +++ b/examples/t-htask-main.rs @@ -16,10 +16,10 @@ mod app { struct Local {} #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { + fn init(_: init::Context) -> (Shared, Local) { rtic::pend(lm3s6965::Interrupt::UART0); - (Shared {}, Local {}, init::Monotonics()) + (Shared {}, Local {}) } #[task(binds = UART0)] diff --git a/examples/t-idle-main.rs b/examples/t-idle-main.rs index 1adc9bf..307ccb2 100644 --- a/examples/t-idle-main.rs +++ b/examples/t-idle-main.rs @@ -16,8 +16,8 @@ mod app { struct Local {} #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { - (Shared {}, Local {}, init::Monotonics()) + fn init(_: init::Context) -> (Shared, Local) { + (Shared {}, Local {}) } #[idle] diff --git a/examples/t-late-not-send.rs b/examples/t-late-not-send.rs index 06aedaa..0fbf237 100644 --- a/examples/t-late-not-send.rs +++ b/examples/t-late-not-send.rs @@ -27,14 +27,13 @@ mod app { struct Local {} #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { + fn init(_: init::Context) -> (Shared, Local) { ( Shared { x: NotSend { _0: PhantomData }, y: None, }, Local {}, - init::Monotonics(), ) } diff --git a/examples/t-schedule.no_rs b/examples/t-schedule.no_rs new file mode 100644 index 0000000..5ec4208 --- /dev/null +++ b/examples/t-schedule.no_rs @@ -0,0 +1,136 @@ +//! [compile-pass] Check `schedule` code generation + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] +mod app { + use cortex_m_semihosting::debug; + use systick_monotonic::*; + + #[monotonic(binds = SysTick, default = true)] + type MyMono = Systick<100>; // 100 Hz / 10 ms granularity + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { + let systick = cx.core.SYST; + + // Initialize the monotonic (SysTick rate in QEMU is 12 MHz) + let mono = Systick::new(systick, 12_000_000); + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + + (Shared {}, Local {}, init::Monotonics(mono)) + } + + #[idle] + fn idle(_: idle::Context) -> ! { + // Task without message passing + + // Not default + let _: Result = + foo::MyMono::spawn_at(monotonics::MyMono::now()); + let handle: Result = foo::MyMono::spawn_after(1.secs()); + let _: Result = handle.unwrap().reschedule_after(1.secs()); + + let handle: Result = foo::MyMono::spawn_after(1.secs()); + let _: Result = + handle.unwrap().reschedule_at(monotonics::MyMono::now()); + + let handle: Result = foo::MyMono::spawn_after(1.secs()); + let _: Result<(), ()> = handle.unwrap().cancel(); + + // Using default + let _: Result = foo::spawn_at(monotonics::now()); + let handle: Result = foo::spawn_after(1.secs()); + let _: Result = handle.unwrap().reschedule_after(1.secs()); + + let handle: Result = foo::spawn_after(1.secs()); + let _: Result = + handle.unwrap().reschedule_at(monotonics::MyMono::now()); + + let handle: Result = foo::spawn_after(1.secs()); + let _: Result<(), ()> = handle.unwrap().cancel(); + + // Task with single message passing + + // Not default + let _: Result = + bar::MyMono::spawn_at(monotonics::MyMono::now(), 0); + let handle: Result = bar::MyMono::spawn_after(1.secs(), 1); + let _: Result = handle.unwrap().reschedule_after(1.secs()); + + let handle: Result = bar::MyMono::spawn_after(1.secs(), 1); + let _: Result = + handle.unwrap().reschedule_at(monotonics::MyMono::now()); + + let handle: Result = bar::MyMono::spawn_after(1.secs(), 1); + let _: Result = handle.unwrap().cancel(); + + // Using default + let _: Result = bar::spawn_at(monotonics::MyMono::now(), 0); + let handle: Result = bar::spawn_after(1.secs(), 1); + let _: Result = handle.unwrap().reschedule_after(1.secs()); + + let handle: Result = bar::spawn_after(1.secs(), 1); + let _: Result = + handle.unwrap().reschedule_at(monotonics::MyMono::now()); + + let handle: Result = bar::spawn_after(1.secs(), 1); + let _: Result = handle.unwrap().cancel(); + + // Task with multiple message passing + + // Not default + let _: Result = + baz::MyMono::spawn_at(monotonics::MyMono::now(), 0, 1); + let handle: Result = + baz::MyMono::spawn_after(1.secs(), 1, 2); + let _: Result = handle.unwrap().reschedule_after(1.secs()); + + let handle: Result = + baz::MyMono::spawn_after(1.secs(), 1, 2); + let _: Result = + handle.unwrap().reschedule_at(monotonics::MyMono::now()); + + let handle: Result = + baz::MyMono::spawn_after(1.secs(), 1, 2); + let _: Result<(u32, u32), ()> = handle.unwrap().cancel(); + + // Using default + let _: Result = + baz::spawn_at(monotonics::MyMono::now(), 0, 1); + let handle: Result = baz::spawn_after(1.secs(), 1, 2); + let _: Result = handle.unwrap().reschedule_after(1.secs()); + + let handle: Result = baz::spawn_after(1.secs(), 1, 2); + let _: Result = + handle.unwrap().reschedule_at(monotonics::MyMono::now()); + + let handle: Result = baz::spawn_after(1.secs(), 1, 2); + let _: Result<(u32, u32), ()> = handle.unwrap().cancel(); + + loop { + cortex_m::asm::nop(); + } + } + + #[task] + fn foo(_: foo::Context) {} + + #[task] + fn bar(_: bar::Context, _x: u32) {} + + #[task] + fn baz(_: baz::Context, _x: u32, _y: u32) {} +} diff --git a/examples/t-schedule.rs b/examples/t-schedule.rs deleted file mode 100644 index 5ec4208..0000000 --- a/examples/t-schedule.rs +++ /dev/null @@ -1,136 +0,0 @@ -//! [compile-pass] Check `schedule` code generation - -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] -mod app { - use cortex_m_semihosting::debug; - use systick_monotonic::*; - - #[monotonic(binds = SysTick, default = true)] - type MyMono = Systick<100>; // 100 Hz / 10 ms granularity - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { - let systick = cx.core.SYST; - - // Initialize the monotonic (SysTick rate in QEMU is 12 MHz) - let mono = Systick::new(systick, 12_000_000); - - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - - (Shared {}, Local {}, init::Monotonics(mono)) - } - - #[idle] - fn idle(_: idle::Context) -> ! { - // Task without message passing - - // Not default - let _: Result = - foo::MyMono::spawn_at(monotonics::MyMono::now()); - let handle: Result = foo::MyMono::spawn_after(1.secs()); - let _: Result = handle.unwrap().reschedule_after(1.secs()); - - let handle: Result = foo::MyMono::spawn_after(1.secs()); - let _: Result = - handle.unwrap().reschedule_at(monotonics::MyMono::now()); - - let handle: Result = foo::MyMono::spawn_after(1.secs()); - let _: Result<(), ()> = handle.unwrap().cancel(); - - // Using default - let _: Result = foo::spawn_at(monotonics::now()); - let handle: Result = foo::spawn_after(1.secs()); - let _: Result = handle.unwrap().reschedule_after(1.secs()); - - let handle: Result = foo::spawn_after(1.secs()); - let _: Result = - handle.unwrap().reschedule_at(monotonics::MyMono::now()); - - let handle: Result = foo::spawn_after(1.secs()); - let _: Result<(), ()> = handle.unwrap().cancel(); - - // Task with single message passing - - // Not default - let _: Result = - bar::MyMono::spawn_at(monotonics::MyMono::now(), 0); - let handle: Result = bar::MyMono::spawn_after(1.secs(), 1); - let _: Result = handle.unwrap().reschedule_after(1.secs()); - - let handle: Result = bar::MyMono::spawn_after(1.secs(), 1); - let _: Result = - handle.unwrap().reschedule_at(monotonics::MyMono::now()); - - let handle: Result = bar::MyMono::spawn_after(1.secs(), 1); - let _: Result = handle.unwrap().cancel(); - - // Using default - let _: Result = bar::spawn_at(monotonics::MyMono::now(), 0); - let handle: Result = bar::spawn_after(1.secs(), 1); - let _: Result = handle.unwrap().reschedule_after(1.secs()); - - let handle: Result = bar::spawn_after(1.secs(), 1); - let _: Result = - handle.unwrap().reschedule_at(monotonics::MyMono::now()); - - let handle: Result = bar::spawn_after(1.secs(), 1); - let _: Result = handle.unwrap().cancel(); - - // Task with multiple message passing - - // Not default - let _: Result = - baz::MyMono::spawn_at(monotonics::MyMono::now(), 0, 1); - let handle: Result = - baz::MyMono::spawn_after(1.secs(), 1, 2); - let _: Result = handle.unwrap().reschedule_after(1.secs()); - - let handle: Result = - baz::MyMono::spawn_after(1.secs(), 1, 2); - let _: Result = - handle.unwrap().reschedule_at(monotonics::MyMono::now()); - - let handle: Result = - baz::MyMono::spawn_after(1.secs(), 1, 2); - let _: Result<(u32, u32), ()> = handle.unwrap().cancel(); - - // Using default - let _: Result = - baz::spawn_at(monotonics::MyMono::now(), 0, 1); - let handle: Result = baz::spawn_after(1.secs(), 1, 2); - let _: Result = handle.unwrap().reschedule_after(1.secs()); - - let handle: Result = baz::spawn_after(1.secs(), 1, 2); - let _: Result = - handle.unwrap().reschedule_at(monotonics::MyMono::now()); - - let handle: Result = baz::spawn_after(1.secs(), 1, 2); - let _: Result<(u32, u32), ()> = handle.unwrap().cancel(); - - loop { - cortex_m::asm::nop(); - } - } - - #[task] - fn foo(_: foo::Context) {} - - #[task] - fn bar(_: bar::Context, _x: u32) {} - - #[task] - fn baz(_: baz::Context, _x: u32, _y: u32) {} -} diff --git a/examples/t-spawn.no_rs b/examples/t-spawn.no_rs new file mode 100644 index 0000000..dad0c83 --- /dev/null +++ b/examples/t-spawn.no_rs @@ -0,0 +1,69 @@ +//! [compile-pass] Check code generation of `spawn` + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] +mod app { + use cortex_m_semihosting::debug; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + let _: Result<(), ()> = foo::spawn(); + let _: Result<(), u32> = bar::spawn(0); + let _: Result<(), (u32, u32)> = baz::spawn(0, 1); + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + + (Shared {}, Local {}) + } + + #[idle] + fn idle(_: idle::Context) -> ! { + let _: Result<(), ()> = foo::spawn(); + let _: Result<(), u32> = bar::spawn(0); + let _: Result<(), (u32, u32)> = baz::spawn(0, 1); + + loop { + cortex_m::asm::nop(); + } + } + + #[task(binds = SVCall)] + fn svcall(_: svcall::Context) { + let _: Result<(), ()> = foo::spawn(); + let _: Result<(), u32> = bar::spawn(0); + let _: Result<(), (u32, u32)> = baz::spawn(0, 1); + } + + #[task(binds = UART0)] + fn uart0(_: uart0::Context) { + let _: Result<(), ()> = foo::spawn(); + let _: Result<(), u32> = bar::spawn(0); + let _: Result<(), (u32, u32)> = baz::spawn(0, 1); + } + + #[task] + async fn foo(_: foo::Context) { + let _: Result<(), ()> = foo::spawn(); + let _: Result<(), u32> = bar::spawn(0); + let _: Result<(), (u32, u32)> = baz::spawn(0, 1); + } + + #[task] + async fn bar(_: bar::Context, _x: u32) {} + + #[task] + async fn baz(_: baz::Context, _x: u32, _y: u32) {} +} diff --git a/examples/t-spawn.rs b/examples/t-spawn.rs deleted file mode 100644 index 2bd771d..0000000 --- a/examples/t-spawn.rs +++ /dev/null @@ -1,68 +0,0 @@ -//! [compile-pass] Check code generation of `spawn` - -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] -mod app { - use cortex_m_semihosting::debug; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { - let _: Result<(), ()> = foo::spawn(); - let _: Result<(), u32> = bar::spawn(0); - let _: Result<(), (u32, u32)> = baz::spawn(0, 1); - - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - - (Shared {}, Local {}, init::Monotonics()) - } - - #[idle] - fn idle(_: idle::Context) -> ! { - let _: Result<(), ()> = foo::spawn(); - let _: Result<(), u32> = bar::spawn(0); - let _: Result<(), (u32, u32)> = baz::spawn(0, 1); - - loop { - cortex_m::asm::nop(); - } - } - - #[task(binds = SVCall)] - fn svcall(_: svcall::Context) { - let _: Result<(), ()> = foo::spawn(); - let _: Result<(), u32> = bar::spawn(0); - let _: Result<(), (u32, u32)> = baz::spawn(0, 1); - } - - #[task(binds = UART0)] - fn uart0(_: uart0::Context) { - let _: Result<(), ()> = foo::spawn(); - let _: Result<(), u32> = bar::spawn(0); - let _: Result<(), (u32, u32)> = baz::spawn(0, 1); - } - - #[task] - fn foo(_: foo::Context) { - let _: Result<(), ()> = foo::spawn(); - let _: Result<(), u32> = bar::spawn(0); - let _: Result<(), (u32, u32)> = baz::spawn(0, 1); - } - - #[task] - fn bar(_: bar::Context, _x: u32) {} - - #[task] - fn baz(_: baz::Context, _x: u32, _y: u32) {} -} diff --git a/examples/task.rs b/examples/task.rs index 2c53aa2..fe1408b 100644 --- a/examples/task.rs +++ b/examples/task.rs @@ -4,6 +4,7 @@ #![deny(warnings)] #![no_main] #![no_std] +#![feature(type_alias_impl_trait)] use panic_semihosting as _; @@ -18,14 +19,14 @@ mod app { struct Local {} #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { + fn init(_: init::Context) -> (Shared, Local) { foo::spawn().unwrap(); - (Shared {}, Local {}, init::Monotonics()) + (Shared {}, Local {}) } #[task] - fn foo(_: foo::Context) { + async fn foo(_: foo::Context) { hprintln!("foo - start").unwrap(); // spawns `bar` onto the task scheduler @@ -43,14 +44,14 @@ mod app { } #[task] - fn bar(_: bar::Context) { + async fn bar(_: bar::Context) { hprintln!("bar").unwrap(); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator } #[task(priority = 2)] - fn baz(_: baz::Context) { + async fn baz(_: baz::Context) { hprintln!("baz").unwrap(); } } -- cgit v1.2.3 From 584ac7e1b335411e3d923aeef92466efdc92ae50 Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Sun, 8 Jan 2023 16:25:46 +0100 Subject: Update UI tests, 1 failing that needs fixing --- macros/ui/async-local-resouces.rs | 28 ------------------------- macros/ui/async-local-resouces.stderr | 5 ----- macros/ui/async-zero-prio-tasks.rs | 19 ----------------- macros/ui/async-zero-prio-tasks.stderr | 11 ---------- macros/ui/extern-interrupt-used.rs | 2 +- macros/ui/extern-interrupt-used.stderr | 2 +- macros/ui/idle-double-local.stderr | 2 +- macros/ui/idle-double-shared.stderr | 2 +- macros/ui/init-divergent.stderr | 4 ++-- macros/ui/init-double-local.stderr | 2 +- macros/ui/init-double-shared.stderr | 2 +- macros/ui/init-input.rs | 2 +- macros/ui/init-input.stderr | 6 +++--- macros/ui/init-no-context.rs | 2 +- macros/ui/init-no-context.stderr | 6 +++--- macros/ui/init-output.stderr | 4 ++-- macros/ui/init-pub.rs | 2 +- macros/ui/init-pub.stderr | 6 +++--- macros/ui/init-unsafe.rs | 2 +- macros/ui/init-unsafe.stderr | 6 +++--- macros/ui/interrupt-double.stderr | 2 +- macros/ui/local-collision-2.rs | 7 ++----- macros/ui/local-collision-2.stderr | 10 ++++----- macros/ui/local-collision.rs | 6 +++--- macros/ui/local-collision.stderr | 4 ++-- macros/ui/local-malformed-1.rs | 4 ++-- macros/ui/local-malformed-2.rs | 4 ++-- macros/ui/local-malformed-2.stderr | 2 +- macros/ui/local-malformed-3.rs | 4 ++-- macros/ui/local-malformed-4.rs | 4 ++-- macros/ui/local-not-declared.rs | 4 ++-- macros/ui/local-not-declared.stderr | 2 +- macros/ui/local-pub.rs | 6 ++++++ macros/ui/local-pub.stderr | 8 +++---- macros/ui/local-shared-attribute.rs | 13 +++++++++--- macros/ui/local-shared-attribute.stderr | 9 ++++---- macros/ui/local-shared.rs | 6 +++--- macros/ui/local-shared.stderr | 4 ++-- macros/ui/monotonic-binds-collision-task.rs | 10 --------- macros/ui/monotonic-binds-collision-task.stderr | 5 ----- macros/ui/monotonic-binds-collision.rs | 10 --------- macros/ui/monotonic-binds-collision.stderr | 5 ----- macros/ui/monotonic-double-binds.rs | 7 ------- macros/ui/monotonic-double-binds.stderr | 5 ----- macros/ui/monotonic-double-default.rs | 7 ------- macros/ui/monotonic-double-default.stderr | 5 ----- macros/ui/monotonic-double-prio.rs | 7 ------- macros/ui/monotonic-double-prio.stderr | 5 ----- macros/ui/monotonic-double.rs | 10 --------- macros/ui/monotonic-double.stderr | 5 ----- macros/ui/monotonic-name-collision.rs | 10 --------- macros/ui/monotonic-name-collision.stderr | 5 ----- macros/ui/monotonic-no-binds.rs | 7 ------- macros/ui/monotonic-no-binds.stderr | 5 ----- macros/ui/monotonic-no-paran.rs | 8 ------- macros/ui/monotonic-no-paran.stderr | 5 ----- macros/ui/monotonic-timer-collision.rs | 10 --------- macros/ui/monotonic-timer-collision.stderr | 5 ----- macros/ui/monotonic-with-attrs.rs | 8 ------- macros/ui/monotonic-with-attrs.stderr | 5 ----- macros/ui/pub-local.stderr | 5 ----- macros/ui/pub-shared.stderr | 5 ----- macros/ui/shared-lock-free.rs | 6 +++--- macros/ui/shared-lock-free.stderr | 17 --------------- macros/ui/shared-not-declared.rs | 4 ++-- macros/ui/shared-not-declared.stderr | 2 +- macros/ui/shared-pub.stderr | 2 +- macros/ui/task-divergent.rs | 2 +- macros/ui/task-divergent.stderr | 8 +++---- macros/ui/task-double-capacity.rs | 7 ------- macros/ui/task-double-capacity.stderr | 5 ----- macros/ui/task-double-local.rs | 2 +- macros/ui/task-double-local.stderr | 2 +- macros/ui/task-double-priority.rs | 2 +- macros/ui/task-double-priority.stderr | 2 +- macros/ui/task-double-shared.rs | 2 +- macros/ui/task-double-shared.stderr | 2 +- macros/ui/task-idle.rs | 2 +- macros/ui/task-idle.stderr | 6 +++--- macros/ui/task-init.rs | 4 ++-- macros/ui/task-init.stderr | 6 +++--- macros/ui/task-interrupt.rs | 2 +- macros/ui/task-interrupt.stderr | 6 +++--- macros/ui/task-no-context.rs | 2 +- macros/ui/task-no-context.stderr | 8 +++---- macros/ui/task-priority-too-high.rs | 2 +- macros/ui/task-pub.rs | 2 +- macros/ui/task-pub.stderr | 8 +++---- macros/ui/task-unsafe.rs | 2 +- macros/ui/task-unsafe.stderr | 8 +++---- macros/ui/task-zero-prio.rs | 19 +++++++++++++++++ macros/ui/task-zero-prio.stderr | 5 +++++ 92 files changed, 152 insertions(+), 368 deletions(-) delete mode 100644 macros/ui/async-local-resouces.rs delete mode 100644 macros/ui/async-local-resouces.stderr delete mode 100644 macros/ui/async-zero-prio-tasks.rs delete mode 100644 macros/ui/async-zero-prio-tasks.stderr delete mode 100644 macros/ui/monotonic-binds-collision-task.rs delete mode 100644 macros/ui/monotonic-binds-collision-task.stderr delete mode 100644 macros/ui/monotonic-binds-collision.rs delete mode 100644 macros/ui/monotonic-binds-collision.stderr delete mode 100644 macros/ui/monotonic-double-binds.rs delete mode 100644 macros/ui/monotonic-double-binds.stderr delete mode 100644 macros/ui/monotonic-double-default.rs delete mode 100644 macros/ui/monotonic-double-default.stderr delete mode 100644 macros/ui/monotonic-double-prio.rs delete mode 100644 macros/ui/monotonic-double-prio.stderr delete mode 100644 macros/ui/monotonic-double.rs delete mode 100644 macros/ui/monotonic-double.stderr delete mode 100644 macros/ui/monotonic-name-collision.rs delete mode 100644 macros/ui/monotonic-name-collision.stderr delete mode 100644 macros/ui/monotonic-no-binds.rs delete mode 100644 macros/ui/monotonic-no-binds.stderr delete mode 100644 macros/ui/monotonic-no-paran.rs delete mode 100644 macros/ui/monotonic-no-paran.stderr delete mode 100644 macros/ui/monotonic-timer-collision.rs delete mode 100644 macros/ui/monotonic-timer-collision.stderr delete mode 100644 macros/ui/monotonic-with-attrs.rs delete mode 100644 macros/ui/monotonic-with-attrs.stderr delete mode 100644 macros/ui/pub-local.stderr delete mode 100644 macros/ui/pub-shared.stderr delete mode 100644 macros/ui/shared-lock-free.stderr delete mode 100644 macros/ui/task-double-capacity.rs delete mode 100644 macros/ui/task-double-capacity.stderr create mode 100644 macros/ui/task-zero-prio.rs create mode 100644 macros/ui/task-zero-prio.stderr diff --git a/macros/ui/async-local-resouces.rs b/macros/ui/async-local-resouces.rs deleted file mode 100644 index 1ba5865..0000000 --- a/macros/ui/async-local-resouces.rs +++ /dev/null @@ -1,28 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[shared] - struct Shared { - #[lock_free] - e: u32, - } - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {} - - // e ok - #[task(priority = 1, shared = [e])] - fn uart0(cx: uart0::Context) {} - - // e ok - #[task(priority = 1, shared = [e])] - fn uart1(cx: uart1::Context) {} - - // e not ok - #[task(priority = 1, shared = [e])] - async fn async_task(cx: async_task::Context) {} -} diff --git a/macros/ui/async-local-resouces.stderr b/macros/ui/async-local-resouces.stderr deleted file mode 100644 index 7ce7517..0000000 --- a/macros/ui/async-local-resouces.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: Lock free shared resource "e" is used by an async tasks, which is forbidden - --> ui/async-local-resouces.rs:26:36 - | -26 | #[task(priority = 1, shared = [e])] - | ^ diff --git a/macros/ui/async-zero-prio-tasks.rs b/macros/ui/async-zero-prio-tasks.rs deleted file mode 100644 index 91e0990..0000000 --- a/macros/ui/async-zero-prio-tasks.rs +++ /dev/null @@ -1,19 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {} - - #[task(priority = 0)] - fn foo(_: foo::Context) {} - - #[idle] - fn idle(_: idle::Context) -> ! {} -} diff --git a/macros/ui/async-zero-prio-tasks.stderr b/macros/ui/async-zero-prio-tasks.stderr deleted file mode 100644 index d617feb..0000000 --- a/macros/ui/async-zero-prio-tasks.stderr +++ /dev/null @@ -1,11 +0,0 @@ -error: Software task "foo" has priority 0, but `#[idle]` is defined. 0-priority software tasks are only allowed if there is no `#[idle]`. - --> ui/async-zero-prio-tasks.rs:15:8 - | -15 | fn foo(_: foo::Context) {} - | ^^^ - -error: Software task "foo" has priority 0, but is not `async`. 0-priority software tasks must be `async`. - --> ui/async-zero-prio-tasks.rs:15:8 - | -15 | fn foo(_: foo::Context) {} - | ^^^ diff --git a/macros/ui/extern-interrupt-used.rs b/macros/ui/extern-interrupt-used.rs index a6e0b3b..6346a7d 100644 --- a/macros/ui/extern-interrupt-used.rs +++ b/macros/ui/extern-interrupt-used.rs @@ -9,7 +9,7 @@ mod app { struct Local {} #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {} + fn init(_: init::Context) -> (Shared, Local) {} #[task(binds = EXTI0)] fn foo(_: foo::Context) {} diff --git a/macros/ui/extern-interrupt-used.stderr b/macros/ui/extern-interrupt-used.stderr index f9510d7..970d39b 100644 --- a/macros/ui/extern-interrupt-used.stderr +++ b/macros/ui/extern-interrupt-used.stderr @@ -1,5 +1,5 @@ error: dispatcher interrupts can't be used as hardware tasks - --> $DIR/extern-interrupt-used.rs:14:20 + --> ui/extern-interrupt-used.rs:14:20 | 14 | #[task(binds = EXTI0)] | ^^^^^ diff --git a/macros/ui/idle-double-local.stderr b/macros/ui/idle-double-local.stderr index d3ba4ec..b558136 100644 --- a/macros/ui/idle-double-local.stderr +++ b/macros/ui/idle-double-local.stderr @@ -1,5 +1,5 @@ error: argument appears more than once - --> $DIR/idle-double-local.rs:5:25 + --> ui/idle-double-local.rs:5:25 | 5 | #[idle(local = [A], local = [B])] | ^^^^^ diff --git a/macros/ui/idle-double-shared.stderr b/macros/ui/idle-double-shared.stderr index 84864a1..6f62ad2 100644 --- a/macros/ui/idle-double-shared.stderr +++ b/macros/ui/idle-double-shared.stderr @@ -1,5 +1,5 @@ error: argument appears more than once - --> $DIR/idle-double-shared.rs:5:26 + --> ui/idle-double-shared.rs:5:26 | 5 | #[idle(shared = [A], shared = [B])] | ^^^^^^ diff --git a/macros/ui/init-divergent.stderr b/macros/ui/init-divergent.stderr index 2d5cc39..9f6acf6 100644 --- a/macros/ui/init-divergent.stderr +++ b/macros/ui/init-divergent.stderr @@ -1,5 +1,5 @@ -error: the `#[init]` function must have signature `fn(init::Context) -> (Shared resources struct, Local resources struct, init::Monotonics)` - --> $DIR/init-divergent.rs:12:8 +error: the `#[init]` function must have signature `fn(init::Context) -> (Shared resources struct, Local resources struct)` + --> ui/init-divergent.rs:12:8 | 12 | fn init(_: init::Context) -> ! {} | ^^^^ diff --git a/macros/ui/init-double-local.stderr b/macros/ui/init-double-local.stderr index 5ffd2c1..07c3b50 100644 --- a/macros/ui/init-double-local.stderr +++ b/macros/ui/init-double-local.stderr @@ -1,5 +1,5 @@ error: argument appears more than once - --> $DIR/init-double-local.rs:5:25 + --> ui/init-double-local.rs:5:25 | 5 | #[init(local = [A], local = [B])] | ^^^^^ diff --git a/macros/ui/init-double-shared.stderr b/macros/ui/init-double-shared.stderr index b6b1f6d..af2a97b 100644 --- a/macros/ui/init-double-shared.stderr +++ b/macros/ui/init-double-shared.stderr @@ -1,5 +1,5 @@ error: unexpected argument - --> $DIR/init-double-shared.rs:5:12 + --> ui/init-double-shared.rs:5:12 | 5 | #[init(shared = [A], shared = [B])] | ^^^^^^ diff --git a/macros/ui/init-input.rs b/macros/ui/init-input.rs index ac2a1bd..d41a503 100644 --- a/macros/ui/init-input.rs +++ b/macros/ui/init-input.rs @@ -9,5 +9,5 @@ mod app { struct Local {} #[init] - fn init(_: init::Context, _undef: u32) -> (Shared, Local, init::Monotonics) {} + fn init(_: init::Context, _undef: u32) -> (Shared, Local) {} } diff --git a/macros/ui/init-input.stderr b/macros/ui/init-input.stderr index 983c469..e236043 100644 --- a/macros/ui/init-input.stderr +++ b/macros/ui/init-input.stderr @@ -1,5 +1,5 @@ -error: the `#[init]` function must have signature `fn(init::Context) -> (Shared resources struct, Local resources struct, init::Monotonics)` - --> $DIR/init-input.rs:12:8 +error: the `#[init]` function must have signature `fn(init::Context) -> (Shared resources struct, Local resources struct)` + --> ui/init-input.rs:12:8 | -12 | fn init(_: init::Context, _undef: u32) -> (Shared, Local, init::Monotonics) {} +12 | fn init(_: init::Context, _undef: u32) -> (Shared, Local) {} | ^^^^ diff --git a/macros/ui/init-no-context.rs b/macros/ui/init-no-context.rs index a74093a..cdce4c5 100644 --- a/macros/ui/init-no-context.rs +++ b/macros/ui/init-no-context.rs @@ -9,5 +9,5 @@ mod app { struct Local {} #[init] - fn init() -> (Shared, Local, init::Monotonics) {} + fn init() -> (Shared, Local) {} } diff --git a/macros/ui/init-no-context.stderr b/macros/ui/init-no-context.stderr index 742e2ab..28e1fd4 100644 --- a/macros/ui/init-no-context.stderr +++ b/macros/ui/init-no-context.stderr @@ -1,5 +1,5 @@ -error: the `#[init]` function must have signature `fn(init::Context) -> (Shared resources struct, Local resources struct, init::Monotonics)` - --> $DIR/init-no-context.rs:12:8 +error: the `#[init]` function must have signature `fn(init::Context) -> (Shared resources struct, Local resources struct)` + --> ui/init-no-context.rs:12:8 | -12 | fn init() -> (Shared, Local, init::Monotonics) {} +12 | fn init() -> (Shared, Local) {} | ^^^^ diff --git a/macros/ui/init-output.stderr b/macros/ui/init-output.stderr index 03e982c..8bc3c83 100644 --- a/macros/ui/init-output.stderr +++ b/macros/ui/init-output.stderr @@ -1,5 +1,5 @@ -error: the `#[init]` function must have signature `fn(init::Context) -> (Shared resources struct, Local resources struct, init::Monotonics)` - --> $DIR/init-output.rs:6:8 +error: the `#[init]` function must have signature `fn(init::Context) -> (Shared resources struct, Local resources struct)` + --> ui/init-output.rs:6:8 | 6 | fn init(_: init::Context) -> u32 { | ^^^^ diff --git a/macros/ui/init-pub.rs b/macros/ui/init-pub.rs index 43375e4..dd59aa1 100644 --- a/macros/ui/init-pub.rs +++ b/macros/ui/init-pub.rs @@ -9,5 +9,5 @@ mod app { struct Local {} #[init] - pub fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {} + pub fn init(_: init::Context) -> (Shared, Local) {} } diff --git a/macros/ui/init-pub.stderr b/macros/ui/init-pub.stderr index eb68e1e..b1610ed 100644 --- a/macros/ui/init-pub.stderr +++ b/macros/ui/init-pub.stderr @@ -1,5 +1,5 @@ -error: the `#[init]` function must have signature `fn(init::Context) -> (Shared resources struct, Local resources struct, init::Monotonics)` - --> $DIR/init-pub.rs:12:12 +error: the `#[init]` function must have signature `fn(init::Context) -> (Shared resources struct, Local resources struct)` + --> ui/init-pub.rs:12:12 | -12 | pub fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {} +12 | pub fn init(_: init::Context) -> (Shared, Local) {} | ^^^^ diff --git a/macros/ui/init-unsafe.rs b/macros/ui/init-unsafe.rs index b5d391d..4f89baf 100644 --- a/macros/ui/init-unsafe.rs +++ b/macros/ui/init-unsafe.rs @@ -3,5 +3,5 @@ #[rtic_macros::mock_app(device = mock)] mod app { #[init] - unsafe fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {} + unsafe fn init(_: init::Context) -> (Shared, Local) {} } diff --git a/macros/ui/init-unsafe.stderr b/macros/ui/init-unsafe.stderr index 2a48533..fd0b8f3 100644 --- a/macros/ui/init-unsafe.stderr +++ b/macros/ui/init-unsafe.stderr @@ -1,5 +1,5 @@ -error: the `#[init]` function must have signature `fn(init::Context) -> (Shared resources struct, Local resources struct, init::Monotonics)` - --> $DIR/init-unsafe.rs:6:15 +error: the `#[init]` function must have signature `fn(init::Context) -> (Shared resources struct, Local resources struct)` + --> ui/init-unsafe.rs:6:15 | -6 | unsafe fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {} +6 | unsafe fn init(_: init::Context) -> (Shared, Local) {} | ^^^^ diff --git a/macros/ui/interrupt-double.stderr b/macros/ui/interrupt-double.stderr index 62b979b..8db34e2 100644 --- a/macros/ui/interrupt-double.stderr +++ b/macros/ui/interrupt-double.stderr @@ -1,5 +1,5 @@ error: this interrupt is already bound - --> $DIR/interrupt-double.rs:8:20 + --> ui/interrupt-double.rs:8:20 | 8 | #[task(binds = UART0)] | ^^^^^ diff --git a/macros/ui/local-collision-2.rs b/macros/ui/local-collision-2.rs index 7bd092c..08bc8e5 100644 --- a/macros/ui/local-collision-2.rs +++ b/macros/ui/local-collision-2.rs @@ -10,12 +10,9 @@ mod app { a: u32, } - fn bar(_: bar::Context) {} - #[task(local = [a: u8 = 3])] - fn bar(_: bar::Context) {} + async fn bar(_: bar::Context) {} #[init(local = [a: u16 = 2])] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {} + fn init(_: init::Context) -> (Shared, Local) {} } - diff --git a/macros/ui/local-collision-2.stderr b/macros/ui/local-collision-2.stderr index 1e4c5fa..47dbbe3 100644 --- a/macros/ui/local-collision-2.stderr +++ b/macros/ui/local-collision-2.stderr @@ -1,17 +1,17 @@ error: Local resource "a" is used by multiple tasks or collides with multiple definitions - --> $DIR/local-collision-2.rs:10:9 + --> ui/local-collision-2.rs:10:9 | 10 | a: u32, | ^ error: Local resource "a" is used by multiple tasks or collides with multiple definitions - --> $DIR/local-collision-2.rs:18:21 + --> ui/local-collision-2.rs:16:21 | -18 | #[init(local = [a: u16 = 2])] +16 | #[init(local = [a: u16 = 2])] | ^ error: Local resource "a" is used by multiple tasks or collides with multiple definitions - --> $DIR/local-collision-2.rs:15:21 + --> ui/local-collision-2.rs:13:21 | -15 | #[task(local = [a: u8 = 3])] +13 | #[task(local = [a: u8 = 3])] | ^ diff --git a/macros/ui/local-collision.rs b/macros/ui/local-collision.rs index 7dbe976..0e4eef7 100644 --- a/macros/ui/local-collision.rs +++ b/macros/ui/local-collision.rs @@ -11,11 +11,11 @@ mod app { } #[task(local = [a])] - fn foo(_: foo::Context) {} + async fn foo(_: foo::Context) {} #[task(local = [a: u8 = 3])] - fn bar(_: bar::Context) {} + async fn bar(_: bar::Context) {} #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {} + fn init(_: init::Context) -> (Shared, Local) {} } diff --git a/macros/ui/local-collision.stderr b/macros/ui/local-collision.stderr index 1ba1da9..47fbb6e 100644 --- a/macros/ui/local-collision.stderr +++ b/macros/ui/local-collision.stderr @@ -1,11 +1,11 @@ error: Local resource "a" is used by multiple tasks or collides with multiple definitions - --> $DIR/local-collision.rs:10:9 + --> ui/local-collision.rs:10:9 | 10 | a: u32, | ^ error: Local resource "a" is used by multiple tasks or collides with multiple definitions - --> $DIR/local-collision.rs:16:21 + --> ui/local-collision.rs:16:21 | 16 | #[task(local = [a: u8 = 3])] | ^ diff --git a/macros/ui/local-malformed-1.rs b/macros/ui/local-malformed-1.rs index 7efcd9c..219eef5 100644 --- a/macros/ui/local-malformed-1.rs +++ b/macros/ui/local-malformed-1.rs @@ -9,8 +9,8 @@ mod app { struct Local {} #[task(local = [a:])] - fn foo(_: foo::Context) {} + async fn foo(_: foo::Context) {} #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {} + fn init(_: init::Context) -> (Shared, Local) {} } diff --git a/macros/ui/local-malformed-2.rs b/macros/ui/local-malformed-2.rs index ce5f891..d691453 100644 --- a/macros/ui/local-malformed-2.rs +++ b/macros/ui/local-malformed-2.rs @@ -9,8 +9,8 @@ mod app { struct Local {} #[task(local = [a: u32])] - fn foo(_: foo::Context) {} + async fn foo(_: foo::Context) {} #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {} + fn init(_: init::Context) -> (Shared, Local) {} } diff --git a/macros/ui/local-malformed-2.stderr b/macros/ui/local-malformed-2.stderr index ceb0405..0b448f0 100644 --- a/macros/ui/local-malformed-2.stderr +++ b/macros/ui/local-malformed-2.stderr @@ -2,4 +2,4 @@ error: malformed, expected 'IDENT: TYPE = EXPR' --> ui/local-malformed-2.rs:11:21 | 11 | #[task(local = [a: u32])] - | ^ + | ^^^^^^ diff --git a/macros/ui/local-malformed-3.rs b/macros/ui/local-malformed-3.rs index 935dc2c..7eddfa4 100644 --- a/macros/ui/local-malformed-3.rs +++ b/macros/ui/local-malformed-3.rs @@ -9,8 +9,8 @@ mod app { struct Local {} #[task(local = [a: u32 =])] - fn foo(_: foo::Context) {} + async fn foo(_: foo::Context) {} #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {} + fn init(_: init::Context) -> (Shared, Local) {} } diff --git a/macros/ui/local-malformed-4.rs b/macros/ui/local-malformed-4.rs index 49661b5..b913947 100644 --- a/macros/ui/local-malformed-4.rs +++ b/macros/ui/local-malformed-4.rs @@ -9,8 +9,8 @@ mod app { struct Local {} #[task(local = [a = u32])] - fn foo(_: foo::Context) {} + async fn foo(_: foo::Context) {} #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {} + fn init(_: init::Context) -> (Shared, Local) {} } diff --git a/macros/ui/local-not-declared.rs b/macros/ui/local-not-declared.rs index 5a38b3d..7c087e4 100644 --- a/macros/ui/local-not-declared.rs +++ b/macros/ui/local-not-declared.rs @@ -9,8 +9,8 @@ mod app { struct Local {} #[task(local = [A])] - fn foo(_: foo::Context) {} + async fn foo(_: foo::Context) {} #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {} + fn init(_: init::Context) -> (Shared, Local) {} } diff --git a/macros/ui/local-not-declared.stderr b/macros/ui/local-not-declared.stderr index 540b4bb..10d4b04 100644 --- a/macros/ui/local-not-declared.stderr +++ b/macros/ui/local-not-declared.stderr @@ -1,5 +1,5 @@ error: this local resource has NOT been declared - --> $DIR/local-not-declared.rs:11:21 + --> ui/local-not-declared.rs:11:21 | 11 | #[task(local = [A])] | ^ diff --git a/macros/ui/local-pub.rs b/macros/ui/local-pub.rs index 8c51754..42da4f4 100644 --- a/macros/ui/local-pub.rs +++ b/macros/ui/local-pub.rs @@ -2,8 +2,14 @@ #[rtic_macros::mock_app(device = mock)] mod app { + #[shared] + struct Shared {} + #[local] struct Local { pub x: u32, } + + #[init] + fn init(_: init::Context) -> (Shared, Local) {} } diff --git a/macros/ui/local-pub.stderr b/macros/ui/local-pub.stderr index 041bc59..e4814ca 100644 --- a/macros/ui/local-pub.stderr +++ b/macros/ui/local-pub.stderr @@ -1,5 +1,5 @@ error: this field must have inherited / private visibility - --> $DIR/local-pub.rs:7:13 - | -7 | pub x: u32, - | ^ + --> ui/local-pub.rs:10:13 + | +10 | pub x: u32, + | ^ diff --git a/macros/ui/local-shared-attribute.rs b/macros/ui/local-shared-attribute.rs index 1ccce4a..c594b5f 100644 --- a/macros/ui/local-shared-attribute.rs +++ b/macros/ui/local-shared-attribute.rs @@ -2,13 +2,20 @@ #[rtic_macros::mock_app(device = mock)] mod app { + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) {} + #[task(local = [ #[test] a: u32 = 0, // Ok #[test] b, // Error ])] - fn foo(_: foo::Context) { - - } + fn foo(_: foo::Context) {} } diff --git a/macros/ui/local-shared-attribute.stderr b/macros/ui/local-shared-attribute.stderr index 5c15fb5..a8130e8 100644 --- a/macros/ui/local-shared-attribute.stderr +++ b/macros/ui/local-shared-attribute.stderr @@ -1,5 +1,6 @@ error: attributes are not supported here - --> $DIR/local-shared-attribute.rs:8:9 - | -8 | #[test] - | ^ + --> ui/local-shared-attribute.rs:17:9 + | +17 | / #[test] +18 | | b, // Error + | |_________^ diff --git a/macros/ui/local-shared.rs b/macros/ui/local-shared.rs index f6fb491..4e8f9f4 100644 --- a/macros/ui/local-shared.rs +++ b/macros/ui/local-shared.rs @@ -12,7 +12,7 @@ mod app { } #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {} + fn init(_: init::Context) -> (Shared, Local) {} // l2 ok #[idle(local = [l2])] @@ -20,9 +20,9 @@ mod app { // l1 rejected (not local) #[task(priority = 1, local = [l1])] - fn uart0(cx: uart0::Context) {} + async fn uart0(cx: uart0::Context) {} // l1 rejected (not lock_free) #[task(priority = 2, local = [l1])] - fn uart1(cx: uart1::Context) {} + async fn uart1(cx: uart1::Context) {} } diff --git a/macros/ui/local-shared.stderr b/macros/ui/local-shared.stderr index 0d22db3..fceb763 100644 --- a/macros/ui/local-shared.stderr +++ b/macros/ui/local-shared.stderr @@ -1,11 +1,11 @@ error: Local resource "l1" is used by multiple tasks or collides with multiple definitions - --> $DIR/local-shared.rs:22:35 + --> ui/local-shared.rs:22:35 | 22 | #[task(priority = 1, local = [l1])] | ^^ error: Local resource "l1" is used by multiple tasks or collides with multiple definitions - --> $DIR/local-shared.rs:26:35 + --> ui/local-shared.rs:26:35 | 26 | #[task(priority = 2, local = [l1])] | ^^ diff --git a/macros/ui/monotonic-binds-collision-task.rs b/macros/ui/monotonic-binds-collision-task.rs deleted file mode 100644 index 1c58f9d..0000000 --- a/macros/ui/monotonic-binds-collision-task.rs +++ /dev/null @@ -1,10 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[monotonic(binds = Tim1)] - type Fast1 = hal::Tim1Monotonic; - - #[task(binds = Tim1)] - fn foo(_: foo::Context) {} -} diff --git a/macros/ui/monotonic-binds-collision-task.stderr b/macros/ui/monotonic-binds-collision-task.stderr deleted file mode 100644 index 8f84986..0000000 --- a/macros/ui/monotonic-binds-collision-task.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: this interrupt is already bound - --> $DIR/monotonic-binds-collision-task.rs:8:20 - | -8 | #[task(binds = Tim1)] - | ^^^^ diff --git a/macros/ui/monotonic-binds-collision.rs b/macros/ui/monotonic-binds-collision.rs deleted file mode 100644 index 4e54814..0000000 --- a/macros/ui/monotonic-binds-collision.rs +++ /dev/null @@ -1,10 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[monotonic(binds = Tim1)] - type Fast1 = hal::Tim1Monotonic; - - #[monotonic(binds = Tim1)] - type Fast2 = hal::Tim2Monotonic; -} diff --git a/macros/ui/monotonic-binds-collision.stderr b/macros/ui/monotonic-binds-collision.stderr deleted file mode 100644 index 62b764b..0000000 --- a/macros/ui/monotonic-binds-collision.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: this interrupt is already bound - --> $DIR/monotonic-binds-collision.rs:8:25 - | -8 | #[monotonic(binds = Tim1)] - | ^^^^ diff --git a/macros/ui/monotonic-double-binds.rs b/macros/ui/monotonic-double-binds.rs deleted file mode 100644 index 1705dc4..0000000 --- a/macros/ui/monotonic-double-binds.rs +++ /dev/null @@ -1,7 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[monotonic(binds = Tim1, binds = Tim2)] - type Fast = hal::Tim1Monotonic; -} diff --git a/macros/ui/monotonic-double-binds.stderr b/macros/ui/monotonic-double-binds.stderr deleted file mode 100644 index c7313df..0000000 --- a/macros/ui/monotonic-double-binds.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: argument appears more than once - --> $DIR/monotonic-double-binds.rs:5:31 - | -5 | #[monotonic(binds = Tim1, binds = Tim2)] - | ^^^^^ diff --git a/macros/ui/monotonic-double-default.rs b/macros/ui/monotonic-double-default.rs deleted file mode 100644 index dc4eac6..0000000 --- a/macros/ui/monotonic-double-default.rs +++ /dev/null @@ -1,7 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[monotonic(binds = Tim1, default = true, default = false)] - type Fast = hal::Tim1Monotonic; -} diff --git a/macros/ui/monotonic-double-default.stderr b/macros/ui/monotonic-double-default.stderr deleted file mode 100644 index 9819d04..0000000 --- a/macros/ui/monotonic-double-default.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: argument appears more than once - --> $DIR/monotonic-double-default.rs:5:47 - | -5 | #[monotonic(binds = Tim1, default = true, default = false)] - | ^^^^^^^ diff --git a/macros/ui/monotonic-double-prio.rs b/macros/ui/monotonic-double-prio.rs deleted file mode 100644 index 4330ddb..0000000 --- a/macros/ui/monotonic-double-prio.rs +++ /dev/null @@ -1,7 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[monotonic(binds = Tim1, priority = 1, priority = 2)] - type Fast = hal::Tim1Monotonic; -} diff --git a/macros/ui/monotonic-double-prio.stderr b/macros/ui/monotonic-double-prio.stderr deleted file mode 100644 index fa888e2..0000000 --- a/macros/ui/monotonic-double-prio.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: argument appears more than once - --> $DIR/monotonic-double-prio.rs:5:45 - | -5 | #[monotonic(binds = Tim1, priority = 1, priority = 2)] - | ^^^^^^^^ diff --git a/macros/ui/monotonic-double.rs b/macros/ui/monotonic-double.rs deleted file mode 100644 index 3c43fae..0000000 --- a/macros/ui/monotonic-double.rs +++ /dev/null @@ -1,10 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[monotonic(binds = Tim1)] - type Fast = hal::Tim1Monotonic; - - #[monotonic(binds = Tim1)] - type Fast = hal::Tim1Monotonic; -} diff --git a/macros/ui/monotonic-double.stderr b/macros/ui/monotonic-double.stderr deleted file mode 100644 index 9fab84c..0000000 --- a/macros/ui/monotonic-double.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: `#[monotonic(...)]` on a specific type must appear at most once - --> ui/monotonic-double.rs:9:10 - | -9 | type Fast = hal::Tim1Monotonic; - | ^^^^ diff --git a/macros/ui/monotonic-name-collision.rs b/macros/ui/monotonic-name-collision.rs deleted file mode 100644 index d8d4431..0000000 --- a/macros/ui/monotonic-name-collision.rs +++ /dev/null @@ -1,10 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[monotonic(binds = Tim1)] - type Fast1 = hal::Tim1Monotonic; - - #[monotonic(binds = Tim2)] - type Fast1 = hal::Tim2Monotonic; -} diff --git a/macros/ui/monotonic-name-collision.stderr b/macros/ui/monotonic-name-collision.stderr deleted file mode 100644 index 6557ee5..0000000 --- a/macros/ui/monotonic-name-collision.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: `#[monotonic(...)]` on a specific type must appear at most once - --> ui/monotonic-name-collision.rs:9:10 - | -9 | type Fast1 = hal::Tim2Monotonic; - | ^^^^^ diff --git a/macros/ui/monotonic-no-binds.rs b/macros/ui/monotonic-no-binds.rs deleted file mode 100644 index 462d73e..0000000 --- a/macros/ui/monotonic-no-binds.rs +++ /dev/null @@ -1,7 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[monotonic()] - type Fast = hal::Tim1Monotonic; -} diff --git a/macros/ui/monotonic-no-binds.stderr b/macros/ui/monotonic-no-binds.stderr deleted file mode 100644 index 0ef7b60..0000000 --- a/macros/ui/monotonic-no-binds.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: `binds = ...` is missing - --> $DIR/monotonic-no-binds.rs:5:17 - | -5 | #[monotonic()] - | ^ diff --git a/macros/ui/monotonic-no-paran.rs b/macros/ui/monotonic-no-paran.rs deleted file mode 100644 index e294bc8..0000000 --- a/macros/ui/monotonic-no-paran.rs +++ /dev/null @@ -1,8 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[monotonic] - type Fast = hal::Tim1Monotonic; -} - diff --git a/macros/ui/monotonic-no-paran.stderr b/macros/ui/monotonic-no-paran.stderr deleted file mode 100644 index c2b32c5..0000000 --- a/macros/ui/monotonic-no-paran.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: expected opening ( in #[monotonic( ... )] - --> ui/monotonic-no-paran.rs:5:7 - | -5 | #[monotonic] - | ^^^^^^^^^ diff --git a/macros/ui/monotonic-timer-collision.rs b/macros/ui/monotonic-timer-collision.rs deleted file mode 100644 index 5663ad7..0000000 --- a/macros/ui/monotonic-timer-collision.rs +++ /dev/null @@ -1,10 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[monotonic(binds = Tim1)] - type Fast1 = hal::Tim1Monotonic; - - #[monotonic(binds = Tim2)] - type Fast2 = hal::Tim1Monotonic; -} diff --git a/macros/ui/monotonic-timer-collision.stderr b/macros/ui/monotonic-timer-collision.stderr deleted file mode 100644 index 239b96b..0000000 --- a/macros/ui/monotonic-timer-collision.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: this type is already used by another monotonic - --> $DIR/monotonic-timer-collision.rs:9:18 - | -9 | type Fast2 = hal::Tim1Monotonic; - | ^^^ diff --git a/macros/ui/monotonic-with-attrs.rs b/macros/ui/monotonic-with-attrs.rs deleted file mode 100644 index 7c63fbb..0000000 --- a/macros/ui/monotonic-with-attrs.rs +++ /dev/null @@ -1,8 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[no_mangle] - #[monotonic(binds = Tim1)] - type Fast = hal::Tim1Monotonic; -} diff --git a/macros/ui/monotonic-with-attrs.stderr b/macros/ui/monotonic-with-attrs.stderr deleted file mode 100644 index 62655d8..0000000 --- a/macros/ui/monotonic-with-attrs.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: Monotonic does not support attributes other than `#[cfg]` - --> $DIR/monotonic-with-attrs.rs:5:7 - | -5 | #[no_mangle] - | ^^^^^^^^^ diff --git a/macros/ui/pub-local.stderr b/macros/ui/pub-local.stderr deleted file mode 100644 index dee818c..0000000 --- a/macros/ui/pub-local.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: this field must have inherited / private visibility - --> $DIR/pub-local.rs:7:13 - | -7 | pub x: u32, - | ^ diff --git a/macros/ui/pub-shared.stderr b/macros/ui/pub-shared.stderr deleted file mode 100644 index 0fdb1ff..0000000 --- a/macros/ui/pub-shared.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: this field must have inherited / private visibility - --> $DIR/pub-shared.rs:7:13 - | -7 | pub x: u32, - | ^ diff --git a/macros/ui/shared-lock-free.rs b/macros/ui/shared-lock-free.rs index c7f8a16..b3a4b9c 100644 --- a/macros/ui/shared-lock-free.rs +++ b/macros/ui/shared-lock-free.rs @@ -17,7 +17,7 @@ mod app { struct Local {} #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {} + fn init(_: init::Context) -> (Shared, Local) {} // e2 ok #[idle(shared = [e2])] @@ -27,12 +27,12 @@ mod app { } // e1 rejected (not lock_free) - #[task(priority = 1, shared = [e1])] + #[task(binds = UART0, priority = 1, shared = [e1])] fn uart0(cx: uart0::Context) { *cx.resources.e1 += 10; } // e1 rejected (not lock_free) - #[task(priority = 2, shared = [e1])] + #[task(binds = UART1, priority = 2, shared = [e1])] fn uart1(cx: uart1::Context) {} } diff --git a/macros/ui/shared-lock-free.stderr b/macros/ui/shared-lock-free.stderr deleted file mode 100644 index c6820e8..0000000 --- a/macros/ui/shared-lock-free.stderr +++ /dev/null @@ -1,17 +0,0 @@ -error: Lock free shared resource "e1" is used by tasks at different priorities - --> $DIR/shared-lock-free.rs:9:9 - | -9 | e1: u32, - | ^^ - -error: Shared resource "e1" is declared lock free but used by tasks at different priorities - --> $DIR/shared-lock-free.rs:30:36 - | -30 | #[task(priority = 1, shared = [e1])] - | ^^ - -error: Shared resource "e1" is declared lock free but used by tasks at different priorities - --> $DIR/shared-lock-free.rs:36:36 - | -36 | #[task(priority = 2, shared = [e1])] - | ^^ diff --git a/macros/ui/shared-not-declared.rs b/macros/ui/shared-not-declared.rs index aca4178..5fef534 100644 --- a/macros/ui/shared-not-declared.rs +++ b/macros/ui/shared-not-declared.rs @@ -9,8 +9,8 @@ mod app { struct Local {} #[task(shared = [A])] - fn foo(_: foo::Context) {} + async fn foo(_: foo::Context) {} #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {} + fn init(_: init::Context) -> (Shared, Local) {} } diff --git a/macros/ui/shared-not-declared.stderr b/macros/ui/shared-not-declared.stderr index c174251..7c5fb32 100644 --- a/macros/ui/shared-not-declared.stderr +++ b/macros/ui/shared-not-declared.stderr @@ -1,5 +1,5 @@ error: this shared resource has NOT been declared - --> $DIR/shared-not-declared.rs:11:22 + --> ui/shared-not-declared.rs:11:22 | 11 | #[task(shared = [A])] | ^ diff --git a/macros/ui/shared-pub.stderr b/macros/ui/shared-pub.stderr index 8f761c6..7148893 100644 --- a/macros/ui/shared-pub.stderr +++ b/macros/ui/shared-pub.stderr @@ -1,5 +1,5 @@ error: this field must have inherited / private visibility - --> $DIR/shared-pub.rs:7:13 + --> ui/shared-pub.rs:7:13 | 7 | pub x: u32, | ^ diff --git a/macros/ui/task-divergent.rs b/macros/ui/task-divergent.rs index 5a471f3..ffe2dc0 100644 --- a/macros/ui/task-divergent.rs +++ b/macros/ui/task-divergent.rs @@ -3,7 +3,7 @@ #[rtic_macros::mock_app(device = mock)] mod app { #[task] - fn foo(_: foo::Context) -> ! { + async fn foo(_: foo::Context) -> ! { loop {} } } diff --git a/macros/ui/task-divergent.stderr b/macros/ui/task-divergent.stderr index b25ca5d..bd22bd3 100644 --- a/macros/ui/task-divergent.stderr +++ b/macros/ui/task-divergent.stderr @@ -1,5 +1,5 @@ -error: this task handler must have type signature `(async) fn(foo::Context, ..)` - --> ui/task-divergent.rs:6:8 +error: this task handler must have type signature `async fn(foo::Context)` + --> ui/task-divergent.rs:6:14 | -6 | fn foo(_: foo::Context) -> ! { - | ^^^ +6 | async fn foo(_: foo::Context) -> ! { + | ^^^ diff --git a/macros/ui/task-double-capacity.rs b/macros/ui/task-double-capacity.rs deleted file mode 100644 index 806d973..0000000 --- a/macros/ui/task-double-capacity.rs +++ /dev/null @@ -1,7 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[task(capacity = 1, capacity = 2)] - fn foo(_: foo::Context) {} -} diff --git a/macros/ui/task-double-capacity.stderr b/macros/ui/task-double-capacity.stderr deleted file mode 100644 index f73bca5..0000000 --- a/macros/ui/task-double-capacity.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: argument appears more than once - --> $DIR/task-double-capacity.rs:5:26 - | -5 | #[task(capacity = 1, capacity = 2)] - | ^^^^^^^^ diff --git a/macros/ui/task-double-local.rs b/macros/ui/task-double-local.rs index 2e465d7..c5277e2 100644 --- a/macros/ui/task-double-local.rs +++ b/macros/ui/task-double-local.rs @@ -3,5 +3,5 @@ #[rtic_macros::mock_app(device = mock)] mod app { #[task(local = [A], local = [B])] - fn foo(_: foo::Context) {} + async fn foo(_: foo::Context) {} } diff --git a/macros/ui/task-double-local.stderr b/macros/ui/task-double-local.stderr index 654ed33..91ed844 100644 --- a/macros/ui/task-double-local.stderr +++ b/macros/ui/task-double-local.stderr @@ -1,5 +1,5 @@ error: argument appears more than once - --> $DIR/task-double-local.rs:5:25 + --> ui/task-double-local.rs:5:25 | 5 | #[task(local = [A], local = [B])] | ^^^^^ diff --git a/macros/ui/task-double-priority.rs b/macros/ui/task-double-priority.rs index 0a2ef0d..5c8bd5b 100644 --- a/macros/ui/task-double-priority.rs +++ b/macros/ui/task-double-priority.rs @@ -3,5 +3,5 @@ #[rtic_macros::mock_app(device = mock)] mod app { #[task(priority = 1, priority = 2)] - fn foo(_: foo::Context) {} + async fn foo(_: foo::Context) {} } diff --git a/macros/ui/task-double-priority.stderr b/macros/ui/task-double-priority.stderr index 3d06dc6..b3c814a 100644 --- a/macros/ui/task-double-priority.stderr +++ b/macros/ui/task-double-priority.stderr @@ -1,5 +1,5 @@ error: argument appears more than once - --> $DIR/task-double-priority.rs:5:26 + --> ui/task-double-priority.rs:5:26 | 5 | #[task(priority = 1, priority = 2)] | ^^^^^^^^ diff --git a/macros/ui/task-double-shared.rs b/macros/ui/task-double-shared.rs index 3b4d411..f9812d3 100644 --- a/macros/ui/task-double-shared.rs +++ b/macros/ui/task-double-shared.rs @@ -3,5 +3,5 @@ #[rtic_macros::mock_app(device = mock)] mod app { #[task(shared = [A], shared = [B])] - fn foo(_: foo::Context) {} + async fn foo(_: foo::Context) {} } diff --git a/macros/ui/task-double-shared.stderr b/macros/ui/task-double-shared.stderr index 6952f06..bb90212 100644 --- a/macros/ui/task-double-shared.stderr +++ b/macros/ui/task-double-shared.stderr @@ -1,5 +1,5 @@ error: argument appears more than once - --> $DIR/task-double-shared.rs:5:26 + --> ui/task-double-shared.rs:5:26 | 5 | #[task(shared = [A], shared = [B])] | ^^^^^^ diff --git a/macros/ui/task-idle.rs b/macros/ui/task-idle.rs index 3be6e28..353c782 100644 --- a/macros/ui/task-idle.rs +++ b/macros/ui/task-idle.rs @@ -9,5 +9,5 @@ mod app { // name collides with `#[idle]` function #[task] - fn foo(_: foo::Context) {} + async fn foo(_: foo::Context) {} } diff --git a/macros/ui/task-idle.stderr b/macros/ui/task-idle.stderr index ba4fc94..4ccc113 100644 --- a/macros/ui/task-idle.stderr +++ b/macros/ui/task-idle.stderr @@ -1,5 +1,5 @@ error: this identifier has already been used - --> $DIR/task-idle.rs:12:8 + --> ui/task-idle.rs:12:14 | -12 | fn foo(_: foo::Context) {} - | ^^^ +12 | async fn foo(_: foo::Context) {} + | ^^^ diff --git a/macros/ui/task-init.rs b/macros/ui/task-init.rs index bab3805..e58fdce 100644 --- a/macros/ui/task-init.rs +++ b/macros/ui/task-init.rs @@ -9,9 +9,9 @@ mod app { struct Local {} #[init] - fn foo(_: foo::Context) -> (Shared, Local, foo::Monotonics) {} + fn foo(_: foo::Context) -> (Shared, Local) {} // name collides with `#[idle]` function #[task] - fn foo(_: foo::Context) {} + async fn foo(_: foo::Context) {} } diff --git a/macros/ui/task-init.stderr b/macros/ui/task-init.stderr index 911af37..161e194 100644 --- a/macros/ui/task-init.stderr +++ b/macros/ui/task-init.stderr @@ -1,5 +1,5 @@ error: this identifier has already been used - --> $DIR/task-init.rs:16:8 + --> ui/task-init.rs:16:14 | -16 | fn foo(_: foo::Context) {} - | ^^^ +16 | async fn foo(_: foo::Context) {} + | ^^^ diff --git a/macros/ui/task-interrupt.rs b/macros/ui/task-interrupt.rs index 5e063de..3d50bd8 100644 --- a/macros/ui/task-interrupt.rs +++ b/macros/ui/task-interrupt.rs @@ -6,5 +6,5 @@ mod app { fn foo(_: foo::Context) {} #[task] - fn foo(_: foo::Context) {} + async fn foo(_: foo::Context) {} } diff --git a/macros/ui/task-interrupt.stderr b/macros/ui/task-interrupt.stderr index 6efb0f9..087b6c6 100644 --- a/macros/ui/task-interrupt.stderr +++ b/macros/ui/task-interrupt.stderr @@ -1,5 +1,5 @@ error: this task is defined multiple times - --> $DIR/task-interrupt.rs:9:8 + --> ui/task-interrupt.rs:9:14 | -9 | fn foo(_: foo::Context) {} - | ^^^ +9 | async fn foo(_: foo::Context) {} + | ^^^ diff --git a/macros/ui/task-no-context.rs b/macros/ui/task-no-context.rs index e2da625..55e8c3b 100644 --- a/macros/ui/task-no-context.rs +++ b/macros/ui/task-no-context.rs @@ -3,5 +3,5 @@ #[rtic_macros::mock_app(device = mock)] mod app { #[task] - fn foo() {} + async fn foo() {} } diff --git a/macros/ui/task-no-context.stderr b/macros/ui/task-no-context.stderr index 8bf3438..91239a1 100644 --- a/macros/ui/task-no-context.stderr +++ b/macros/ui/task-no-context.stderr @@ -1,5 +1,5 @@ -error: this task handler must have type signature `(async) fn(foo::Context, ..)` - --> ui/task-no-context.rs:6:8 +error: this task handler must have type signature `async fn(foo::Context)` + --> ui/task-no-context.rs:6:14 | -6 | fn foo() {} - | ^^^ +6 | async fn foo() {} + | ^^^ diff --git a/macros/ui/task-priority-too-high.rs b/macros/ui/task-priority-too-high.rs index 8c32beb..f33ba56 100644 --- a/macros/ui/task-priority-too-high.rs +++ b/macros/ui/task-priority-too-high.rs @@ -3,5 +3,5 @@ #[rtic_macros::mock_app(device = mock)] mod app { #[task(priority = 256)] - fn foo(_: foo::Context) {} + async fn foo(_: foo::Context) {} } diff --git a/macros/ui/task-pub.rs b/macros/ui/task-pub.rs index 3cbd523..1ae533f 100644 --- a/macros/ui/task-pub.rs +++ b/macros/ui/task-pub.rs @@ -3,5 +3,5 @@ #[rtic_macros::mock_app(device = mock)] mod app { #[task] - pub fn foo(_: foo::Context) {} + pub async fn foo(_: foo::Context) {} } diff --git a/macros/ui/task-pub.stderr b/macros/ui/task-pub.stderr index 56e09b1..72c4e63 100644 --- a/macros/ui/task-pub.stderr +++ b/macros/ui/task-pub.stderr @@ -1,5 +1,5 @@ -error: this task handler must have type signature `(async) fn(foo::Context, ..)` - --> ui/task-pub.rs:6:12 +error: this task handler must have type signature `async fn(foo::Context)` + --> ui/task-pub.rs:6:18 | -6 | pub fn foo(_: foo::Context) {} - | ^^^ +6 | pub async fn foo(_: foo::Context) {} + | ^^^ diff --git a/macros/ui/task-unsafe.rs b/macros/ui/task-unsafe.rs index 44255f0..a8383ef 100644 --- a/macros/ui/task-unsafe.rs +++ b/macros/ui/task-unsafe.rs @@ -3,5 +3,5 @@ #[rtic_macros::mock_app(device = mock)] mod app { #[task] - unsafe fn foo(_: foo::Context) {} + async unsafe fn foo(_: foo::Context) {} } diff --git a/macros/ui/task-unsafe.stderr b/macros/ui/task-unsafe.stderr index 424c5af..4908481 100644 --- a/macros/ui/task-unsafe.stderr +++ b/macros/ui/task-unsafe.stderr @@ -1,5 +1,5 @@ -error: this task handler must have type signature `(async) fn(foo::Context, ..)` - --> ui/task-unsafe.rs:6:15 +error: this task handler must have type signature `async fn(foo::Context)` + --> ui/task-unsafe.rs:6:21 | -6 | unsafe fn foo(_: foo::Context) {} - | ^^^ +6 | async unsafe fn foo(_: foo::Context) {} + | ^^^ diff --git a/macros/ui/task-zero-prio.rs b/macros/ui/task-zero-prio.rs new file mode 100644 index 0000000..de3c86f --- /dev/null +++ b/macros/ui/task-zero-prio.rs @@ -0,0 +1,19 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) {} + + #[task(priority = 0)] + fn foo(_: foo::Context) {} + + #[idle] + fn idle(_: idle::Context) -> ! {} +} diff --git a/macros/ui/task-zero-prio.stderr b/macros/ui/task-zero-prio.stderr new file mode 100644 index 0000000..f2d8223 --- /dev/null +++ b/macros/ui/task-zero-prio.stderr @@ -0,0 +1,5 @@ +error: this task handler must have type signature `async fn(foo::Context)` + --> ui/task-zero-prio.rs:15:8 + | +15 | fn foo(_: foo::Context) {} + | ^^^ -- cgit v1.2.3 From cbe592688047e41ebfd0f15e7bf5799f81bfcd4a Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Sun, 8 Jan 2023 16:36:48 +0100 Subject: Fix failing UI test --- macros/src/syntax/analyze.rs | 18 ++++++++++-------- macros/ui/shared-lock-free.stderr | 17 +++++++++++++++++ 2 files changed, 27 insertions(+), 8 deletions(-) create mode 100644 macros/ui/shared-lock-free.stderr diff --git a/macros/src/syntax/analyze.rs b/macros/src/syntax/analyze.rs index ff0577d..dd5a9b4 100644 --- a/macros/src/syntax/analyze.rs +++ b/macros/src/syntax/analyze.rs @@ -13,17 +13,17 @@ use crate::syntax::{ pub(crate) fn app(app: &App) -> Result { // Collect all tasks into a vector - type TaskName = String; + type TaskName = Ident; type Priority = u8; // The task list is a Tuple (Name, Shared Resources, Local Resources, Priority) let task_resources_list: Vec<(TaskName, Vec<&Ident>, &LocalResources, Priority)> = Some(&app.init) .iter() - .map(|ht| ("init".to_string(), Vec::new(), &ht.args.local_resources, 0)) + .map(|ht| (ht.name.clone(), Vec::new(), &ht.args.local_resources, 0)) .chain(app.idle.iter().map(|ht| { ( - "idle".to_string(), + ht.name.clone(), ht.args .shared_resources .iter() @@ -35,7 +35,7 @@ pub(crate) fn app(app: &App) -> Result { })) .chain(app.software_tasks.iter().map(|(name, ht)| { ( - name.to_string(), + name.clone(), ht.args .shared_resources .iter() @@ -47,7 +47,7 @@ pub(crate) fn app(app: &App) -> Result { })) .chain(app.hardware_tasks.iter().map(|(name, ht)| { ( - name.to_string(), + name.clone(), ht.args .shared_resources .iter() @@ -77,16 +77,17 @@ pub(crate) fn app(app: &App) -> Result { for r in tr { // Get all uses of resources annotated lock_free if lf_res == r { - // lock_free resources are not allowed in async tasks - error.push(syn::Error::new( + // Check so async tasks do not use lock free resources + if app.software_tasks.get(task).is_some() { + error.push(syn::Error::new( r.span(), format!( "Lock free shared resource {:?} is used by an async tasks, which is forbidden", r.to_string(), ), )); + } - // TODO: Should this be removed? // HashMap returns the previous existing object if old.key == new.key if let Some(lf_res) = lf_hash.insert(r.to_string(), (task, r, priority)) { // Check if priority differ, if it does, append to @@ -95,6 +96,7 @@ pub(crate) fn app(app: &App) -> Result { lf_res_with_error.push(lf_res.1); lf_res_with_error.push(r); } + // If the resource already violates lock free properties if lf_res_with_error.contains(&r) { lf_res_with_error.push(lf_res.1); diff --git a/macros/ui/shared-lock-free.stderr b/macros/ui/shared-lock-free.stderr new file mode 100644 index 0000000..51e99a0 --- /dev/null +++ b/macros/ui/shared-lock-free.stderr @@ -0,0 +1,17 @@ +error: Lock free shared resource "e1" is used by tasks at different priorities + --> ui/shared-lock-free.rs:9:9 + | +9 | e1: u32, + | ^^ + +error: Shared resource "e1" is declared lock free but used by tasks at different priorities + --> ui/shared-lock-free.rs:30:51 + | +30 | #[task(binds = UART0, priority = 1, shared = [e1])] + | ^^ + +error: Shared resource "e1" is declared lock free but used by tasks at different priorities + --> ui/shared-lock-free.rs:36:51 + | +36 | #[task(binds = UART1, priority = 2, shared = [e1])] + | ^^ -- cgit v1.2.3 From 9a67f00a30f14df3b9635913f728afd0b40c138d Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Sun, 8 Jan 2023 19:16:36 +0100 Subject: Fix typos --- examples/init.rs | 2 +- macros/src/codegen/module.rs | 2 +- xtask/src/command.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/init.rs b/examples/init.rs index c807a3c..d37903e 100644 --- a/examples/init.rs +++ b/examples/init.rs @@ -29,7 +29,7 @@ mod app { let _x: &'static mut u32 = cx.local.x; // Access to the critical section token, - // to indicate that this is a critical seciton + // to indicate that this is a critical section let _cs_token: bare_metal::CriticalSection = cx.cs; hprintln!("init").unwrap(); diff --git a/macros/src/codegen/module.rs b/macros/src/codegen/module.rs index a64abd8..c6f7690 100644 --- a/macros/src/codegen/module.rs +++ b/macros/src/codegen/module.rs @@ -140,7 +140,7 @@ pub fn codegen(ctxt: Context, app: &App, analysis: &Analysis) -> TokenStream2 { let interrupt = &analysis .interrupts .get(&priority) - .expect("RTIC-ICE: interrupt identifer not found") + .expect("RTIC-ICE: interrupt identifier not found") .0; let internal_spawn_ident = util::internal_task_ident(name, "spawn"); diff --git a/xtask/src/command.rs b/xtask/src/command.rs index 889540c..418f440 100644 --- a/xtask/src/command.rs +++ b/xtask/src/command.rs @@ -136,7 +136,7 @@ pub fn run_command(command: &CargoCommand) -> anyhow::Result { }) } -/// Check if `run` was sucessful. +/// Check if `run` was successful. /// returns Ok in case the run went as expected, /// Err otherwise pub fn run_successful(run: &RunResult, expected_output_file: String) -> Result<(), TestRunError> { -- cgit v1.2.3 From ceaf3613d3256f60b139a4f93220e3c298603b83 Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Sun, 8 Jan 2023 19:40:31 +0100 Subject: Update semihosting --- examples/async-task-multiple-prios.rs | 16 ++++------- examples/async-task.rs | 8 +++--- examples/big-struct-opt.rs | 4 +-- examples/binds.rs | 7 ++--- examples/complex.rs | 53 +++++++++++++++++------------------ examples/destructure.rs | 4 +-- examples/extern_binds.rs | 6 ++-- examples/extern_spawn.rs | 2 +- examples/generics.rs | 6 ++-- examples/hardware.rs | 7 ++--- examples/idle-wfi.rs | 4 +-- examples/idle.rs | 4 +-- examples/init.rs | 2 +- examples/locals.rs | 6 ++-- examples/lock.rs | 10 +++---- examples/multilock.rs | 2 +- examples/not-sync.rs | 6 ++-- examples/only-shared-access.rs | 4 +-- examples/preempt.rs | 10 +++---- examples/ramfunc.rs | 2 +- examples/resource-user-struct.rs | 4 +-- examples/shared.rs | 2 +- examples/spawn.rs | 4 +-- examples/static.rs | 2 +- examples/task.rs | 10 +++---- 25 files changed, 88 insertions(+), 97 deletions(-) diff --git a/examples/async-task-multiple-prios.rs b/examples/async-task-multiple-prios.rs index 2f3a0f7..f614820 100644 --- a/examples/async-task-multiple-prios.rs +++ b/examples/async-task-multiple-prios.rs @@ -24,8 +24,8 @@ mod app { struct Local {} #[init] - fn init(cx: init::Context) -> (Shared, Local) { - hprintln!("init").unwrap(); + fn init(_: init::Context) -> (Shared, Local) { + hprintln!("init"); async_task1::spawn().ok(); async_task2::spawn().ok(); @@ -51,8 +51,7 @@ mod app { *a += 1; *a }) - ) - .ok(); + ); } #[task(priority = 1, shared = [a, b])] @@ -63,8 +62,7 @@ mod app { *a += 1; *a }) - ) - .ok(); + ); } #[task(priority = 2, shared = [a, b])] @@ -75,8 +73,7 @@ mod app { *a += 1; *a }) - ) - .ok(); + ); } #[task(priority = 2, shared = [a, b])] @@ -87,7 +84,6 @@ mod app { *a += 1; *a }) - ) - .ok(); + ); } } diff --git a/examples/async-task.rs b/examples/async-task.rs index 210a865..780bc08 100644 --- a/examples/async-task.rs +++ b/examples/async-task.rs @@ -24,7 +24,7 @@ mod app { #[init] fn init(_cx: init::Context) -> (Shared, Local) { - hprintln!("init").unwrap(); + hprintln!("init"); async_task::spawn().unwrap(); async_task2::spawn().unwrap(); @@ -44,18 +44,18 @@ mod app { #[task(binds = UART1, shared = [a])] fn hw_task(cx: hw_task::Context) { let hw_task::SharedResources { a: _, .. } = cx.shared; - hprintln!("hello from hw").ok(); + hprintln!("hello from hw"); } #[task(shared = [a])] async fn async_task(cx: async_task::Context) { let async_task::SharedResources { a: _, .. } = cx.shared; - hprintln!("hello from async").ok(); + hprintln!("hello from async"); } #[task(priority = 2, shared = [a])] async fn async_task2(cx: async_task2::Context) { let async_task2::SharedResources { a: _, .. } = cx.shared; - hprintln!("hello from async2").ok(); + hprintln!("hello from async2"); } } diff --git a/examples/big-struct-opt.rs b/examples/big-struct-opt.rs index 4bf93b2..3100a0e 100644 --- a/examples/big-struct-opt.rs +++ b/examples/big-struct-opt.rs @@ -67,13 +67,13 @@ mod app { fn uart0(mut cx: uart0::Context) { cx.shared .big_struct - .lock(|b| hprintln!("uart0 data:{:?}", &b.data[0..5]).unwrap()); + .lock(|b| hprintln!("uart0 data:{:?}", &b.data[0..5])); } #[task(shared = [big_struct], priority = 2)] async fn async_task(mut cx: async_task::Context) { cx.shared .big_struct - .lock(|b| hprintln!("async_task data:{:?}", &b.data[0..5]).unwrap()); + .lock(|b| hprintln!("async_task data:{:?}", &b.data[0..5])); } } diff --git a/examples/binds.rs b/examples/binds.rs index ec25ccc..d78dffb 100644 --- a/examples/binds.rs +++ b/examples/binds.rs @@ -23,14 +23,14 @@ mod app { fn init(_: init::Context) -> (Shared, Local) { rtic::pend(Interrupt::UART0); - hprintln!("init").unwrap(); + hprintln!("init"); (Shared {}, Local {}) } #[idle] fn idle(_: idle::Context) -> ! { - hprintln!("idle").unwrap(); + hprintln!("idle"); rtic::pend(Interrupt::UART0); @@ -49,7 +49,6 @@ mod app { "foo called {} time{}", *cx.local.times, if *cx.local.times > 1 { "s" } else { "" } - ) - .unwrap(); + ); } } diff --git a/examples/complex.rs b/examples/complex.rs index df9c862..ab39792 100644 --- a/examples/complex.rs +++ b/examples/complex.rs @@ -25,7 +25,7 @@ mod app { #[init] fn init(_: init::Context) -> (Shared, Local) { - hprintln!("init").unwrap(); + hprintln!("init"); ( Shared { @@ -39,31 +39,31 @@ mod app { #[idle(shared = [s2, s3])] fn idle(mut cx: idle::Context) -> ! { - hprintln!("idle p0 started").ok(); + hprintln!("idle p0 started"); rtic::pend(Interrupt::GPIOC); cx.shared.s3.lock(|s| { - hprintln!("idle enter lock s3 {}", s).ok(); - hprintln!("idle pend t0").ok(); + hprintln!("idle enter lock s3 {}", s); + hprintln!("idle pend t0"); rtic::pend(Interrupt::GPIOA); // t0 p2, with shared ceiling 3 - hprintln!("idle pend t1").ok(); + hprintln!("idle pend t1"); rtic::pend(Interrupt::GPIOB); // t1 p3, with shared ceiling 3 - hprintln!("idle pend t2").ok(); + hprintln!("idle pend t2"); rtic::pend(Interrupt::GPIOC); // t2 p4, no sharing - hprintln!("idle still in lock s3 {}", s).ok(); + hprintln!("idle still in lock s3 {}", s); }); - hprintln!("\nback in idle").ok(); + hprintln!("\nback in idle"); cx.shared.s2.lock(|s| { - hprintln!("enter lock s2 {}", s).ok(); - hprintln!("idle pend t0").ok(); + hprintln!("enter lock s2 {}", s); + hprintln!("idle pend t0"); rtic::pend(Interrupt::GPIOA); // t0 p2, with shared ceiling 2 - hprintln!("idle pend t1").ok(); + hprintln!("idle pend t1"); rtic::pend(Interrupt::GPIOB); // t1 p3, no sharing - hprintln!("idle pend t2").ok(); + hprintln!("idle pend t2"); rtic::pend(Interrupt::GPIOC); // t2 p4, no sharing - hprintln!("idle still in lock s2 {}", s).ok(); + hprintln!("idle still in lock s2 {}", s); }); - hprintln!("\nidle exit").ok(); + hprintln!("\nidle exit"); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator @@ -81,9 +81,8 @@ mod app { "t0 p2 called {} time{}", *cx.local.times, if *cx.local.times > 1 { "s" } else { "" } - ) - .ok(); - hprintln!("t0 p2 exit").ok(); + ); + hprintln!("t0 p2 exit"); } #[task(binds = GPIOB, priority = 3, local = [times: u32 = 0], shared = [s3, s4])] @@ -95,19 +94,18 @@ mod app { "t1 p3 called {} time{}", *cx.local.times, if *cx.local.times > 1 { "s" } else { "" } - ) - .ok(); + ); cx.shared.s4.lock(|s| { - hprintln!("t1 enter lock s4 {}", s).ok(); - hprintln!("t1 pend t0").ok(); + hprintln!("t1 enter lock s4 {}", s); + hprintln!("t1 pend t0"); rtic::pend(Interrupt::GPIOA); // t0 p2, with shared ceiling 2 - hprintln!("t1 pend t2").ok(); + hprintln!("t1 pend t2"); rtic::pend(Interrupt::GPIOC); // t2 p4, no sharing - hprintln!("t1 still in lock s4 {}", s).ok(); + hprintln!("t1 still in lock s4 {}", s); }); - hprintln!("t1 p3 exit").ok(); + hprintln!("t1 p3 exit"); } #[task(binds = GPIOC, priority = 4, local = [times: u32 = 0], shared = [s4])] @@ -119,13 +117,12 @@ mod app { "t2 p4 called {} time{}", *cx.local.times, if *cx.local.times > 1 { "s" } else { "" } - ) - .unwrap(); + ); cx.shared.s4.lock(|s| { - hprintln!("enter lock s4 {}", s).ok(); + hprintln!("enter lock s4 {}", s); *s += 1; }); - hprintln!("t3 p4 exit").ok(); + hprintln!("t3 p4 exit"); } } diff --git a/examples/destructure.rs b/examples/destructure.rs index 89336bf..dc5d8ef 100644 --- a/examples/destructure.rs +++ b/examples/destructure.rs @@ -43,7 +43,7 @@ mod app { let b = cx.shared.b; let c = cx.shared.c; - hprintln!("foo: a = {}, b = {}, c = {}", a, b, c).unwrap(); + hprintln!("foo: a = {}, b = {}, c = {}", a, b, c); } // De-structure-ing syntax @@ -51,6 +51,6 @@ mod app { async fn bar(cx: bar::Context) { let bar::SharedResources { a, b, c, .. } = cx.shared; - hprintln!("bar: a = {}, b = {}, c = {}", a, b, c).unwrap(); + hprintln!("bar: a = {}, b = {}, c = {}", a, b, c); } } diff --git a/examples/extern_binds.rs b/examples/extern_binds.rs index c9fc108..23b99d5 100644 --- a/examples/extern_binds.rs +++ b/examples/extern_binds.rs @@ -10,7 +10,7 @@ use panic_semihosting as _; // Free function implementing the interrupt bound task `foo`. fn foo(_: app::foo::Context) { - hprintln!("foo called").ok(); + hprintln!("foo called"); } #[rtic::app(device = lm3s6965)] @@ -29,14 +29,14 @@ mod app { fn init(_: init::Context) -> (Shared, Local) { rtic::pend(Interrupt::UART0); - hprintln!("init").unwrap(); + hprintln!("init"); (Shared {}, Local {}) } #[idle] fn idle(_: idle::Context) -> ! { - hprintln!("idle").unwrap(); + hprintln!("idle"); rtic::pend(Interrupt::UART0); diff --git a/examples/extern_spawn.rs b/examples/extern_spawn.rs index 2d9d7b6..8a3928d 100644 --- a/examples/extern_spawn.rs +++ b/examples/extern_spawn.rs @@ -12,7 +12,7 @@ use panic_semihosting as _; // Free function implementing the spawnable task `foo`. // Notice, you need to indicate an anonymous lifetime <'a_> async fn foo(_c: app::foo::Context<'_>) { - hprintln!("foo").unwrap(); + hprintln!("foo"); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator } diff --git a/examples/generics.rs b/examples/generics.rs index a73b00f..7117349 100644 --- a/examples/generics.rs +++ b/examples/generics.rs @@ -32,7 +32,7 @@ mod app { #[task(binds = UART0, shared = [shared], local = [state: u32 = 0])] fn uart0(c: uart0::Context) { - hprintln!("UART0(STATE = {})", *c.local.state).unwrap(); + hprintln!("UART0(STATE = {})", *c.local.state); // second argument has type `shared::shared` super::advance(c.local.state, c.shared.shared); @@ -44,7 +44,7 @@ mod app { #[task(binds = UART1, priority = 2, shared = [shared], local = [state: u32 = 0])] fn uart1(c: uart1::Context) { - hprintln!("UART1(STATE = {})", *c.local.state).unwrap(); + hprintln!("UART1(STATE = {})", *c.local.state); // second argument has type `shared::shared` super::advance(c.local.state, c.shared.shared); @@ -61,5 +61,5 @@ fn advance(state: &mut u32, mut shared: impl Mutex) { (old, *shared) }); - hprintln!("shared: {} -> {}", old, new).unwrap(); + hprintln!("shared: {} -> {}", old, new); } diff --git a/examples/hardware.rs b/examples/hardware.rs index 8e4f423..d3ceab9 100644 --- a/examples/hardware.rs +++ b/examples/hardware.rs @@ -24,7 +24,7 @@ mod app { // `init` returns because interrupts are disabled rtic::pend(Interrupt::UART0); // equivalent to NVIC::pend - hprintln!("init").unwrap(); + hprintln!("init"); (Shared {}, Local {}) } @@ -33,7 +33,7 @@ mod app { fn idle(_: idle::Context) -> ! { // interrupts are enabled again; the `UART0` handler runs at this point - hprintln!("idle").unwrap(); + hprintln!("idle"); rtic::pend(Interrupt::UART0); @@ -53,7 +53,6 @@ mod app { "UART0 called {} time{}", *cx.local.times, if *cx.local.times > 1 { "s" } else { "" } - ) - .unwrap(); + ); } } diff --git a/examples/idle-wfi.rs b/examples/idle-wfi.rs index dd2d43f..a68fe84 100644 --- a/examples/idle-wfi.rs +++ b/examples/idle-wfi.rs @@ -19,7 +19,7 @@ mod app { #[init] fn init(mut cx: init::Context) -> (Shared, Local) { - hprintln!("init").unwrap(); + hprintln!("init"); // Set the ARM SLEEPONEXIT bit to go to sleep after handling interrupts // See https://developer.arm.com/docs/100737/0100/power-management/sleep-mode/sleep-on-exit-bit @@ -33,7 +33,7 @@ mod app { // Locals in idle have lifetime 'static let _x: &'static mut u32 = cx.local.x; - hprintln!("idle").unwrap(); + hprintln!("idle"); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator diff --git a/examples/idle.rs b/examples/idle.rs index 9a7a727..78f1697 100644 --- a/examples/idle.rs +++ b/examples/idle.rs @@ -19,7 +19,7 @@ mod app { #[init] fn init(_: init::Context) -> (Shared, Local) { - hprintln!("init").unwrap(); + hprintln!("init"); (Shared {}, Local {}) } @@ -29,7 +29,7 @@ mod app { // Locals in idle have lifetime 'static let _x: &'static mut u32 = cx.local.x; - hprintln!("idle").unwrap(); + hprintln!("idle"); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator diff --git a/examples/init.rs b/examples/init.rs index d37903e..1e362be 100644 --- a/examples/init.rs +++ b/examples/init.rs @@ -32,7 +32,7 @@ mod app { // to indicate that this is a critical section let _cs_token: bare_metal::CriticalSection = cx.cs; - hprintln!("init").unwrap(); + hprintln!("init"); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator diff --git a/examples/locals.rs b/examples/locals.rs index a35b4c9..4e3b98b 100644 --- a/examples/locals.rs +++ b/examples/locals.rs @@ -45,7 +45,7 @@ mod app { let local_to_idle = cx.local.local_to_idle; *local_to_idle += 1; - hprintln!("idle: local_to_idle = {}", local_to_idle).unwrap(); + hprintln!("idle: local_to_idle = {}", local_to_idle); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator @@ -69,7 +69,7 @@ mod app { // error: no `local_to_bar` field in `foo::LocalResources` // cx.local.local_to_bar += 1; - hprintln!("foo: local_to_foo = {}", local_to_foo).unwrap(); + hprintln!("foo: local_to_foo = {}", local_to_foo); } // `local_to_bar` can only be accessed from this context @@ -81,6 +81,6 @@ mod app { // error: no `local_to_foo` field in `bar::LocalResources` // cx.local.local_to_foo += 1; - hprintln!("bar: local_to_bar = {}", local_to_bar).unwrap(); + hprintln!("bar: local_to_bar = {}", local_to_bar); } } diff --git a/examples/lock.rs b/examples/lock.rs index 50b6aaa..3c1a514 100644 --- a/examples/lock.rs +++ b/examples/lock.rs @@ -30,7 +30,7 @@ mod app { // when omitted priority is assumed to be `1` #[task(shared = [shared])] async fn foo(mut c: foo::Context) { - hprintln!("A").unwrap(); + hprintln!("A"); // the lower priority task requires a critical section to access the data c.shared.shared.lock(|shared| { @@ -40,7 +40,7 @@ mod app { // bar will *not* run right now due to the critical section bar::spawn().unwrap(); - hprintln!("B - shared = {}", *shared).unwrap(); + hprintln!("B - shared = {}", *shared); // baz does not contend for `shared` so it's allowed to run now baz::spawn().unwrap(); @@ -48,7 +48,7 @@ mod app { // critical section is over: bar can now start - hprintln!("E").unwrap(); + hprintln!("E"); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator } @@ -62,11 +62,11 @@ mod app { *shared }); - hprintln!("D - shared = {}", shared).unwrap(); + hprintln!("D - shared = {}", shared); } #[task(priority = 3)] async fn baz(_: baz::Context) { - hprintln!("C").unwrap(); + hprintln!("C"); } } diff --git a/examples/multilock.rs b/examples/multilock.rs index 7bea2d3..2eb285e 100644 --- a/examples/multilock.rs +++ b/examples/multilock.rs @@ -48,7 +48,7 @@ mod app { *s2 += 1; *s3 += 1; - hprintln!("Multiple locks, s1: {}, s2: {}, s3: {}", *s1, *s2, *s3).unwrap(); + hprintln!("Multiple locks, s1: {}, s2: {}, s3: {}", *s1, *s2, *s3); }); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator diff --git a/examples/not-sync.rs b/examples/not-sync.rs index eb5c9f8..28a48f2 100644 --- a/examples/not-sync.rs +++ b/examples/not-sync.rs @@ -32,7 +32,7 @@ mod app { #[init] fn init(_: init::Context) -> (Shared, Local) { - hprintln!("init").unwrap(); + hprintln!("init"); foo::spawn().unwrap(); bar::spawn().unwrap(); @@ -56,12 +56,12 @@ mod app { #[task(shared = [&shared])] async fn foo(c: foo::Context) { let shared: &NotSync = c.shared.shared; - hprintln!("foo a {}", shared.data).unwrap(); + hprintln!("foo a {}", shared.data); } #[task(shared = [&shared])] async fn bar(c: bar::Context) { let shared: &NotSync = c.shared.shared; - hprintln!("foo a {}", shared.data).unwrap(); + hprintln!("foo a {}", shared.data); } } diff --git a/examples/only-shared-access.rs b/examples/only-shared-access.rs index b506e44..09cb23a 100644 --- a/examples/only-shared-access.rs +++ b/examples/only-shared-access.rs @@ -31,13 +31,13 @@ mod app { #[task(shared = [&key])] async fn foo(cx: foo::Context) { let key: &u32 = cx.shared.key; - hprintln!("foo(key = {:#x})", key).unwrap(); + hprintln!("foo(key = {:#x})", key); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator } #[task(priority = 2, shared = [&key])] async fn bar(cx: bar::Context) { - hprintln!("bar(key = {:#x})", cx.shared.key).unwrap(); + hprintln!("bar(key = {:#x})", cx.shared.key); } } diff --git a/examples/preempt.rs b/examples/preempt.rs index aad9125..960fc57 100644 --- a/examples/preempt.rs +++ b/examples/preempt.rs @@ -26,21 +26,21 @@ mod app { #[task(priority = 1)] async fn foo(_: foo::Context) { - hprintln!("foo - start").unwrap(); + hprintln!("foo - start"); baz::spawn().unwrap(); - hprintln!("foo - end").unwrap(); + hprintln!("foo - end"); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator } #[task(priority = 2)] async fn bar(_: bar::Context) { - hprintln!(" bar").unwrap(); + hprintln!(" bar"); } #[task(priority = 2)] async fn baz(_: baz::Context) { - hprintln!(" baz - start").unwrap(); + hprintln!(" baz - start"); bar::spawn().unwrap(); - hprintln!(" baz - end").unwrap(); + hprintln!(" baz - end"); } } diff --git a/examples/ramfunc.rs b/examples/ramfunc.rs index dd1f76e..316f6d8 100644 --- a/examples/ramfunc.rs +++ b/examples/ramfunc.rs @@ -33,7 +33,7 @@ mod app { #[inline(never)] #[task] async fn foo(_: foo::Context) { - hprintln!("foo").unwrap(); + hprintln!("foo"); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator } diff --git a/examples/resource-user-struct.rs b/examples/resource-user-struct.rs index a4478ce..2acbbc3 100644 --- a/examples/resource-user-struct.rs +++ b/examples/resource-user-struct.rs @@ -55,7 +55,7 @@ mod app { *shared }); - hprintln!("UART0: shared = {}", shared).unwrap(); + hprintln!("UART0: shared = {}", shared); } // `shared` can be accessed from this context @@ -66,6 +66,6 @@ mod app { *shared }); - hprintln!("UART1: shared = {}", shared).unwrap(); + hprintln!("UART1: shared = {}", shared); } } diff --git a/examples/shared.rs b/examples/shared.rs index fdc1b1c..fd31cfb 100644 --- a/examples/shared.rs +++ b/examples/shared.rs @@ -34,7 +34,7 @@ mod app { fn idle(mut c: idle::Context) -> ! { loop { if let Some(byte) = c.shared.c.lock(|c| c.dequeue()) { - hprintln!("received message: {}", byte).unwrap(); + hprintln!("received message: {}", byte); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator } else { diff --git a/examples/spawn.rs b/examples/spawn.rs index 266ace8..384f0a0 100644 --- a/examples/spawn.rs +++ b/examples/spawn.rs @@ -20,7 +20,7 @@ mod app { #[init] fn init(_: init::Context) -> (Shared, Local) { - hprintln!("init").unwrap(); + hprintln!("init"); foo::spawn().unwrap(); (Shared {}, Local {}) @@ -28,7 +28,7 @@ mod app { #[task] async fn foo(_: foo::Context) { - hprintln!("foo").unwrap(); + hprintln!("foo"); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator } diff --git a/examples/static.rs b/examples/static.rs index 9c981db..822224e 100644 --- a/examples/static.rs +++ b/examples/static.rs @@ -38,7 +38,7 @@ mod app { loop { // Lock-free access to the same underlying queue! if let Some(data) = c.local.c.dequeue() { - hprintln!("received message: {}", data).unwrap(); + hprintln!("received message: {}", data); // Run foo until data if data == 3 { diff --git a/examples/task.rs b/examples/task.rs index fe1408b..50287ed 100644 --- a/examples/task.rs +++ b/examples/task.rs @@ -27,31 +27,31 @@ mod app { #[task] async fn foo(_: foo::Context) { - hprintln!("foo - start").unwrap(); + hprintln!("foo - start"); // spawns `bar` onto the task scheduler // `foo` and `bar` have the same priority so `bar` will not run until // after `foo` terminates bar::spawn().unwrap(); - hprintln!("foo - middle").unwrap(); + hprintln!("foo - middle"); // spawns `baz` onto the task scheduler // `baz` has higher priority than `foo` so it immediately preempts `foo` baz::spawn().unwrap(); - hprintln!("foo - end").unwrap(); + hprintln!("foo - end"); } #[task] async fn bar(_: bar::Context) { - hprintln!("bar").unwrap(); + hprintln!("bar"); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator } #[task(priority = 2)] async fn baz(_: baz::Context) { - hprintln!("baz").unwrap(); + hprintln!("baz"); } } -- cgit v1.2.3 From 35c97b61c17a30de675eb1c7f852a100b200a0c2 Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Sun, 8 Jan 2023 19:56:47 +0100 Subject: All examples pass with `cargo xtask --target all` --- ci/expected/async-task-multiple-prios.run | 9 +++++---- ci/expected/async-task.run | 3 ++- ci/expected/big-struct-opt.run | 3 +++ ci/expected/destructure.run | 4 ++-- ci/expected/extern_spawn.run | 3 +-- ci/expected/locals.run | 2 +- ci/expected/not-sync.run | 3 +++ examples/binds.rs | 3 +-- examples/extern_binds.rs | 3 +-- examples/generics.rs | 1 + examples/hardware.rs | 3 +-- examples/not-sync.rs | 2 +- xtask/src/main.rs | 10 ++++++---- 13 files changed, 28 insertions(+), 21 deletions(-) diff --git a/ci/expected/async-task-multiple-prios.run b/ci/expected/async-task-multiple-prios.run index 9b0f533..0b42df0 100644 --- a/ci/expected/async-task-multiple-prios.run +++ b/ci/expected/async-task-multiple-prios.run @@ -1,5 +1,6 @@ init -hello from normal 2 -hello from async 2 -hello from normal 1 -hello from async 1 +hello from async 3 a 1 +hello from async 4 a 2 +hello from async 1 a 3 +hello from async 2 a 4 +idle diff --git a/ci/expected/async-task.run b/ci/expected/async-task.run index f7ce3a6..6787fa8 100644 --- a/ci/expected/async-task.run +++ b/ci/expected/async-task.run @@ -1,3 +1,4 @@ init -hello from normal +hello from async2 hello from async +idle diff --git a/ci/expected/big-struct-opt.run b/ci/expected/big-struct-opt.run index e69de29..7fdef35 100644 --- a/ci/expected/big-struct-opt.run +++ b/ci/expected/big-struct-opt.run @@ -0,0 +1,3 @@ +async_task data:[22, 22, 22, 22, 22] +uart0 data:[22, 22, 22, 22, 22] +idle diff --git a/ci/expected/destructure.run b/ci/expected/destructure.run index b9b7cc9..25a4b1b 100644 --- a/ci/expected/destructure.run +++ b/ci/expected/destructure.run @@ -1,2 +1,2 @@ -foo: a = 0, b = 0, c = 0 -bar: a = 0, b = 0, c = 0 +bar: a = 0, b = 1, c = 2 +foo: a = 0, b = 1, c = 2 diff --git a/ci/expected/extern_spawn.run b/ci/expected/extern_spawn.run index 2f8c74f..257cc56 100644 --- a/ci/expected/extern_spawn.run +++ b/ci/expected/extern_spawn.run @@ -1,2 +1 @@ -foo 1, 2 -foo 2, 3 +foo diff --git a/ci/expected/locals.run b/ci/expected/locals.run index bf1d207..4f1d350 100644 --- a/ci/expected/locals.run +++ b/ci/expected/locals.run @@ -1,3 +1,3 @@ -foo: local_to_foo = 1 bar: local_to_bar = 1 +foo: local_to_foo = 1 idle: local_to_idle = 1 diff --git a/ci/expected/not-sync.run b/ci/expected/not-sync.run index e69de29..cd91476 100644 --- a/ci/expected/not-sync.run +++ b/ci/expected/not-sync.run @@ -0,0 +1,3 @@ +init +bar a 13 +foo a 13 diff --git a/examples/binds.rs b/examples/binds.rs index d78dffb..0c1ed97 100644 --- a/examples/binds.rs +++ b/examples/binds.rs @@ -34,10 +34,9 @@ mod app { rtic::pend(Interrupt::UART0); - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - loop { cortex_m::asm::nop(); + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator } } diff --git a/examples/extern_binds.rs b/examples/extern_binds.rs index 23b99d5..b24e7a1 100644 --- a/examples/extern_binds.rs +++ b/examples/extern_binds.rs @@ -40,10 +40,9 @@ mod app { rtic::pend(Interrupt::UART0); - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - loop { cortex_m::asm::nop(); + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator } } diff --git a/examples/generics.rs b/examples/generics.rs index 7117349..dfd47ad 100644 --- a/examples/generics.rs +++ b/examples/generics.rs @@ -39,6 +39,7 @@ mod app { rtic::pend(Interrupt::UART1); + cortex_m::asm::nop(); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator } diff --git a/examples/hardware.rs b/examples/hardware.rs index d3ceab9..61eb635 100644 --- a/examples/hardware.rs +++ b/examples/hardware.rs @@ -37,10 +37,9 @@ mod app { rtic::pend(Interrupt::UART0); - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - loop { cortex_m::asm::nop(); + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator } } diff --git a/examples/not-sync.rs b/examples/not-sync.rs index 28a48f2..5d868df 100644 --- a/examples/not-sync.rs +++ b/examples/not-sync.rs @@ -62,6 +62,6 @@ mod app { #[task(shared = [&shared])] async fn bar(c: bar::Context) { let shared: &NotSync = c.shared.shared; - hprintln!("foo a {}", shared.data); + hprintln!("bar a {}", shared.data); } } diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 76ce04b..7eada91 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -87,12 +87,14 @@ fn main() -> anyhow::Result<()> { let targets = [ARMV7M, ARMV6M]; let examples: Vec<_> = std::fs::read_dir("./examples")? - .filter_map(|path| { - path.map(|p| p.path().file_stem().unwrap().to_str().unwrap().to_string()) - .ok() - }) + .filter_map(|p| p.ok()) + .map(|p| p.path()) + .filter(|p| p.display().to_string().ends_with(".rs")) + .map(|path| path.file_stem().unwrap().to_str().unwrap().to_string()) .collect(); + println!("examples: {examples:?}"); + let opts = Options::from_args(); let target = &opts.target; -- cgit v1.2.3 From 6d252785e83218eeb5d080836281c90b86ca0e03 Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Sun, 8 Jan 2023 21:10:06 +0100 Subject: Support 0 prio tasks --- ci/expected/zero-prio-task.run | 3 ++ examples/zero-prio-task.rs | 56 +++++++++++++++++++++ macros/src/analyze.rs | 1 + macros/src/check.rs | 1 + macros/src/codegen/async_dispatchers.rs | 87 ++++++++++++++++++++++----------- macros/src/codegen/main.rs | 13 +++-- macros/src/codegen/module.rs | 17 ++++--- macros/src/codegen/util.rs | 4 ++ macros/src/syntax/analyze.rs | 33 +++++++------ 9 files changed, 157 insertions(+), 58 deletions(-) create mode 100644 ci/expected/zero-prio-task.run create mode 100644 examples/zero-prio-task.rs diff --git a/ci/expected/zero-prio-task.run b/ci/expected/zero-prio-task.run new file mode 100644 index 0000000..123b0f2 --- /dev/null +++ b/ci/expected/zero-prio-task.run @@ -0,0 +1,3 @@ +init +hello from async +hello from async2 diff --git a/examples/zero-prio-task.rs b/examples/zero-prio-task.rs new file mode 100644 index 0000000..fc38509 --- /dev/null +++ b/examples/zero-prio-task.rs @@ -0,0 +1,56 @@ +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use core::marker::PhantomData; +use panic_semihosting as _; + +pub struct NotSend { + _0: PhantomData<*const ()>, +} + +#[rtic::app(device = lm3s6965, peripherals = true)] +mod app { + use super::NotSend; + use core::marker::PhantomData; + use cortex_m_semihosting::{debug, hprintln}; + + #[shared] + struct Shared { + x: NotSend, + } + + #[local] + struct Local { + y: NotSend, + } + + #[init] + fn init(_cx: init::Context) -> (Shared, Local) { + hprintln!("init"); + + async_task::spawn().unwrap(); + async_task2::spawn().unwrap(); + + ( + Shared { + x: NotSend { _0: PhantomData }, + }, + Local { + y: NotSend { _0: PhantomData }, + }, + ) + } + + #[task(priority = 0, shared = [x], local = [y])] + async fn async_task(_: async_task::Context) { + hprintln!("hello from async"); + } + + #[task(priority = 0, shared = [x])] + async fn async_task2(_: async_task2::Context) { + hprintln!("hello from async2"); + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } +} diff --git a/macros/src/analyze.rs b/macros/src/analyze.rs index cb42ad6..65774f6 100644 --- a/macros/src/analyze.rs +++ b/macros/src/analyze.rs @@ -36,6 +36,7 @@ pub fn app(analysis: analyze::Analysis, app: &App) -> Analysis { let interrupts: BTreeMap = priorities .iter() + .filter(|prio| **prio > 0) // 0 prio tasks are run in main .copied() .rev() .map(|p| (p, available_interrupt.pop().expect("UNREACHABLE"))) diff --git a/macros/src/check.rs b/macros/src/check.rs index 312b84d..72d0a27 100644 --- a/macros/src/check.rs +++ b/macros/src/check.rs @@ -32,6 +32,7 @@ pub fn app(app: &App) -> parse::Result<()> { first = Some(name); task.args.priority }) + .filter(|prio| *prio > 0) .collect::>(); let need = priorities.len(); diff --git a/macros/src/codegen/async_dispatchers.rs b/macros/src/codegen/async_dispatchers.rs index 62b17fe..f6408e1 100644 --- a/macros/src/codegen/async_dispatchers.rs +++ b/macros/src/codegen/async_dispatchers.rs @@ -26,9 +26,22 @@ pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { for (&level, channel) in &analysis.channels { let mut stmts = vec![]; - let device = &app.args.device; - let enum_ = util::interrupt_ident(); - let interrupt = util::suffixed(&interrupts[&level].0.to_string()); + + let dispatcher_name = if level > 0 { + util::suffixed(&interrupts.get(&level).expect("UNREACHABLE").0.to_string()) + } else { + util::zero_prio_dispatcher_ident() + }; + + let pend_interrupt = if level > 0 { + let device = &app.args.device; + let enum_ = util::interrupt_ident(); + + quote!(rtic::pend(#device::#enum_::#dispatcher_name);) + } else { + // For 0 priority tasks we don't need to pend anything + quote!() + }; for name in channel.tasks.iter() { let exec_name = util::internal_task_ident(name, "EXEC"); @@ -60,40 +73,56 @@ pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { #executor_run_ident.store(false, core::sync::atomic::Ordering::Relaxed); if (&mut *#exec_name.get_mut()).poll(|| { #executor_run_ident.store(true, core::sync::atomic::Ordering::Release); - rtic::pend(#device::#enum_::#interrupt); + #pend_interrupt }) && #rq.load(core::sync::atomic::Ordering::Relaxed) { // If the ready queue is not empty and the executor finished, restart this // dispatch to check if the executor should be restarted. - rtic::pend(#device::#enum_::#interrupt); + #pend_interrupt } } )); } - let doc = format!( - "Interrupt handler to dispatch async tasks at priority {}", - level - ); - let attribute = &interrupts[&level].1.attrs; - items.push(quote!( - #[allow(non_snake_case)] - #[doc = #doc] - #[no_mangle] - #(#attribute)* - unsafe fn #interrupt() { - /// The priority of this interrupt handler - const PRIORITY: u8 = #level; - - rtic::export::run(PRIORITY, || { - // Have the acquire/release semantics outside the checks to no overdo it - core::sync::atomic::fence(core::sync::atomic::Ordering::Acquire); - - #(#stmts)* - - core::sync::atomic::fence(core::sync::atomic::Ordering::Release); - }); - } - )); + if level > 0 { + let doc = format!( + "Interrupt handler to dispatch async tasks at priority {}", + level + ); + let attribute = &interrupts.get(&level).expect("UNREACHABLE").1.attrs; + items.push(quote!( + #[allow(non_snake_case)] + #[doc = #doc] + #[no_mangle] + #(#attribute)* + unsafe fn #dispatcher_name() { + /// The priority of this interrupt handler + const PRIORITY: u8 = #level; + + rtic::export::run(PRIORITY, || { + // Have the acquire/release semantics outside the checks to no overdo it + core::sync::atomic::fence(core::sync::atomic::Ordering::Acquire); + + #(#stmts)* + + core::sync::atomic::fence(core::sync::atomic::Ordering::Release); + }); + } + )); + } else { + items.push(quote!( + #[allow(non_snake_case)] + unsafe fn #dispatcher_name() -> ! { + loop { + // Have the acquire/release semantics outside the checks to no overdo it + core::sync::atomic::fence(core::sync::atomic::Ordering::Acquire); + + #(#stmts)* + + core::sync::atomic::fence(core::sync::atomic::Ordering::Release); + } + } + )); + } } quote!(#(#items)*) diff --git a/macros/src/codegen/main.rs b/macros/src/codegen/main.rs index 90f09ae..8e7138f 100644 --- a/macros/src/codegen/main.rs +++ b/macros/src/codegen/main.rs @@ -16,11 +16,14 @@ pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { let name = &idle.name; quote!(#name(#name::Context::new())) } else { - // TODO: No idle defined, check for 0-priority tasks and generate an executor if needed - - quote!(loop { - rtic::export::nop() - }) + if analysis.channels.get(&0).is_some() { + let dispatcher = util::zero_prio_dispatcher_ident(); + quote!(#dispatcher();) + } else { + quote!(loop { + rtic::export::nop() + }) + } }; let main = util::suffixed("main"); diff --git a/macros/src/codegen/module.rs b/macros/src/codegen/module.rs index c6f7690..70fbb5e 100644 --- a/macros/src/codegen/module.rs +++ b/macros/src/codegen/module.rs @@ -135,13 +135,14 @@ pub fn codegen(ctxt: Context, app: &App, analysis: &Analysis) -> TokenStream2 { // Store a copy of the task cfgs task_cfgs = cfgs.clone(); - let device = &app.args.device; - let enum_ = util::interrupt_ident(); - let interrupt = &analysis - .interrupts - .get(&priority) - .expect("RTIC-ICE: interrupt identifier not found") - .0; + let pend_interrupt = if priority > 0 { + let device = &app.args.device; + let enum_ = util::interrupt_ident(); + let interrupt = &analysis.interrupts.get(&priority).expect("UREACHABLE").0; + quote!(rtic::pend(#device::#enum_::#interrupt);) + } else { + quote!() + }; let internal_spawn_ident = util::internal_task_ident(name, "spawn"); @@ -160,7 +161,7 @@ pub fn codegen(ctxt: Context, app: &App, analysis: &Analysis) -> TokenStream2 { Err(()) } else { #rq.store(true, core::sync::atomic::Ordering::Release); - rtic::pend(#device::#enum_::#interrupt); + #pend_interrupt Ok(()) } } diff --git a/macros/src/codegen/util.rs b/macros/src/codegen/util.rs index a071ca2..6552839 100644 --- a/macros/src/codegen/util.rs +++ b/macros/src/codegen/util.rs @@ -187,6 +187,10 @@ pub fn need_to_lock_ident(name: &Ident) -> Ident { Ident::new(&format!("{}_that_needs_to_be_locked", name), name.span()) } +pub fn zero_prio_dispatcher_ident() -> Ident { + Ident::new("__rtic_internal_async_0_prio_dispatcher", Span::call_site()) +} + /// The name to get better RT flag errors pub fn rt_err_ident() -> Ident { Ident::new( diff --git a/macros/src/syntax/analyze.rs b/macros/src/syntax/analyze.rs index dd5a9b4..b70ceb8 100644 --- a/macros/src/syntax/analyze.rs +++ b/macros/src/syntax/analyze.rs @@ -248,33 +248,34 @@ pub(crate) fn app(app: &App) -> Result { } } - // Most shared resources need to be `Send` + // Most shared resources need to be `Send`, only 0 prio does not need it let mut send_types = SendTypes::new(); - let owned_by_idle = Ownership::Owned { priority: 0 }; + for (name, res) in app.shared_resources.iter() { - // Handle not owned by idle if ownerships .get(name) - .map(|ownership| *ownership != owned_by_idle) + .map(|ownership| match *ownership { + Ownership::Owned { priority: ceiling } + | Ownership::CoOwned { priority: ceiling } + | Ownership::Contended { ceiling } => ceiling != 0, + }) .unwrap_or(false) { send_types.insert(res.ty.clone()); } } - // Most local resources need to be `Send` as well + // Most local resources need to be `Send` as well, only 0 prio does not need it for (name, res) in app.local_resources.iter() { - if let Some(idle) = &app.idle { - // Only Send if not in idle or not at idle prio - if idle.args.local_resources.get(name).is_none() - && !ownerships - .get(name) - .map(|ownership| *ownership != owned_by_idle) - .unwrap_or(false) - { - send_types.insert(res.ty.clone()); - } - } else { + if ownerships + .get(name) + .map(|ownership| match *ownership { + Ownership::Owned { priority: ceiling } + | Ownership::CoOwned { priority: ceiling } + | Ownership::Contended { ceiling } => ceiling != 0, + }) + .unwrap_or(false) + { send_types.insert(res.ty.clone()); } } -- cgit v1.2.3 From c40c89bb4edc22c4a60d8677c660a9ab7eb47e92 Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Sun, 8 Jan 2023 21:30:53 +0100 Subject: Clippy fixes --- build.rs | 3 +-- macros/src/check.rs | 3 +-- macros/src/codegen/async_dispatchers.rs | 3 +-- macros/src/codegen/main.rs | 14 ++++++-------- macros/src/codegen/pre_init.rs | 6 ++---- macros/src/codegen/util.rs | 16 ++++++++-------- macros/src/lib.rs | 7 +++---- macros/src/syntax/parse/app.rs | 6 ++---- macros/src/syntax/parse/hardware_task.rs | 10 ++++------ macros/src/syntax/parse/idle.rs | 5 ++--- macros/src/syntax/parse/init.rs | 5 ++--- macros/src/syntax/parse/software_task.rs | 10 ++++------ macros/src/syntax/parse/util.rs | 14 +++++--------- src/export.rs | 4 ++-- 14 files changed, 43 insertions(+), 63 deletions(-) diff --git a/build.rs b/build.rs index ff9ebe3..38d2c18 100644 --- a/build.rs +++ b/build.rs @@ -19,8 +19,7 @@ fn main() { && !(target.starts_with("thumbv6m") | target.starts_with("thumbv8m.base")) { panic!( - "Unknown target '{}'. Need to update BASEPRI logic in build.rs.", - target + "Unknown target '{target}'. Need to update BASEPRI logic in build.rs." ); } diff --git a/macros/src/check.rs b/macros/src/check.rs index 72d0a27..a05c82e 100644 --- a/macros/src/check.rs +++ b/macros/src/check.rs @@ -41,8 +41,7 @@ pub fn app(app: &App) -> parse::Result<()> { let s = { format!( "not enough interrupts to dispatch \ - all software tasks (need: {}; given: {})", - need, given + all software tasks (need: {need}; given: {given})" ) }; diff --git a/macros/src/codegen/async_dispatchers.rs b/macros/src/codegen/async_dispatchers.rs index f6408e1..be02ad0 100644 --- a/macros/src/codegen/async_dispatchers.rs +++ b/macros/src/codegen/async_dispatchers.rs @@ -85,8 +85,7 @@ pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { if level > 0 { let doc = format!( - "Interrupt handler to dispatch async tasks at priority {}", - level + "Interrupt handler to dispatch async tasks at priority {level}" ); let attribute = &interrupts.get(&level).expect("UNREACHABLE").1.attrs; items.push(quote!( diff --git a/macros/src/codegen/main.rs b/macros/src/codegen/main.rs index 8e7138f..2775d25 100644 --- a/macros/src/codegen/main.rs +++ b/macros/src/codegen/main.rs @@ -15,15 +15,13 @@ pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { let call_idle = if let Some(idle) = &app.idle { let name = &idle.name; quote!(#name(#name::Context::new())) + } else if analysis.channels.get(&0).is_some() { + let dispatcher = util::zero_prio_dispatcher_ident(); + quote!(#dispatcher();) } else { - if analysis.channels.get(&0).is_some() { - let dispatcher = util::zero_prio_dispatcher_ident(); - quote!(#dispatcher();) - } else { - quote!(loop { - rtic::export::nop() - }) - } + quote!(loop { + rtic::export::nop() + }) }; let main = util::suffixed("main"); diff --git a/macros/src/codegen/pre_init.rs b/macros/src/codegen/pre_init.rs index 1492688..28ba29c 100644 --- a/macros/src/codegen/pre_init.rs +++ b/macros/src/codegen/pre_init.rs @@ -40,8 +40,7 @@ pub fn codegen(app: &App, analysis: &Analysis) -> Vec { } })) { let es = format!( - "Maximum priority used by interrupt vector '{}' is more than supported by hardware", - name + "Maximum priority used by interrupt vector '{name}' is more than supported by hardware" ); // Compile time assert that this priority is supported by the device stmts.push(quote!( @@ -69,8 +68,7 @@ pub fn codegen(app: &App, analysis: &Analysis) -> Vec { } }) { let es = format!( - "Maximum priority used by interrupt vector '{}' is more than supported by hardware", - name + "Maximum priority used by interrupt vector '{name}' is more than supported by hardware" ); // Compile time assert that this priority is supported by the device stmts.push(quote!( diff --git a/macros/src/codegen/util.rs b/macros/src/codegen/util.rs index 6552839..a0caf0a 100644 --- a/macros/src/codegen/util.rs +++ b/macros/src/codegen/util.rs @@ -51,7 +51,7 @@ pub fn impl_mutex( /// Generates an identifier for the `EXECUTOR_RUN` atomics (`async` API) pub fn executor_run_ident(task: &Ident) -> Ident { - mark_internal_name(&format!("{}_EXECUTOR_RUN", task)) + mark_internal_name(&format!("{task}_EXECUTOR_RUN")) } pub fn interrupt_ident() -> Ident { @@ -78,12 +78,12 @@ pub fn is_exception(name: &Ident) -> bool { /// Mark a name as internal pub fn mark_internal_name(name: &str) -> Ident { - Ident::new(&format!("{}_{}", RTIC_INTERNAL, name), Span::call_site()) + Ident::new(&format!("{RTIC_INTERNAL}_{name}"), Span::call_site()) } /// Generate an internal identifier for tasks pub fn internal_task_ident(task: &Ident, ident_name: &str) -> Ident { - mark_internal_name(&format!("{}_{}", task, ident_name)) + mark_internal_name(&format!("{task}_{ident_name}")) } fn link_section_index() -> usize { @@ -153,7 +153,7 @@ pub fn local_resources_ident(ctxt: Context, app: &App) -> Ident { /// Generates an identifier for a ready queue, async task version pub fn rq_async_ident(async_task_name: &Ident) -> Ident { - mark_internal_name(&format!("ASYNC_TASK_{}_RQ", async_task_name)) + mark_internal_name(&format!("ASYNC_TASK_{async_task_name}_RQ")) } /// Suffixed identifier @@ -163,7 +163,7 @@ pub fn suffixed(name: &str) -> Ident { } pub fn static_shared_resource_ident(name: &Ident) -> Ident { - mark_internal_name(&format!("shared_resource_{}", name)) + mark_internal_name(&format!("shared_resource_{name}")) } /// Generates an Ident for the number of 32 bit chunks used for Mask storage. @@ -176,15 +176,15 @@ pub fn priority_masks_ident() -> Ident { } pub fn static_local_resource_ident(name: &Ident) -> Ident { - mark_internal_name(&format!("local_resource_{}", name)) + mark_internal_name(&format!("local_resource_{name}")) } pub fn declared_static_local_resource_ident(name: &Ident, task_name: &Ident) -> Ident { - mark_internal_name(&format!("local_{}_{}", task_name, name)) + mark_internal_name(&format!("local_{task_name}_{name}")) } pub fn need_to_lock_ident(name: &Ident) -> Ident { - Ident::new(&format!("{}_that_needs_to_be_locked", name), name.span()) + Ident::new(&format!("{name}_that_needs_to_be_locked"), name.span()) } pub fn zero_prio_dispatcher_ident() -> Ident { diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 34f2bb6..a8422d0 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -39,9 +39,8 @@ pub fn app(args: TokenStream, input: TokenStream) -> TokenStream { Ok(x) => x, }; - match check::app(&app) { - Err(e) => return e.to_compile_error().into(), - _ => {} + if let Err(e) = check::app(&app) { + return e.to_compile_error().into(); } let analysis = analyze::app(analysis, &app); @@ -86,7 +85,7 @@ pub fn app(args: TokenStream, input: TokenStream) -> TokenStream { // Try to write the expanded code to disk if let Some(out_str) = out_dir.to_str() { - fs::write(format!("{}/rtic-expansion.rs", out_str), ts.to_string()).ok(); + fs::write(format!("{out_str}/rtic-expansion.rs"), ts.to_string()).ok(); } ts.into() diff --git a/macros/src/syntax/parse/app.rs b/macros/src/syntax/parse/app.rs index 8a9242e..e797f75 100644 --- a/macros/src/syntax/parse/app.rs +++ b/macros/src/syntax/parse/app.rs @@ -450,8 +450,7 @@ impl App { return Err(parse::Error::new( init.user_shared_struct.span(), format!( - "This name and the one defined on `#[shared]` are not the same. Should this be `{}`?", - shared_resources_ident + "This name and the one defined on `#[shared]` are not the same. Should this be `{shared_resources_ident}`?" ), )); } @@ -460,8 +459,7 @@ impl App { return Err(parse::Error::new( init.user_local_struct.span(), format!( - "This name and the one defined on `#[local]` are not the same. Should this be `{}`?", - local_resources_ident + "This name and the one defined on `#[local]` are not the same. Should this be `{local_resources_ident}`?" ), )); } diff --git a/macros/src/syntax/parse/hardware_task.rs b/macros/src/syntax/parse/hardware_task.rs index ff94bc5..6207e56 100644 --- a/macros/src/syntax/parse/hardware_task.rs +++ b/macros/src/syntax/parse/hardware_task.rs @@ -39,9 +39,8 @@ impl HardwareTask { Err(parse::Error::new( span, - &format!( - "this task handler must have type signature `fn({}::Context)`", - name + format!( + "this task handler must have type signature `fn({name}::Context)`" ), )) } @@ -83,9 +82,8 @@ impl HardwareTask { Err(parse::Error::new( span, - &format!( - "this task handler must have type signature `fn({}::Context)`", - name + format!( + "this task handler must have type signature `fn({name}::Context)`" ), )) } diff --git a/macros/src/syntax/parse/idle.rs b/macros/src/syntax/parse/idle.rs index ffec358..aa2ef5e 100644 --- a/macros/src/syntax/parse/idle.rs +++ b/macros/src/syntax/parse/idle.rs @@ -34,9 +34,8 @@ impl Idle { Err(parse::Error::new( item.sig.ident.span(), - &format!( - "this `#[idle]` function must have signature `fn({}::Context) -> !`", - name + format!( + "this `#[idle]` function must have signature `fn({name}::Context) -> !`" ), )) } diff --git a/macros/src/syntax/parse/init.rs b/macros/src/syntax/parse/init.rs index 61d3539..23130c8 100644 --- a/macros/src/syntax/parse/init.rs +++ b/macros/src/syntax/parse/init.rs @@ -41,9 +41,8 @@ impl Init { Err(parse::Error::new( span, - &format!( - "the `#[init]` function must have signature `fn({}::Context) -> (Shared resources struct, Local resources struct)`", - name + format!( + "the `#[init]` function must have signature `fn({name}::Context) -> (Shared resources struct, Local resources struct)`" ), )) } diff --git a/macros/src/syntax/parse/software_task.rs b/macros/src/syntax/parse/software_task.rs index 6be597e..319620a 100644 --- a/macros/src/syntax/parse/software_task.rs +++ b/macros/src/syntax/parse/software_task.rs @@ -33,9 +33,8 @@ impl SoftwareTask { Err(parse::Error::new( span, - &format!( - "this task handler must have type signature `async fn({}::Context)`", - name + format!( + "this task handler must have type signature `async fn({name}::Context)`" ), )) } @@ -71,9 +70,8 @@ impl SoftwareTask { Err(parse::Error::new( span, - &format!( - "this task handler must have type signature `async fn({}::Context)`", - name + format!( + "this task handler must have type signature `async fn({name}::Context)`" ), )) } diff --git a/macros/src/syntax/parse/util.rs b/macros/src/syntax/parse/util.rs index 28c3eac..900ef9d 100644 --- a/macros/src/syntax/parse/util.rs +++ b/macros/src/syntax/parse/util.rs @@ -234,17 +234,13 @@ pub fn parse_local_resources(content: ParseStream<'_>) -> parse::Result, name: &str) -> Option> { let mut inputs = inputs.into_iter(); - match inputs.next() { - Some(FnArg::Typed(first)) => { - if type_is_path(&first.ty, &[name, "Context"]) { - // No more inputs - if inputs.next().is_none() { - return Some(first.pat); - } + if let Some(FnArg::Typed(first)) = inputs.next() { + if type_is_path(&first.ty, &[name, "Context"]) { + // No more inputs + if inputs.next().is_none() { + return Some(first.pat); } } - - _ => {} } None diff --git a/src/export.rs b/src/export.rs index bfd0f6d..091cfb8 100644 --- a/src/export.rs +++ b/src/export.rs @@ -298,9 +298,9 @@ pub unsafe fn lock( if ceiling >= 4 { // safe to manipulate outside critical section // execute closure under protection of raised system ceiling - let r = interrupt::free(|_| f(&mut *ptr)); + // safe to manipulate outside critical section - r + interrupt::free(|_| f(&mut *ptr)) } else { // safe to manipulate outside critical section let mask = compute_mask(0, ceiling, masks); -- cgit v1.2.3 From 95e494968053a17ac05a0c1cec9d8b2c7d450296 Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Sun, 8 Jan 2023 21:33:44 +0100 Subject: Start CI, disable docs building --- .github/workflows/build.yml | 552 +++++++++++++++---------------- build.rs | 4 +- macros/src/codegen/async_dispatchers.rs | 4 +- macros/src/syntax/parse/hardware_task.rs | 8 +- macros/src/syntax/parse/idle.rs | 4 +- macros/src/syntax/parse/software_task.rs | 8 +- src/export.rs | 2 +- ui/exception-invalid.rs | 4 +- ui/extern-interrupt-not-enough.rs | 6 +- ui/extern-interrupt-not-enough.stderr | 6 +- ui/extern-interrupt-used.rs | 4 +- ui/task-priority-too-high.rs | 4 +- ui/task-priority-too-high.stderr | 2 +- ui/unknown-interrupt.rs | 4 +- 14 files changed, 299 insertions(+), 313 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5e1467c..35c0bff 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -39,7 +39,7 @@ jobs: - thumbv6m-none-eabi - x86_64-unknown-linux-gnu toolchain: - - stable + - nightly steps: - name: Checkout uses: actions/checkout@v3 @@ -93,7 +93,7 @@ jobs: - thumbv8m.base-none-eabi - thumbv8m.main-none-eabi toolchain: - - stable + - nightly steps: - name: Checkout uses: actions/checkout@v3 @@ -125,7 +125,7 @@ jobs: - thumbv7m-none-eabi - thumbv6m-none-eabi toolchain: - - stable + - nightly steps: - name: Checkout uses: actions/checkout@v3 @@ -168,7 +168,7 @@ jobs: target: - x86_64-unknown-linux-gnu toolchain: - - stable + - nightly steps: - name: Checkout uses: actions/checkout@v3 @@ -224,276 +224,276 @@ jobs: - name: Run cargo test run: cargo test --test tests - # Build documentation, check links - docs: - name: docs - runs-on: ubuntu-22.04 - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Cache pip installed linkchecker - uses: actions/cache@v3 - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip - restore-keys: | - ${{ runner.os }}-pip- - - - name: Set up Python 3.x - uses: actions/setup-python@v4 - with: - # Semantic version range syntax or exact version of a Python version - python-version: '3.x' - - # You can test your matrix by printing the current Python version - - name: Display Python version - run: python -c "import sys; print(sys.version)" - - - name: Install dependencies - run: pip install git+https://github.com/linkchecker/linkchecker.git - - - name: Remove cargo-config - run: rm -f .cargo/config - - - name: Fail on warnings - run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs macros/src/lib.rs - - - name: Build docs - run: cargo doc - - - name: Check links - run: | - td=$(mktemp -d) - cp -r target/doc $td/api - linkchecker $td/api/rtic/ - linkchecker $td/api/cortex_m_rtic_macros/ - - # Build the books - mdbook: - name: mdbook - runs-on: ubuntu-22.04 - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Set up Python 3.x - uses: actions/setup-python@v4 - with: - # Semantic version range syntax or exact version of a Python version - python-version: '3.x' - - # You can test your matrix by printing the current Python version - - name: Display Python version - run: python -c "import sys; print(sys.version)" - - - name: Install dependencies - run: pip install git+https://github.com/linkchecker/linkchecker.git - - - name: mdBook Action - uses: peaceiris/actions-mdbook@v1 - with: - mdbook-version: 'latest' - - - name: Build book in English - shell: 'script --return --quiet --command "bash {0}"' - run: cd book/en && if mdbook build |& tee /dev/tty | grep "\[ERROR\]"; then exit 1; else exit 0; fi - - - name: Build book in Russian - shell: 'script --return --quiet --command "bash {0}"' - run: cd book/ru && if mdbook build |& tee /dev/tty | grep "\[ERROR\]"; then echo "Russian book needs updating!"; else exit 0; fi - - - name: Check links - run: | - td=$(mktemp -d) - mkdir $td/book - cp -r book/en/book $td/book/en - cp -r book/ru/book $td/book/ru - cp LICENSE-* $td/book/en - cp LICENSE-* $td/book/ru - - linkchecker $td/book/en/ - linkchecker $td/book/ru/ - - # Update stable branch - # - # This needs to run before book is built - mergetostablebranch: - name: If CI passes, merge master branch into release/vX - runs-on: ubuntu-22.04 - needs: - - style - - check - - clippy - - checkexamples - - testexamples - - checkmacros - - testmacros - - tests - - docs - - mdbook - - # Only run this when pushing to master branch - if: github.ref == 'refs/heads/master' - steps: - - uses: actions/checkout@v3 - - - name: Get crate version and print output branch release/vX - id: crateversionbranch - # Parse metadata for version number, extract the Semver Major - run: | - VERSION=$(cargo metadata --format-version 1 --no-deps --offline | jq -r '.packages[] | select(.name =="cortex-m-rtic") | .version') - VERSIONMAJOR=${VERSION%.*.*} - echo "branch=release/v$VERSIONMAJOR" >> $GITHUB_ENV - echo "versionmajor=$VERSIONMAJOR" >> $GITHUB_ENV - echo "version=$VERSION" >> $GITHUB_ENV - - - uses: everlytic/branch-merge@1.1.5 - with: - github_token: ${{ github.token }} - source_ref: 'master' - target_branch: ${{ env.branch }} - commit_message_template: '[Bors] Merged {source_ref} into target {target_branch}' - - # Only runs when pushing to master branch - # Bors run CI against staging branch, - # if that succeeds Borst tries against master branch - # If all tests pass, then deploy stage is run - deploy: - name: deploy - runs-on: ubuntu-22.04 - needs: - mergetostablebranch - - # Only run this when pushing to master branch - if: github.ref == 'refs/heads/master' - steps: - - uses: actions/checkout@v3 - - - name: Set up Python 3.x - uses: actions/setup-python@v4 - with: - # Semantic version range syntax or exact version of a Python version - python-version: '3.x' - - # You can test your matrix by printing the current Python version - - name: Display Python version - run: python -c "import sys; print(sys.version)" - - - name: mdBook Action - uses: peaceiris/actions-mdbook@v1 - with: - mdbook-version: 'latest' - - - name: Get crate version - id: crateversion - # Parse metadata for version number, extract the Semver Major - run: | - VERSION=$(cargo metadata --format-version 1 --no-deps --offline | jq -r '.packages[] | select(.name =="cortex-m-rtic") | .version') - VERSIONMAJOR=${VERSION%.*.*} - echo "branch=release/v$VERSIONMAJOR" >> $GITHUB_ENV - echo "versionmajor=$VERSIONMAJOR" >> $GITHUB_ENV - echo "version=$VERSION" >> $GITHUB_ENV - - - name: Remove cargo-config - run: rm -f .cargo/config - - - name: Build docs - run: cargo doc - - - name: Build books - shell: 'script --return --quiet --command "bash {0}"' - run: | - langs=( en ru ) - devver=( dev ) - # The latest stable must be the first element in the array - vers=( "1" "0.5" "0.4" ) - - # All releases start with "v" - # followed by MAJOR.MINOR.PATCH, see semver.org - # Store first in array as stable - stable=${vers} - crateversion={{ env.versionmajor }} - - echo "Latest stable version: $stable" - echo "Current crate version: $crateversion" - - # Create directories - td=$(mktemp -d) - mkdir -p $td/$devver/book/ - cp -r target/doc $td/$devver/api - - # Redirect rtic.rs/meeting/index.html to hackmd - mkdir $td/meeting - sed "s|URL|https://hackmd.io/c_mFUZL-Q2C6614MlrrxOg|g" redirect.html > $td/meeting/index.html - sed -i "s|Page Redirection|RTIC Meeting|" $td/meeting/index.html - sed -i "s|If you|Redirecting to RTIC HackMD. If you|" $td/meeting/index.html - - # Redirect the main site to the stable release - sed "s|URL|$stable|g" redirect.html > $td/index.html - - # Create the redirects for dev-version - # If the current stable and the version being built differ, - # then there is a dev-version and the links should point to it. - if [[ "$stable" != "$crateversion" ]]; - then - sed 's|URL|rtic/index.html|g' redirect.html > $td/$devver/api/index.html - sed 's|URL|book/en|g' redirect.html > $td/$devver/index.html - else - # If the current stable and the "dev" version in master branch - # share the same major version, redirect dev/ to stable book - sed 's|URL|rtic.rs/$stable/api/rtic|g' redirect.html > $td/$devver/api/index.html - sed 's|URL|rtic.rs/$stable|g' redirect.html > $td/$devver/index.html - fi - - # Build books - for lang in ${langs[@]}; do - ( cd book/$lang && - if mdbook build |& tee /dev/tty | grep "\[ERROR\]"; then exit 1; else exit 0; fi - ) - cp -r book/$lang/book $td/$devver/book/$lang - cp LICENSE-* $td/$devver/book/$lang/ - done - - # Build older versions, including stable - root=$(pwd) - for ver in ${vers[@]}; do - prefix=${ver} - - mkdir -p $td/$prefix/book - src=$(mktemp -d) - curl -L https://github.com/rtic-rs/cortex-m-rtic/archive/release/v${ver}.tar.gz | tar xz --strip-components 1 -C $src - - pushd $src - rm -f .cargo/config - cargo doc || cargo doc --features timer-queue - cp -r target/doc $td/$prefix/api - sed 's|URL|rtic/index.html|g' $root/redirect.html > $td/$prefix/api/index.html - for lang in ${langs[@]}; do - ( cd book/$lang && - if mdbook build |& tee /dev/tty | grep "\[ERROR\]"; then exit 1; else exit 0; fi - ) - cp -r book/$lang/book $td/$prefix/book/$lang - cp LICENSE-* $td/$prefix/book/$lang/ - done - sed 's|URL|book/en|g' $root/redirect.html > $td/$prefix/index.html - popd - - rm -rf $src - done - - # Copy the stable book to the stable alias - cp -r $td/$stable $td/stable - - # Forward CNAME file - cp CNAME $td/ - mv $td/ bookstodeploy - - - name: Deploy to GH-pages - uses: peaceiris/actions-gh-pages@v3 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./bookstodeploy - force_orphan: true +# # Build documentation, check links +# docs: +# name: docs +# runs-on: ubuntu-22.04 +# steps: +# - name: Checkout +# uses: actions/checkout@v3 +# +# - name: Cache pip installed linkchecker +# uses: actions/cache@v3 +# with: +# path: ~/.cache/pip +# key: ${{ runner.os }}-pip +# restore-keys: | +# ${{ runner.os }}-pip- +# +# - name: Set up Python 3.x +# uses: actions/setup-python@v4 +# with: +# # Semantic version range syntax or exact version of a Python version +# python-version: '3.x' +# +# # You can test your matrix by printing the current Python version +# - name: Display Python version +# run: python -c "import sys; print(sys.version)" +# +# - name: Install dependencies +# run: pip install git+https://github.com/linkchecker/linkchecker.git +# +# - name: Remove cargo-config +# run: rm -f .cargo/config +# +# - name: Fail on warnings +# run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs macros/src/lib.rs +# +# - name: Build docs +# run: cargo doc +# +# - name: Check links +# run: | +# td=$(mktemp -d) +# cp -r target/doc $td/api +# linkchecker $td/api/rtic/ +# linkchecker $td/api/cortex_m_rtic_macros/ +# +# # Build the books +# mdbook: +# name: mdbook +# runs-on: ubuntu-22.04 +# steps: +# - name: Checkout +# uses: actions/checkout@v3 +# - name: Set up Python 3.x +# uses: actions/setup-python@v4 +# with: +# # Semantic version range syntax or exact version of a Python version +# python-version: '3.x' +# +# # You can test your matrix by printing the current Python version +# - name: Display Python version +# run: python -c "import sys; print(sys.version)" +# +# - name: Install dependencies +# run: pip install git+https://github.com/linkchecker/linkchecker.git +# +# - name: mdBook Action +# uses: peaceiris/actions-mdbook@v1 +# with: +# mdbook-version: 'latest' +# +# - name: Build book in English +# shell: 'script --return --quiet --command "bash {0}"' +# run: cd book/en && if mdbook build |& tee /dev/tty | grep "\[ERROR\]"; then exit 1; else exit 0; fi +# +# - name: Build book in Russian +# shell: 'script --return --quiet --command "bash {0}"' +# run: cd book/ru && if mdbook build |& tee /dev/tty | grep "\[ERROR\]"; then echo "Russian book needs updating!"; else exit 0; fi +# +# - name: Check links +# run: | +# td=$(mktemp -d) +# mkdir $td/book +# cp -r book/en/book $td/book/en +# cp -r book/ru/book $td/book/ru +# cp LICENSE-* $td/book/en +# cp LICENSE-* $td/book/ru +# +# linkchecker $td/book/en/ +# linkchecker $td/book/ru/ +# +# # Update stable branch +# # +# # This needs to run before book is built +# mergetostablebranch: +# name: If CI passes, merge master branch into release/vX +# runs-on: ubuntu-22.04 +# needs: +# - style +# - check +# - clippy +# - checkexamples +# - testexamples +# - checkmacros +# - testmacros +# - tests +# - docs +# - mdbook +# +# # Only run this when pushing to master branch +# if: github.ref == 'refs/heads/master' +# steps: +# - uses: actions/checkout@v3 +# +# - name: Get crate version and print output branch release/vX +# id: crateversionbranch +# # Parse metadata for version number, extract the Semver Major +# run: | +# VERSION=$(cargo metadata --format-version 1 --no-deps --offline | jq -r '.packages[] | select(.name =="cortex-m-rtic") | .version') +# VERSIONMAJOR=${VERSION%.*.*} +# echo "branch=release/v$VERSIONMAJOR" >> $GITHUB_ENV +# echo "versionmajor=$VERSIONMAJOR" >> $GITHUB_ENV +# echo "version=$VERSION" >> $GITHUB_ENV +# +# - uses: everlytic/branch-merge@1.1.5 +# with: +# github_token: ${{ github.token }} +# source_ref: 'master' +# target_branch: ${{ env.branch }} +# commit_message_template: '[Bors] Merged {source_ref} into target {target_branch}' +# +# # Only runs when pushing to master branch +# # Bors run CI against staging branch, +# # if that succeeds Borst tries against master branch +# # If all tests pass, then deploy stage is run +# deploy: +# name: deploy +# runs-on: ubuntu-22.04 +# needs: +# mergetostablebranch +# +# # Only run this when pushing to master branch +# if: github.ref == 'refs/heads/master' +# steps: +# - uses: actions/checkout@v3 +# +# - name: Set up Python 3.x +# uses: actions/setup-python@v4 +# with: +# # Semantic version range syntax or exact version of a Python version +# python-version: '3.x' +# +# # You can test your matrix by printing the current Python version +# - name: Display Python version +# run: python -c "import sys; print(sys.version)" +# +# - name: mdBook Action +# uses: peaceiris/actions-mdbook@v1 +# with: +# mdbook-version: 'latest' +# +# - name: Get crate version +# id: crateversion +# # Parse metadata for version number, extract the Semver Major +# run: | +# VERSION=$(cargo metadata --format-version 1 --no-deps --offline | jq -r '.packages[] | select(.name =="cortex-m-rtic") | .version') +# VERSIONMAJOR=${VERSION%.*.*} +# echo "branch=release/v$VERSIONMAJOR" >> $GITHUB_ENV +# echo "versionmajor=$VERSIONMAJOR" >> $GITHUB_ENV +# echo "version=$VERSION" >> $GITHUB_ENV +# +# - name: Remove cargo-config +# run: rm -f .cargo/config +# +# - name: Build docs +# run: cargo doc +# +# - name: Build books +# shell: 'script --return --quiet --command "bash {0}"' +# run: | +# langs=( en ru ) +# devver=( dev ) +# # The latest stable must be the first element in the array +# vers=( "1" "0.5" "0.4" ) +# +# # All releases start with "v" +# # followed by MAJOR.MINOR.PATCH, see semver.org +# # Store first in array as stable +# stable=${vers} +# crateversion={{ env.versionmajor }} +# +# echo "Latest stable version: $stable" +# echo "Current crate version: $crateversion" +# +# # Create directories +# td=$(mktemp -d) +# mkdir -p $td/$devver/book/ +# cp -r target/doc $td/$devver/api +# +# # Redirect rtic.rs/meeting/index.html to hackmd +# mkdir $td/meeting +# sed "s|URL|https://hackmd.io/c_mFUZL-Q2C6614MlrrxOg|g" redirect.html > $td/meeting/index.html +# sed -i "s|Page Redirection|RTIC Meeting|" $td/meeting/index.html +# sed -i "s|If you|Redirecting to RTIC HackMD. If you|" $td/meeting/index.html +# +# # Redirect the main site to the stable release +# sed "s|URL|$stable|g" redirect.html > $td/index.html +# +# # Create the redirects for dev-version +# # If the current stable and the version being built differ, +# # then there is a dev-version and the links should point to it. +# if [[ "$stable" != "$crateversion" ]]; +# then +# sed 's|URL|rtic/index.html|g' redirect.html > $td/$devver/api/index.html +# sed 's|URL|book/en|g' redirect.html > $td/$devver/index.html +# else +# # If the current stable and the "dev" version in master branch +# # share the same major version, redirect dev/ to stable book +# sed 's|URL|rtic.rs/$stable/api/rtic|g' redirect.html > $td/$devver/api/index.html +# sed 's|URL|rtic.rs/$stable|g' redirect.html > $td/$devver/index.html +# fi +# +# # Build books +# for lang in ${langs[@]}; do +# ( cd book/$lang && +# if mdbook build |& tee /dev/tty | grep "\[ERROR\]"; then exit 1; else exit 0; fi +# ) +# cp -r book/$lang/book $td/$devver/book/$lang +# cp LICENSE-* $td/$devver/book/$lang/ +# done +# +# # Build older versions, including stable +# root=$(pwd) +# for ver in ${vers[@]}; do +# prefix=${ver} +# +# mkdir -p $td/$prefix/book +# src=$(mktemp -d) +# curl -L https://github.com/rtic-rs/cortex-m-rtic/archive/release/v${ver}.tar.gz | tar xz --strip-components 1 -C $src +# +# pushd $src +# rm -f .cargo/config +# cargo doc || cargo doc --features timer-queue +# cp -r target/doc $td/$prefix/api +# sed 's|URL|rtic/index.html|g' $root/redirect.html > $td/$prefix/api/index.html +# for lang in ${langs[@]}; do +# ( cd book/$lang && +# if mdbook build |& tee /dev/tty | grep "\[ERROR\]"; then exit 1; else exit 0; fi +# ) +# cp -r book/$lang/book $td/$prefix/book/$lang +# cp LICENSE-* $td/$prefix/book/$lang/ +# done +# sed 's|URL|book/en|g' $root/redirect.html > $td/$prefix/index.html +# popd +# +# rm -rf $src +# done +# +# # Copy the stable book to the stable alias +# cp -r $td/$stable $td/stable +# +# # Forward CNAME file +# cp CNAME $td/ +# mv $td/ bookstodeploy +# +# - name: Deploy to GH-pages +# uses: peaceiris/actions-gh-pages@v3 +# with: +# github_token: ${{ secrets.GITHUB_TOKEN }} +# publish_dir: ./bookstodeploy +# force_orphan: true # Refs: https://github.com/rust-lang/crater/blob/9ab6f9697c901c4a44025cf0a39b73ad5b37d198/.github/workflows/bors.yml#L125-L149 # @@ -511,8 +511,8 @@ jobs: - checkmacros - testmacros - tests - - docs - - mdbook +# - docs +# - mdbook runs-on: ubuntu-22.04 steps: - name: Mark the job as a success diff --git a/build.rs b/build.rs index 38d2c18..35f8303 100644 --- a/build.rs +++ b/build.rs @@ -18,9 +18,7 @@ fn main() { } else if target.starts_with("thumb") && !(target.starts_with("thumbv6m") | target.starts_with("thumbv8m.base")) { - panic!( - "Unknown target '{target}'. Need to update BASEPRI logic in build.rs." - ); + panic!("Unknown target '{target}'. Need to update BASEPRI logic in build.rs."); } println!("cargo:rerun-if-changed=build.rs"); diff --git a/macros/src/codegen/async_dispatchers.rs b/macros/src/codegen/async_dispatchers.rs index be02ad0..341f76f 100644 --- a/macros/src/codegen/async_dispatchers.rs +++ b/macros/src/codegen/async_dispatchers.rs @@ -84,9 +84,7 @@ pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { } if level > 0 { - let doc = format!( - "Interrupt handler to dispatch async tasks at priority {level}" - ); + let doc = format!("Interrupt handler to dispatch async tasks at priority {level}"); let attribute = &interrupts.get(&level).expect("UNREACHABLE").1.attrs; items.push(quote!( #[allow(non_snake_case)] diff --git a/macros/src/syntax/parse/hardware_task.rs b/macros/src/syntax/parse/hardware_task.rs index 6207e56..c13426f 100644 --- a/macros/src/syntax/parse/hardware_task.rs +++ b/macros/src/syntax/parse/hardware_task.rs @@ -39,9 +39,7 @@ impl HardwareTask { Err(parse::Error::new( span, - format!( - "this task handler must have type signature `fn({name}::Context)`" - ), + format!("this task handler must have type signature `fn({name}::Context)`"), )) } } @@ -82,9 +80,7 @@ impl HardwareTask { Err(parse::Error::new( span, - format!( - "this task handler must have type signature `fn({name}::Context)`" - ), + format!("this task handler must have type signature `fn({name}::Context)`"), )) } } diff --git a/macros/src/syntax/parse/idle.rs b/macros/src/syntax/parse/idle.rs index aa2ef5e..f049cca 100644 --- a/macros/src/syntax/parse/idle.rs +++ b/macros/src/syntax/parse/idle.rs @@ -34,9 +34,7 @@ impl Idle { Err(parse::Error::new( item.sig.ident.span(), - format!( - "this `#[idle]` function must have signature `fn({name}::Context) -> !`" - ), + format!("this `#[idle]` function must have signature `fn({name}::Context) -> !`"), )) } } diff --git a/macros/src/syntax/parse/software_task.rs b/macros/src/syntax/parse/software_task.rs index 319620a..fb9b37c 100644 --- a/macros/src/syntax/parse/software_task.rs +++ b/macros/src/syntax/parse/software_task.rs @@ -33,9 +33,7 @@ impl SoftwareTask { Err(parse::Error::new( span, - format!( - "this task handler must have type signature `async fn({name}::Context)`" - ), + format!("this task handler must have type signature `async fn({name}::Context)`"), )) } } @@ -70,9 +68,7 @@ impl SoftwareTask { Err(parse::Error::new( span, - format!( - "this task handler must have type signature `async fn({name}::Context)`" - ), + format!("this task handler must have type signature `async fn({name}::Context)`"), )) } } diff --git a/src/export.rs b/src/export.rs index 091cfb8..7beaf16 100644 --- a/src/export.rs +++ b/src/export.rs @@ -298,7 +298,7 @@ pub unsafe fn lock( if ceiling >= 4 { // safe to manipulate outside critical section // execute closure under protection of raised system ceiling - + // safe to manipulate outside critical section interrupt::free(|_| f(&mut *ptr)) } else { diff --git a/ui/exception-invalid.rs b/ui/exception-invalid.rs index 07d3c21..4f8e943 100644 --- a/ui/exception-invalid.rs +++ b/ui/exception-invalid.rs @@ -9,8 +9,8 @@ mod app { struct Local {} #[init] - fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { - (Shared {}, Local {}, init::Monotonics()) + fn init(cx: init::Context) -> (Shared, Local) { + (Shared {}, Local {}) } #[task(binds = NonMaskableInt)] diff --git a/ui/extern-interrupt-not-enough.rs b/ui/extern-interrupt-not-enough.rs index 1dbe923..94c8ee1 100644 --- a/ui/extern-interrupt-not-enough.rs +++ b/ui/extern-interrupt-not-enough.rs @@ -9,10 +9,10 @@ mod app { struct Local {} #[init] - fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { - (Shared {}, Local {}, init::Monotonics()) + fn init(cx: init::Context) -> (Shared, Local) { + (Shared {}, Local {}) } #[task] - fn a(_: a::Context) {} + async fn a(_: a::Context) {} } diff --git a/ui/extern-interrupt-not-enough.stderr b/ui/extern-interrupt-not-enough.stderr index 6f28b7a..e6c01b9 100644 --- a/ui/extern-interrupt-not-enough.stderr +++ b/ui/extern-interrupt-not-enough.stderr @@ -1,5 +1,5 @@ error: not enough interrupts to dispatch all software tasks (need: 1; given: 0) - --> ui/extern-interrupt-not-enough.rs:17:8 + --> ui/extern-interrupt-not-enough.rs:17:14 | -17 | fn a(_: a::Context) {} - | ^ +17 | async fn a(_: a::Context) {} + | ^ diff --git a/ui/extern-interrupt-used.rs b/ui/extern-interrupt-used.rs index 882d5e3..42de4c0 100644 --- a/ui/extern-interrupt-used.rs +++ b/ui/extern-interrupt-used.rs @@ -9,8 +9,8 @@ mod app { struct Local {} #[init] - fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { - (Shared {}, Local {}, init::Monotonics()) + fn init(cx: init::Context) -> (Shared, Local) { + (Shared {}, Local {}) } #[task(binds = UART0)] diff --git a/ui/task-priority-too-high.rs b/ui/task-priority-too-high.rs index 46ab561..44e4a25 100644 --- a/ui/task-priority-too-high.rs +++ b/ui/task-priority-too-high.rs @@ -9,8 +9,8 @@ mod app { struct Local {} #[init] - fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { - (Shared {}, Local {}, init::Monotonics()) + fn init(cx: init::Context) -> (Shared, Local) { + (Shared {}, Local {}) } #[task(binds = GPIOA, priority = 1)] diff --git a/ui/task-priority-too-high.stderr b/ui/task-priority-too-high.stderr index a7a15eb..1256377 100644 --- a/ui/task-priority-too-high.stderr +++ b/ui/task-priority-too-high.stderr @@ -1,7 +1,7 @@ warning: unused variable: `cx` --> ui/task-priority-too-high.rs:12:13 | -12 | fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { +12 | fn init(cx: init::Context) -> (Shared, Local) { | ^^ help: if this is intentional, prefix it with an underscore: `_cx` | = note: `#[warn(unused_variables)]` on by default diff --git a/ui/unknown-interrupt.rs b/ui/unknown-interrupt.rs index f2bc629..3c6c69f 100644 --- a/ui/unknown-interrupt.rs +++ b/ui/unknown-interrupt.rs @@ -9,7 +9,7 @@ mod app { struct Local {} #[init] - fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { - (Shared {}, Local {}, init::Monotonics()) + fn init(cx: init::Context) -> (Shared, Local) { + (Shared {}, Local {}) } } -- cgit v1.2.3 From 1eabb94f0424d7ff85786ad05615da69a379f01d Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Mon, 9 Jan 2023 09:48:39 +0100 Subject: New executor design --- src/export.rs | 68 +-------------------------------- src/export/executor.rs | 100 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+), 67 deletions(-) create mode 100644 src/export/executor.rs diff --git a/src/export.rs b/src/export.rs index 7beaf16..6017dcf 100644 --- a/src/export.rs +++ b/src/export.rs @@ -8,73 +8,7 @@ pub use cortex_m::{ Peripherals, }; -pub mod executor { - use core::{ - future::Future, - mem, - pin::Pin, - task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, - }; - - static WAKER_VTABLE: RawWakerVTable = - RawWakerVTable::new(waker_clone, waker_wake, waker_wake, waker_drop); - - unsafe fn waker_clone(p: *const ()) -> RawWaker { - RawWaker::new(p, &WAKER_VTABLE) - } - - unsafe fn waker_wake(p: *const ()) { - // The only thing we need from a waker is the function to call to pend the async - // dispatcher. - let f: fn() = mem::transmute(p); - f(); - } - - unsafe fn waker_drop(_: *const ()) { - // nop - } - - //============ - // AsyncTaskExecutor - - pub struct AsyncTaskExecutor { - task: Option, - } - - impl AsyncTaskExecutor { - pub const fn new() -> Self { - Self { task: None } - } - - pub fn is_running(&self) -> bool { - self.task.is_some() - } - - pub fn spawn(&mut self, future: F) { - self.task = Some(future); - } - - pub fn poll(&mut self, wake: fn()) -> bool { - if let Some(future) = &mut self.task { - unsafe { - let waker = Waker::from_raw(RawWaker::new(wake as *const (), &WAKER_VTABLE)); - let mut cx = Context::from_waker(&waker); - let future = Pin::new_unchecked(future); - - match future.poll(&mut cx) { - Poll::Ready(_) => { - self.task = None; - true // Only true if we finished now - } - Poll::Pending => false, - } - } - } else { - false - } - } - } -} +pub mod executor; /// Mask is used to store interrupt masks on systems without a BASEPRI register (M0, M0+, M23). /// It needs to be large enough to cover all the relevant interrupts in use. diff --git a/src/export/executor.rs b/src/export/executor.rs new file mode 100644 index 0000000..874ee19 --- /dev/null +++ b/src/export/executor.rs @@ -0,0 +1,100 @@ +use core::{ + cell::UnsafeCell, + future::Future, + mem::{self, MaybeUninit}, + pin::Pin, + sync::atomic::{AtomicBool, Ordering}, + task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, +}; + +static WAKER_VTABLE: RawWakerVTable = + RawWakerVTable::new(waker_clone, waker_wake, waker_wake, waker_drop); + +unsafe fn waker_clone(p: *const ()) -> RawWaker { + RawWaker::new(p, &WAKER_VTABLE) +} + +unsafe fn waker_wake(p: *const ()) { + // The only thing we need from a waker is the function to call to pend the async + // dispatcher. + let f: fn() = mem::transmute(p); + f(); +} + +unsafe fn waker_drop(_: *const ()) { + // nop +} + +//============ +// AsyncTaskExecutor + +/// Executor for an async task. +pub struct AsyncTaskExecutor { + // `task` is proteced by the `running` flag. + task: UnsafeCell>, + running: AtomicBool, + pending: AtomicBool, +} + +unsafe impl Sync for AsyncTaskExecutor {} + +impl AsyncTaskExecutor { + /// Create a new executor. + pub const fn new() -> Self { + Self { + task: UnsafeCell::new(MaybeUninit::uninit()), + running: AtomicBool::new(false), + pending: AtomicBool::new(false), + } + } + + /// Check if there is an active task in the executor. + pub fn is_running(&self) -> bool { + self.running.load(Ordering::Relaxed) + } + + /// Checks if a waker has pended the executor. + pub fn is_pending(&self) -> bool { + self.pending.load(Ordering::Relaxed) + } + + // Used by wakers to indicate that the executor needs to run. + pub fn set_pending(&self) { + self.pending.store(true, Ordering::Release); + } + + /// Try to reserve the executor for a future. + /// Used in conjunction with `spawn_unchecked` to reserve the executor before spawning. + /// + /// This could have been joined with `spawn_unchecked` for a complete safe API, however the + /// codegen needs to see if the reserve fails so it can give back input parameters. If spawning + /// was done within the same call the input parameters would be lost and could not be returned. + pub fn try_reserve(&self) -> bool { + self.running + .compare_exchange(false, true, Ordering::AcqRel, Ordering::Relaxed) + .is_ok() + } + + /// Spawn a future, only valid to do after `try_reserve` succeeds. + pub unsafe fn spawn_unchecked(&self, future: F) { + debug_assert!(self.running.load(Ordering::Relaxed)); + + self.task.get().write(MaybeUninit::new(future)); + } + + /// Poll the future in the executor. + pub fn poll(&self, wake: fn()) { + if self.is_running() { + let waker = unsafe { Waker::from_raw(RawWaker::new(wake as *const (), &WAKER_VTABLE)) }; + let mut cx = Context::from_waker(&waker); + let future = unsafe { Pin::new_unchecked(&mut *(self.task.get() as *mut F)) }; + + match future.poll(&mut cx) { + Poll::Ready(_) => { + self.running.store(false, Ordering::Release); + } + Poll::Pending => {} + } + } + } +} -- cgit v1.2.3 From cd790a94286cdc307d399b7f7a43e305e90de5bf Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Mon, 9 Jan 2023 21:02:53 +0100 Subject: More work on new spawn/executor --- Cargo.toml | 2 ++ macros/src/codegen/async_dispatchers.rs | 50 +++++---------------------------- macros/src/codegen/module.rs | 29 ++++++++++--------- macros/src/codegen/software_tasks.rs | 14 +-------- macros/src/codegen/util.rs | 10 ------- src/export.rs | 25 ++--------------- src/export/executor.rs | 11 +++++--- xtask/src/command.rs | 10 ++++++- 8 files changed, 43 insertions(+), 108 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index cad9291..6eb691d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,8 @@ rtic-monotonic = "1.0.0" rtic-core = "1.0.0" heapless = "0.7.7" bare-metal = "1.0.0" +#portable-atomic = { version = "0.3.19" } +atomic-polyfill = "1" [build-dependencies] version_check = "0.9" diff --git a/macros/src/codegen/async_dispatchers.rs b/macros/src/codegen/async_dispatchers.rs index 341f76f..012bd61 100644 --- a/macros/src/codegen/async_dispatchers.rs +++ b/macros/src/codegen/async_dispatchers.rs @@ -16,11 +16,10 @@ pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { items.push(quote!( #[allow(non_camel_case_types)] - type #type_name = impl core::future::Future + 'static; + type #type_name = impl core::future::Future; #[allow(non_upper_case_globals)] - static #exec_name: - rtic::RacyCell> = - rtic::RacyCell::new(rtic::export::executor::AsyncTaskExecutor::new()); + static #exec_name: rtic::export::executor::AsyncTaskExecutor<#type_name> = + rtic::export::executor::AsyncTaskExecutor::new(); )); } @@ -47,38 +46,13 @@ pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { let exec_name = util::internal_task_ident(name, "EXEC"); // let task = &app.software_tasks[name]; // let cfgs = &task.cfgs; - let executor_run_ident = util::executor_run_ident(name); - - let rq = util::rq_async_ident(name); - - items.push(quote!( - #[doc(hidden)] - #[allow(non_camel_case_types)] - #[allow(non_upper_case_globals)] - static #rq: core::sync::atomic::AtomicBool = core::sync::atomic::AtomicBool::new(false); - )); stmts.push(quote!( - if !(&*#exec_name.get()).is_running() { - // TODO Fix this to be compare and swap - if #rq.load(core::sync::atomic::Ordering::Relaxed) { - #rq.store(false, core::sync::atomic::Ordering::Relaxed); - - (&mut *#exec_name.get_mut()).spawn(#name(#name::Context::new())); - #executor_run_ident.store(true, core::sync::atomic::Ordering::Relaxed); - } - } - - if #executor_run_ident.load(core::sync::atomic::Ordering::Relaxed) { - #executor_run_ident.store(false, core::sync::atomic::Ordering::Relaxed); - if (&mut *#exec_name.get_mut()).poll(|| { - #executor_run_ident.store(true, core::sync::atomic::Ordering::Release); + if #exec_name.check_and_clear_pending() { + #exec_name.poll(|| { + #exec_name.set_pending(); #pend_interrupt - }) && #rq.load(core::sync::atomic::Ordering::Relaxed) { - // If the ready queue is not empty and the executor finished, restart this - // dispatch to check if the executor should be restarted. - #pend_interrupt - } + }); } )); } @@ -96,12 +70,7 @@ pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { const PRIORITY: u8 = #level; rtic::export::run(PRIORITY, || { - // Have the acquire/release semantics outside the checks to no overdo it - core::sync::atomic::fence(core::sync::atomic::Ordering::Acquire); - #(#stmts)* - - core::sync::atomic::fence(core::sync::atomic::Ordering::Release); }); } )); @@ -110,12 +79,7 @@ pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { #[allow(non_snake_case)] unsafe fn #dispatcher_name() -> ! { loop { - // Have the acquire/release semantics outside the checks to no overdo it - core::sync::atomic::fence(core::sync::atomic::Ordering::Acquire); - #(#stmts)* - - core::sync::atomic::fence(core::sync::atomic::Ordering::Release); } } )); diff --git a/macros/src/codegen/module.rs b/macros/src/codegen/module.rs index 70fbb5e..19cf241 100644 --- a/macros/src/codegen/module.rs +++ b/macros/src/codegen/module.rs @@ -98,6 +98,7 @@ pub fn codegen(ctxt: Context, app: &App, analysis: &Analysis) -> TokenStream2 { }; let internal_context_name = util::internal_task_ident(name, "Context"); + let exec_name = util::internal_task_ident(name, "EXEC"); items.push(quote!( #(#cfgs)* @@ -147,25 +148,25 @@ pub fn codegen(ctxt: Context, app: &App, analysis: &Analysis) -> TokenStream2 { let internal_spawn_ident = util::internal_task_ident(name, "spawn"); // Spawn caller - let rq = util::rq_async_ident(name); items.push(quote!( - - #(#cfgs)* - /// Spawns the task directly - #[allow(non_snake_case)] - #[doc(hidden)] - pub fn #internal_spawn_ident() -> Result<(), ()> { - unsafe { - // TODO: Fix this to be compare and swap - if #rq.load(core::sync::atomic::Ordering::Acquire) { - Err(()) - } else { - #rq.store(true, core::sync::atomic::Ordering::Release); + #(#cfgs)* + /// Spawns the task directly + #[allow(non_snake_case)] + #[doc(hidden)] + pub fn #internal_spawn_ident() -> Result<(), ()> { + if #exec_name.try_reserve() { + unsafe { + // TODO: Add args here + #exec_name.spawn_unchecked(#name(#name::Context::new())); + } #pend_interrupt + Ok(()) + } else { + Err(()) } } - })); + )); module_items.push(quote!( #(#cfgs)* diff --git a/macros/src/codegen/software_tasks.rs b/macros/src/codegen/software_tasks.rs index 4cb1fa9..b923283 100644 --- a/macros/src/codegen/software_tasks.rs +++ b/macros/src/codegen/software_tasks.rs @@ -1,7 +1,7 @@ use crate::syntax::{ast::App, Context}; use crate::{ analyze::Analysis, - codegen::{local_resources_struct, module, shared_resources_struct, util}, + codegen::{local_resources_struct, module, shared_resources_struct}, }; use proc_macro2::TokenStream as TokenStream2; use quote::quote; @@ -13,18 +13,6 @@ pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { // Any task for (name, task) in app.software_tasks.iter() { - let executor_ident = util::executor_run_ident(name); - mod_app.push(quote!( - #[allow(non_camel_case_types)] - #[allow(non_upper_case_globals)] - #[doc(hidden)] - static #executor_ident: core::sync::atomic::AtomicBool = - core::sync::atomic::AtomicBool::new(false); - )); - - // `${task}Resources` - - // `${task}Locals` if !task.args.local_resources.is_empty() { let (item, constructor) = local_resources_struct::codegen(Context::SoftwareTask(name), app); diff --git a/macros/src/codegen/util.rs b/macros/src/codegen/util.rs index a0caf0a..0558d9d 100644 --- a/macros/src/codegen/util.rs +++ b/macros/src/codegen/util.rs @@ -49,11 +49,6 @@ pub fn impl_mutex( ) } -/// Generates an identifier for the `EXECUTOR_RUN` atomics (`async` API) -pub fn executor_run_ident(task: &Ident) -> Ident { - mark_internal_name(&format!("{task}_EXECUTOR_RUN")) -} - pub fn interrupt_ident() -> Ident { let span = Span::call_site(); Ident::new("interrupt", span) @@ -151,11 +146,6 @@ pub fn local_resources_ident(ctxt: Context, app: &App) -> Ident { mark_internal_name(&s) } -/// Generates an identifier for a ready queue, async task version -pub fn rq_async_ident(async_task_name: &Ident) -> Ident { - mark_internal_name(&format!("ASYNC_TASK_{async_task_name}_RQ")) -} - /// Suffixed identifier pub fn suffixed(name: &str) -> Ident { let span = Span::call_site(); diff --git a/src/export.rs b/src/export.rs index 6017dcf..cdca972 100644 --- a/src/export.rs +++ b/src/export.rs @@ -1,5 +1,4 @@ pub use bare_metal::CriticalSection; -use core::sync::atomic::{AtomicBool, Ordering}; pub use cortex_m::{ asm::nop, asm::wfi, @@ -7,6 +6,8 @@ pub use cortex_m::{ peripheral::{scb::SystemHandler, DWT, NVIC, SCB, SYST}, Peripherals, }; +//pub use portable_atomic as atomic; +pub use atomic_polyfill as atomic; pub mod executor; @@ -72,28 +73,6 @@ where f(); } -pub struct Barrier { - inner: AtomicBool, -} - -impl Barrier { - pub const fn new() -> Self { - Barrier { - inner: AtomicBool::new(false), - } - } - - pub fn release(&self) { - self.inner.store(true, Ordering::Release); - } - - pub fn wait(&self) { - while !self.inner.load(Ordering::Acquire) { - core::hint::spin_loop() - } - } -} - /// Const helper to check architecture pub const fn have_basepri() -> bool { #[cfg(have_basepri)] diff --git a/src/export/executor.rs b/src/export/executor.rs index 874ee19..2f88eff 100644 --- a/src/export/executor.rs +++ b/src/export/executor.rs @@ -1,9 +1,9 @@ +use super::atomic::{AtomicBool, Ordering}; use core::{ cell::UnsafeCell, future::Future, mem::{self, MaybeUninit}, pin::Pin, - sync::atomic::{AtomicBool, Ordering}, task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, }; @@ -53,9 +53,11 @@ impl AsyncTaskExecutor { self.running.load(Ordering::Relaxed) } - /// Checks if a waker has pended the executor. - pub fn is_pending(&self) -> bool { - self.pending.load(Ordering::Relaxed) + /// Checks if a waker has pended the executor and simultaneously clears the flag. + pub fn check_and_clear_pending(&self) -> bool { + self.pending + .compare_exchange(true, false, Ordering::Relaxed, Ordering::Relaxed) + .is_ok() } // Used by wakers to indicate that the executor needs to run. @@ -80,6 +82,7 @@ impl AsyncTaskExecutor { debug_assert!(self.running.load(Ordering::Relaxed)); self.task.get().write(MaybeUninit::new(future)); + self.set_pending(); } /// Poll the future in the executor. diff --git a/xtask/src/command.rs b/xtask/src/command.rs index 418f440..4e90369 100644 --- a/xtask/src/command.rs +++ b/xtask/src/command.rs @@ -70,7 +70,15 @@ impl<'a> CargoCommand<'a> { features, mode, } => { - let mut args = vec!["+nightly", self.name(), "--examples", "--target", target]; + let mut args = vec![ + "+nightly", + self.name(), + "--examples", + "--target", + target, + "--features", + "test-critical-section", + ]; if let Some(feature_name) = features { args.extend_from_slice(&["--features", feature_name]); -- cgit v1.2.3 From d6d58b0eb88242cf63724e1420bd29f8a4489916 Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Tue, 10 Jan 2023 21:03:10 +0100 Subject: Async tasks can now take arguments at spawn again --- ci/expected/async-task.run | 1 + examples/async-task.rs | 6 ++++ macros/src/codegen/module.rs | 11 +++--- macros/src/codegen/software_tasks.rs | 3 +- macros/src/codegen/util.rs | 54 +++++++++++++++++++++++++++-- macros/src/syntax/analyze.rs | 5 +++ macros/src/syntax/ast.rs | 5 ++- macros/src/syntax/parse/hardware_task.rs | 58 +++++++++++++------------------- macros/src/syntax/parse/idle.rs | 18 +++++----- macros/src/syntax/parse/init.rs | 22 ++++++------ macros/src/syntax/parse/software_task.rs | 10 +++--- macros/src/syntax/parse/util.rs | 30 +++++++++++------ macros/ui/task-divergent.stderr | 2 +- macros/ui/task-no-context.stderr | 2 +- macros/ui/task-pub.stderr | 2 +- macros/ui/task-unsafe.stderr | 2 +- macros/ui/task-zero-prio.stderr | 2 +- 17 files changed, 153 insertions(+), 80 deletions(-) diff --git a/ci/expected/async-task.run b/ci/expected/async-task.run index 6787fa8..1f93a4c 100644 --- a/ci/expected/async-task.run +++ b/ci/expected/async-task.run @@ -1,4 +1,5 @@ init hello from async2 hello from async +hello from async with args a: 1, b: 2 idle diff --git a/examples/async-task.rs b/examples/async-task.rs index 780bc08..e1ab143 100644 --- a/examples/async-task.rs +++ b/examples/async-task.rs @@ -27,6 +27,7 @@ mod app { hprintln!("init"); async_task::spawn().unwrap(); + async_task_args::spawn(1, 2).unwrap(); async_task2::spawn().unwrap(); (Shared { a: 0 }, Local {}) @@ -53,6 +54,11 @@ mod app { hprintln!("hello from async"); } + #[task] + async fn async_task_args(_cx: async_task_args::Context, a: u32, b: i32) { + hprintln!("hello from async with args a: {}, b: {}", a, b); + } + #[task(priority = 2, shared = [a])] async fn async_task2(cx: async_task2::Context) { let async_task2::SharedResources { a: _, .. } = cx.shared; diff --git a/macros/src/codegen/module.rs b/macros/src/codegen/module.rs index 19cf241..666bd04 100644 --- a/macros/src/codegen/module.rs +++ b/macros/src/codegen/module.rs @@ -146,6 +146,8 @@ pub fn codegen(ctxt: Context, app: &App, analysis: &Analysis) -> TokenStream2 { }; let internal_spawn_ident = util::internal_task_ident(name, "spawn"); + let (input_args, input_tupled, input_untupled, input_ty) = + util::regroup_inputs(&spawnee.inputs); // Spawn caller items.push(quote!( @@ -153,17 +155,18 @@ pub fn codegen(ctxt: Context, app: &App, analysis: &Analysis) -> TokenStream2 { /// Spawns the task directly #[allow(non_snake_case)] #[doc(hidden)] - pub fn #internal_spawn_ident() -> Result<(), ()> { + pub fn #internal_spawn_ident(#(#input_args,)*) -> Result<(), #input_ty> { if #exec_name.try_reserve() { + // This unsafe is protected by `try_reserve`, see its documentation for details unsafe { - // TODO: Add args here - #exec_name.spawn_unchecked(#name(#name::Context::new())); + #exec_name.spawn_unchecked(#name(#name::Context::new() #(,#input_untupled)*)); } + #pend_interrupt Ok(()) } else { - Err(()) + Err(#input_tupled) } } )); diff --git a/macros/src/codegen/software_tasks.rs b/macros/src/codegen/software_tasks.rs index b923283..34fc851 100644 --- a/macros/src/codegen/software_tasks.rs +++ b/macros/src/codegen/software_tasks.rs @@ -36,12 +36,13 @@ pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { let attrs = &task.attrs; let cfgs = &task.cfgs; let stmts = &task.stmts; + let inputs = &task.inputs; user_tasks.push(quote!( #(#attrs)* #(#cfgs)* #[allow(non_snake_case)] - async fn #name(#context: #name::Context<'static>) { + async fn #name<'a>(#context: #name::Context<'a> #(,#inputs)*) { use rtic::Mutex as _; use rtic::mutex::prelude::*; diff --git a/macros/src/codegen/util.rs b/macros/src/codegen/util.rs index 0558d9d..e121487 100644 --- a/macros/src/codegen/util.rs +++ b/macros/src/codegen/util.rs @@ -1,9 +1,8 @@ -use core::sync::atomic::{AtomicUsize, Ordering}; - use crate::syntax::{ast::App, Context}; +use core::sync::atomic::{AtomicUsize, Ordering}; use proc_macro2::{Span, TokenStream as TokenStream2}; use quote::quote; -use syn::{Attribute, Ident}; +use syn::{Attribute, Ident, PatType}; const RTIC_INTERNAL: &str = "__rtic_internal"; @@ -94,6 +93,55 @@ pub fn link_section_uninit() -> TokenStream2 { quote!(#[link_section = #section]) } +/// Regroups the inputs of a task +/// +/// `inputs` could be &[`input: Foo`] OR &[`mut x: i32`, `ref y: i64`] +pub fn regroup_inputs( + inputs: &[PatType], +) -> ( + // args e.g. &[`_0`], &[`_0: i32`, `_1: i64`] + Vec, + // tupled e.g. `_0`, `(_0, _1)` + TokenStream2, + // untupled e.g. &[`_0`], &[`_0`, `_1`] + Vec, + // ty e.g. `Foo`, `(i32, i64)` + TokenStream2, +) { + if inputs.len() == 1 { + let ty = &inputs[0].ty; + + ( + vec![quote!(_0: #ty)], + quote!(_0), + vec![quote!(_0)], + quote!(#ty), + ) + } else { + let mut args = vec![]; + let mut pats = vec![]; + let mut tys = vec![]; + + for (i, input) in inputs.iter().enumerate() { + let i = Ident::new(&format!("_{}", i), Span::call_site()); + let ty = &input.ty; + + args.push(quote!(#i: #ty)); + + pats.push(quote!(#i)); + + tys.push(quote!(#ty)); + } + + let tupled = { + let pats = pats.clone(); + quote!((#(#pats,)*)) + }; + let ty = quote!((#(#tys,)*)); + (args, tupled, pats, ty) + } +} + /// Get the ident for the name of the task pub fn get_task_name(ctxt: Context, app: &App) -> Ident { let s = match ctxt { diff --git a/macros/src/syntax/analyze.rs b/macros/src/syntax/analyze.rs index b70ceb8..3ed1487 100644 --- a/macros/src/syntax/analyze.rs +++ b/macros/src/syntax/analyze.rs @@ -287,6 +287,11 @@ pub(crate) fn app(app: &App) -> Result { let channel = channels.entry(spawnee_prio).or_default(); channel.tasks.insert(name.clone()); + + // All inputs are send as we do not know from where they may be spawned. + spawnee.inputs.iter().for_each(|input| { + send_types.insert(input.ty.clone()); + }); } // No channel should ever be empty diff --git a/macros/src/syntax/ast.rs b/macros/src/syntax/ast.rs index da6016a..27e6773 100644 --- a/macros/src/syntax/ast.rs +++ b/macros/src/syntax/ast.rs @@ -1,6 +1,6 @@ //! Abstract Syntax Tree -use syn::{Attribute, Expr, Ident, Item, ItemUse, Pat, Path, Stmt, Type}; +use syn::{Attribute, Expr, Ident, Item, ItemUse, Pat, PatType, Path, Stmt, Type}; use crate::syntax::Map; @@ -205,6 +205,9 @@ pub struct SoftwareTask { /// The context argument pub context: Box, + /// The inputs of this software task + pub inputs: Vec, + /// The statements that make up the task handler pub stmts: Vec, diff --git a/macros/src/syntax/parse/hardware_task.rs b/macros/src/syntax/parse/hardware_task.rs index c13426f..7f6dfbe 100644 --- a/macros/src/syntax/parse/hardware_task.rs +++ b/macros/src/syntax/parse/hardware_task.rs @@ -15,25 +15,20 @@ impl HardwareTask { let name = item.sig.ident.to_string(); - if name == "init" || name == "idle" { - return Err(parse::Error::new( - span, - "tasks cannot be named `init` or `idle`", - )); - } - if valid_signature { - if let Some(context) = util::parse_inputs(item.sig.inputs, &name) { - let FilterAttrs { cfgs, attrs, .. } = util::filter_attributes(item.attrs); + if let Some((context, Ok(rest))) = util::parse_inputs(item.sig.inputs, &name) { + if rest.is_empty() { + let FilterAttrs { cfgs, attrs, .. } = util::filter_attributes(item.attrs); - return Ok(HardwareTask { - args, - cfgs, - attrs, - context, - stmts: item.block.stmts, - is_extern: false, - }); + return Ok(HardwareTask { + args, + cfgs, + attrs, + context, + stmts: item.block.stmts, + is_extern: false, + }); + } } } @@ -56,25 +51,20 @@ impl HardwareTask { let name = item.sig.ident.to_string(); - if name == "init" || name == "idle" { - return Err(parse::Error::new( - span, - "tasks cannot be named `init` or `idle`", - )); - } - if valid_signature { - if let Some(context) = util::parse_inputs(item.sig.inputs, &name) { - let FilterAttrs { cfgs, attrs, .. } = util::filter_attributes(item.attrs); + if let Some((context, Ok(rest))) = util::parse_inputs(item.sig.inputs, &name) { + if rest.is_empty() { + let FilterAttrs { cfgs, attrs, .. } = util::filter_attributes(item.attrs); - return Ok(HardwareTask { - args, - cfgs, - attrs, - context, - stmts: Vec::::new(), - is_extern: true, - }); + return Ok(HardwareTask { + args, + cfgs, + attrs, + context, + stmts: Vec::::new(), + is_extern: true, + }); + } } } diff --git a/macros/src/syntax/parse/idle.rs b/macros/src/syntax/parse/idle.rs index f049cca..124c136 100644 --- a/macros/src/syntax/parse/idle.rs +++ b/macros/src/syntax/parse/idle.rs @@ -21,14 +21,16 @@ impl Idle { let name = item.sig.ident.to_string(); if valid_signature { - if let Some(context) = util::parse_inputs(item.sig.inputs, &name) { - return Ok(Idle { - args, - attrs: item.attrs, - context, - name: item.sig.ident, - stmts: item.block.stmts, - }); + if let Some((context, Ok(rest))) = util::parse_inputs(item.sig.inputs, &name) { + if rest.is_empty() { + return Ok(Idle { + args, + attrs: item.attrs, + context, + name: item.sig.ident, + stmts: item.block.stmts, + }); + } } } diff --git a/macros/src/syntax/parse/init.rs b/macros/src/syntax/parse/init.rs index 23130c8..0aea20b 100644 --- a/macros/src/syntax/parse/init.rs +++ b/macros/src/syntax/parse/init.rs @@ -25,16 +25,18 @@ impl Init { if let Ok((user_shared_struct, user_local_struct)) = util::type_is_init_return(&item.sig.output) { - if let Some(context) = util::parse_inputs(item.sig.inputs, &name) { - return Ok(Init { - args, - attrs: item.attrs, - context, - name: item.sig.ident, - stmts: item.block.stmts, - user_shared_struct, - user_local_struct, - }); + if let Some((context, Ok(rest))) = util::parse_inputs(item.sig.inputs, &name) { + if rest.is_empty() { + return Ok(Init { + args, + attrs: item.attrs, + context, + name: item.sig.ident, + stmts: item.block.stmts, + user_shared_struct, + user_local_struct, + }); + } } } } diff --git a/macros/src/syntax/parse/software_task.rs b/macros/src/syntax/parse/software_task.rs index fb9b37c..769aa65 100644 --- a/macros/src/syntax/parse/software_task.rs +++ b/macros/src/syntax/parse/software_task.rs @@ -17,7 +17,7 @@ impl SoftwareTask { let name = item.sig.ident.to_string(); if valid_signature { - if let Some(context) = util::parse_inputs(item.sig.inputs, &name) { + if let Some((context, Ok(inputs))) = util::parse_inputs(item.sig.inputs, &name) { let FilterAttrs { cfgs, attrs, .. } = util::filter_attributes(item.attrs); return Ok(SoftwareTask { @@ -25,6 +25,7 @@ impl SoftwareTask { attrs, cfgs, context, + inputs, stmts: item.block.stmts, is_extern: false, }); @@ -33,7 +34,7 @@ impl SoftwareTask { Err(parse::Error::new( span, - format!("this task handler must have type signature `async fn({name}::Context)`"), + format!("this task handler must have type signature `async fn({name}::Context, ..)`"), )) } } @@ -52,7 +53,7 @@ impl SoftwareTask { let name = item.sig.ident.to_string(); if valid_signature { - if let Some(context) = util::parse_inputs(item.sig.inputs, &name) { + if let Some((context, Ok(inputs))) = util::parse_inputs(item.sig.inputs, &name) { let FilterAttrs { cfgs, attrs, .. } = util::filter_attributes(item.attrs); return Ok(SoftwareTask { @@ -60,6 +61,7 @@ impl SoftwareTask { attrs, cfgs, context, + inputs, stmts: Vec::::new(), is_extern: true, }); @@ -68,7 +70,7 @@ impl SoftwareTask { Err(parse::Error::new( span, - format!("this task handler must have type signature `async fn({name}::Context)`"), + format!("this task handler must have type signature `async fn({name}::Context, ..)`"), )) } } diff --git a/macros/src/syntax/parse/util.rs b/macros/src/syntax/parse/util.rs index 900ef9d..5a5e0c0 100644 --- a/macros/src/syntax/parse/util.rs +++ b/macros/src/syntax/parse/util.rs @@ -3,8 +3,8 @@ use syn::{ parse::{self, ParseStream}, punctuated::Punctuated, spanned::Spanned, - Abi, AttrStyle, Attribute, Expr, FnArg, ForeignItemFn, Ident, ItemFn, Pat, Path, PathArguments, - ReturnType, Token, Type, Visibility, + Abi, AttrStyle, Attribute, Expr, FnArg, ForeignItemFn, Ident, ItemFn, Pat, PatType, Path, + PathArguments, ReturnType, Token, Type, Visibility, }; use crate::syntax::{ @@ -231,19 +231,29 @@ pub fn parse_local_resources(content: ParseStream<'_>) -> parse::Result, name: &str) -> Option> { +type ParseInputResult = Option<(Box, Result, FnArg>)>; + +pub fn parse_inputs(inputs: Punctuated, name: &str) -> ParseInputResult { let mut inputs = inputs.into_iter(); - if let Some(FnArg::Typed(first)) = inputs.next() { - if type_is_path(&first.ty, &[name, "Context"]) { - // No more inputs - if inputs.next().is_none() { - return Some(first.pat); + match inputs.next() { + Some(FnArg::Typed(first)) => { + if type_is_path(&first.ty, &[name, "Context"]) { + let rest = inputs + .map(|arg| match arg { + FnArg::Typed(arg) => Ok(arg), + _ => Err(arg), + }) + .collect::, _>>(); + + Some((first.pat, rest)) + } else { + None } } - } - None + _ => None, + } } pub fn type_is_bottom(ty: &ReturnType) -> bool { diff --git a/macros/ui/task-divergent.stderr b/macros/ui/task-divergent.stderr index bd22bd3..dd00208 100644 --- a/macros/ui/task-divergent.stderr +++ b/macros/ui/task-divergent.stderr @@ -1,4 +1,4 @@ -error: this task handler must have type signature `async fn(foo::Context)` +error: this task handler must have type signature `async fn(foo::Context, ..)` --> ui/task-divergent.rs:6:14 | 6 | async fn foo(_: foo::Context) -> ! { diff --git a/macros/ui/task-no-context.stderr b/macros/ui/task-no-context.stderr index 91239a1..62147aa 100644 --- a/macros/ui/task-no-context.stderr +++ b/macros/ui/task-no-context.stderr @@ -1,4 +1,4 @@ -error: this task handler must have type signature `async fn(foo::Context)` +error: this task handler must have type signature `async fn(foo::Context, ..)` --> ui/task-no-context.rs:6:14 | 6 | async fn foo() {} diff --git a/macros/ui/task-pub.stderr b/macros/ui/task-pub.stderr index 72c4e63..7b9813d 100644 --- a/macros/ui/task-pub.stderr +++ b/macros/ui/task-pub.stderr @@ -1,4 +1,4 @@ -error: this task handler must have type signature `async fn(foo::Context)` +error: this task handler must have type signature `async fn(foo::Context, ..)` --> ui/task-pub.rs:6:18 | 6 | pub async fn foo(_: foo::Context) {} diff --git a/macros/ui/task-unsafe.stderr b/macros/ui/task-unsafe.stderr index 4908481..90ac76f 100644 --- a/macros/ui/task-unsafe.stderr +++ b/macros/ui/task-unsafe.stderr @@ -1,4 +1,4 @@ -error: this task handler must have type signature `async fn(foo::Context)` +error: this task handler must have type signature `async fn(foo::Context, ..)` --> ui/task-unsafe.rs:6:21 | 6 | async unsafe fn foo(_: foo::Context) {} diff --git a/macros/ui/task-zero-prio.stderr b/macros/ui/task-zero-prio.stderr index f2d8223..1ab9aab 100644 --- a/macros/ui/task-zero-prio.stderr +++ b/macros/ui/task-zero-prio.stderr @@ -1,4 +1,4 @@ -error: this task handler must have type signature `async fn(foo::Context)` +error: this task handler must have type signature `async fn(foo::Context, ..)` --> ui/task-zero-prio.rs:15:8 | 15 | fn foo(_: foo::Context) {} -- cgit v1.2.3 From 5688a5d332cdaffaca64ade5b138a3676ac7cd32 Mon Sep 17 00:00:00 2001 From: Per Lindgren Date: Thu, 12 Jan 2023 08:50:12 +0100 Subject: executor update for less unsafe and more clear --- macros/src/codegen/async_dispatchers.rs | 11 ++++---- macros/src/codegen/module.rs | 8 +++--- src/export/executor.rs | 45 +++++++++++++++++++-------------- 3 files changed, 34 insertions(+), 30 deletions(-) diff --git a/macros/src/codegen/async_dispatchers.rs b/macros/src/codegen/async_dispatchers.rs index 012bd61..a12ad32 100644 --- a/macros/src/codegen/async_dispatchers.rs +++ b/macros/src/codegen/async_dispatchers.rs @@ -44,16 +44,15 @@ pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { for name in channel.tasks.iter() { let exec_name = util::internal_task_ident(name, "EXEC"); + // TODO: Fix cfg // let task = &app.software_tasks[name]; // let cfgs = &task.cfgs; stmts.push(quote!( - if #exec_name.check_and_clear_pending() { - #exec_name.poll(|| { - #exec_name.set_pending(); - #pend_interrupt - }); - } + #exec_name.poll(|| { + #exec_name.set_pending(); + #pend_interrupt + }); )); } diff --git a/macros/src/codegen/module.rs b/macros/src/codegen/module.rs index 666bd04..f4c188a 100644 --- a/macros/src/codegen/module.rs +++ b/macros/src/codegen/module.rs @@ -156,11 +156,8 @@ pub fn codegen(ctxt: Context, app: &App, analysis: &Analysis) -> TokenStream2 { #[allow(non_snake_case)] #[doc(hidden)] pub fn #internal_spawn_ident(#(#input_args,)*) -> Result<(), #input_ty> { - if #exec_name.try_reserve() { - // This unsafe is protected by `try_reserve`, see its documentation for details - unsafe { - #exec_name.spawn_unchecked(#name(#name::Context::new() #(,#input_untupled)*)); - } + + if #exec_name.spawn(|| #name(unsafe { #name::Context::new() } #(,#input_untupled)*) ) { #pend_interrupt @@ -168,6 +165,7 @@ pub fn codegen(ctxt: Context, app: &App, analysis: &Analysis) -> TokenStream2 { } else { Err(#input_tupled) } + } )); diff --git a/src/export/executor.rs b/src/export/executor.rs index 2f88eff..e64cc43 100644 --- a/src/export/executor.rs +++ b/src/export/executor.rs @@ -30,7 +30,7 @@ unsafe fn waker_drop(_: *const ()) { /// Executor for an async task. pub struct AsyncTaskExecutor { - // `task` is proteced by the `running` flag. + // `task` is protected by the `running` flag. task: UnsafeCell>, running: AtomicBool, pending: AtomicBool, @@ -40,6 +40,7 @@ unsafe impl Sync for AsyncTaskExecutor {} impl AsyncTaskExecutor { /// Create a new executor. + #[inline(always)] pub const fn new() -> Self { Self { task: UnsafeCell::new(MaybeUninit::uninit()), @@ -49,45 +50,51 @@ impl AsyncTaskExecutor { } /// Check if there is an active task in the executor. + #[inline(always)] pub fn is_running(&self) -> bool { self.running.load(Ordering::Relaxed) } /// Checks if a waker has pended the executor and simultaneously clears the flag. - pub fn check_and_clear_pending(&self) -> bool { + #[inline(always)] + fn check_and_clear_pending(&self) -> bool { + // Ordering::Acquire to enforce that update of task is visible to poll self.pending - .compare_exchange(true, false, Ordering::Relaxed, Ordering::Relaxed) + .compare_exchange(true, false, Ordering::Acquire, Ordering::Relaxed) .is_ok() } // Used by wakers to indicate that the executor needs to run. + #[inline(always)] pub fn set_pending(&self) { self.pending.store(true, Ordering::Release); } - /// Try to reserve the executor for a future. - /// Used in conjunction with `spawn_unchecked` to reserve the executor before spawning. - /// - /// This could have been joined with `spawn_unchecked` for a complete safe API, however the - /// codegen needs to see if the reserve fails so it can give back input parameters. If spawning - /// was done within the same call the input parameters would be lost and could not be returned. - pub fn try_reserve(&self) -> bool { - self.running + /// Spawn a future + #[inline(always)] + pub fn spawn(&self, future: impl Fn() -> F) -> bool { + // Try to reserve the executor for a future. + if self + .running .compare_exchange(false, true, Ordering::AcqRel, Ordering::Relaxed) .is_ok() - } - - /// Spawn a future, only valid to do after `try_reserve` succeeds. - pub unsafe fn spawn_unchecked(&self, future: F) { - debug_assert!(self.running.load(Ordering::Relaxed)); + { + // This unsafe is protected by `running` being false and the atomic setting it to true. + unsafe { + self.task.get().write(MaybeUninit::new(future())); + } + self.set_pending(); - self.task.get().write(MaybeUninit::new(future)); - self.set_pending(); + true + } else { + false + } } /// Poll the future in the executor. + #[inline(always)] pub fn poll(&self, wake: fn()) { - if self.is_running() { + if self.is_running() && self.check_and_clear_pending() { let waker = unsafe { Waker::from_raw(RawWaker::new(wake as *const (), &WAKER_VTABLE)) }; let mut cx = Context::from_waker(&waker); let future = unsafe { Pin::new_unchecked(&mut *(self.task.get() as *mut F)) }; -- cgit v1.2.3 From 4601782466c518d313ba79d9437bf7a3f8dbbf76 Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Sat, 14 Jan 2023 21:11:55 +0100 Subject: monotonic experiments --- Cargo.toml | 2 +- rtic-timer/Cargo.toml | 11 ++ rtic-timer/src/lib.rs | 138 +++++++++++++++++ rtic-timer/src/sll.rs | 421 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 571 insertions(+), 1 deletion(-) create mode 100644 rtic-timer/Cargo.toml create mode 100644 rtic-timer/src/lib.rs create mode 100644 rtic-timer/src/sll.rs diff --git a/Cargo.toml b/Cargo.toml index 6eb691d..c22d023 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,7 +51,7 @@ codegen-units = 1 lto = true [workspace] -members = ["macros", "xtask"] +members = ["macros", "xtask", "rtic-timer"] # do not optimize proc-macro deps or build scripts [profile.dev.build-override] diff --git a/rtic-timer/Cargo.toml b/rtic-timer/Cargo.toml new file mode 100644 index 0000000..8e2e2ad --- /dev/null +++ b/rtic-timer/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "rtic-timer" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +cortex-m = "0.7.6" +rtic-monotonic = "1.0.0" +fugit = "0.3.6" \ No newline at end of file diff --git a/rtic-timer/src/lib.rs b/rtic-timer/src/lib.rs new file mode 100644 index 0000000..e7051d2 --- /dev/null +++ b/rtic-timer/src/lib.rs @@ -0,0 +1,138 @@ +#![no_std] + +use core::sync::atomic::{AtomicU32, Ordering}; +use core::{cmp::Ordering, task::Waker}; +use cortex_m::peripheral::{syst::SystClkSource, SYST}; +pub use fugit::{self, ExtU64}; +pub use rtic_monotonic::Monotonic; + +mod sll; +use sll::{IntrusiveSortedLinkedList, Min as IsslMin, Node as IntrusiveNode}; + +pub struct Timer { + cnt: AtomicU32, + // queue: IntrusiveSortedLinkedList<'static, WakerNotReady, IsslMin>, +} + +#[allow(non_snake_case)] +#[no_mangle] +fn SysTick() { + // .. + let cnt = unsafe { + static mut CNT: u32 = 0; + &mut CNT + }; + + *cnt = cnt.wrapping_add(1); +} + +/// Systick implementing `rtic_monotonic::Monotonic` which runs at a +/// settable rate using the `TIMER_HZ` parameter. +pub struct Systick { + systick: SYST, + cnt: u64, +} + +impl Systick { + /// Provide a new `Monotonic` based on SysTick. + /// + /// The `sysclk` parameter is the speed at which SysTick runs at. This value should come from + /// the clock generation function of the used HAL. + /// + /// Notice that the actual rate of the timer is a best approximation based on the given + /// `sysclk` and `TIMER_HZ`. + pub fn new(mut systick: SYST, sysclk: u32) -> Self { + // + TIMER_HZ / 2 provides round to nearest instead of round to 0. + // - 1 as the counter range is inclusive [0, reload] + let reload = (sysclk + TIMER_HZ / 2) / TIMER_HZ - 1; + + assert!(reload <= 0x00ff_ffff); + assert!(reload > 0); + + systick.disable_counter(); + systick.set_clock_source(SystClkSource::Core); + systick.set_reload(reload); + + Systick { systick, cnt: 0 } + } +} + +impl Monotonic for Systick { + const DISABLE_INTERRUPT_ON_EMPTY_QUEUE: bool = false; + + type Instant = fugit::TimerInstantU64; + type Duration = fugit::TimerDurationU64; + + fn now(&mut self) -> Self::Instant { + if self.systick.has_wrapped() { + self.cnt = self.cnt.wrapping_add(1); + } + + Self::Instant::from_ticks(self.cnt) + } + + unsafe fn reset(&mut self) { + self.systick.clear_current(); + self.systick.enable_counter(); + } + + #[inline(always)] + fn set_compare(&mut self, _val: Self::Instant) { + // No need to do something here, we get interrupts anyway. + } + + #[inline(always)] + fn clear_compare_flag(&mut self) { + // NOOP with SysTick interrupt + } + + #[inline(always)] + fn zero() -> Self::Instant { + Self::Instant::from_ticks(0) + } + + #[inline(always)] + fn on_interrupt(&mut self) { + if self.systick.has_wrapped() { + self.cnt = self.cnt.wrapping_add(1); + } + } +} + +struct WakerNotReady +where + Mono: Monotonic, +{ + pub waker: Waker, + pub instant: Mono::Instant, + pub marker: u32, +} + +impl Eq for WakerNotReady where Mono: Monotonic {} + +impl Ord for WakerNotReady +where + Mono: Monotonic, +{ + fn cmp(&self, other: &Self) -> Ordering { + self.instant.cmp(&other.instant) + } +} + +impl PartialEq for WakerNotReady +where + Mono: Monotonic, +{ + fn eq(&self, other: &Self) -> bool { + self.instant == other.instant + } +} + +impl PartialOrd for WakerNotReady +where + Mono: Monotonic, +{ + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} diff --git a/rtic-timer/src/sll.rs b/rtic-timer/src/sll.rs new file mode 100644 index 0000000..43b53c1 --- /dev/null +++ b/rtic-timer/src/sll.rs @@ -0,0 +1,421 @@ +//! An intrusive sorted priority linked list, designed for use in `Future`s in RTIC. +use core::cmp::Ordering; +use core::fmt; +use core::marker::PhantomData; +use core::ops::{Deref, DerefMut}; +use core::ptr::NonNull; + +/// Marker for Min sorted [`IntrusiveSortedLinkedList`]. +pub struct Min; + +/// Marker for Max sorted [`IntrusiveSortedLinkedList`]. +pub struct Max; + +/// The linked list kind: min-list or max-list +pub trait Kind: private::Sealed { + #[doc(hidden)] + fn ordering() -> Ordering; +} + +impl Kind for Min { + fn ordering() -> Ordering { + Ordering::Less + } +} + +impl Kind for Max { + fn ordering() -> Ordering { + Ordering::Greater + } +} + +/// Sealed traits +mod private { + pub trait Sealed {} +} + +impl private::Sealed for Max {} +impl private::Sealed for Min {} + +/// A node in the [`IntrusiveSortedLinkedList`]. +pub struct Node { + pub val: T, + next: Option>>, +} + +impl Node { + pub fn new(val: T) -> Self { + Self { val, next: None } + } +} + +/// The linked list. +pub struct IntrusiveSortedLinkedList<'a, T, K> { + head: Option>>, + _kind: PhantomData, + _lt: PhantomData<&'a ()>, +} + +impl<'a, T, K> fmt::Debug for IntrusiveSortedLinkedList<'a, T, K> +where + T: Ord + core::fmt::Debug, + K: Kind, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut l = f.debug_list(); + let mut current = self.head; + + while let Some(head) = current { + let head = unsafe { head.as_ref() }; + current = head.next; + + l.entry(&head.val); + } + + l.finish() + } +} + +impl<'a, T, K> IntrusiveSortedLinkedList<'a, T, K> +where + T: Ord, + K: Kind, +{ + pub const fn new() -> Self { + Self { + head: None, + _kind: PhantomData, + _lt: PhantomData, + } + } + + // Push to the list. + pub fn push(&mut self, new: &'a mut Node) { + unsafe { + if let Some(head) = self.head { + if head.as_ref().val.cmp(&new.val) != K::ordering() { + // This is newer than head, replace head + new.next = self.head; + self.head = Some(NonNull::new_unchecked(new)); + } else { + // It's not head, search the list for the correct placement + let mut current = head; + + while let Some(next) = current.as_ref().next { + if next.as_ref().val.cmp(&new.val) != K::ordering() { + break; + } + + current = next; + } + + new.next = current.as_ref().next; + current.as_mut().next = Some(NonNull::new_unchecked(new)); + } + } else { + // List is empty, place at head + self.head = Some(NonNull::new_unchecked(new)) + } + } + } + + /// Get an iterator over the sorted list. + pub fn iter(&self) -> Iter<'_, T, K> { + Iter { + _list: self, + index: self.head, + } + } + + /// Find an element in the list that can be changed and resorted. + pub fn find_mut(&mut self, mut f: F) -> Option> + where + F: FnMut(&T) -> bool, + { + let head = self.head?; + + // Special-case, first element + if f(&unsafe { head.as_ref() }.val) { + return Some(FindMut { + is_head: true, + prev_index: None, + index: self.head, + list: self, + maybe_changed: false, + }); + } + + let mut current = head; + + while let Some(next) = unsafe { current.as_ref() }.next { + if f(&unsafe { next.as_ref() }.val) { + return Some(FindMut { + is_head: false, + prev_index: Some(current), + index: Some(next), + list: self, + maybe_changed: false, + }); + } + + current = next; + } + + None + } + + /// Peek at the first element. + pub fn peek(&self) -> Option<&T> { + self.head.map(|head| unsafe { &head.as_ref().val }) + } + + /// Pops the first element in the list. + /// + /// Complexity is worst-case `O(1)`. + pub fn pop(&mut self) -> Option<&'a Node> { + if let Some(head) = self.head { + let v = unsafe { head.as_ref() }; + self.head = v.next; + Some(v) + } else { + None + } + } + + /// Checks if the linked list is empty. + #[inline] + pub fn is_empty(&self) -> bool { + self.head.is_none() + } +} + +/// Iterator for the linked list. +pub struct Iter<'a, T, K> +where + T: Ord, + K: Kind, +{ + _list: &'a IntrusiveSortedLinkedList<'a, T, K>, + index: Option>>, +} + +impl<'a, T, K> Iterator for Iter<'a, T, K> +where + T: Ord, + K: Kind, +{ + type Item = &'a T; + + fn next(&mut self) -> Option { + let index = self.index?; + + let node = unsafe { index.as_ref() }; + self.index = node.next; + + Some(&node.val) + } +} + +/// Comes from [`IntrusiveSortedLinkedList::find_mut`]. +pub struct FindMut<'a, 'b, T, K> +where + T: Ord + 'b, + K: Kind, +{ + list: &'a mut IntrusiveSortedLinkedList<'b, T, K>, + is_head: bool, + prev_index: Option>>, + index: Option>>, + maybe_changed: bool, +} + +impl<'a, 'b, T, K> FindMut<'a, 'b, T, K> +where + T: Ord, + K: Kind, +{ + unsafe fn pop_internal(&mut self) -> &'b mut Node { + if self.is_head { + // If it is the head element, we can do a normal pop + let mut head = self.list.head.unwrap_unchecked(); + let v = head.as_mut(); + self.list.head = v.next; + v + } else { + // Somewhere in the list + let mut prev = self.prev_index.unwrap_unchecked(); + let mut curr = self.index.unwrap_unchecked(); + + // Re-point the previous index + prev.as_mut().next = curr.as_ref().next; + + curr.as_mut() + } + } + + /// This will pop the element from the list. + /// + /// Complexity is worst-case `O(1)`. + #[inline] + pub fn pop(mut self) -> &'b mut Node { + unsafe { self.pop_internal() } + } + + /// This will resort the element into the correct position in the list if needed. The resorting + /// will only happen if the element has been accessed mutably. + /// + /// Same as calling `drop`. + /// + /// Complexity is worst-case `O(N)`. + #[inline] + pub fn finish(self) { + drop(self) + } +} + +impl<'b, T, K> Drop for FindMut<'_, 'b, T, K> +where + T: Ord + 'b, + K: Kind, +{ + fn drop(&mut self) { + // Only resort the list if the element has changed + if self.maybe_changed { + unsafe { + let val = self.pop_internal(); + self.list.push(val); + } + } + } +} + +impl Deref for FindMut<'_, '_, T, K> +where + T: Ord, + K: Kind, +{ + type Target = T; + + fn deref(&self) -> &Self::Target { + unsafe { &self.index.unwrap_unchecked().as_ref().val } + } +} + +impl DerefMut for FindMut<'_, '_, T, K> +where + T: Ord, + K: Kind, +{ + fn deref_mut(&mut self) -> &mut Self::Target { + self.maybe_changed = true; + unsafe { &mut self.index.unwrap_unchecked().as_mut().val } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn const_new() { + static mut _V1: IntrusiveSortedLinkedList = IntrusiveSortedLinkedList::new(); + } + + #[test] + fn test_peek() { + let mut ll: IntrusiveSortedLinkedList = IntrusiveSortedLinkedList::new(); + + let mut a = Node { val: 1, next: None }; + ll.push(&mut a); + assert_eq!(ll.peek().unwrap(), &1); + + let mut a = Node { val: 2, next: None }; + ll.push(&mut a); + assert_eq!(ll.peek().unwrap(), &2); + + let mut a = Node { val: 3, next: None }; + ll.push(&mut a); + assert_eq!(ll.peek().unwrap(), &3); + + let mut ll: IntrusiveSortedLinkedList = IntrusiveSortedLinkedList::new(); + + let mut a = Node { val: 2, next: None }; + ll.push(&mut a); + assert_eq!(ll.peek().unwrap(), &2); + + let mut a = Node { val: 1, next: None }; + ll.push(&mut a); + assert_eq!(ll.peek().unwrap(), &1); + + let mut a = Node { val: 3, next: None }; + ll.push(&mut a); + assert_eq!(ll.peek().unwrap(), &1); + } + + #[test] + fn test_empty() { + let ll: IntrusiveSortedLinkedList = IntrusiveSortedLinkedList::new(); + + assert!(ll.is_empty()) + } + + #[test] + fn test_updating() { + let mut ll: IntrusiveSortedLinkedList = IntrusiveSortedLinkedList::new(); + + let mut a = Node { val: 1, next: None }; + ll.push(&mut a); + + let mut a = Node { val: 2, next: None }; + ll.push(&mut a); + + let mut a = Node { val: 3, next: None }; + ll.push(&mut a); + + let mut find = ll.find_mut(|v| *v == 2).unwrap(); + + *find += 1000; + find.finish(); + + assert_eq!(ll.peek().unwrap(), &1002); + + let mut find = ll.find_mut(|v| *v == 3).unwrap(); + + *find += 1000; + find.finish(); + + assert_eq!(ll.peek().unwrap(), &1003); + + // Remove largest element + ll.find_mut(|v| *v == 1003).unwrap().pop(); + + assert_eq!(ll.peek().unwrap(), &1002); + } + + #[test] + fn test_updating_1() { + let mut ll: IntrusiveSortedLinkedList = IntrusiveSortedLinkedList::new(); + + let mut a = Node { val: 1, next: None }; + ll.push(&mut a); + + let v = ll.pop().unwrap(); + + assert_eq!(v.val, 1); + } + + #[test] + fn test_updating_2() { + let mut ll: IntrusiveSortedLinkedList = IntrusiveSortedLinkedList::new(); + + let mut a = Node { val: 1, next: None }; + ll.push(&mut a); + + let mut find = ll.find_mut(|v| *v == 1).unwrap(); + + *find += 1000; + find.finish(); + + assert_eq!(ll.peek().unwrap(), &1001); + } +} -- cgit v1.2.3 From b8b881f446a226d6f3c4a7db7c9174590b47dbf6 Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Thu, 19 Jan 2023 13:56:59 +0100 Subject: Fix so deny(missing_docs) work --- examples/async-task-multiple-prios.rs | 3 +++ examples/async-task.rs | 3 +++ examples/big-struct-opt.rs | 1 + examples/binds.rs | 1 + examples/complex.rs | 1 + examples/declared_locals.rs | 1 + examples/destructure.rs | 1 + examples/extern_binds.rs | 1 + examples/extern_spawn.rs | 1 + examples/generics.rs | 1 + examples/hardware.rs | 1 + examples/idle-wfi.rs | 1 + examples/idle.rs | 1 + examples/init.rs | 1 + examples/locals.rs | 1 + examples/lock.rs | 1 + examples/multilock.rs | 1 + examples/not-sync.rs | 2 ++ examples/only-shared-access.rs | 1 + examples/peripherals-taken.rs | 3 +++ examples/preempt.rs | 1 + examples/ramfunc.rs | 2 ++ examples/resource-user-struct.rs | 1 + examples/shared.rs | 1 + examples/smallest.rs | 1 + examples/spawn.rs | 1 + examples/static.rs | 1 + examples/t-binds.rs | 1 + examples/t-cfg-resources.rs | 3 ++- examples/t-htask-main.rs | 3 +++ examples/t-idle-main.rs | 3 +++ examples/t-late-not-send.rs | 3 ++- examples/task.rs | 1 + examples/zero-prio-task.rs | 4 ++++ macros/src/codegen/local_resources_struct.rs | 2 ++ macros/src/codegen/module.rs | 1 + macros/src/codegen/shared_resources_struct.rs | 4 ++++ 37 files changed, 58 insertions(+), 2 deletions(-) diff --git a/examples/async-task-multiple-prios.rs b/examples/async-task-multiple-prios.rs index f614820..5c9674d 100644 --- a/examples/async-task-multiple-prios.rs +++ b/examples/async-task-multiple-prios.rs @@ -1,6 +1,9 @@ +//! examples/async-task-multiple-prios.rs + #![no_main] #![no_std] #![feature(type_alias_impl_trait)] +#![deny(missing_docs)] use panic_semihosting as _; diff --git a/examples/async-task.rs b/examples/async-task.rs index e1ab143..7730c54 100644 --- a/examples/async-task.rs +++ b/examples/async-task.rs @@ -1,6 +1,9 @@ +//! examples/async-task.rs + #![no_main] #![no_std] #![feature(type_alias_impl_trait)] +#![deny(missing_docs)] use panic_semihosting as _; diff --git a/examples/big-struct-opt.rs b/examples/big-struct-opt.rs index 3100a0e..408a2de 100644 --- a/examples/big-struct-opt.rs +++ b/examples/big-struct-opt.rs @@ -6,6 +6,7 @@ #![no_main] #![no_std] #![feature(type_alias_impl_trait)] +#![deny(missing_docs)] use panic_semihosting as _; diff --git a/examples/binds.rs b/examples/binds.rs index 0c1ed97..cf078ff 100644 --- a/examples/binds.rs +++ b/examples/binds.rs @@ -4,6 +4,7 @@ #![deny(warnings)] #![no_main] #![no_std] +#![deny(missing_docs)] use panic_semihosting as _; diff --git a/examples/complex.rs b/examples/complex.rs index ab39792..c1e9c6c 100644 --- a/examples/complex.rs +++ b/examples/complex.rs @@ -2,6 +2,7 @@ #![deny(unsafe_code)] #![deny(warnings)] +#![deny(missing_docs)] #![no_main] #![no_std] diff --git a/examples/declared_locals.rs b/examples/declared_locals.rs index 79001aa..c845191 100644 --- a/examples/declared_locals.rs +++ b/examples/declared_locals.rs @@ -2,6 +2,7 @@ #![deny(unsafe_code)] #![deny(warnings)] +#![deny(missing_docs)] #![no_main] #![no_std] diff --git a/examples/destructure.rs b/examples/destructure.rs index dc5d8ef..81eff3b 100644 --- a/examples/destructure.rs +++ b/examples/destructure.rs @@ -2,6 +2,7 @@ #![deny(unsafe_code)] #![deny(warnings)] +#![deny(missing_docs)] #![no_main] #![no_std] #![feature(type_alias_impl_trait)] diff --git a/examples/extern_binds.rs b/examples/extern_binds.rs index b24e7a1..142a11d 100644 --- a/examples/extern_binds.rs +++ b/examples/extern_binds.rs @@ -2,6 +2,7 @@ #![deny(unsafe_code)] #![deny(warnings)] +#![deny(missing_docs)] #![no_main] #![no_std] diff --git a/examples/extern_spawn.rs b/examples/extern_spawn.rs index 8a3928d..b2b95b9 100644 --- a/examples/extern_spawn.rs +++ b/examples/extern_spawn.rs @@ -2,6 +2,7 @@ #![deny(unsafe_code)] #![deny(warnings)] +#![deny(missing_docs)] #![no_main] #![no_std] #![feature(type_alias_impl_trait)] diff --git a/examples/generics.rs b/examples/generics.rs index dfd47ad..2f23cce 100644 --- a/examples/generics.rs +++ b/examples/generics.rs @@ -2,6 +2,7 @@ #![deny(unsafe_code)] #![deny(warnings)] +#![deny(missing_docs)] #![no_main] #![no_std] diff --git a/examples/hardware.rs b/examples/hardware.rs index 61eb635..62ae0d6 100644 --- a/examples/hardware.rs +++ b/examples/hardware.rs @@ -2,6 +2,7 @@ #![deny(unsafe_code)] #![deny(warnings)] +#![deny(missing_docs)] #![no_main] #![no_std] diff --git a/examples/idle-wfi.rs b/examples/idle-wfi.rs index a68fe84..8134ce3 100644 --- a/examples/idle-wfi.rs +++ b/examples/idle-wfi.rs @@ -2,6 +2,7 @@ #![deny(unsafe_code)] #![deny(warnings)] +#![deny(missing_docs)] #![no_main] #![no_std] diff --git a/examples/idle.rs b/examples/idle.rs index 78f1697..0c4bd04 100644 --- a/examples/idle.rs +++ b/examples/idle.rs @@ -2,6 +2,7 @@ #![deny(unsafe_code)] #![deny(warnings)] +#![deny(missing_docs)] #![no_main] #![no_std] diff --git a/examples/init.rs b/examples/init.rs index 1e362be..c3081bf 100644 --- a/examples/init.rs +++ b/examples/init.rs @@ -2,6 +2,7 @@ #![deny(unsafe_code)] #![deny(warnings)] +#![deny(missing_docs)] #![no_main] #![no_std] diff --git a/examples/locals.rs b/examples/locals.rs index 4e3b98b..ec3d59d 100644 --- a/examples/locals.rs +++ b/examples/locals.rs @@ -2,6 +2,7 @@ #![feature(type_alias_impl_trait)] #![deny(unsafe_code)] +#![deny(missing_docs)] #![deny(warnings)] #![no_main] #![no_std] diff --git a/examples/lock.rs b/examples/lock.rs index 3c1a514..203ae6f 100644 --- a/examples/lock.rs +++ b/examples/lock.rs @@ -2,6 +2,7 @@ #![deny(unsafe_code)] #![deny(warnings)] +#![deny(missing_docs)] #![no_main] #![no_std] #![feature(type_alias_impl_trait)] diff --git a/examples/multilock.rs b/examples/multilock.rs index 2eb285e..6208cac 100644 --- a/examples/multilock.rs +++ b/examples/multilock.rs @@ -2,6 +2,7 @@ #![deny(unsafe_code)] #![deny(warnings)] +#![deny(missing_docs)] #![no_main] #![no_std] #![feature(type_alias_impl_trait)] diff --git a/examples/not-sync.rs b/examples/not-sync.rs index 5d868df..6d1ddae 100644 --- a/examples/not-sync.rs +++ b/examples/not-sync.rs @@ -2,6 +2,7 @@ // #![deny(unsafe_code)] #![deny(warnings)] +#![deny(missing_docs)] #![no_main] #![no_std] #![feature(type_alias_impl_trait)] @@ -9,6 +10,7 @@ use core::marker::PhantomData; use panic_semihosting as _; +/// Not sync pub struct NotSync { _0: PhantomData<*const ()>, data: u32, diff --git a/examples/only-shared-access.rs b/examples/only-shared-access.rs index 09cb23a..1d006e6 100644 --- a/examples/only-shared-access.rs +++ b/examples/only-shared-access.rs @@ -2,6 +2,7 @@ #![deny(unsafe_code)] #![deny(warnings)] +#![deny(missing_docs)] #![no_main] #![no_std] #![feature(type_alias_impl_trait)] diff --git a/examples/peripherals-taken.rs b/examples/peripherals-taken.rs index 9b01466..2f710e9 100644 --- a/examples/peripherals-taken.rs +++ b/examples/peripherals-taken.rs @@ -1,5 +1,8 @@ +//! examples/peripherals-taken.rs + #![deny(unsafe_code)] #![deny(warnings)] +#![deny(missing_docs)] #![no_main] #![no_std] diff --git a/examples/preempt.rs b/examples/preempt.rs index 960fc57..4b11907 100644 --- a/examples/preempt.rs +++ b/examples/preempt.rs @@ -3,6 +3,7 @@ #![no_main] #![no_std] #![feature(type_alias_impl_trait)] +#![deny(missing_docs)] use panic_semihosting as _; use rtic::app; diff --git a/examples/ramfunc.rs b/examples/ramfunc.rs index 316f6d8..e2e7f67 100644 --- a/examples/ramfunc.rs +++ b/examples/ramfunc.rs @@ -1,9 +1,11 @@ //! examples/ramfunc.rs #![deny(warnings)] +#![deny(missing_docs)] #![no_main] #![no_std] #![feature(type_alias_impl_trait)] + use panic_semihosting as _; #[rtic::app( diff --git a/examples/resource-user-struct.rs b/examples/resource-user-struct.rs index 2acbbc3..fcbacae 100644 --- a/examples/resource-user-struct.rs +++ b/examples/resource-user-struct.rs @@ -2,6 +2,7 @@ #![deny(unsafe_code)] #![deny(warnings)] +#![deny(missing_docs)] #![no_main] #![no_std] diff --git a/examples/shared.rs b/examples/shared.rs index fd31cfb..d0633fb 100644 --- a/examples/shared.rs +++ b/examples/shared.rs @@ -2,6 +2,7 @@ #![deny(unsafe_code)] #![deny(warnings)] +#![deny(missing_docs)] #![no_main] #![no_std] diff --git a/examples/smallest.rs b/examples/smallest.rs index 5071392..e54ae44 100644 --- a/examples/smallest.rs +++ b/examples/smallest.rs @@ -2,6 +2,7 @@ #![no_main] #![no_std] +#![deny(missing_docs)] use panic_semihosting as _; // panic handler use rtic::app; diff --git a/examples/spawn.rs b/examples/spawn.rs index 384f0a0..d30ecf1 100644 --- a/examples/spawn.rs +++ b/examples/spawn.rs @@ -2,6 +2,7 @@ #![deny(unsafe_code)] #![deny(warnings)] +#![deny(missing_docs)] #![no_main] #![no_std] #![feature(type_alias_impl_trait)] diff --git a/examples/static.rs b/examples/static.rs index 822224e..7f656f4 100644 --- a/examples/static.rs +++ b/examples/static.rs @@ -2,6 +2,7 @@ #![deny(unsafe_code)] #![deny(warnings)] +#![deny(missing_docs)] #![no_main] #![no_std] #![feature(type_alias_impl_trait)] diff --git a/examples/t-binds.rs b/examples/t-binds.rs index 785348b..bdeb391 100644 --- a/examples/t-binds.rs +++ b/examples/t-binds.rs @@ -2,6 +2,7 @@ #![deny(unsafe_code)] #![deny(warnings)] +#![deny(missing_docs)] #![no_main] #![no_std] diff --git a/examples/t-cfg-resources.rs b/examples/t-cfg-resources.rs index 0174f33..0328700 100644 --- a/examples/t-cfg-resources.rs +++ b/examples/t-cfg-resources.rs @@ -1,7 +1,8 @@ //! [compile-pass] check that `#[cfg]` attributes applied on resources work -//! + #![no_main] #![no_std] +#![deny(missing_docs)] use panic_semihosting as _; diff --git a/examples/t-htask-main.rs b/examples/t-htask-main.rs index 0595e9f..8f885bc 100644 --- a/examples/t-htask-main.rs +++ b/examples/t-htask-main.rs @@ -1,5 +1,8 @@ +//! examples/h-task-main.rs + #![deny(unsafe_code)] #![deny(warnings)] +#![deny(missing_docs)] #![no_main] #![no_std] diff --git a/examples/t-idle-main.rs b/examples/t-idle-main.rs index 307ccb2..43215cf 100644 --- a/examples/t-idle-main.rs +++ b/examples/t-idle-main.rs @@ -1,5 +1,8 @@ +//! examples/t-idle-main.rs + #![deny(unsafe_code)] #![deny(warnings)] +#![deny(missing_docs)] #![no_main] #![no_std] diff --git a/examples/t-late-not-send.rs b/examples/t-late-not-send.rs index 0fbf237..44d1d85 100644 --- a/examples/t-late-not-send.rs +++ b/examples/t-late-not-send.rs @@ -2,11 +2,12 @@ #![no_main] #![no_std] +#![deny(missing_docs)] use core::marker::PhantomData; - use panic_semihosting as _; +/// Not send pub struct NotSend { _0: PhantomData<*const ()>, } diff --git a/examples/task.rs b/examples/task.rs index 50287ed..ab6a1e0 100644 --- a/examples/task.rs +++ b/examples/task.rs @@ -2,6 +2,7 @@ #![deny(unsafe_code)] #![deny(warnings)] +#![deny(missing_docs)] #![no_main] #![no_std] #![feature(type_alias_impl_trait)] diff --git a/examples/zero-prio-task.rs b/examples/zero-prio-task.rs index fc38509..c810e8f 100644 --- a/examples/zero-prio-task.rs +++ b/examples/zero-prio-task.rs @@ -1,10 +1,14 @@ +//! examples/zero-prio-task.rs + #![no_main] #![no_std] #![feature(type_alias_impl_trait)] +#![deny(missing_docs)] use core::marker::PhantomData; use panic_semihosting as _; +/// Does not impl send pub struct NotSend { _0: PhantomData<*const ()>, } diff --git a/macros/src/codegen/local_resources_struct.rs b/macros/src/codegen/local_resources_struct.rs index e268508..100c3eb 100644 --- a/macros/src/codegen/local_resources_struct.rs +++ b/macros/src/codegen/local_resources_struct.rs @@ -50,6 +50,7 @@ pub fn codegen(ctxt: Context, app: &App) -> (TokenStream2, TokenStream2) { fields.push(quote!( #(#cfgs)* + #[allow(missing_docs)] pub #name: &#lt mut #ty )); @@ -88,6 +89,7 @@ pub fn codegen(ctxt: Context, app: &App) -> (TokenStream2, TokenStream2) { let constructor = quote!( impl<'a> #ident<'a> { #[inline(always)] + #[allow(missing_docs)] pub unsafe fn new() -> Self { #ident { #(#values,)* diff --git a/macros/src/codegen/module.rs b/macros/src/codegen/module.rs index f4c188a..4725b9a 100644 --- a/macros/src/codegen/module.rs +++ b/macros/src/codegen/module.rs @@ -114,6 +114,7 @@ pub fn codegen(ctxt: Context, app: &App, analysis: &Analysis) -> TokenStream2 { #(#cfgs)* impl<'a> #internal_context_name<'a> { #[inline(always)] + #[allow(missing_docs)] pub unsafe fn new(#core) -> Self { #internal_context_name { __rtic_internal_p: ::core::marker::PhantomData, diff --git a/macros/src/codegen/shared_resources_struct.rs b/macros/src/codegen/shared_resources_struct.rs index 24c93de..fa6f0fc 100644 --- a/macros/src/codegen/shared_resources_struct.rs +++ b/macros/src/codegen/shared_resources_struct.rs @@ -47,16 +47,19 @@ pub fn codegen(ctxt: Context, app: &App) -> (TokenStream2, TokenStream2) { fields.push(quote!( #(#cfgs)* + #[allow(missing_docs)] pub #name: &#lt #mut_ #ty )); } else if access.is_shared() { fields.push(quote!( #(#cfgs)* + #[allow(missing_docs)] pub #name: &'a #ty )); } else { fields.push(quote!( #(#cfgs)* + #[allow(missing_docs)] pub #name: shared_resources::#shared_name<'a> )); @@ -103,6 +106,7 @@ pub fn codegen(ctxt: Context, app: &App) -> (TokenStream2, TokenStream2) { let constructor = quote!( impl<'a> #ident<'a> { #[inline(always)] + #[allow(missing_docs)] pub unsafe fn new() -> Self { #ident { #(#values,)* -- cgit v1.2.3 From 306aa47170fd59369b7a184924e287dc3706d64d Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Mon, 23 Jan 2023 20:05:47 +0100 Subject: Add rtic-timer (timerqueue + monotonic) and rtic-monotonics (systick-monotonic) --- .cargo/config.toml | 13 - CHANGELOG.md | 603 --------------------- Cargo.toml | 80 --- build.rs | 25 - ci/expected/async-delay.run | 7 - ci/expected/async-infinite-loop.run | 6 - ci/expected/async-task-multiple-prios.run | 6 - ci/expected/async-task.run | 5 - ci/expected/async-timeout.run | 5 - ci/expected/big-struct-opt.run | 3 - ci/expected/binds.run | 4 - ci/expected/cancel-reschedule.run | 3 - ci/expected/capacity.run | 5 - ci/expected/cfg-monotonic.run | 0 ci/expected/cfg-whole-task.run | 0 ci/expected/common.run | 0 ci/expected/complex.run | 47 -- ci/expected/declared_locals.run | 0 ci/expected/destructure.run | 2 - ci/expected/extern_binds.run | 4 - ci/expected/extern_spawn.run | 1 - ci/expected/generics.run | 6 - ci/expected/hardware.run | 4 - ci/expected/idle-wfi.run | 2 - ci/expected/idle.run | 2 - ci/expected/init.run | 1 - ci/expected/locals.run | 3 - ci/expected/lock-free.run | 2 - ci/expected/lock.run | 5 - ci/expected/message.run | 6 - ci/expected/message_passing.run | 3 - ci/expected/multilock.run | 1 - ci/expected/not-sync.run | 3 - ci/expected/only-shared-access.run | 2 - ci/expected/periodic-at.run | 4 - ci/expected/periodic-at2.run | 7 - ci/expected/periodic.run | 4 - ci/expected/peripherals-taken.run | 0 ci/expected/pool.run | 0 ci/expected/preempt.run | 5 - ci/expected/ramfunc.run | 1 - ci/expected/ramfunc.run.grep.bar | 1 - ci/expected/ramfunc.run.grep.foo | 1 - ci/expected/resource-user-struct.run | 2 - ci/expected/schedule.run | 4 - ci/expected/shared.run | 1 - ci/expected/smallest.run | 0 ci/expected/spawn.run | 2 - ci/expected/static.run | 3 - ci/expected/t-binds.run | 0 ci/expected/t-cfg-resources.run | 0 ci/expected/t-htask-main.run | 0 ci/expected/t-idle-main.run | 0 ci/expected/t-late-not-send.run | 0 ci/expected/t-schedule.run | 0 ci/expected/t-spawn.run | 0 ci/expected/task.run | 5 - ci/expected/zero-prio-task.run | 3 - examples/async-delay.no_rs | 63 --- examples/async-infinite-loop.no_rs | 53 -- examples/async-task-multiple-prios.rs | 92 ---- examples/async-task.rs | 70 --- examples/async-timeout.no_rs | 87 --- examples/big-struct-opt.rs | 80 --- examples/binds.rs | 54 -- examples/cancel-reschedule.no_rs | 73 --- examples/capacity.no_rs | 49 -- examples/cfg-monotonic.rs | 121 ----- examples/cfg-whole-task.no_rs | 94 ---- examples/common.no_rs | 102 ---- examples/complex.rs | 129 ----- examples/declared_locals.rs | 47 -- examples/destructure.rs | 57 -- examples/extern_binds.rs | 54 -- examples/extern_spawn.rs | 41 -- examples/generics.rs | 67 --- examples/hardware.rs | 58 -- examples/idle-wfi.rs | 48 -- examples/idle.rs | 41 -- examples/init.rs | 42 -- examples/locals.rs | 87 --- examples/lock-free.no_rs | 50 -- examples/lock.rs | 73 --- examples/message.no_rs | 52 -- examples/message_passing.no_rs | 37 -- examples/multilock.rs | 57 -- examples/not-sync.rs | 69 --- examples/only-shared-access.rs | 44 -- examples/periodic-at.no_rs | 49 -- examples/periodic-at2.no_rs | 61 --- examples/periodic.no_rs | 48 -- examples/peripherals-taken.rs | 28 - examples/pool.no_rs | 70 --- examples/preempt.rs | 47 -- examples/ramfunc.rs | 50 -- examples/resource-user-struct.rs | 72 --- examples/schedule.no_rs | 64 --- examples/shared.rs | 51 -- examples/smallest.rs | 25 - examples/spawn.rs | 36 -- examples/static.rs | 61 --- examples/t-binds.rs | 45 -- examples/t-cfg-resources.rs | 42 -- examples/t-htask-main.rs | 32 -- examples/t-idle-main.rs | 33 -- examples/t-late-not-send.rs | 48 -- examples/t-schedule.no_rs | 136 ----- examples/t-spawn.no_rs | 69 --- examples/task.rs | 58 -- examples/zero-prio-task.rs | 60 -- macros/.gitignore | 2 - macros/Cargo.toml | 41 -- macros/src/analyze.rs | 49 -- macros/src/bindings.rs | 1 - macros/src/check.rs | 70 --- macros/src/codegen.rs | 75 --- macros/src/codegen/assertions.rs | 53 -- macros/src/codegen/async_dispatchers.rs | 89 --- macros/src/codegen/hardware_tasks.rs | 87 --- macros/src/codegen/idle.rs | 58 -- macros/src/codegen/init.rs | 95 ---- macros/src/codegen/local_resources.rs | 65 --- macros/src/codegen/local_resources_struct.rs | 102 ---- macros/src/codegen/main.rs | 52 -- macros/src/codegen/module.rs | 194 ------- macros/src/codegen/post_init.rs | 47 -- macros/src/codegen/pre_init.rs | 85 --- macros/src/codegen/shared_resources.rs | 183 ------- macros/src/codegen/shared_resources_struct.rs | 119 ---- macros/src/codegen/software_tasks.rs | 64 --- macros/src/codegen/util.rs | 238 -------- macros/src/lib.rs | 92 ---- macros/src/syntax.rs | 121 ----- macros/src/syntax/.github/bors.toml | 3 - macros/src/syntax/.github/workflows/build.yml | 213 -------- macros/src/syntax/.github/workflows/changelog.yml | 28 - .../workflows/properties/build.properties.json | 6 - macros/src/syntax/.gitignore | 4 - macros/src/syntax/.travis.yml | 31 -- macros/src/syntax/accessors.rs | 113 ---- macros/src/syntax/analyze.rs | 417 -------------- macros/src/syntax/ast.rs | 335 ------------ macros/src/syntax/check.rs | 66 --- macros/src/syntax/optimize.rs | 36 -- macros/src/syntax/parse.rs | 363 ------------- macros/src/syntax/parse/app.rs | 480 ---------------- macros/src/syntax/parse/hardware_task.rs | 76 --- macros/src/syntax/parse/idle.rs | 42 -- macros/src/syntax/parse/init.rs | 51 -- macros/src/syntax/parse/resource.rs | 55 -- macros/src/syntax/parse/software_task.rs | 76 --- macros/src/syntax/parse/util.rs | 338 ------------ macros/src/tests.rs | 4 - macros/src/tests/single.rs | 40 -- macros/tests/ui.rs | 7 - macros/ui/extern-interrupt-used.rs | 16 - macros/ui/extern-interrupt-used.stderr | 5 - macros/ui/idle-double-local.rs | 9 - macros/ui/idle-double-local.stderr | 5 - macros/ui/idle-double-shared.rs | 9 - macros/ui/idle-double-shared.stderr | 5 - macros/ui/idle-input.rs | 9 - macros/ui/idle-input.stderr | 5 - macros/ui/idle-no-context.rs | 9 - macros/ui/idle-no-context.stderr | 5 - macros/ui/idle-not-divergent.rs | 7 - macros/ui/idle-not-divergent.stderr | 5 - macros/ui/idle-output.rs | 9 - macros/ui/idle-output.stderr | 5 - macros/ui/idle-pub.rs | 9 - macros/ui/idle-pub.stderr | 5 - macros/ui/idle-unsafe.rs | 9 - macros/ui/idle-unsafe.stderr | 5 - macros/ui/init-divergent.rs | 13 - macros/ui/init-divergent.stderr | 5 - macros/ui/init-double-local.rs | 7 - macros/ui/init-double-local.stderr | 5 - macros/ui/init-double-shared.rs | 7 - macros/ui/init-double-shared.stderr | 5 - macros/ui/init-input.rs | 13 - macros/ui/init-input.stderr | 5 - macros/ui/init-no-context.rs | 13 - macros/ui/init-no-context.stderr | 5 - macros/ui/init-output.rs | 9 - macros/ui/init-output.stderr | 5 - macros/ui/init-pub.rs | 13 - macros/ui/init-pub.stderr | 5 - macros/ui/init-unsafe.rs | 7 - macros/ui/init-unsafe.stderr | 5 - macros/ui/interrupt-double.rs | 10 - macros/ui/interrupt-double.stderr | 5 - macros/ui/local-collision-2.rs | 18 - macros/ui/local-collision-2.stderr | 17 - macros/ui/local-collision.rs | 21 - macros/ui/local-collision.stderr | 11 - macros/ui/local-malformed-1.rs | 16 - macros/ui/local-malformed-1.stderr | 5 - macros/ui/local-malformed-2.rs | 16 - macros/ui/local-malformed-2.stderr | 5 - macros/ui/local-malformed-3.rs | 16 - macros/ui/local-malformed-3.stderr | 5 - macros/ui/local-malformed-4.rs | 16 - macros/ui/local-malformed-4.stderr | 5 - macros/ui/local-not-declared.rs | 16 - macros/ui/local-not-declared.stderr | 5 - macros/ui/local-pub.rs | 15 - macros/ui/local-pub.stderr | 5 - macros/ui/local-shared-attribute.rs | 21 - macros/ui/local-shared-attribute.stderr | 6 - macros/ui/local-shared.rs | 28 - macros/ui/local-shared.stderr | 11 - macros/ui/shared-lock-free.rs | 38 -- macros/ui/shared-lock-free.stderr | 17 - macros/ui/shared-not-declared.rs | 16 - macros/ui/shared-not-declared.stderr | 5 - macros/ui/shared-pub.rs | 9 - macros/ui/shared-pub.stderr | 5 - macros/ui/task-divergent.rs | 9 - macros/ui/task-divergent.stderr | 5 - macros/ui/task-double-local.rs | 7 - macros/ui/task-double-local.stderr | 5 - macros/ui/task-double-priority.rs | 7 - macros/ui/task-double-priority.stderr | 5 - macros/ui/task-double-shared.rs | 7 - macros/ui/task-double-shared.stderr | 5 - macros/ui/task-idle.rs | 13 - macros/ui/task-idle.stderr | 5 - macros/ui/task-init.rs | 17 - macros/ui/task-init.stderr | 5 - macros/ui/task-interrupt.rs | 10 - macros/ui/task-interrupt.stderr | 5 - macros/ui/task-no-context.rs | 7 - macros/ui/task-no-context.stderr | 5 - macros/ui/task-priority-too-high.rs | 7 - macros/ui/task-priority-too-high.stderr | 5 - macros/ui/task-priority-too-low.rs | 7 - macros/ui/task-priority-too-low.stderr | 5 - macros/ui/task-pub.rs | 7 - macros/ui/task-pub.stderr | 5 - macros/ui/task-unsafe.rs | 7 - macros/ui/task-unsafe.stderr | 5 - macros/ui/task-zero-prio.rs | 19 - macros/ui/task-zero-prio.stderr | 5 - rtic-monotonics/.gitignore | 6 + rtic-monotonics/Cargo.toml | 12 + rtic-monotonics/rust-toolchain.toml | 4 + rtic-monotonics/src/lib.rs | 11 + rtic-monotonics/src/systick_monotonic.rs | 1 + rtic-timer/.gitignore | 6 + rtic-timer/Cargo.toml | 7 +- rtic-timer/rust-toolchain.toml | 4 + rtic-timer/src/lib.rs | 390 +++++++++---- rtic-timer/src/linked_list.rs | 173 ++++++ rtic-timer/src/monotonic.rs | 60 ++ rtic-timer/src/sll.rs | 421 -------------- rtic/.cargo/config.toml | 13 + rtic/.gitignore | 6 + rtic/CHANGELOG.md | 603 +++++++++++++++++++++ rtic/Cargo.toml | 80 +++ rtic/build.rs | 25 + rtic/ci/expected/async-delay.run | 7 + rtic/ci/expected/async-infinite-loop.run | 6 + rtic/ci/expected/async-task-multiple-prios.run | 6 + rtic/ci/expected/async-task.run | 5 + rtic/ci/expected/async-timeout.run | 5 + rtic/ci/expected/big-struct-opt.run | 3 + rtic/ci/expected/binds.run | 4 + rtic/ci/expected/cancel-reschedule.run | 3 + rtic/ci/expected/capacity.run | 5 + rtic/ci/expected/cfg-whole-task.run | 0 rtic/ci/expected/common.run | 0 rtic/ci/expected/complex.run | 47 ++ rtic/ci/expected/declared_locals.run | 0 rtic/ci/expected/destructure.run | 2 + rtic/ci/expected/extern_binds.run | 4 + rtic/ci/expected/extern_spawn.run | 1 + rtic/ci/expected/generics.run | 6 + rtic/ci/expected/hardware.run | 4 + rtic/ci/expected/idle-wfi.run | 2 + rtic/ci/expected/idle.run | 2 + rtic/ci/expected/init.run | 1 + rtic/ci/expected/locals.run | 3 + rtic/ci/expected/lock-free.run | 2 + rtic/ci/expected/lock.run | 5 + rtic/ci/expected/message.run | 6 + rtic/ci/expected/message_passing.run | 3 + rtic/ci/expected/multilock.run | 1 + rtic/ci/expected/not-sync.run | 3 + rtic/ci/expected/only-shared-access.run | 2 + rtic/ci/expected/periodic-at.run | 4 + rtic/ci/expected/periodic-at2.run | 7 + rtic/ci/expected/periodic.run | 4 + rtic/ci/expected/peripherals-taken.run | 0 rtic/ci/expected/pool.run | 0 rtic/ci/expected/preempt.run | 5 + rtic/ci/expected/ramfunc.run | 1 + rtic/ci/expected/ramfunc.run.grep.bar | 1 + rtic/ci/expected/ramfunc.run.grep.foo | 1 + rtic/ci/expected/resource-user-struct.run | 2 + rtic/ci/expected/schedule.run | 4 + rtic/ci/expected/shared.run | 1 + rtic/ci/expected/smallest.run | 0 rtic/ci/expected/spawn.run | 2 + rtic/ci/expected/static.run | 3 + rtic/ci/expected/t-binds.run | 0 rtic/ci/expected/t-cfg-resources.run | 0 rtic/ci/expected/t-htask-main.run | 0 rtic/ci/expected/t-idle-main.run | 0 rtic/ci/expected/t-late-not-send.run | 0 rtic/ci/expected/t-schedule.run | 0 rtic/ci/expected/t-spawn.run | 0 rtic/ci/expected/task.run | 5 + rtic/ci/expected/zero-prio-task.run | 3 + rtic/examples/async-delay.no_rs | 63 +++ rtic/examples/async-infinite-loop.no_rs | 53 ++ rtic/examples/async-task-multiple-prios.rs | 92 ++++ rtic/examples/async-task.rs | 70 +++ rtic/examples/async-timeout.no_rs | 87 +++ rtic/examples/big-struct-opt.rs | 80 +++ rtic/examples/binds.rs | 54 ++ rtic/examples/cancel-reschedule.no_rs | 73 +++ rtic/examples/capacity.no_rs | 49 ++ rtic/examples/cfg-whole-task.no_rs | 94 ++++ rtic/examples/common.no_rs | 102 ++++ rtic/examples/complex.rs | 129 +++++ rtic/examples/declared_locals.rs | 47 ++ rtic/examples/destructure.rs | 57 ++ rtic/examples/extern_binds.rs | 54 ++ rtic/examples/extern_spawn.rs | 41 ++ rtic/examples/generics.rs | 67 +++ rtic/examples/hardware.rs | 58 ++ rtic/examples/idle-wfi.rs | 48 ++ rtic/examples/idle.rs | 41 ++ rtic/examples/init.rs | 42 ++ rtic/examples/locals.rs | 87 +++ rtic/examples/lock-free.no_rs | 50 ++ rtic/examples/lock.rs | 73 +++ rtic/examples/message.no_rs | 52 ++ rtic/examples/message_passing.no_rs | 37 ++ rtic/examples/multilock.rs | 57 ++ rtic/examples/not-sync.rs | 69 +++ rtic/examples/only-shared-access.rs | 44 ++ rtic/examples/periodic-at.no_rs | 49 ++ rtic/examples/periodic-at2.no_rs | 61 +++ rtic/examples/periodic.no_rs | 48 ++ rtic/examples/peripherals-taken.rs | 28 + rtic/examples/pool.no_rs | 70 +++ rtic/examples/preempt.rs | 47 ++ rtic/examples/ramfunc.rs | 50 ++ rtic/examples/resource-user-struct.rs | 72 +++ rtic/examples/schedule.no_rs | 64 +++ rtic/examples/shared.rs | 51 ++ rtic/examples/smallest.rs | 25 + rtic/examples/spawn.rs | 36 ++ rtic/examples/static.rs | 61 +++ rtic/examples/t-binds.rs | 45 ++ rtic/examples/t-cfg-resources.rs | 42 ++ rtic/examples/t-htask-main.rs | 32 ++ rtic/examples/t-idle-main.rs | 33 ++ rtic/examples/t-late-not-send.rs | 48 ++ rtic/examples/t-schedule.no_rs | 136 +++++ rtic/examples/t-spawn.no_rs | 69 +++ rtic/examples/task.rs | 58 ++ rtic/examples/zero-prio-task.rs | 60 ++ rtic/macros/.gitignore | 2 + rtic/macros/Cargo.toml | 41 ++ rtic/macros/src/analyze.rs | 49 ++ rtic/macros/src/bindings.rs | 1 + rtic/macros/src/check.rs | 70 +++ rtic/macros/src/codegen.rs | 75 +++ rtic/macros/src/codegen/assertions.rs | 53 ++ rtic/macros/src/codegen/async_dispatchers.rs | 89 +++ rtic/macros/src/codegen/hardware_tasks.rs | 87 +++ rtic/macros/src/codegen/idle.rs | 58 ++ rtic/macros/src/codegen/init.rs | 95 ++++ rtic/macros/src/codegen/local_resources.rs | 65 +++ rtic/macros/src/codegen/local_resources_struct.rs | 102 ++++ rtic/macros/src/codegen/main.rs | 52 ++ rtic/macros/src/codegen/module.rs | 194 +++++++ rtic/macros/src/codegen/post_init.rs | 47 ++ rtic/macros/src/codegen/pre_init.rs | 85 +++ rtic/macros/src/codegen/shared_resources.rs | 183 +++++++ rtic/macros/src/codegen/shared_resources_struct.rs | 119 ++++ rtic/macros/src/codegen/software_tasks.rs | 64 +++ rtic/macros/src/codegen/util.rs | 238 ++++++++ rtic/macros/src/lib.rs | 92 ++++ rtic/macros/src/syntax.rs | 121 +++++ rtic/macros/src/syntax/.github/bors.toml | 3 + rtic/macros/src/syntax/.github/workflows/build.yml | 213 ++++++++ .../src/syntax/.github/workflows/changelog.yml | 28 + .../workflows/properties/build.properties.json | 6 + rtic/macros/src/syntax/.gitignore | 4 + rtic/macros/src/syntax/.travis.yml | 31 ++ rtic/macros/src/syntax/accessors.rs | 113 ++++ rtic/macros/src/syntax/analyze.rs | 417 ++++++++++++++ rtic/macros/src/syntax/ast.rs | 335 ++++++++++++ rtic/macros/src/syntax/check.rs | 66 +++ rtic/macros/src/syntax/optimize.rs | 36 ++ rtic/macros/src/syntax/parse.rs | 363 +++++++++++++ rtic/macros/src/syntax/parse/app.rs | 480 ++++++++++++++++ rtic/macros/src/syntax/parse/hardware_task.rs | 76 +++ rtic/macros/src/syntax/parse/idle.rs | 42 ++ rtic/macros/src/syntax/parse/init.rs | 51 ++ rtic/macros/src/syntax/parse/resource.rs | 55 ++ rtic/macros/src/syntax/parse/software_task.rs | 76 +++ rtic/macros/src/syntax/parse/util.rs | 338 ++++++++++++ rtic/macros/tests/ui.rs | 7 + rtic/macros/ui/extern-interrupt-used.rs | 16 + rtic/macros/ui/extern-interrupt-used.stderr | 5 + rtic/macros/ui/idle-double-local.rs | 9 + rtic/macros/ui/idle-double-local.stderr | 5 + rtic/macros/ui/idle-double-shared.rs | 9 + rtic/macros/ui/idle-double-shared.stderr | 5 + rtic/macros/ui/idle-input.rs | 9 + rtic/macros/ui/idle-input.stderr | 5 + rtic/macros/ui/idle-no-context.rs | 9 + rtic/macros/ui/idle-no-context.stderr | 5 + rtic/macros/ui/idle-not-divergent.rs | 7 + rtic/macros/ui/idle-not-divergent.stderr | 5 + rtic/macros/ui/idle-output.rs | 9 + rtic/macros/ui/idle-output.stderr | 5 + rtic/macros/ui/idle-pub.rs | 9 + rtic/macros/ui/idle-pub.stderr | 5 + rtic/macros/ui/idle-unsafe.rs | 9 + rtic/macros/ui/idle-unsafe.stderr | 5 + rtic/macros/ui/init-divergent.rs | 13 + rtic/macros/ui/init-divergent.stderr | 5 + rtic/macros/ui/init-double-local.rs | 7 + rtic/macros/ui/init-double-local.stderr | 5 + rtic/macros/ui/init-double-shared.rs | 7 + rtic/macros/ui/init-double-shared.stderr | 5 + rtic/macros/ui/init-input.rs | 13 + rtic/macros/ui/init-input.stderr | 5 + rtic/macros/ui/init-no-context.rs | 13 + rtic/macros/ui/init-no-context.stderr | 5 + rtic/macros/ui/init-output.rs | 9 + rtic/macros/ui/init-output.stderr | 5 + rtic/macros/ui/init-pub.rs | 13 + rtic/macros/ui/init-pub.stderr | 5 + rtic/macros/ui/init-unsafe.rs | 7 + rtic/macros/ui/init-unsafe.stderr | 5 + rtic/macros/ui/interrupt-double.rs | 10 + rtic/macros/ui/interrupt-double.stderr | 5 + rtic/macros/ui/local-collision-2.rs | 18 + rtic/macros/ui/local-collision-2.stderr | 17 + rtic/macros/ui/local-collision.rs | 21 + rtic/macros/ui/local-collision.stderr | 11 + rtic/macros/ui/local-malformed-1.rs | 16 + rtic/macros/ui/local-malformed-1.stderr | 5 + rtic/macros/ui/local-malformed-2.rs | 16 + rtic/macros/ui/local-malformed-2.stderr | 5 + rtic/macros/ui/local-malformed-3.rs | 16 + rtic/macros/ui/local-malformed-3.stderr | 5 + rtic/macros/ui/local-malformed-4.rs | 16 + rtic/macros/ui/local-malformed-4.stderr | 5 + rtic/macros/ui/local-not-declared.rs | 16 + rtic/macros/ui/local-not-declared.stderr | 5 + rtic/macros/ui/local-pub.rs | 15 + rtic/macros/ui/local-pub.stderr | 5 + rtic/macros/ui/local-shared-attribute.rs | 21 + rtic/macros/ui/local-shared-attribute.stderr | 6 + rtic/macros/ui/local-shared.rs | 28 + rtic/macros/ui/local-shared.stderr | 11 + rtic/macros/ui/shared-lock-free.rs | 38 ++ rtic/macros/ui/shared-lock-free.stderr | 17 + rtic/macros/ui/shared-not-declared.rs | 16 + rtic/macros/ui/shared-not-declared.stderr | 5 + rtic/macros/ui/shared-pub.rs | 9 + rtic/macros/ui/shared-pub.stderr | 5 + rtic/macros/ui/task-divergent.rs | 9 + rtic/macros/ui/task-divergent.stderr | 5 + rtic/macros/ui/task-double-local.rs | 7 + rtic/macros/ui/task-double-local.stderr | 5 + rtic/macros/ui/task-double-priority.rs | 7 + rtic/macros/ui/task-double-priority.stderr | 5 + rtic/macros/ui/task-double-shared.rs | 7 + rtic/macros/ui/task-double-shared.stderr | 5 + rtic/macros/ui/task-idle.rs | 13 + rtic/macros/ui/task-idle.stderr | 5 + rtic/macros/ui/task-init.rs | 17 + rtic/macros/ui/task-init.stderr | 5 + rtic/macros/ui/task-interrupt.rs | 10 + rtic/macros/ui/task-interrupt.stderr | 5 + rtic/macros/ui/task-no-context.rs | 7 + rtic/macros/ui/task-no-context.stderr | 5 + rtic/macros/ui/task-priority-too-high.rs | 7 + rtic/macros/ui/task-priority-too-high.stderr | 5 + rtic/macros/ui/task-priority-too-low.rs | 7 + rtic/macros/ui/task-priority-too-low.stderr | 5 + rtic/macros/ui/task-pub.rs | 7 + rtic/macros/ui/task-pub.stderr | 5 + rtic/macros/ui/task-unsafe.rs | 7 + rtic/macros/ui/task-unsafe.stderr | 5 + rtic/macros/ui/task-zero-prio.rs | 19 + rtic/macros/ui/task-zero-prio.stderr | 5 + rtic/rust-toolchain.toml | 4 + rtic/src/export.rs | 324 +++++++++++ rtic/src/export/executor.rs | 110 ++++ rtic/src/lib.rs | 121 +++++ rtic/tests/tests.rs | 7 + rtic/ui/exception-invalid.rs | 18 + rtic/ui/exception-invalid.stderr | 5 + rtic/ui/extern-interrupt-not-enough.rs | 18 + rtic/ui/extern-interrupt-not-enough.stderr | 5 + rtic/ui/extern-interrupt-used.rs | 18 + rtic/ui/extern-interrupt-used.stderr | 5 + rtic/ui/task-priority-too-high.rs | 44 ++ rtic/ui/task-priority-too-high.stderr | 15 + rtic/ui/unknown-interrupt.rs | 15 + rtic/ui/unknown-interrupt.stderr | 5 + rtic/ui/v6m-interrupt-not-enough.rs_no | 54 ++ rtic/xtask/Cargo.toml | 9 + rtic/xtask/src/build.rs | 13 + rtic/xtask/src/command.rs | 172 ++++++ rtic/xtask/src/main.rs | 176 ++++++ rust-toolchain.toml | 4 - src/export.rs | 324 ----------- src/export/executor.rs | 110 ---- src/lib.rs | 121 ----- tests/tests.rs | 7 - ui/exception-invalid.rs | 18 - ui/exception-invalid.stderr | 5 - ui/extern-interrupt-not-enough.rs | 18 - ui/extern-interrupt-not-enough.stderr | 5 - ui/extern-interrupt-used.rs | 18 - ui/extern-interrupt-used.stderr | 5 - ui/task-priority-too-high.rs | 44 -- ui/task-priority-too-high.stderr | 15 - ui/unknown-interrupt.rs | 15 - ui/unknown-interrupt.stderr | 5 - ui/v6m-interrupt-not-enough.rs_no | 54 -- xtask/Cargo.toml | 9 - xtask/src/build.rs | 13 - xtask/src/command.rs | 172 ------ xtask/src/main.rs | 176 ------ 535 files changed, 11202 insertions(+), 11308 deletions(-) delete mode 100644 .cargo/config.toml delete mode 100644 CHANGELOG.md delete mode 100644 Cargo.toml delete mode 100644 build.rs delete mode 100644 ci/expected/async-delay.run delete mode 100644 ci/expected/async-infinite-loop.run delete mode 100644 ci/expected/async-task-multiple-prios.run delete mode 100644 ci/expected/async-task.run delete mode 100644 ci/expected/async-timeout.run delete mode 100644 ci/expected/big-struct-opt.run delete mode 100644 ci/expected/binds.run delete mode 100644 ci/expected/cancel-reschedule.run delete mode 100644 ci/expected/capacity.run delete mode 100644 ci/expected/cfg-monotonic.run delete mode 100644 ci/expected/cfg-whole-task.run delete mode 100644 ci/expected/common.run delete mode 100644 ci/expected/complex.run delete mode 100644 ci/expected/declared_locals.run delete mode 100644 ci/expected/destructure.run delete mode 100644 ci/expected/extern_binds.run delete mode 100644 ci/expected/extern_spawn.run delete mode 100644 ci/expected/generics.run delete mode 100644 ci/expected/hardware.run delete mode 100644 ci/expected/idle-wfi.run delete mode 100644 ci/expected/idle.run delete mode 100644 ci/expected/init.run delete mode 100644 ci/expected/locals.run delete mode 100644 ci/expected/lock-free.run delete mode 100644 ci/expected/lock.run delete mode 100644 ci/expected/message.run delete mode 100644 ci/expected/message_passing.run delete mode 100644 ci/expected/multilock.run delete mode 100644 ci/expected/not-sync.run delete mode 100644 ci/expected/only-shared-access.run delete mode 100644 ci/expected/periodic-at.run delete mode 100644 ci/expected/periodic-at2.run delete mode 100644 ci/expected/periodic.run delete mode 100644 ci/expected/peripherals-taken.run delete mode 100644 ci/expected/pool.run delete mode 100644 ci/expected/preempt.run delete mode 100644 ci/expected/ramfunc.run delete mode 100644 ci/expected/ramfunc.run.grep.bar delete mode 100644 ci/expected/ramfunc.run.grep.foo delete mode 100644 ci/expected/resource-user-struct.run delete mode 100644 ci/expected/schedule.run delete mode 100644 ci/expected/shared.run delete mode 100644 ci/expected/smallest.run delete mode 100644 ci/expected/spawn.run delete mode 100644 ci/expected/static.run delete mode 100644 ci/expected/t-binds.run delete mode 100644 ci/expected/t-cfg-resources.run delete mode 100644 ci/expected/t-htask-main.run delete mode 100644 ci/expected/t-idle-main.run delete mode 100644 ci/expected/t-late-not-send.run delete mode 100644 ci/expected/t-schedule.run delete mode 100644 ci/expected/t-spawn.run delete mode 100644 ci/expected/task.run delete mode 100644 ci/expected/zero-prio-task.run delete mode 100644 examples/async-delay.no_rs delete mode 100644 examples/async-infinite-loop.no_rs delete mode 100644 examples/async-task-multiple-prios.rs delete mode 100644 examples/async-task.rs delete mode 100644 examples/async-timeout.no_rs delete mode 100644 examples/big-struct-opt.rs delete mode 100644 examples/binds.rs delete mode 100644 examples/cancel-reschedule.no_rs delete mode 100644 examples/capacity.no_rs delete mode 100644 examples/cfg-monotonic.rs delete mode 100644 examples/cfg-whole-task.no_rs delete mode 100644 examples/common.no_rs delete mode 100644 examples/complex.rs delete mode 100644 examples/declared_locals.rs delete mode 100644 examples/destructure.rs delete mode 100644 examples/extern_binds.rs delete mode 100644 examples/extern_spawn.rs delete mode 100644 examples/generics.rs delete mode 100644 examples/hardware.rs delete mode 100644 examples/idle-wfi.rs delete mode 100644 examples/idle.rs delete mode 100644 examples/init.rs delete mode 100644 examples/locals.rs delete mode 100644 examples/lock-free.no_rs delete mode 100644 examples/lock.rs delete mode 100644 examples/message.no_rs delete mode 100644 examples/message_passing.no_rs delete mode 100644 examples/multilock.rs delete mode 100644 examples/not-sync.rs delete mode 100644 examples/only-shared-access.rs delete mode 100644 examples/periodic-at.no_rs delete mode 100644 examples/periodic-at2.no_rs delete mode 100644 examples/periodic.no_rs delete mode 100644 examples/peripherals-taken.rs delete mode 100644 examples/pool.no_rs delete mode 100644 examples/preempt.rs delete mode 100644 examples/ramfunc.rs delete mode 100644 examples/resource-user-struct.rs delete mode 100644 examples/schedule.no_rs delete mode 100644 examples/shared.rs delete mode 100644 examples/smallest.rs delete mode 100644 examples/spawn.rs delete mode 100644 examples/static.rs delete mode 100644 examples/t-binds.rs delete mode 100644 examples/t-cfg-resources.rs delete mode 100644 examples/t-htask-main.rs delete mode 100644 examples/t-idle-main.rs delete mode 100644 examples/t-late-not-send.rs delete mode 100644 examples/t-schedule.no_rs delete mode 100644 examples/t-spawn.no_rs delete mode 100644 examples/task.rs delete mode 100644 examples/zero-prio-task.rs delete mode 100644 macros/.gitignore delete mode 100644 macros/Cargo.toml delete mode 100644 macros/src/analyze.rs delete mode 100644 macros/src/bindings.rs delete mode 100644 macros/src/check.rs delete mode 100644 macros/src/codegen.rs delete mode 100644 macros/src/codegen/assertions.rs delete mode 100644 macros/src/codegen/async_dispatchers.rs delete mode 100644 macros/src/codegen/hardware_tasks.rs delete mode 100644 macros/src/codegen/idle.rs delete mode 100644 macros/src/codegen/init.rs delete mode 100644 macros/src/codegen/local_resources.rs delete mode 100644 macros/src/codegen/local_resources_struct.rs delete mode 100644 macros/src/codegen/main.rs delete mode 100644 macros/src/codegen/module.rs delete mode 100644 macros/src/codegen/post_init.rs delete mode 100644 macros/src/codegen/pre_init.rs delete mode 100644 macros/src/codegen/shared_resources.rs delete mode 100644 macros/src/codegen/shared_resources_struct.rs delete mode 100644 macros/src/codegen/software_tasks.rs delete mode 100644 macros/src/codegen/util.rs delete mode 100644 macros/src/lib.rs delete mode 100644 macros/src/syntax.rs delete mode 100644 macros/src/syntax/.github/bors.toml delete mode 100644 macros/src/syntax/.github/workflows/build.yml delete mode 100644 macros/src/syntax/.github/workflows/changelog.yml delete mode 100644 macros/src/syntax/.github/workflows/properties/build.properties.json delete mode 100644 macros/src/syntax/.gitignore delete mode 100644 macros/src/syntax/.travis.yml delete mode 100644 macros/src/syntax/accessors.rs delete mode 100644 macros/src/syntax/analyze.rs delete mode 100644 macros/src/syntax/ast.rs delete mode 100644 macros/src/syntax/check.rs delete mode 100644 macros/src/syntax/optimize.rs delete mode 100644 macros/src/syntax/parse.rs delete mode 100644 macros/src/syntax/parse/app.rs delete mode 100644 macros/src/syntax/parse/hardware_task.rs delete mode 100644 macros/src/syntax/parse/idle.rs delete mode 100644 macros/src/syntax/parse/init.rs delete mode 100644 macros/src/syntax/parse/resource.rs delete mode 100644 macros/src/syntax/parse/software_task.rs delete mode 100644 macros/src/syntax/parse/util.rs delete mode 100644 macros/src/tests.rs delete mode 100644 macros/src/tests/single.rs delete mode 100644 macros/tests/ui.rs delete mode 100644 macros/ui/extern-interrupt-used.rs delete mode 100644 macros/ui/extern-interrupt-used.stderr delete mode 100644 macros/ui/idle-double-local.rs delete mode 100644 macros/ui/idle-double-local.stderr delete mode 100644 macros/ui/idle-double-shared.rs delete mode 100644 macros/ui/idle-double-shared.stderr delete mode 100644 macros/ui/idle-input.rs delete mode 100644 macros/ui/idle-input.stderr delete mode 100644 macros/ui/idle-no-context.rs delete mode 100644 macros/ui/idle-no-context.stderr delete mode 100644 macros/ui/idle-not-divergent.rs delete mode 100644 macros/ui/idle-not-divergent.stderr delete mode 100644 macros/ui/idle-output.rs delete mode 100644 macros/ui/idle-output.stderr delete mode 100644 macros/ui/idle-pub.rs delete mode 100644 macros/ui/idle-pub.stderr delete mode 100644 macros/ui/idle-unsafe.rs delete mode 100644 macros/ui/idle-unsafe.stderr delete mode 100644 macros/ui/init-divergent.rs delete mode 100644 macros/ui/init-divergent.stderr delete mode 100644 macros/ui/init-double-local.rs delete mode 100644 macros/ui/init-double-local.stderr delete mode 100644 macros/ui/init-double-shared.rs delete mode 100644 macros/ui/init-double-shared.stderr delete mode 100644 macros/ui/init-input.rs delete mode 100644 macros/ui/init-input.stderr delete mode 100644 macros/ui/init-no-context.rs delete mode 100644 macros/ui/init-no-context.stderr delete mode 100644 macros/ui/init-output.rs delete mode 100644 macros/ui/init-output.stderr delete mode 100644 macros/ui/init-pub.rs delete mode 100644 macros/ui/init-pub.stderr delete mode 100644 macros/ui/init-unsafe.rs delete mode 100644 macros/ui/init-unsafe.stderr delete mode 100644 macros/ui/interrupt-double.rs delete mode 100644 macros/ui/interrupt-double.stderr delete mode 100644 macros/ui/local-collision-2.rs delete mode 100644 macros/ui/local-collision-2.stderr delete mode 100644 macros/ui/local-collision.rs delete mode 100644 macros/ui/local-collision.stderr delete mode 100644 macros/ui/local-malformed-1.rs delete mode 100644 macros/ui/local-malformed-1.stderr delete mode 100644 macros/ui/local-malformed-2.rs delete mode 100644 macros/ui/local-malformed-2.stderr delete mode 100644 macros/ui/local-malformed-3.rs delete mode 100644 macros/ui/local-malformed-3.stderr delete mode 100644 macros/ui/local-malformed-4.rs delete mode 100644 macros/ui/local-malformed-4.stderr delete mode 100644 macros/ui/local-not-declared.rs delete mode 100644 macros/ui/local-not-declared.stderr delete mode 100644 macros/ui/local-pub.rs delete mode 100644 macros/ui/local-pub.stderr delete mode 100644 macros/ui/local-shared-attribute.rs delete mode 100644 macros/ui/local-shared-attribute.stderr delete mode 100644 macros/ui/local-shared.rs delete mode 100644 macros/ui/local-shared.stderr delete mode 100644 macros/ui/shared-lock-free.rs delete mode 100644 macros/ui/shared-lock-free.stderr delete mode 100644 macros/ui/shared-not-declared.rs delete mode 100644 macros/ui/shared-not-declared.stderr delete mode 100644 macros/ui/shared-pub.rs delete mode 100644 macros/ui/shared-pub.stderr delete mode 100644 macros/ui/task-divergent.rs delete mode 100644 macros/ui/task-divergent.stderr delete mode 100644 macros/ui/task-double-local.rs delete mode 100644 macros/ui/task-double-local.stderr delete mode 100644 macros/ui/task-double-priority.rs delete mode 100644 macros/ui/task-double-priority.stderr delete mode 100644 macros/ui/task-double-shared.rs delete mode 100644 macros/ui/task-double-shared.stderr delete mode 100644 macros/ui/task-idle.rs delete mode 100644 macros/ui/task-idle.stderr delete mode 100644 macros/ui/task-init.rs delete mode 100644 macros/ui/task-init.stderr delete mode 100644 macros/ui/task-interrupt.rs delete mode 100644 macros/ui/task-interrupt.stderr delete mode 100644 macros/ui/task-no-context.rs delete mode 100644 macros/ui/task-no-context.stderr delete mode 100644 macros/ui/task-priority-too-high.rs delete mode 100644 macros/ui/task-priority-too-high.stderr delete mode 100644 macros/ui/task-priority-too-low.rs delete mode 100644 macros/ui/task-priority-too-low.stderr delete mode 100644 macros/ui/task-pub.rs delete mode 100644 macros/ui/task-pub.stderr delete mode 100644 macros/ui/task-unsafe.rs delete mode 100644 macros/ui/task-unsafe.stderr delete mode 100644 macros/ui/task-zero-prio.rs delete mode 100644 macros/ui/task-zero-prio.stderr create mode 100644 rtic-monotonics/.gitignore create mode 100644 rtic-monotonics/Cargo.toml create mode 100644 rtic-monotonics/rust-toolchain.toml create mode 100644 rtic-monotonics/src/lib.rs create mode 100644 rtic-monotonics/src/systick_monotonic.rs create mode 100644 rtic-timer/.gitignore create mode 100644 rtic-timer/rust-toolchain.toml create mode 100644 rtic-timer/src/linked_list.rs create mode 100644 rtic-timer/src/monotonic.rs delete mode 100644 rtic-timer/src/sll.rs create mode 100644 rtic/.cargo/config.toml create mode 100644 rtic/.gitignore create mode 100644 rtic/CHANGELOG.md create mode 100644 rtic/Cargo.toml create mode 100644 rtic/build.rs create mode 100644 rtic/ci/expected/async-delay.run create mode 100644 rtic/ci/expected/async-infinite-loop.run create mode 100644 rtic/ci/expected/async-task-multiple-prios.run create mode 100644 rtic/ci/expected/async-task.run create mode 100644 rtic/ci/expected/async-timeout.run create mode 100644 rtic/ci/expected/big-struct-opt.run create mode 100644 rtic/ci/expected/binds.run create mode 100644 rtic/ci/expected/cancel-reschedule.run create mode 100644 rtic/ci/expected/capacity.run create mode 100644 rtic/ci/expected/cfg-whole-task.run create mode 100644 rtic/ci/expected/common.run create mode 100644 rtic/ci/expected/complex.run create mode 100644 rtic/ci/expected/declared_locals.run create mode 100644 rtic/ci/expected/destructure.run create mode 100644 rtic/ci/expected/extern_binds.run create mode 100644 rtic/ci/expected/extern_spawn.run create mode 100644 rtic/ci/expected/generics.run create mode 100644 rtic/ci/expected/hardware.run create mode 100644 rtic/ci/expected/idle-wfi.run create mode 100644 rtic/ci/expected/idle.run create mode 100644 rtic/ci/expected/init.run create mode 100644 rtic/ci/expected/locals.run create mode 100644 rtic/ci/expected/lock-free.run create mode 100644 rtic/ci/expected/lock.run create mode 100644 rtic/ci/expected/message.run create mode 100644 rtic/ci/expected/message_passing.run create mode 100644 rtic/ci/expected/multilock.run create mode 100644 rtic/ci/expected/not-sync.run create mode 100644 rtic/ci/expected/only-shared-access.run create mode 100644 rtic/ci/expected/periodic-at.run create mode 100644 rtic/ci/expected/periodic-at2.run create mode 100644 rtic/ci/expected/periodic.run create mode 100644 rtic/ci/expected/peripherals-taken.run create mode 100644 rtic/ci/expected/pool.run create mode 100644 rtic/ci/expected/preempt.run create mode 100644 rtic/ci/expected/ramfunc.run create mode 100644 rtic/ci/expected/ramfunc.run.grep.bar create mode 100644 rtic/ci/expected/ramfunc.run.grep.foo create mode 100644 rtic/ci/expected/resource-user-struct.run create mode 100644 rtic/ci/expected/schedule.run create mode 100644 rtic/ci/expected/shared.run create mode 100644 rtic/ci/expected/smallest.run create mode 100644 rtic/ci/expected/spawn.run create mode 100644 rtic/ci/expected/static.run create mode 100644 rtic/ci/expected/t-binds.run create mode 100644 rtic/ci/expected/t-cfg-resources.run create mode 100644 rtic/ci/expected/t-htask-main.run create mode 100644 rtic/ci/expected/t-idle-main.run create mode 100644 rtic/ci/expected/t-late-not-send.run create mode 100644 rtic/ci/expected/t-schedule.run create mode 100644 rtic/ci/expected/t-spawn.run create mode 100644 rtic/ci/expected/task.run create mode 100644 rtic/ci/expected/zero-prio-task.run create mode 100644 rtic/examples/async-delay.no_rs create mode 100644 rtic/examples/async-infinite-loop.no_rs create mode 100644 rtic/examples/async-task-multiple-prios.rs create mode 100644 rtic/examples/async-task.rs create mode 100644 rtic/examples/async-timeout.no_rs create mode 100644 rtic/examples/big-struct-opt.rs create mode 100644 rtic/examples/binds.rs create mode 100644 rtic/examples/cancel-reschedule.no_rs create mode 100644 rtic/examples/capacity.no_rs create mode 100644 rtic/examples/cfg-whole-task.no_rs create mode 100644 rtic/examples/common.no_rs create mode 100644 rtic/examples/complex.rs create mode 100644 rtic/examples/declared_locals.rs create mode 100644 rtic/examples/destructure.rs create mode 100644 rtic/examples/extern_binds.rs create mode 100644 rtic/examples/extern_spawn.rs create mode 100644 rtic/examples/generics.rs create mode 100644 rtic/examples/hardware.rs create mode 100644 rtic/examples/idle-wfi.rs create mode 100644 rtic/examples/idle.rs create mode 100644 rtic/examples/init.rs create mode 100644 rtic/examples/locals.rs create mode 100644 rtic/examples/lock-free.no_rs create mode 100644 rtic/examples/lock.rs create mode 100644 rtic/examples/message.no_rs create mode 100644 rtic/examples/message_passing.no_rs create mode 100644 rtic/examples/multilock.rs create mode 100644 rtic/examples/not-sync.rs create mode 100644 rtic/examples/only-shared-access.rs create mode 100644 rtic/examples/periodic-at.no_rs create mode 100644 rtic/examples/periodic-at2.no_rs create mode 100644 rtic/examples/periodic.no_rs create mode 100644 rtic/examples/peripherals-taken.rs create mode 100644 rtic/examples/pool.no_rs create mode 100644 rtic/examples/preempt.rs create mode 100644 rtic/examples/ramfunc.rs create mode 100644 rtic/examples/resource-user-struct.rs create mode 100644 rtic/examples/schedule.no_rs create mode 100644 rtic/examples/shared.rs create mode 100644 rtic/examples/smallest.rs create mode 100644 rtic/examples/spawn.rs create mode 100644 rtic/examples/static.rs create mode 100644 rtic/examples/t-binds.rs create mode 100644 rtic/examples/t-cfg-resources.rs create mode 100644 rtic/examples/t-htask-main.rs create mode 100644 rtic/examples/t-idle-main.rs create mode 100644 rtic/examples/t-late-not-send.rs create mode 100644 rtic/examples/t-schedule.no_rs create mode 100644 rtic/examples/t-spawn.no_rs create mode 100644 rtic/examples/task.rs create mode 100644 rtic/examples/zero-prio-task.rs create mode 100644 rtic/macros/.gitignore create mode 100644 rtic/macros/Cargo.toml create mode 100644 rtic/macros/src/analyze.rs create mode 100644 rtic/macros/src/bindings.rs create mode 100644 rtic/macros/src/check.rs create mode 100644 rtic/macros/src/codegen.rs create mode 100644 rtic/macros/src/codegen/assertions.rs create mode 100644 rtic/macros/src/codegen/async_dispatchers.rs create mode 100644 rtic/macros/src/codegen/hardware_tasks.rs create mode 100644 rtic/macros/src/codegen/idle.rs create mode 100644 rtic/macros/src/codegen/init.rs create mode 100644 rtic/macros/src/codegen/local_resources.rs create mode 100644 rtic/macros/src/codegen/local_resources_struct.rs create mode 100644 rtic/macros/src/codegen/main.rs create mode 100644 rtic/macros/src/codegen/module.rs create mode 100644 rtic/macros/src/codegen/post_init.rs create mode 100644 rtic/macros/src/codegen/pre_init.rs create mode 100644 rtic/macros/src/codegen/shared_resources.rs create mode 100644 rtic/macros/src/codegen/shared_resources_struct.rs create mode 100644 rtic/macros/src/codegen/software_tasks.rs create mode 100644 rtic/macros/src/codegen/util.rs create mode 100644 rtic/macros/src/lib.rs create mode 100644 rtic/macros/src/syntax.rs create mode 100644 rtic/macros/src/syntax/.github/bors.toml create mode 100644 rtic/macros/src/syntax/.github/workflows/build.yml create mode 100644 rtic/macros/src/syntax/.github/workflows/changelog.yml create mode 100644 rtic/macros/src/syntax/.github/workflows/properties/build.properties.json create mode 100644 rtic/macros/src/syntax/.gitignore create mode 100644 rtic/macros/src/syntax/.travis.yml create mode 100644 rtic/macros/src/syntax/accessors.rs create mode 100644 rtic/macros/src/syntax/analyze.rs create mode 100644 rtic/macros/src/syntax/ast.rs create mode 100644 rtic/macros/src/syntax/check.rs create mode 100644 rtic/macros/src/syntax/optimize.rs create mode 100644 rtic/macros/src/syntax/parse.rs create mode 100644 rtic/macros/src/syntax/parse/app.rs create mode 100644 rtic/macros/src/syntax/parse/hardware_task.rs create mode 100644 rtic/macros/src/syntax/parse/idle.rs create mode 100644 rtic/macros/src/syntax/parse/init.rs create mode 100644 rtic/macros/src/syntax/parse/resource.rs create mode 100644 rtic/macros/src/syntax/parse/software_task.rs create mode 100644 rtic/macros/src/syntax/parse/util.rs create mode 100644 rtic/macros/tests/ui.rs create mode 100644 rtic/macros/ui/extern-interrupt-used.rs create mode 100644 rtic/macros/ui/extern-interrupt-used.stderr create mode 100644 rtic/macros/ui/idle-double-local.rs create mode 100644 rtic/macros/ui/idle-double-local.stderr create mode 100644 rtic/macros/ui/idle-double-shared.rs create mode 100644 rtic/macros/ui/idle-double-shared.stderr create mode 100644 rtic/macros/ui/idle-input.rs create mode 100644 rtic/macros/ui/idle-input.stderr create mode 100644 rtic/macros/ui/idle-no-context.rs create mode 100644 rtic/macros/ui/idle-no-context.stderr create mode 100644 rtic/macros/ui/idle-not-divergent.rs create mode 100644 rtic/macros/ui/idle-not-divergent.stderr create mode 100644 rtic/macros/ui/idle-output.rs create mode 100644 rtic/macros/ui/idle-output.stderr create mode 100644 rtic/macros/ui/idle-pub.rs create mode 100644 rtic/macros/ui/idle-pub.stderr create mode 100644 rtic/macros/ui/idle-unsafe.rs create mode 100644 rtic/macros/ui/idle-unsafe.stderr create mode 100644 rtic/macros/ui/init-divergent.rs create mode 100644 rtic/macros/ui/init-divergent.stderr create mode 100644 rtic/macros/ui/init-double-local.rs create mode 100644 rtic/macros/ui/init-double-local.stderr create mode 100644 rtic/macros/ui/init-double-shared.rs create mode 100644 rtic/macros/ui/init-double-shared.stderr create mode 100644 rtic/macros/ui/init-input.rs create mode 100644 rtic/macros/ui/init-input.stderr create mode 100644 rtic/macros/ui/init-no-context.rs create mode 100644 rtic/macros/ui/init-no-context.stderr create mode 100644 rtic/macros/ui/init-output.rs create mode 100644 rtic/macros/ui/init-output.stderr create mode 100644 rtic/macros/ui/init-pub.rs create mode 100644 rtic/macros/ui/init-pub.stderr create mode 100644 rtic/macros/ui/init-unsafe.rs create mode 100644 rtic/macros/ui/init-unsafe.stderr create mode 100644 rtic/macros/ui/interrupt-double.rs create mode 100644 rtic/macros/ui/interrupt-double.stderr create mode 100644 rtic/macros/ui/local-collision-2.rs create mode 100644 rtic/macros/ui/local-collision-2.stderr create mode 100644 rtic/macros/ui/local-collision.rs create mode 100644 rtic/macros/ui/local-collision.stderr create mode 100644 rtic/macros/ui/local-malformed-1.rs create mode 100644 rtic/macros/ui/local-malformed-1.stderr create mode 100644 rtic/macros/ui/local-malformed-2.rs create mode 100644 rtic/macros/ui/local-malformed-2.stderr create mode 100644 rtic/macros/ui/local-malformed-3.rs create mode 100644 rtic/macros/ui/local-malformed-3.stderr create mode 100644 rtic/macros/ui/local-malformed-4.rs create mode 100644 rtic/macros/ui/local-malformed-4.stderr create mode 100644 rtic/macros/ui/local-not-declared.rs create mode 100644 rtic/macros/ui/local-not-declared.stderr create mode 100644 rtic/macros/ui/local-pub.rs create mode 100644 rtic/macros/ui/local-pub.stderr create mode 100644 rtic/macros/ui/local-shared-attribute.rs create mode 100644 rtic/macros/ui/local-shared-attribute.stderr create mode 100644 rtic/macros/ui/local-shared.rs create mode 100644 rtic/macros/ui/local-shared.stderr create mode 100644 rtic/macros/ui/shared-lock-free.rs create mode 100644 rtic/macros/ui/shared-lock-free.stderr create mode 100644 rtic/macros/ui/shared-not-declared.rs create mode 100644 rtic/macros/ui/shared-not-declared.stderr create mode 100644 rtic/macros/ui/shared-pub.rs create mode 100644 rtic/macros/ui/shared-pub.stderr create mode 100644 rtic/macros/ui/task-divergent.rs create mode 100644 rtic/macros/ui/task-divergent.stderr create mode 100644 rtic/macros/ui/task-double-local.rs create mode 100644 rtic/macros/ui/task-double-local.stderr create mode 100644 rtic/macros/ui/task-double-priority.rs create mode 100644 rtic/macros/ui/task-double-priority.stderr create mode 100644 rtic/macros/ui/task-double-shared.rs create mode 100644 rtic/macros/ui/task-double-shared.stderr create mode 100644 rtic/macros/ui/task-idle.rs create mode 100644 rtic/macros/ui/task-idle.stderr create mode 100644 rtic/macros/ui/task-init.rs create mode 100644 rtic/macros/ui/task-init.stderr create mode 100644 rtic/macros/ui/task-interrupt.rs create mode 100644 rtic/macros/ui/task-interrupt.stderr create mode 100644 rtic/macros/ui/task-no-context.rs create mode 100644 rtic/macros/ui/task-no-context.stderr create mode 100644 rtic/macros/ui/task-priority-too-high.rs create mode 100644 rtic/macros/ui/task-priority-too-high.stderr create mode 100644 rtic/macros/ui/task-priority-too-low.rs create mode 100644 rtic/macros/ui/task-priority-too-low.stderr create mode 100644 rtic/macros/ui/task-pub.rs create mode 100644 rtic/macros/ui/task-pub.stderr create mode 100644 rtic/macros/ui/task-unsafe.rs create mode 100644 rtic/macros/ui/task-unsafe.stderr create mode 100644 rtic/macros/ui/task-zero-prio.rs create mode 100644 rtic/macros/ui/task-zero-prio.stderr create mode 100644 rtic/rust-toolchain.toml create mode 100644 rtic/src/export.rs create mode 100644 rtic/src/export/executor.rs create mode 100644 rtic/src/lib.rs create mode 100644 rtic/tests/tests.rs create mode 100644 rtic/ui/exception-invalid.rs create mode 100644 rtic/ui/exception-invalid.stderr create mode 100644 rtic/ui/extern-interrupt-not-enough.rs create mode 100644 rtic/ui/extern-interrupt-not-enough.stderr create mode 100644 rtic/ui/extern-interrupt-used.rs create mode 100644 rtic/ui/extern-interrupt-used.stderr create mode 100644 rtic/ui/task-priority-too-high.rs create mode 100644 rtic/ui/task-priority-too-high.stderr create mode 100644 rtic/ui/unknown-interrupt.rs create mode 100644 rtic/ui/unknown-interrupt.stderr create mode 100644 rtic/ui/v6m-interrupt-not-enough.rs_no create mode 100644 rtic/xtask/Cargo.toml create mode 100644 rtic/xtask/src/build.rs create mode 100644 rtic/xtask/src/command.rs create mode 100644 rtic/xtask/src/main.rs delete mode 100644 rust-toolchain.toml delete mode 100644 src/export.rs delete mode 100644 src/export/executor.rs delete mode 100644 src/lib.rs delete mode 100644 tests/tests.rs delete mode 100644 ui/exception-invalid.rs delete mode 100644 ui/exception-invalid.stderr delete mode 100644 ui/extern-interrupt-not-enough.rs delete mode 100644 ui/extern-interrupt-not-enough.stderr delete mode 100644 ui/extern-interrupt-used.rs delete mode 100644 ui/extern-interrupt-used.stderr delete mode 100644 ui/task-priority-too-high.rs delete mode 100644 ui/task-priority-too-high.stderr delete mode 100644 ui/unknown-interrupt.rs delete mode 100644 ui/unknown-interrupt.stderr delete mode 100644 ui/v6m-interrupt-not-enough.rs_no delete mode 100644 xtask/Cargo.toml delete mode 100644 xtask/src/build.rs delete mode 100644 xtask/src/command.rs delete mode 100644 xtask/src/main.rs diff --git a/.cargo/config.toml b/.cargo/config.toml deleted file mode 100644 index d70faef..0000000 --- a/.cargo/config.toml +++ /dev/null @@ -1,13 +0,0 @@ -[alias] -xtask = "run --package xtask --" - -[target.thumbv6m-none-eabi] -runner = "qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb -nographic -semihosting-config enable=on,target=native -kernel" - -[target.thumbv7m-none-eabi] -runner = "qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb -nographic -semihosting-config enable=on,target=native -kernel" - -[target.'cfg(all(target_arch = "arm", target_os = "none"))'] -rustflags = [ - "-C", "link-arg=-Tlink.x", -] \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index d172278..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,603 +0,0 @@ -# Change Log - -All notable changes to this project will be documented in this file. -This project adheres to [Semantic Versioning](http://semver.org/). - -For each category, *Added*, *Changed*, *Fixed* add new entries at the top! - -## [Unreleased] - -### Added - -### Fixed - -### Changed - -## [v1.1.4] - 2023-02-26 - -### Added - -- CFG: Support #[cfg] on HW task, cleanup for SW tasks -- CFG: Slightly improved support for #[cfg] on Monotonics -- CI: Check examples also for thumbv8.{base,main} -- Allow custom `link_section` attributes for late resources - -### Fixed - -- Attempt to handle docs generation enabling `deny(missing_docs)` -- Book: Editorial review -- Use native GHA rustup and cargo -- Distinguish between thumbv8m.base and thumbv8m.main for basepri usage. - -### Changed - -- Updated dev-dependency cortex-m-semihosting to v0.5 -- CI: Updated to setup-python@v4 -- CI: Updated to checkout@v3 -- Tuned redirect message for rtic.rs/meeting - -## [v1.1.3] - 2022-06-23 - -### Added - -### Fixed - -- Bump cortex-m-rtic-macros to 1.1.5 -- fix ci: use SYST::PTR - -#### cortex-m-rtic-macros v1.1.5 - 2022-06-23 - -- Bump rtic-syntax to 1.0.2 - -#### cortex-m-rtic-macros v1.1.4 - 2022-05-24 - -- Fix macros to Rust 2021 - -#### cortex-m-rtic-macros v1.1.3 - 2022-05-24 - -- Fix clash with defmt - -### Changed - -## [v1.1.2] - 2022-05-09 - -### Added - -### Fixed - -- Generation of masks for the source masking scheduling for thumbv6 - -### Changed - -## [v1.1.1] - 2022-04-13 - YANKED - -### Added - -### Fixed - -- Fixed `marcro` version - -### Changed - -## [v1.1.0] - 2022-04-13 - YANKED - -### Added - -- Improve how CHANGELOG.md merges are handled -- If current $stable and master version matches, dev-book redirects to $stable book -- During deploy stage, merge master branch into current stable IFF cargo package version matches -- Rework branch structure, release/vVERSION -- Cargo clippy in CI -- Use rust-cache Github Action -- Support for NVIC based SPR based scheduling for armv6m. -- CI changelog entry enforcer -- `examples/periodic-at.rs`, an example of a periodic timer without accumulated drift. -- `examples/periodic-at2.rs`, an example of a periodic process with two tasks, with offset timing. - Here we depict two alternative usages of the timer type, explicit and trait based. -- book: Update `Monotonic` tips. - -### Fixed - -- Re-export `rtic_core::prelude` as `rtic::mutex::prelude` to allow glob imports + Clippy -- Fix all except `must_use` lints from clippy::pedantic -- Fix dated migration docs for spawn -- Remove obsolete action-rs tool-cache -- Force mdBook to return error codes -- Readded missing ramfunc output to book - -### Changed - -- Try to detect `target-dir` for rtic-expansion.rs - -## [v1.0.0] - 2021-12-25 - -### Changed - -- Bump RTIC dependencies also updated to v1.0.0 -- Edition 2021 -- Change default `idle` behaviour to be `NOP` instead of `WFI` - -## [v0.6.0-rc.4] - 2021-11-09 - -- Updated to use the new generic `Monotonic` trait - -## [v0.6.0-rc.3] - 2021-11-08 - -### Fixed - -- Match rtic-syntax Analysis-struct updates from https://github.com/rtic-rs/rtic-syntax/pull/61 - -## [v0.6.0-rc.2] - 2021-09-28 - -- Fixed issue with `cortex_m` being used by the codegen instead of using the `rtic::export::...` which could make an app not compile if Systick is used and the user did not have the cortex-m crate as a dependency - -## [v0.6.0-rc.1] - 2021-09-27 - -- Documentation updates -- Monotonic handlers default to maximum priority instead of minimum (to follow RTIC 0.5) -- Better support for `rust-analyzer` - -## [v0.5.9] - 2021-09-27 - -- Removed the `cortex-m-rt` dependency -- Docs updates - -## [v0.5.8] - 2021-08-19 - -- Feature flag was added to support `cortex-m v0.7.x` -- MSRV raised to 1.38. - -## [v0.6.0-alpha.5] - 2021-07-09 - -### Changed - -- The new resources syntax is implemented. - -## [v0.5.7] - 2021-07-05 - -- Backport: "you must enable the rt feature" compile time detection - -## [v0.6.0-alpha.4] - 2021-05-27 - -### Fixed - -- Fixed codegen structure to not have issues with local paths -- Default paths for monotonics now work properly -- New `embedded-time` version to `0.11` - -## [v0.6.0-alpha.3] - 2021-0X-XX - -- Lost in the ether... - -## [v0.6.0-alpha.2] - 2021-04-08 - -### Added - -- Cancel and reschedule support to the monotonics - -### Fixed - -- UB in `spawn_at` -- `#[cfg]` and other attributes now work on hardware tasks -- Type aliases now work in `mod app` - -### Changed - -- The access to monotonic static methods was for example `MyMono::now()`, and is now `monotonics::MyMono::now()` - -## [v0.6.0-alpha.1] - 2021-03-04 - -### Added - -- Support for multi-locks, see `examples/multilock.rs` for syntax. -- New monotonic syntax and support, see `#[monotonic]` - -## [v0.5.6] - 2021-03-03 - -- **Security** Use latest security patched heapless - -## [v0.6.0-alpha.0] - 2020-11-14 - -### Added - -- Allow annotating resources to activate special resource locking behaviour. - - `#[lock_free]`, there might be several tasks with the same priority accessing - the resource without critical section. - - `#[task_local]`, there must be only one task, similar to a task local - resource, but (optionally) set-up by init. This is similar to move. - -- Improved ergonomics allowing separation of task signatures to actual implementation in extern block `extern "Rust" { #[task(..)] fn t(..); }`. - -### Changed - -- [breaking-change] [PR 400] Move dispatchers from extern block to app argument. - -[PR 400]: https://github.com/rtic-rs/cortex-m-rtic/pull/400 - -- [breaking-change] [PR 399] Locking resources are now always required to achieve a symmetric UI. - -[PR 399]: https://github.com/rtic-rs/cortex-m-rtic/pull/399 - -- [breaking-change] [PR 390] Rework whole spawn/schedule, support `foo::spawn( ... )`, - `foo::schedule( ... )`. - -[PR 390]: https://github.com/rtic-rs/cortex-m-rtic/pull/390 - -- [breaking-change] [PR 368] `struct Resources` changed to attribute `#[resources]` on a struct. - -- [breaking-change] [PR 368] Mod over const, instead of `const APP: () = {` use `mod app {`. - -- [breaking-change] [PR 372] Init function always return `LateResources` for a symmetric API. - -- [PR 355] Multi-core support was removed to reduce overall complexity. - -[PR 368]: https://github.com/rtic-rs/cortex-m-rtic/pull/368 -[PR 372]: https://github.com/rtic-rs/cortex-m-rtic/pull/372 -[PR 355]: https://github.com/rtic-rs/cortex-m-rtic/pull/355 - -## [v0.5.5] - 2020-08-27 - -- Includes the previous soundness fix. -- Fixes wrong use of the `cortex_m` crate which can cause some projects to stop compiling. - -## [v0.5.4] - 2020-08-26 - YANKED - -- **Soundness fix in RTIC**, it was previously possible to get the `cortex_m::Peripherals` more than once, causing UB. - -## [v0.5.3] - 2020-06-12 - -- Added migration guide from `cortex-m-rtfm` to `cortex-m-rtic` -- No code changes, only a version compatibility release with `cortex-m-rtfm` to ease the transition -for users. - -## [v0.5.2] - 2020-06-11 - -- Using safe `DWT` interface -- Using GitHub Actions now -- Improved CI speed -- Now `main` can be used as function name -- Fixed so one can `cfg`-out resources when using a newer compiler - -## [v0.5.1] - 2019-11-19 - -- Fixed arithmetic wrapping bug in src/cyccntr.rs - elapsed and duration could cause an internal overflow trap - on subtraction in debug mode. - -- Fixed bug in SysTick implementation where the SysTick could be disabled by - accident - -## [v0.5.0] - 2019-11-14 - -### Added - -- Experimental support for homogeneous and heterogeneous multi-core - microcontrollers has been added. Support is gated behind the `homogeneous` and - `heterogeneous` Cargo features. - -### Changed - -- [breaking-change] [RFC 155] "explicit `Context` parameter" has been - implemented. - -[RFC 155]: https://github.com/rtic-rs/cortex-m-rtic/issues/155 - -- [breaking-change] [RFC 147] "all functions must be safe" has been - implemented. - -[RFC 147]: https://github.com/rtic-rs/cortex-m-rtic/issues/147 - -- All the queues internally used by the framework now use `AtomicU8` indices - instead of `AtomicUsize`; this reduces the static memory used by the - framework. - -- [breaking-change] when the `capacity` argument is omitted, the capacity of - the task is assumed to be `1`. Before, a reasonable (but hard to predict) - capacity was computed based on the number of `spawn` references the task had. - -- [breaking-change] resources that are appear as exclusive references - (`&mut-`) no longer appear behind the `Exclusive` newtype. - -- [breaking-change] the `timer-queue` Cargo feature has been removed. The - `schedule` API can be used without enabling any Cargo feature. - -- [breaking-change] when the `schedule` API is used the type of - `init::Context.core` changes from `cortex_m::Peripherals` to - `rtic::Peripherals`. The fields of `rtic::Peripherals` do not change when - Cargo features are enabled. - -- [breaking-change] the monotonic timer used to implement the `schedule` API - is now user configurable via the `#[app(monotonic = ..)]` argument. IMPORTANT: - it is now the responsibility of the application author to configure and - initialize the chosen `monotonic` timer during the `#[init]` phase. - -- [breaking-change] the `peripherals` field is not include in `init::Context` - by default. One must opt-in using the `#[app(peripherals = ..)]` argument. - -- [breaking-change] the `#[exception]` and `#[interrupt]` attributes have been - removed. Hardware tasks are now declared using the `#[task(binds = ..)]` - attribute. - -- [breaking-change] the syntax to declare resources has changed. Instead of - using a `static [mut]` variable for each resource, all resources must be - declared in a `Resources` structure. - -### Removed - -- [breaking-change] the integration with the `owned_singleton` crate has been - removed. You can use `heapless::Pool` instead of `alloc_singleton`. - -- [breaking-change] late resources can no longer be initialized using the assign - syntax. `init::LateResources` is the only method to initialize late resources. - See [PR #140] for more details. - -[PR #140]: https://github.com/rtic-rs/cortex-m-rtic/pull/140 - -## [v0.4.3] - 2019-04-21 - -### Changed - -- Checking that the specified priorities are supported by the target device is - now done at compile time. - -### Fixed - -- Building this crate with the "nightly" feature and a recent compiler has been - fixed. - -## [v0.4.2] - 2019-02-27 - -### Added - -- `Duration` now has an `as_cycles` method to get the number of clock cycles - contained in it. - -- An opt-in "nightly" feature that reduces static memory usage, shortens - initialization time and reduces runtime overhead has been added. To use this - feature you need a nightly compiler! - -- [RFC 128] has been implemented. The `exception` and `interrupt` have gained a - `binds` argument that lets you give the handler an arbitrary name. For - example: - -[RFC 128]: https://github.com/rtic-rs/cortex-m-rtic/issues/128 - -``` rust -// on v0.4.1 you had to write -#[interrupt] -fn USART0() { .. } - -// on v0.4.2 you can write -#[interrupt(binds = USART0)] -fn on_new_frame() { .. } -``` - -### Changed - -- Builds are now reproducible. `cargo build; cargo clean; cargo build` will - produce binaries that are exactly the same (after `objcopy -O ihex`). This - wasn't the case before because we used randomly generated identifiers for - memory safety but now all the randomness is gone. - -### Fixed - -- Fixed a `non_camel_case_types` warning that showed up when using a recent - nightly. - -- Fixed a bug that allowed you to enter the `capacity` and `priority` arguments - in the `task` attribute more than once. Now all arguments can only be stated - once in the list, as it should be. - -## [v0.4.1] - 2019-02-12 - -### Added - -- The RTIC book has been translated to Russian. You can find the translation - online at https://japaric.github.io/cortex-m-rtic/book/ru/ - -- `Duration` now implements the `Default` trait. - -### Changed - -- [breaking-change] [soundness-fix] `init` can not contain any early return as - that would result in late resources not being initialized and thus undefined - behavior. - -- Use an absolute link to the book so it works when landing from crates.io - documentation page - -- The initialization function can now be written as `fn init() -> - init::LateResources` when late resources are used. This is preferred over the - old `fn init()` form. See the section on late resources (resources chapter) in - the book for more details. - -### Fixed - -- `#[interrupt]` and `#[exception]` no longer produce warnings on recent nightlies. - -## [v0.4.0] - 2018-11-03 - YANKED - -Yanked due to a soundness issue in `init`; the issue has been mostly fixed in v0.4.1. - -### Changed - -- This crate now compiles on stable 1.31. - -- [breaking-change] The `app!` macro has been transformed into an attribute. See - the documentation for details. - -- [breaking-change] Applications that use this library must be written using the - 2018 edition. - -- [breaking-change] The `Resource` trait has been renamed to `Mutex`. - `Resource.claim_mut` has been renamed to `Mutex.lock` and its signature has - changed (no `Threshold` token is required). - -- [breaking-change] The name of the library has changed to `rtic`. The package - name is still `cortex-m-rtic`. - -- [breaking-change] `cortex_m_rtic::set_pending` has been renamed to - `rtic::pend`. - -### Added - -- Software tasks, which can be immediately spawn and scheduled to run in the - future. - -- `Instant` and `Duration` API. - -- Integration with the [`Singleton`] abstraction. - -[`Singleton`]: https://docs.rs/owned-singleton/0.1.0/owned_singleton/ - -### Removed - -- [breaking-change] The `Threshold` token has been removed. - -- [breaking-change] The `bkpt` and `wfi` re-exports have been removed. - -- [breaking-change] `rtic::atomic` has been removed. - -## [v0.3.4] - 2018-08-27 - -### Changed - -- The documentation link to point to GH pages. - -## [v0.3.3] - 2018-08-24 - -### Fixed - -- Compilation with latest nightly - -## [v0.3.2] - 2018-04-16 - -### Added - -- Span information to error messages - -### Changed - -- Some non fatal error messages have become warning messages. For example, specifying an empty list - of resources now produces a warning instead of a hard error. - -## [v0.3.1] - 2018-01-16 - -### Fixed - -- Documentation link - -## [v0.3.0] - 2018-01-15 - -### Added - -- [feat] `&'static mut` references can be safely created by assigning resources to `init`. See the - `init.resources` section of the `app!` macro documentation and the `safe-static-mut-ref` example - for details. - -### Changed - -- [breaking-change] svd2rust dependency has been bumped to v0.12.0 - -- [breaking-change] resources assigned to tasks, or to idle, that were not declared in the top - `resources` field generate compiler errors. Before these were assumed to be peripherals, that's no - longer the case. - -- [breaking-change] the layout of `init::Peripherals` has changed. This struct now has two fields: - `core` and `device`. The value of the `core` field is a struct that owns all the core peripherals - of the device and the value of the `device` field is a struct that owns all the device specific - peripherals of the device. - -## [v0.2.2] - 2017-11-22 - -### Added - -- Support for runtime initialized resources ("late" resources). - -## [v0.2.1] - 2017-07-29 - -### Fixed - -- Link to `app!` macro documentation. - -## [v0.2.0] - 2017-07-29 - -### Added - -- The `app!` macro, a macro to declare the tasks and resources of an - application. - -- The `Resource` trait, which is used to write generic code that deals with - resources. - -- Support for system handlers like SYS_TICK. - -### Changed - -- [breaking-change] The signature of the `atomic` function has changed. - -- [breaking-change] The threshold token has become a concrete type and lost its - `raise` method. - -### Removed - -- [breaking-change] The `tasks!` and `peripherals!` macros. - -- [breaking-change] The ceiling and priority tokens. - -- [breaking-change] The `Local`, `Resource` and `Peripheral` structs. - -- [breaking-change] The traits related to type level integers. - -## [v0.1.1] - 2017-06-05 - -### Changed - -- `peripherals!`: The `register_block` field is now optional - -## v0.1.0 - 2017-05-09 - -- Initial release - -[Unreleased]: https://github.com/rtic-rs/cortex-m-rtic/compare/v1.1.4...HEAD -[v1.1.4]: https://github.com/rtic-rs/cortex-m-rtic/compare/v1.1.3...v1.1.4 -[v1.1.3]: https://github.com/rtic-rs/cortex-m-rtic/compare/v1.1.2...v1.1.3 -[v1.1.2]: https://github.com/rtic-rs/cortex-m-rtic/compare/v1.1.1...v1.1.2 -[v1.1.1]: https://github.com/rtic-rs/cortex-m-rtic/compare/v1.1.0...v1.1.1 -[v1.1.0]: https://github.com/rtic-rs/cortex-m-rtic/compare/v1.0.0...v1.1.0 -[v1.0.0]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.6.0-rc.4...v1.0.0 -[v0.6.0-rc.4]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.6.0-rc.3...v0.6.0-rc.4 -[v0.6.0-rc.3]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.6.0-rc.2...v0.6.0-rc.3 -[v0.6.0-rc.2]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.6.0-rc.1...v0.6.0-rc.2 -[v0.6.0-rc.1]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.6.0-rc.0...v0.6.0-rc.1 -[v0.6.0-rc.0]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.6.0-alpha.5...v0.6.0-rc.0 -[v0.6.0-alpha.5]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.6.0-alpha.4...v0.6.0-alpha.5 -[v0.6.0-alpha.4]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.6.0-alpha.3...v0.6.0-alpha.4 -[v0.6.0-alpha.3]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.6.0-alpha.2...v0.6.0-alpha.3 -[v0.6.0-alpha.2]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.6.0-alpha.1...v0.6.0-alpha.2 -[v0.6.0-alpha.1]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.6.0-alpha.0...v0.6.0-alpha.1 -[v0.6.0-alpha.0]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.5.5...v0.6.0-alpha.0 -[v0.5.x unreleased]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.5.8...v0.5.x -[v0.5.9]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.5.8...v0.5.9 -[v0.5.8]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.5.7...v0.5.8 -[v0.5.7]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.5.6...v0.5.7 -[v0.5.6]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.5.5...v0.5.6 -[v0.5.5]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.5.4...v0.5.5 -[v0.5.4]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.5.3...v0.5.4 -[v0.5.3]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.5.2...v0.5.3 -[v0.5.2]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.5.1...v0.5.2 -[v0.5.1]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.5.0...v0.5.1 -[v0.5.0]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.4.3...v0.5.0 -[v0.4.3]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.4.2...v0.4.3 -[v0.4.2]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.4.1...v0.4.2 -[v0.4.1]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.4.0...v0.4.1 -[v0.4.0]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.3.4...v0.4.0 -[v0.3.4]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.3.3...v0.3.4 -[v0.3.3]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.3.2...v0.3.3 -[v0.3.2]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.3.1...v0.3.2 -[v0.3.1]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.3.0...v0.3.1 -[v0.3.0]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.2.2...v0.3.0 -[v0.2.2]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.2.1...v0.2.2 -[v0.2.1]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.2.0...v0.2.1 -[v0.2.0]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.1.1...v0.2.0 -[v0.1.1]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.1.0...v0.1.1 diff --git a/Cargo.toml b/Cargo.toml deleted file mode 100644 index c22d023..0000000 --- a/Cargo.toml +++ /dev/null @@ -1,80 +0,0 @@ -[package] -authors = [ - "The Real-Time Interrupt-driven Concurrency developers", - "Emil Fresk ", - "Henrik Tjäder ", - "Jorge Aparicio ", - "Per Lindgren ", -] -categories = ["concurrency", "embedded", "no-std", "asynchronous"] -description = "Real-Time Interrupt-driven Concurrency (RTIC): a concurrency framework for building real-time systems" -documentation = "https://rtic.rs/" -edition = "2021" -keywords = ["arm", "cortex-m", "risc-v", "embedded", "async", "runtime", "futures", "await", "no-std", "rtos", "bare-metal"] -license = "MIT OR Apache-2.0" -name = "rtic" -readme = "README.md" -repository = "https://github.com/rtic-rs/rtic" - -version = "2.0.0-alpha.0" - -[lib] -name = "rtic" - -[dependencies] -cortex-m = "0.7.0" -rtic-macros = { path = "macros", version = "2.0.0-alpha.0" } -rtic-monotonic = "1.0.0" -rtic-core = "1.0.0" -heapless = "0.7.7" -bare-metal = "1.0.0" -#portable-atomic = { version = "0.3.19" } -atomic-polyfill = "1" - -[build-dependencies] -version_check = "0.9" - -[dev-dependencies] -lm3s6965 = "0.1.3" -cortex-m-semihosting = "0.5.0" -systick-monotonic = "1.0.0" - -[dev-dependencies.panic-semihosting] -features = ["exit"] -version = "0.6.0" - -[target.x86_64-unknown-linux-gnu.dev-dependencies] -trybuild = "1" - -[profile.release] -codegen-units = 1 -lto = true - -[workspace] -members = ["macros", "xtask", "rtic-timer"] - -# do not optimize proc-macro deps or build scripts -[profile.dev.build-override] -codegen-units = 16 -debug = false -debug-assertions = false -opt-level = 0 -overflow-checks = false - - -[profile.release.build-override] -codegen-units = 16 -debug = false -debug-assertions = false -opt-level = 0 -overflow-checks = false - -[patch.crates-io] -lm3s6965 = { git = "https://github.com/japaric/lm3s6965" } - -[features] -test-critical-section = ["cortex-m/critical-section-single-core"] - -# [[example]] -# name = "pool" -# required-features = ["test-critical-section"] diff --git a/build.rs b/build.rs deleted file mode 100644 index 35f8303..0000000 --- a/build.rs +++ /dev/null @@ -1,25 +0,0 @@ -use std::env; - -fn main() { - let target = env::var("TARGET").unwrap(); - - if version_check::Channel::read().unwrap().is_nightly() { - println!("cargo:rustc-cfg=rustc_is_nightly"); - } - - // These targets all have know support for the BASEPRI register. - if target.starts_with("thumbv7m") - | target.starts_with("thumbv7em") - | target.starts_with("thumbv8m.main") - { - println!("cargo:rustc-cfg=have_basepri"); - - // These targets are all known to _not_ have the BASEPRI register. - } else if target.starts_with("thumb") - && !(target.starts_with("thumbv6m") | target.starts_with("thumbv8m.base")) - { - panic!("Unknown target '{target}'. Need to update BASEPRI logic in build.rs."); - } - - println!("cargo:rerun-if-changed=build.rs"); -} diff --git a/ci/expected/async-delay.run b/ci/expected/async-delay.run deleted file mode 100644 index 61852ab..0000000 --- a/ci/expected/async-delay.run +++ /dev/null @@ -1,7 +0,0 @@ -init -hello from bar -hello from baz -hello from foo -bye from foo -bye from bar -bye from baz diff --git a/ci/expected/async-infinite-loop.run b/ci/expected/async-infinite-loop.run deleted file mode 100644 index f9fd4e4..0000000 --- a/ci/expected/async-infinite-loop.run +++ /dev/null @@ -1,6 +0,0 @@ -init -hello from async 0 -hello from async 1 -hello from async 2 -hello from async 3 -hello from async 4 diff --git a/ci/expected/async-task-multiple-prios.run b/ci/expected/async-task-multiple-prios.run deleted file mode 100644 index 0b42df0..0000000 --- a/ci/expected/async-task-multiple-prios.run +++ /dev/null @@ -1,6 +0,0 @@ -init -hello from async 3 a 1 -hello from async 4 a 2 -hello from async 1 a 3 -hello from async 2 a 4 -idle diff --git a/ci/expected/async-task.run b/ci/expected/async-task.run deleted file mode 100644 index 1f93a4c..0000000 --- a/ci/expected/async-task.run +++ /dev/null @@ -1,5 +0,0 @@ -init -hello from async2 -hello from async -hello from async with args a: 1, b: 2 -idle diff --git a/ci/expected/async-timeout.run b/ci/expected/async-timeout.run deleted file mode 100644 index a807423..0000000 --- a/ci/expected/async-timeout.run +++ /dev/null @@ -1,5 +0,0 @@ -init -hello from bar -hello from foo -foo no timeout -bar timeout diff --git a/ci/expected/big-struct-opt.run b/ci/expected/big-struct-opt.run deleted file mode 100644 index 7fdef35..0000000 --- a/ci/expected/big-struct-opt.run +++ /dev/null @@ -1,3 +0,0 @@ -async_task data:[22, 22, 22, 22, 22] -uart0 data:[22, 22, 22, 22, 22] -idle diff --git a/ci/expected/binds.run b/ci/expected/binds.run deleted file mode 100644 index f84cff0..0000000 --- a/ci/expected/binds.run +++ /dev/null @@ -1,4 +0,0 @@ -init -foo called 1 time -idle -foo called 2 times diff --git a/ci/expected/cancel-reschedule.run b/ci/expected/cancel-reschedule.run deleted file mode 100644 index 5a94752..0000000 --- a/ci/expected/cancel-reschedule.run +++ /dev/null @@ -1,3 +0,0 @@ -init -foo -bar diff --git a/ci/expected/capacity.run b/ci/expected/capacity.run deleted file mode 100644 index f96815d..0000000 --- a/ci/expected/capacity.run +++ /dev/null @@ -1,5 +0,0 @@ -foo(0) -foo(1) -foo(2) -foo(3) -bar diff --git a/ci/expected/cfg-monotonic.run b/ci/expected/cfg-monotonic.run deleted file mode 100644 index e69de29..0000000 diff --git a/ci/expected/cfg-whole-task.run b/ci/expected/cfg-whole-task.run deleted file mode 100644 index e69de29..0000000 diff --git a/ci/expected/common.run b/ci/expected/common.run deleted file mode 100644 index e69de29..0000000 diff --git a/ci/expected/complex.run b/ci/expected/complex.run deleted file mode 100644 index 5df884d..0000000 --- a/ci/expected/complex.run +++ /dev/null @@ -1,47 +0,0 @@ -init -idle p0 started -t2 p4 called 1 time -enter lock s4 0 -t3 p4 exit -idle enter lock s3 0 -idle pend t0 -idle pend t1 -idle pend t2 -t2 p4 called 2 times -enter lock s4 1 -t3 p4 exit -idle still in lock s3 0 -t1 p3 called 1 time -t1 enter lock s4 2 -t1 pend t0 -t1 pend t2 -t1 still in lock s4 2 -t2 p4 called 3 times -enter lock s4 2 -t3 p4 exit -t1 p3 exit -t0 p2 called 1 time -t0 p2 exit - -back in idle -enter lock s2 0 -idle pend t0 -idle pend t1 -t1 p3 called 2 times -t1 enter lock s4 3 -t1 pend t0 -t1 pend t2 -t1 still in lock s4 3 -t2 p4 called 4 times -enter lock s4 3 -t3 p4 exit -t1 p3 exit -idle pend t2 -t2 p4 called 5 times -enter lock s4 4 -t3 p4 exit -idle still in lock s2 0 -t0 p2 called 2 times -t0 p2 exit - -idle exit diff --git a/ci/expected/declared_locals.run b/ci/expected/declared_locals.run deleted file mode 100644 index e69de29..0000000 diff --git a/ci/expected/destructure.run b/ci/expected/destructure.run deleted file mode 100644 index 25a4b1b..0000000 --- a/ci/expected/destructure.run +++ /dev/null @@ -1,2 +0,0 @@ -bar: a = 0, b = 1, c = 2 -foo: a = 0, b = 1, c = 2 diff --git a/ci/expected/extern_binds.run b/ci/expected/extern_binds.run deleted file mode 100644 index 9d925d5..0000000 --- a/ci/expected/extern_binds.run +++ /dev/null @@ -1,4 +0,0 @@ -init -foo called -idle -foo called diff --git a/ci/expected/extern_spawn.run b/ci/expected/extern_spawn.run deleted file mode 100644 index 257cc56..0000000 --- a/ci/expected/extern_spawn.run +++ /dev/null @@ -1 +0,0 @@ -foo diff --git a/ci/expected/generics.run b/ci/expected/generics.run deleted file mode 100644 index fb31731..0000000 --- a/ci/expected/generics.run +++ /dev/null @@ -1,6 +0,0 @@ -UART1(STATE = 0) -shared: 0 -> 1 -UART0(STATE = 0) -shared: 1 -> 2 -UART1(STATE = 1) -shared: 2 -> 4 diff --git a/ci/expected/hardware.run b/ci/expected/hardware.run deleted file mode 100644 index ef00864..0000000 --- a/ci/expected/hardware.run +++ /dev/null @@ -1,4 +0,0 @@ -init -UART0 called 1 time -idle -UART0 called 2 times diff --git a/ci/expected/idle-wfi.run b/ci/expected/idle-wfi.run deleted file mode 100644 index 4307776..0000000 --- a/ci/expected/idle-wfi.run +++ /dev/null @@ -1,2 +0,0 @@ -init -idle diff --git a/ci/expected/idle.run b/ci/expected/idle.run deleted file mode 100644 index 4307776..0000000 --- a/ci/expected/idle.run +++ /dev/null @@ -1,2 +0,0 @@ -init -idle diff --git a/ci/expected/init.run b/ci/expected/init.run deleted file mode 100644 index b1b7161..0000000 --- a/ci/expected/init.run +++ /dev/null @@ -1 +0,0 @@ -init diff --git a/ci/expected/locals.run b/ci/expected/locals.run deleted file mode 100644 index 4f1d350..0000000 --- a/ci/expected/locals.run +++ /dev/null @@ -1,3 +0,0 @@ -bar: local_to_bar = 1 -foo: local_to_foo = 1 -idle: local_to_idle = 1 diff --git a/ci/expected/lock-free.run b/ci/expected/lock-free.run deleted file mode 100644 index 18de0ec..0000000 --- a/ci/expected/lock-free.run +++ /dev/null @@ -1,2 +0,0 @@ - foo = 1 - bar = 2 diff --git a/ci/expected/lock.run b/ci/expected/lock.run deleted file mode 100644 index a987b37..0000000 --- a/ci/expected/lock.run +++ /dev/null @@ -1,5 +0,0 @@ -A -B - shared = 1 -C -D - shared = 2 -E diff --git a/ci/expected/message.run b/ci/expected/message.run deleted file mode 100644 index 11814db..0000000 --- a/ci/expected/message.run +++ /dev/null @@ -1,6 +0,0 @@ -foo -bar(0) -baz(1, 2) -foo -bar(1) -baz(2, 3) diff --git a/ci/expected/message_passing.run b/ci/expected/message_passing.run deleted file mode 100644 index a1448d8..0000000 --- a/ci/expected/message_passing.run +++ /dev/null @@ -1,3 +0,0 @@ -foo 1, 1 -foo 1, 2 -foo 2, 3 diff --git a/ci/expected/multilock.run b/ci/expected/multilock.run deleted file mode 100644 index dd8c1f2..0000000 --- a/ci/expected/multilock.run +++ /dev/null @@ -1 +0,0 @@ -Multiple locks, s1: 1, s2: 1, s3: 1 diff --git a/ci/expected/not-sync.run b/ci/expected/not-sync.run deleted file mode 100644 index cd91476..0000000 --- a/ci/expected/not-sync.run +++ /dev/null @@ -1,3 +0,0 @@ -init -bar a 13 -foo a 13 diff --git a/ci/expected/only-shared-access.run b/ci/expected/only-shared-access.run deleted file mode 100644 index dcc73e6..0000000 --- a/ci/expected/only-shared-access.run +++ /dev/null @@ -1,2 +0,0 @@ -bar(key = 0xdeadbeef) -foo(key = 0xdeadbeef) diff --git a/ci/expected/periodic-at.run b/ci/expected/periodic-at.run deleted file mode 100644 index bf5bb06..0000000 --- a/ci/expected/periodic-at.run +++ /dev/null @@ -1,4 +0,0 @@ -foo Instant { ticks: 0 } -foo Instant { ticks: 10 } -foo Instant { ticks: 20 } -foo Instant { ticks: 30 } diff --git a/ci/expected/periodic-at2.run b/ci/expected/periodic-at2.run deleted file mode 100644 index 6e56421..0000000 --- a/ci/expected/periodic-at2.run +++ /dev/null @@ -1,7 +0,0 @@ -foo Instant { ticks: 0 } -bar Instant { ticks: 10 } -foo Instant { ticks: 30 } -bar Instant { ticks: 40 } -foo Instant { ticks: 60 } -bar Instant { ticks: 70 } -foo Instant { ticks: 90 } diff --git a/ci/expected/periodic.run b/ci/expected/periodic.run deleted file mode 100644 index a1f8944..0000000 --- a/ci/expected/periodic.run +++ /dev/null @@ -1,4 +0,0 @@ -foo -foo -foo -foo diff --git a/ci/expected/peripherals-taken.run b/ci/expected/peripherals-taken.run deleted file mode 100644 index e69de29..0000000 diff --git a/ci/expected/pool.run b/ci/expected/pool.run deleted file mode 100644 index e69de29..0000000 diff --git a/ci/expected/preempt.run b/ci/expected/preempt.run deleted file mode 100644 index 932b2b3..0000000 --- a/ci/expected/preempt.run +++ /dev/null @@ -1,5 +0,0 @@ -foo - start - baz - start - baz - end - bar -foo - end diff --git a/ci/expected/ramfunc.run b/ci/expected/ramfunc.run deleted file mode 100644 index 257cc56..0000000 --- a/ci/expected/ramfunc.run +++ /dev/null @@ -1 +0,0 @@ -foo diff --git a/ci/expected/ramfunc.run.grep.bar b/ci/expected/ramfunc.run.grep.bar deleted file mode 100644 index 33e002f..0000000 --- a/ci/expected/ramfunc.run.grep.bar +++ /dev/null @@ -1 +0,0 @@ -20000000 t ramfunc::bar::h9d6714fe5a3b0c89 diff --git a/ci/expected/ramfunc.run.grep.foo b/ci/expected/ramfunc.run.grep.foo deleted file mode 100644 index 44e8822..0000000 --- a/ci/expected/ramfunc.run.grep.foo +++ /dev/null @@ -1 +0,0 @@ -00000162 t ramfunc::foo::h30e7789b08c08e19 diff --git a/ci/expected/resource-user-struct.run b/ci/expected/resource-user-struct.run deleted file mode 100644 index a587a94..0000000 --- a/ci/expected/resource-user-struct.run +++ /dev/null @@ -1,2 +0,0 @@ -UART0: shared = 1 -UART1: shared = 2 diff --git a/ci/expected/schedule.run b/ci/expected/schedule.run deleted file mode 100644 index 1dbd445..0000000 --- a/ci/expected/schedule.run +++ /dev/null @@ -1,4 +0,0 @@ -init -foo -bar -baz diff --git a/ci/expected/shared.run b/ci/expected/shared.run deleted file mode 100644 index 6d3d3e4..0000000 --- a/ci/expected/shared.run +++ /dev/null @@ -1 +0,0 @@ -received message: 42 diff --git a/ci/expected/smallest.run b/ci/expected/smallest.run deleted file mode 100644 index e69de29..0000000 diff --git a/ci/expected/spawn.run b/ci/expected/spawn.run deleted file mode 100644 index 240cd18..0000000 --- a/ci/expected/spawn.run +++ /dev/null @@ -1,2 +0,0 @@ -init -foo diff --git a/ci/expected/static.run b/ci/expected/static.run deleted file mode 100644 index 3d3f46f..0000000 --- a/ci/expected/static.run +++ /dev/null @@ -1,3 +0,0 @@ -received message: 1 -received message: 2 -received message: 3 diff --git a/ci/expected/t-binds.run b/ci/expected/t-binds.run deleted file mode 100644 index e69de29..0000000 diff --git a/ci/expected/t-cfg-resources.run b/ci/expected/t-cfg-resources.run deleted file mode 100644 index e69de29..0000000 diff --git a/ci/expected/t-htask-main.run b/ci/expected/t-htask-main.run deleted file mode 100644 index e69de29..0000000 diff --git a/ci/expected/t-idle-main.run b/ci/expected/t-idle-main.run deleted file mode 100644 index e69de29..0000000 diff --git a/ci/expected/t-late-not-send.run b/ci/expected/t-late-not-send.run deleted file mode 100644 index e69de29..0000000 diff --git a/ci/expected/t-schedule.run b/ci/expected/t-schedule.run deleted file mode 100644 index e69de29..0000000 diff --git a/ci/expected/t-spawn.run b/ci/expected/t-spawn.run deleted file mode 100644 index e69de29..0000000 diff --git a/ci/expected/task.run b/ci/expected/task.run deleted file mode 100644 index de45dce..0000000 --- a/ci/expected/task.run +++ /dev/null @@ -1,5 +0,0 @@ -foo - start -foo - middle -baz -foo - end -bar diff --git a/ci/expected/zero-prio-task.run b/ci/expected/zero-prio-task.run deleted file mode 100644 index 123b0f2..0000000 --- a/ci/expected/zero-prio-task.run +++ /dev/null @@ -1,3 +0,0 @@ -init -hello from async -hello from async2 diff --git a/examples/async-delay.no_rs b/examples/async-delay.no_rs deleted file mode 100644 index fb478c3..0000000 --- a/examples/async-delay.no_rs +++ /dev/null @@ -1,63 +0,0 @@ -#![no_main] -#![no_std] -#![feature(type_alias_impl_trait)] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0, UART0], peripherals = true)] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - use systick_monotonic::*; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[monotonic(binds = SysTick, default = true)] - type MyMono = Systick<100>; - - #[init] - fn init(cx: init::Context) -> (Shared, Local) { - hprintln!("init").unwrap(); - - foo::spawn().ok(); - bar::spawn().ok(); - baz::spawn().ok(); - - (Shared {}, Local {}) - } - - #[idle] - fn idle(_: idle::Context) -> ! { - // debug::exit(debug::EXIT_SUCCESS); - loop { - // hprintln!("idle"); - cortex_m::asm::wfi(); // put the MCU in sleep mode until interrupt occurs - } - } - - #[task] - async fn foo(_cx: foo::Context) { - hprintln!("hello from foo").ok(); - monotonics::delay(100.millis()).await; - hprintln!("bye from foo").ok(); - } - - #[task] - async fn bar(_cx: bar::Context) { - hprintln!("hello from bar").ok(); - monotonics::delay(200.millis()).await; - hprintln!("bye from bar").ok(); - } - - #[task] - async fn baz(_cx: baz::Context) { - hprintln!("hello from baz").ok(); - monotonics::delay(300.millis()).await; - hprintln!("bye from baz").ok(); - - debug::exit(debug::EXIT_SUCCESS); - } -} diff --git a/examples/async-infinite-loop.no_rs b/examples/async-infinite-loop.no_rs deleted file mode 100644 index a95f998..0000000 --- a/examples/async-infinite-loop.no_rs +++ /dev/null @@ -1,53 +0,0 @@ -#![no_main] -#![no_std] -#![feature(type_alias_impl_trait)] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0, UART0], peripherals = true)] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - use systick_monotonic::*; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[monotonic(binds = SysTick, default = true)] - type MyMono = Systick<100>; - - #[init] - fn init(cx: init::Context) -> (Shared, Local) { - hprintln!("init").unwrap(); - - foo::spawn().ok(); - - (Shared {}, Local {}) - } - - #[idle] - fn idle(_: idle::Context) -> ! { - loop { - cortex_m::asm::wfi(); // put the MCU in sleep mode until interrupt occurs - } - } - - // Infinite loops are not allowed in RTIC, however in async tasks they are - if there is an - // await inside the loop. - #[task] - async fn foo(_cx: foo::Context) { - let mut i = 0; - loop { - if i == 5 { - debug::exit(debug::EXIT_SUCCESS); - } - - hprintln!("hello from async {}", i).ok(); - monotonics::delay(100.millis()).await; // This makes it okey! - - i += 1; - } - } -} diff --git a/examples/async-task-multiple-prios.rs b/examples/async-task-multiple-prios.rs deleted file mode 100644 index 5c9674d..0000000 --- a/examples/async-task-multiple-prios.rs +++ /dev/null @@ -1,92 +0,0 @@ -//! examples/async-task-multiple-prios.rs - -#![no_main] -#![no_std] -#![feature(type_alias_impl_trait)] -#![deny(missing_docs)] - -use panic_semihosting as _; - -// NOTES: -// -// - Async tasks cannot have `#[lock_free]` resources, as they can interleave and each async -// task can have a mutable reference stored. -// - Spawning an async task equates to it being polled once. - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0, QEI0])] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - - #[shared] - struct Shared { - a: u32, - b: u32, - } - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) { - hprintln!("init"); - - async_task1::spawn().ok(); - async_task2::spawn().ok(); - async_task3::spawn().ok(); - async_task4::spawn().ok(); - - (Shared { a: 0, b: 0 }, Local {}) - } - - #[idle] - fn idle(_: idle::Context) -> ! { - loop { - hprintln!("idle"); - debug::exit(debug::EXIT_SUCCESS); - } - } - - #[task(priority = 1, shared = [a, b])] - async fn async_task1(mut cx: async_task1::Context) { - hprintln!( - "hello from async 1 a {}", - cx.shared.a.lock(|a| { - *a += 1; - *a - }) - ); - } - - #[task(priority = 1, shared = [a, b])] - async fn async_task2(mut cx: async_task2::Context) { - hprintln!( - "hello from async 2 a {}", - cx.shared.a.lock(|a| { - *a += 1; - *a - }) - ); - } - - #[task(priority = 2, shared = [a, b])] - async fn async_task3(mut cx: async_task3::Context) { - hprintln!( - "hello from async 3 a {}", - cx.shared.a.lock(|a| { - *a += 1; - *a - }) - ); - } - - #[task(priority = 2, shared = [a, b])] - async fn async_task4(mut cx: async_task4::Context) { - hprintln!( - "hello from async 4 a {}", - cx.shared.a.lock(|a| { - *a += 1; - *a - }) - ); - } -} diff --git a/examples/async-task.rs b/examples/async-task.rs deleted file mode 100644 index 7730c54..0000000 --- a/examples/async-task.rs +++ /dev/null @@ -1,70 +0,0 @@ -//! examples/async-task.rs - -#![no_main] -#![no_std] -#![feature(type_alias_impl_trait)] -#![deny(missing_docs)] - -use panic_semihosting as _; - -// NOTES: -// -// - Async tasks cannot have `#[lock_free]` resources, as they can interleave and each async -// task can have a mutable reference stored. -// - Spawning an async task equates to it being polled once. - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0, UART0], peripherals = true)] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - - #[shared] - struct Shared { - a: u32, - } - - #[local] - struct Local {} - - #[init] - fn init(_cx: init::Context) -> (Shared, Local) { - hprintln!("init"); - - async_task::spawn().unwrap(); - async_task_args::spawn(1, 2).unwrap(); - async_task2::spawn().unwrap(); - - (Shared { a: 0 }, Local {}) - } - - #[idle(shared = [a])] - fn idle(_: idle::Context) -> ! { - loop { - hprintln!("idle"); - debug::exit(debug::EXIT_SUCCESS); - cortex_m::asm::wfi(); // put the MCU in sleep mode until interrupt occurs - } - } - - #[task(binds = UART1, shared = [a])] - fn hw_task(cx: hw_task::Context) { - let hw_task::SharedResources { a: _, .. } = cx.shared; - hprintln!("hello from hw"); - } - - #[task(shared = [a])] - async fn async_task(cx: async_task::Context) { - let async_task::SharedResources { a: _, .. } = cx.shared; - hprintln!("hello from async"); - } - - #[task] - async fn async_task_args(_cx: async_task_args::Context, a: u32, b: i32) { - hprintln!("hello from async with args a: {}, b: {}", a, b); - } - - #[task(priority = 2, shared = [a])] - async fn async_task2(cx: async_task2::Context) { - let async_task2::SharedResources { a: _, .. } = cx.shared; - hprintln!("hello from async2"); - } -} diff --git a/examples/async-timeout.no_rs b/examples/async-timeout.no_rs deleted file mode 100644 index 3f68df7..0000000 --- a/examples/async-timeout.no_rs +++ /dev/null @@ -1,87 +0,0 @@ -#![no_main] -#![no_std] -#![feature(type_alias_impl_trait)] - -use panic_semihosting as _; - -// NOTES: -// -// - Async tasks cannot have `#[lock_free]` resources, as they can interleve and each async -// task can have a mutable reference stored. -// - Spawning an async task equates to it being polled once. - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0, UART0], peripherals = true)] -mod app { - use core::{ - future::Future, - pin::Pin, - task::{Context, Poll}, - }; - use cortex_m_semihosting::{debug, hprintln}; - use systick_monotonic::*; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[monotonic(binds = SysTick, default = true)] - type MyMono = Systick<100>; - - #[init] - fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { - hprintln!("init").unwrap(); - - foo::spawn().ok(); - bar::spawn().ok(); - - ( - Shared {}, - Local {}, - init::Monotonics(Systick::new(cx.core.SYST, 12_000_000)), - ) - } - - #[idle] - fn idle(_: idle::Context) -> ! { - loop { - cortex_m::asm::wfi(); // put the MCU in sleep mode until interrupt occurs - } - } - - #[task] - async fn foo(_cx: foo::Context) { - hprintln!("hello from foo").ok(); - - // This will not timeout - match monotonics::timeout_after(monotonics::delay(100.millis()), 200.millis()).await { - Ok(_) => hprintln!("foo no timeout").ok(), - Err(_) => hprintln!("foo timeout").ok(), - }; - } - - #[task] - async fn bar(_cx: bar::Context) { - hprintln!("hello from bar").ok(); - - // This will timeout - match monotonics::timeout_after(NeverEndingFuture {}, 300.millis()).await { - Ok(_) => hprintln!("bar no timeout").ok(), - Err(_) => hprintln!("bar timeout").ok(), - }; - - debug::exit(debug::EXIT_SUCCESS); - } - - pub struct NeverEndingFuture {} - - impl Future for NeverEndingFuture { - type Output = (); - - fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll { - // Never finish - Poll::Pending - } - } -} diff --git a/examples/big-struct-opt.rs b/examples/big-struct-opt.rs deleted file mode 100644 index 408a2de..0000000 --- a/examples/big-struct-opt.rs +++ /dev/null @@ -1,80 +0,0 @@ -//! examples/big-struct-opt.rs -//! -//! Example on how to initialize a large struct without needing to copy it via `LateResources`, -//! effectively saving stack space needed for the copies. - -#![no_main] -#![no_std] -#![feature(type_alias_impl_trait)] -#![deny(missing_docs)] - -use panic_semihosting as _; - -/// Some big struct -pub struct BigStruct { - /// Big content - pub data: [u8; 2048], -} - -impl BigStruct { - fn new() -> Self { - BigStruct { data: [22; 2048] } - } -} - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] -mod app { - use super::BigStruct; - use core::mem::MaybeUninit; - use cortex_m_semihosting::{debug, hprintln}; - use lm3s6965::Interrupt; - - #[shared] - struct Shared { - big_struct: &'static mut BigStruct, - } - - #[local] - struct Local {} - - #[init(local = [bs: MaybeUninit = MaybeUninit::uninit()])] - fn init(cx: init::Context) -> (Shared, Local) { - let big_struct = unsafe { - // write directly into the static storage - cx.local.bs.as_mut_ptr().write(BigStruct::new()); - &mut *cx.local.bs.as_mut_ptr() - }; - - rtic::pend(Interrupt::UART0); - async_task::spawn().unwrap(); - ( - Shared { - // assign the reference so we can use the resource - big_struct, - }, - Local {}, - ) - } - - #[idle] - fn idle(_: idle::Context) -> ! { - loop { - hprintln!("idle"); - debug::exit(debug::EXIT_SUCCESS); - } - } - - #[task(binds = UART0, shared = [big_struct])] - fn uart0(mut cx: uart0::Context) { - cx.shared - .big_struct - .lock(|b| hprintln!("uart0 data:{:?}", &b.data[0..5])); - } - - #[task(shared = [big_struct], priority = 2)] - async fn async_task(mut cx: async_task::Context) { - cx.shared - .big_struct - .lock(|b| hprintln!("async_task data:{:?}", &b.data[0..5])); - } -} diff --git a/examples/binds.rs b/examples/binds.rs deleted file mode 100644 index cf078ff..0000000 --- a/examples/binds.rs +++ /dev/null @@ -1,54 +0,0 @@ -//! examples/binds.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] -#![deny(missing_docs)] - -use panic_semihosting as _; - -// `examples/interrupt.rs` rewritten to use `binds` -#[rtic::app(device = lm3s6965)] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - use lm3s6965::Interrupt; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) { - rtic::pend(Interrupt::UART0); - - hprintln!("init"); - - (Shared {}, Local {}) - } - - #[idle] - fn idle(_: idle::Context) -> ! { - hprintln!("idle"); - - rtic::pend(Interrupt::UART0); - - loop { - cortex_m::asm::nop(); - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } - } - - #[task(binds = UART0, local = [times: u32 = 0])] - fn foo(cx: foo::Context) { - *cx.local.times += 1; - - hprintln!( - "foo called {} time{}", - *cx.local.times, - if *cx.local.times > 1 { "s" } else { "" } - ); - } -} diff --git a/examples/cancel-reschedule.no_rs b/examples/cancel-reschedule.no_rs deleted file mode 100644 index a38a9c4..0000000 --- a/examples/cancel-reschedule.no_rs +++ /dev/null @@ -1,73 +0,0 @@ -//! examples/cancel-reschedule.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - use systick_monotonic::*; - - #[monotonic(binds = SysTick, default = true)] - type MyMono = Systick<100>; // 100 Hz / 10 ms granularity - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { - let systick = cx.core.SYST; - - // Initialize the monotonic (SysTick rate in QEMU is 12 MHz) - let mono = Systick::new(systick, 12_000_000); - - hprintln!("init").ok(); - - // Schedule `foo` to run 1 second in the future - foo::spawn_after(1.secs()).unwrap(); - - ( - Shared {}, - Local {}, - init::Monotonics(mono), // Give the monotonic to RTIC - ) - } - - #[task] - fn foo(_: foo::Context) { - hprintln!("foo").ok(); - - // Schedule `bar` to run 2 seconds in the future (1 second after foo runs) - let spawn_handle = baz::spawn_after(2.secs()).unwrap(); - bar::spawn_after(1.secs(), spawn_handle, false).unwrap(); // Change to true - } - - #[task] - fn bar(_: bar::Context, baz_handle: baz::SpawnHandle, do_reschedule: bool) { - hprintln!("bar").ok(); - - if do_reschedule { - // Reschedule baz 2 seconds from now, instead of the original 1 second - // from now. - baz_handle.reschedule_after(2.secs()).unwrap(); - // Or baz_handle.reschedule_at(/* time */) - } else { - // Or cancel it - baz_handle.cancel().unwrap(); - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } - } - - #[task] - fn baz(_: baz::Context) { - hprintln!("baz").ok(); - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } -} diff --git a/examples/capacity.no_rs b/examples/capacity.no_rs deleted file mode 100644 index a617269..0000000 --- a/examples/capacity.no_rs +++ /dev/null @@ -1,49 +0,0 @@ -//! examples/capacity.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - use lm3s6965::Interrupt; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { - rtic::pend(Interrupt::UART0); - - (Shared {}, Local {}, init::Monotonics()) - } - - #[task(binds = UART0)] - fn uart0(_: uart0::Context) { - foo::spawn(0).unwrap(); - foo::spawn(1).unwrap(); - foo::spawn(2).unwrap(); - foo::spawn(3).unwrap(); - - bar::spawn().unwrap(); - } - - #[task(capacity = 4)] - fn foo(_: foo::Context, x: u32) { - hprintln!("foo({})", x).unwrap(); - } - - #[task] - fn bar(_: bar::Context) { - hprintln!("bar").unwrap(); - - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } -} diff --git a/examples/cfg-monotonic.rs b/examples/cfg-monotonic.rs deleted file mode 100644 index 88c0d6f..0000000 --- a/examples/cfg-monotonic.rs +++ /dev/null @@ -1,121 +0,0 @@ -//! examples/cfg-monotonic.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![deny(missing_docs)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0, QEI0])] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - use systick_monotonic::*; // Implements the `Monotonic` trait - - // A monotonic timer to enable scheduling in RTIC - #[cfg(feature = "killmono")] - #[monotonic(binds = SysTick, default = true)] - type MyMono = Systick<100>; // 100 Hz / 10 ms granularity - - // Not allowed by current rtic-syntax: - // error: `#[monotonic(...)]` on a specific type must appear at most once - // --> examples/cfg-monotonic.rs:23:10 - // | - // 23 | type MyMono = Systick<100>; // 100 Hz / 10 ms granularity - // | ^^^^^^ - // #[monotonic(binds = SysTick, default = true)] - // type MyMono = Systick<100>; // 100 Hz / 10 ms granularity - - // Not allowed by current rtic-syntax: - // error: this interrupt is already bound - // --> examples/cfg-monotonic.rs:31:25 - // | - // 31 | #[monotonic(binds = SysTick, default = true)] - // | ^^^^^^^ - // #[monotonic(binds = SysTick, default = true)] - // type MyMono2 = DwtSystick<100>; // 100 Hz / 10 ms granularity - - // Resources shared between tasks - #[shared] - struct Shared { - s1: u32, - s2: i32, - } - - // Local resources to specific tasks (cannot be shared) - #[local] - struct Local { - l1: u8, - l2: i8, - } - - #[init] - fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { - let _systick = cx.core.SYST; - - // Initialize the monotonic (SysTick rate in QEMU is 12 MHz) - #[cfg(feature = "killmono")] - let mono = Systick::new(systick, 12_000_000); - - // Spawn the task `foo` directly after `init` finishes - foo::spawn().unwrap(); - - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - - ( - // Initialization of shared resources - Shared { s1: 0, s2: 1 }, - // Initialization of task local resources - Local { l1: 2, l2: 3 }, - // Move the monotonic timer to the RTIC run-time, this enables - // scheduling - #[cfg(feature = "killmono")] - init::Monotonics(mono), - init::Monotonics(), - ) - } - - // Background task, runs whenever no other tasks are running - #[idle] - fn idle(_: idle::Context) -> ! { - loop { - continue; - } - } - - // Software task, not bound to a hardware interrupt. - // This task takes the task local resource `l1` - // The resources `s1` and `s2` are shared between all other tasks. - #[task(shared = [s1, s2], local = [l1])] - fn foo(_: foo::Context) { - // This task is only spawned once in `init`, hence this task will run - // only once - - hprintln!("foo"); - } - - // Software task, also not bound to a hardware interrupt - // This task takes the task local resource `l2` - // The resources `s1` and `s2` are shared between all other tasks. - #[task(shared = [s1, s2], local = [l2])] - fn bar(_: bar::Context) { - hprintln!("bar"); - - // Run `bar` once per second - // bar::spawn_after(1.secs()).unwrap(); - } - - // Hardware task, bound to a hardware interrupt - // The resources `s1` and `s2` are shared between all other tasks. - #[task(binds = UART0, priority = 3, shared = [s1, s2])] - fn uart0_interrupt(_: uart0_interrupt::Context) { - // This task is bound to the interrupt `UART0` and will run - // whenever the interrupt fires - - // Note that RTIC does NOT clear the interrupt flag, this is up to the - // user - - hprintln!("UART0 interrupt!"); - } -} diff --git a/examples/cfg-whole-task.no_rs b/examples/cfg-whole-task.no_rs deleted file mode 100644 index f41866d..0000000 --- a/examples/cfg-whole-task.no_rs +++ /dev/null @@ -1,94 +0,0 @@ -//! examples/cfg-whole-task.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0, QEI0])] -mod app { - use cortex_m_semihosting::debug; - #[cfg(debug_assertions)] - use cortex_m_semihosting::hprintln; - - #[shared] - struct Shared { - count: u32, - #[cfg(never)] - unused: u32, - } - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { - foo::spawn().unwrap(); - foo::spawn().unwrap(); - - ( - Shared { - count: 0, - #[cfg(never)] - unused: 1, - }, - Local {}, - init::Monotonics(), - ) - } - - #[idle] - fn idle(_: idle::Context) -> ! { - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - - loop { - cortex_m::asm::nop(); - } - } - - #[task(capacity = 2, shared = [count])] - fn foo(mut _cx: foo::Context) { - #[cfg(debug_assertions)] - { - _cx.shared.count.lock(|count| *count += 1); - - log::spawn(_cx.shared.count.lock(|count| *count)).unwrap(); - } - - // this wouldn't compile in `release` mode - // *_cx.shared.count += 1; - - // .. - } - - // The whole task should disappear, - // currently still present in the Tasks enum - #[cfg(never)] - #[task(capacity = 2, shared = [count])] - fn foo2(mut _cx: foo2::Context) { - #[cfg(debug_assertions)] - { - _cx.shared.count.lock(|count| *count += 10); - - log::spawn(_cx.shared.count.lock(|count| *count)).unwrap(); - } - - // this wouldn't compile in `release` mode - // *_cx.shared.count += 1; - - // .. - } - - #[cfg(debug_assertions)] - #[task(capacity = 2)] - fn log(_: log::Context, n: u32) { - hprintln!( - "foo has been called {} time{}", - n, - if n == 1 { "" } else { "s" } - ) - .ok(); - } -} diff --git a/examples/common.no_rs b/examples/common.no_rs deleted file mode 100644 index 1fe671e..0000000 --- a/examples/common.no_rs +++ /dev/null @@ -1,102 +0,0 @@ -//! examples/common.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0, QEI0])] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - use systick_monotonic::*; // Implements the `Monotonic` trait - - // A monotonic timer to enable scheduling in RTIC - #[monotonic(binds = SysTick, default = true)] - type MyMono = Systick<100>; // 100 Hz / 10 ms granularity - - // Resources shared between tasks - #[shared] - struct Shared { - s1: u32, - s2: i32, - } - - // Local resources to specific tasks (cannot be shared) - #[local] - struct Local { - l1: u8, - l2: i8, - } - - #[init] - fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { - let systick = cx.core.SYST; - - // Initialize the monotonic (SysTick rate in QEMU is 12 MHz) - let mono = Systick::new(systick, 12_000_000); - - // Spawn the task `foo` directly after `init` finishes - foo::spawn().unwrap(); - - // Spawn the task `bar` 1 second after `init` finishes, this is enabled - // by the `#[monotonic(..)]` above - bar::spawn_after(1.secs()).unwrap(); - - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - - ( - // Initialization of shared resources - Shared { s1: 0, s2: 1 }, - // Initialization of task local resources - Local { l1: 2, l2: 3 }, - // Move the monotonic timer to the RTIC run-time, this enables - // scheduling - init::Monotonics(mono), - ) - } - - // Background task, runs whenever no other tasks are running - #[idle] - fn idle(_: idle::Context) -> ! { - loop { - continue; - } - } - - // Software task, not bound to a hardware interrupt. - // This task takes the task local resource `l1` - // The resources `s1` and `s2` are shared between all other tasks. - #[task(shared = [s1, s2], local = [l1])] - fn foo(_: foo::Context) { - // This task is only spawned once in `init`, hence this task will run - // only once - - hprintln!("foo").ok(); - } - - // Software task, also not bound to a hardware interrupt - // This task takes the task local resource `l2` - // The resources `s1` and `s2` are shared between all other tasks. - #[task(shared = [s1, s2], local = [l2])] - fn bar(_: bar::Context) { - hprintln!("bar").ok(); - - // Run `bar` once per second - bar::spawn_after(1.secs()).unwrap(); - } - - // Hardware task, bound to a hardware interrupt - // The resources `s1` and `s2` are shared between all other tasks. - #[task(binds = UART0, priority = 3, shared = [s1, s2])] - fn uart0_interrupt(_: uart0_interrupt::Context) { - // This task is bound to the interrupt `UART0` and will run - // whenever the interrupt fires - - // Note that RTIC does NOT clear the interrupt flag, this is up to the - // user - - hprintln!("UART0 interrupt!").ok(); - } -} diff --git a/examples/complex.rs b/examples/complex.rs deleted file mode 100644 index c1e9c6c..0000000 --- a/examples/complex.rs +++ /dev/null @@ -1,129 +0,0 @@ -//! examples/complex.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![deny(missing_docs)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965)] -mod app { - - use cortex_m_semihosting::{debug, hprintln}; - use lm3s6965::Interrupt; - - #[shared] - struct Shared { - s2: u32, // shared with ceiling 2 - s3: u32, // shared with ceiling 3 - s4: u32, // shared with ceiling 4 - } - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) { - hprintln!("init"); - - ( - Shared { - s2: 0, - s3: 0, - s4: 0, - }, - Local {}, - ) - } - - #[idle(shared = [s2, s3])] - fn idle(mut cx: idle::Context) -> ! { - hprintln!("idle p0 started"); - rtic::pend(Interrupt::GPIOC); - cx.shared.s3.lock(|s| { - hprintln!("idle enter lock s3 {}", s); - hprintln!("idle pend t0"); - rtic::pend(Interrupt::GPIOA); // t0 p2, with shared ceiling 3 - hprintln!("idle pend t1"); - rtic::pend(Interrupt::GPIOB); // t1 p3, with shared ceiling 3 - hprintln!("idle pend t2"); - rtic::pend(Interrupt::GPIOC); // t2 p4, no sharing - hprintln!("idle still in lock s3 {}", s); - }); - hprintln!("\nback in idle"); - - cx.shared.s2.lock(|s| { - hprintln!("enter lock s2 {}", s); - hprintln!("idle pend t0"); - rtic::pend(Interrupt::GPIOA); // t0 p2, with shared ceiling 2 - hprintln!("idle pend t1"); - rtic::pend(Interrupt::GPIOB); // t1 p3, no sharing - hprintln!("idle pend t2"); - rtic::pend(Interrupt::GPIOC); // t2 p4, no sharing - hprintln!("idle still in lock s2 {}", s); - }); - hprintln!("\nidle exit"); - - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - - loop { - cortex_m::asm::nop(); - } - } - - #[task(binds = GPIOA, priority = 2, local = [times: u32 = 0], shared = [s2, s3])] - fn t0(cx: t0::Context) { - // Safe access to local `static mut` variable - *cx.local.times += 1; - - hprintln!( - "t0 p2 called {} time{}", - *cx.local.times, - if *cx.local.times > 1 { "s" } else { "" } - ); - hprintln!("t0 p2 exit"); - } - - #[task(binds = GPIOB, priority = 3, local = [times: u32 = 0], shared = [s3, s4])] - fn t1(mut cx: t1::Context) { - // Safe access to local `static mut` variable - *cx.local.times += 1; - - hprintln!( - "t1 p3 called {} time{}", - *cx.local.times, - if *cx.local.times > 1 { "s" } else { "" } - ); - - cx.shared.s4.lock(|s| { - hprintln!("t1 enter lock s4 {}", s); - hprintln!("t1 pend t0"); - rtic::pend(Interrupt::GPIOA); // t0 p2, with shared ceiling 2 - hprintln!("t1 pend t2"); - rtic::pend(Interrupt::GPIOC); // t2 p4, no sharing - hprintln!("t1 still in lock s4 {}", s); - }); - - hprintln!("t1 p3 exit"); - } - - #[task(binds = GPIOC, priority = 4, local = [times: u32 = 0], shared = [s4])] - fn t2(mut cx: t2::Context) { - // Safe access to local `static mut` variable - *cx.local.times += 1; - - hprintln!( - "t2 p4 called {} time{}", - *cx.local.times, - if *cx.local.times > 1 { "s" } else { "" } - ); - - cx.shared.s4.lock(|s| { - hprintln!("enter lock s4 {}", s); - *s += 1; - }); - hprintln!("t3 p4 exit"); - } -} diff --git a/examples/declared_locals.rs b/examples/declared_locals.rs deleted file mode 100644 index c845191..0000000 --- a/examples/declared_locals.rs +++ /dev/null @@ -1,47 +0,0 @@ -//! examples/declared_locals.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![deny(missing_docs)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965)] -mod app { - use cortex_m_semihosting::debug; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init(local = [a: u32 = 0])] - fn init(cx: init::Context) -> (Shared, Local) { - // Locals in `#[init]` have 'static lifetime - let _a: &'static mut u32 = cx.local.a; - - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - - (Shared {}, Local {}) - } - - #[idle(local = [a: u32 = 0])] - fn idle(cx: idle::Context) -> ! { - // Locals in `#[idle]` have 'static lifetime - let _a: &'static mut u32 = cx.local.a; - - loop {} - } - - #[task(binds = UART0, local = [a: u32 = 0])] - fn foo(cx: foo::Context) { - // Locals in `#[task]`s have a local lifetime - let _a: &mut u32 = cx.local.a; - - // error: explicit lifetime required in the type of `cx` - // let _a: &'static mut u32 = cx.local.a; - } -} diff --git a/examples/destructure.rs b/examples/destructure.rs deleted file mode 100644 index 81eff3b..0000000 --- a/examples/destructure.rs +++ /dev/null @@ -1,57 +0,0 @@ -//! examples/destructure.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![deny(missing_docs)] -#![no_main] -#![no_std] -#![feature(type_alias_impl_trait)] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [UART0])] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - - #[shared] - struct Shared { - a: u32, - b: u32, - c: u32, - } - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) { - foo::spawn().unwrap(); - bar::spawn().unwrap(); - - (Shared { a: 0, b: 1, c: 2 }, Local {}) - } - - #[idle] - fn idle(_: idle::Context) -> ! { - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - loop {} - } - - // Direct destructure - #[task(shared = [&a, &b, &c])] - async fn foo(cx: foo::Context) { - let a = cx.shared.a; - let b = cx.shared.b; - let c = cx.shared.c; - - hprintln!("foo: a = {}, b = {}, c = {}", a, b, c); - } - - // De-structure-ing syntax - #[task(shared = [&a, &b, &c])] - async fn bar(cx: bar::Context) { - let bar::SharedResources { a, b, c, .. } = cx.shared; - - hprintln!("bar: a = {}, b = {}, c = {}", a, b, c); - } -} diff --git a/examples/extern_binds.rs b/examples/extern_binds.rs deleted file mode 100644 index 142a11d..0000000 --- a/examples/extern_binds.rs +++ /dev/null @@ -1,54 +0,0 @@ -//! examples/extern_binds.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![deny(missing_docs)] -#![no_main] -#![no_std] - -use cortex_m_semihosting::hprintln; -use panic_semihosting as _; - -// Free function implementing the interrupt bound task `foo`. -fn foo(_: app::foo::Context) { - hprintln!("foo called"); -} - -#[rtic::app(device = lm3s6965)] -mod app { - use crate::foo; - use cortex_m_semihosting::{debug, hprintln}; - use lm3s6965::Interrupt; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) { - rtic::pend(Interrupt::UART0); - - hprintln!("init"); - - (Shared {}, Local {}) - } - - #[idle] - fn idle(_: idle::Context) -> ! { - hprintln!("idle"); - - rtic::pend(Interrupt::UART0); - - loop { - cortex_m::asm::nop(); - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } - } - - extern "Rust" { - #[task(binds = UART0)] - fn foo(_: foo::Context); - } -} diff --git a/examples/extern_spawn.rs b/examples/extern_spawn.rs deleted file mode 100644 index b2b95b9..0000000 --- a/examples/extern_spawn.rs +++ /dev/null @@ -1,41 +0,0 @@ -//! examples/extern_spawn.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![deny(missing_docs)] -#![no_main] -#![no_std] -#![feature(type_alias_impl_trait)] - -use cortex_m_semihosting::{debug, hprintln}; -use panic_semihosting as _; - -// Free function implementing the spawnable task `foo`. -// Notice, you need to indicate an anonymous lifetime <'a_> -async fn foo(_c: app::foo::Context<'_>) { - hprintln!("foo"); - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator -} - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] -mod app { - use crate::foo; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) { - foo::spawn().unwrap(); - - (Shared {}, Local {}) - } - - extern "Rust" { - #[task()] - async fn foo(_c: foo::Context); - } -} diff --git a/examples/generics.rs b/examples/generics.rs deleted file mode 100644 index 2f23cce..0000000 --- a/examples/generics.rs +++ /dev/null @@ -1,67 +0,0 @@ -//! examples/generics.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![deny(missing_docs)] -#![no_main] -#![no_std] - -use cortex_m_semihosting::hprintln; -use panic_semihosting as _; -use rtic::Mutex; - -#[rtic::app(device = lm3s6965)] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - use lm3s6965::Interrupt; - - #[shared] - struct Shared { - shared: u32, - } - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) { - rtic::pend(Interrupt::UART0); - rtic::pend(Interrupt::UART1); - - (Shared { shared: 0 }, Local {}) - } - - #[task(binds = UART0, shared = [shared], local = [state: u32 = 0])] - fn uart0(c: uart0::Context) { - hprintln!("UART0(STATE = {})", *c.local.state); - - // second argument has type `shared::shared` - super::advance(c.local.state, c.shared.shared); - - rtic::pend(Interrupt::UART1); - - cortex_m::asm::nop(); - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } - - #[task(binds = UART1, priority = 2, shared = [shared], local = [state: u32 = 0])] - fn uart1(c: uart1::Context) { - hprintln!("UART1(STATE = {})", *c.local.state); - - // second argument has type `shared::shared` - super::advance(c.local.state, c.shared.shared); - } -} - -// the second parameter is generic: it can be any type that implements the `Mutex` trait -fn advance(state: &mut u32, mut shared: impl Mutex) { - *state += 1; - - let (old, new) = shared.lock(|shared: &mut u32| { - let old = *shared; - *shared += *state; - (old, *shared) - }); - - hprintln!("shared: {} -> {}", old, new); -} diff --git a/examples/hardware.rs b/examples/hardware.rs deleted file mode 100644 index 62ae0d6..0000000 --- a/examples/hardware.rs +++ /dev/null @@ -1,58 +0,0 @@ -//! examples/hardware.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![deny(missing_docs)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965)] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - use lm3s6965::Interrupt; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) { - // Pends the UART0 interrupt but its handler won't run until *after* - // `init` returns because interrupts are disabled - rtic::pend(Interrupt::UART0); // equivalent to NVIC::pend - - hprintln!("init"); - - (Shared {}, Local {}) - } - - #[idle] - fn idle(_: idle::Context) -> ! { - // interrupts are enabled again; the `UART0` handler runs at this point - - hprintln!("idle"); - - rtic::pend(Interrupt::UART0); - - loop { - cortex_m::asm::nop(); - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } - } - - #[task(binds = UART0, local = [times: u32 = 0])] - fn uart0(cx: uart0::Context) { - // Safe access to local `static mut` variable - *cx.local.times += 1; - - hprintln!( - "UART0 called {} time{}", - *cx.local.times, - if *cx.local.times > 1 { "s" } else { "" } - ); - } -} diff --git a/examples/idle-wfi.rs b/examples/idle-wfi.rs deleted file mode 100644 index 8134ce3..0000000 --- a/examples/idle-wfi.rs +++ /dev/null @@ -1,48 +0,0 @@ -//! examples/idle-wfi.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![deny(missing_docs)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965)] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(mut cx: init::Context) -> (Shared, Local) { - hprintln!("init"); - - // Set the ARM SLEEPONEXIT bit to go to sleep after handling interrupts - // See https://developer.arm.com/docs/100737/0100/power-management/sleep-mode/sleep-on-exit-bit - cx.core.SCB.set_sleepdeep(); - - (Shared {}, Local {}) - } - - #[idle(local = [x: u32 = 0])] - fn idle(cx: idle::Context) -> ! { - // Locals in idle have lifetime 'static - let _x: &'static mut u32 = cx.local.x; - - hprintln!("idle"); - - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - - loop { - // Now Wait For Interrupt is used instead of a busy-wait loop - // to allow MCU to sleep between interrupts - // https://developer.arm.com/documentation/ddi0406/c/Application-Level-Architecture/Instruction-Details/Alphabetical-list-of-instructions/WFI - rtic::export::wfi() - } - } -} diff --git a/examples/idle.rs b/examples/idle.rs deleted file mode 100644 index 0c4bd04..0000000 --- a/examples/idle.rs +++ /dev/null @@ -1,41 +0,0 @@ -//! examples/idle.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![deny(missing_docs)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965)] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) { - hprintln!("init"); - - (Shared {}, Local {}) - } - - #[idle(local = [x: u32 = 0])] - fn idle(cx: idle::Context) -> ! { - // Locals in idle have lifetime 'static - let _x: &'static mut u32 = cx.local.x; - - hprintln!("idle"); - - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - - loop { - cortex_m::asm::nop(); - } - } -} diff --git a/examples/init.rs b/examples/init.rs deleted file mode 100644 index c3081bf..0000000 --- a/examples/init.rs +++ /dev/null @@ -1,42 +0,0 @@ -//! examples/init.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![deny(missing_docs)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, peripherals = true)] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init(local = [x: u32 = 0])] - fn init(cx: init::Context) -> (Shared, Local) { - // Cortex-M peripherals - let _core: cortex_m::Peripherals = cx.core; - - // Device specific peripherals - let _device: lm3s6965::Peripherals = cx.device; - - // Locals in `init` have 'static lifetime - let _x: &'static mut u32 = cx.local.x; - - // Access to the critical section token, - // to indicate that this is a critical section - let _cs_token: bare_metal::CriticalSection = cx.cs; - - hprintln!("init"); - - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - - (Shared {}, Local {}) - } -} diff --git a/examples/locals.rs b/examples/locals.rs deleted file mode 100644 index ec3d59d..0000000 --- a/examples/locals.rs +++ /dev/null @@ -1,87 +0,0 @@ -//! examples/locals.rs - -#![feature(type_alias_impl_trait)] -#![deny(unsafe_code)] -#![deny(missing_docs)] -#![deny(warnings)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [UART0, UART1])] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - - #[shared] - struct Shared {} - - #[local] - struct Local { - local_to_foo: i64, - local_to_bar: i64, - local_to_idle: i64, - } - - // `#[init]` cannot access locals from the `#[local]` struct as they are initialized here. - #[init] - fn init(_: init::Context) -> (Shared, Local) { - foo::spawn().unwrap(); - bar::spawn().unwrap(); - - ( - Shared {}, - // initial values for the `#[local]` resources - Local { - local_to_foo: 0, - local_to_bar: 0, - local_to_idle: 0, - }, - ) - } - - // `local_to_idle` can only be accessed from this context - #[idle(local = [local_to_idle])] - fn idle(cx: idle::Context) -> ! { - let local_to_idle = cx.local.local_to_idle; - *local_to_idle += 1; - - hprintln!("idle: local_to_idle = {}", local_to_idle); - - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - - // error: no `local_to_foo` field in `idle::LocalResources` - // _cx.local.local_to_foo += 1; - - // error: no `local_to_bar` field in `idle::LocalResources` - // _cx.local.local_to_bar += 1; - - loop { - cortex_m::asm::nop(); - } - } - - // `local_to_foo` can only be accessed from this context - #[task(local = [local_to_foo])] - async fn foo(cx: foo::Context) { - let local_to_foo = cx.local.local_to_foo; - *local_to_foo += 1; - - // error: no `local_to_bar` field in `foo::LocalResources` - // cx.local.local_to_bar += 1; - - hprintln!("foo: local_to_foo = {}", local_to_foo); - } - - // `local_to_bar` can only be accessed from this context - #[task(local = [local_to_bar])] - async fn bar(cx: bar::Context) { - let local_to_bar = cx.local.local_to_bar; - *local_to_bar += 1; - - // error: no `local_to_foo` field in `bar::LocalResources` - // cx.local.local_to_foo += 1; - - hprintln!("bar: local_to_bar = {}", local_to_bar); - } -} diff --git a/examples/lock-free.no_rs b/examples/lock-free.no_rs deleted file mode 100644 index 053307c..0000000 --- a/examples/lock-free.no_rs +++ /dev/null @@ -1,50 +0,0 @@ -//! examples/lock-free.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] -#![feature(type_alias_impl_trait)] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [GPIOA])] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - - #[shared] - struct Shared { - #[lock_free] // <- lock-free shared resource - counter: u64, - } - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) { - foo::spawn().unwrap(); - - (Shared { counter: 0 }, Local {}) - } - - #[task(shared = [counter])] // <- same priority - async fn foo(c: foo::Context) { - bar::spawn().unwrap(); - - *c.shared.counter += 1; // <- no lock API required - let counter = *c.shared.counter; - hprintln!(" foo = {}", counter).unwrap(); - } - - #[task(shared = [counter])] // <- same priority - async fn bar(c: bar::Context) { - foo::spawn().unwrap(); - - *c.shared.counter += 1; // <- no lock API required - let counter = *c.shared.counter; - hprintln!(" bar = {}", counter).unwrap(); - - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } -} diff --git a/examples/lock.rs b/examples/lock.rs deleted file mode 100644 index 203ae6f..0000000 --- a/examples/lock.rs +++ /dev/null @@ -1,73 +0,0 @@ -//! examples/lock.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![deny(missing_docs)] -#![no_main] -#![no_std] -#![feature(type_alias_impl_trait)] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [GPIOA, GPIOB, GPIOC])] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - - #[shared] - struct Shared { - shared: u32, - } - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) { - foo::spawn().unwrap(); - - (Shared { shared: 0 }, Local {}) - } - - // when omitted priority is assumed to be `1` - #[task(shared = [shared])] - async fn foo(mut c: foo::Context) { - hprintln!("A"); - - // the lower priority task requires a critical section to access the data - c.shared.shared.lock(|shared| { - // data can only be modified within this critical section (closure) - *shared += 1; - - // bar will *not* run right now due to the critical section - bar::spawn().unwrap(); - - hprintln!("B - shared = {}", *shared); - - // baz does not contend for `shared` so it's allowed to run now - baz::spawn().unwrap(); - }); - - // critical section is over: bar can now start - - hprintln!("E"); - - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } - - #[task(priority = 2, shared = [shared])] - async fn bar(mut c: bar::Context) { - // the higher priority task does still need a critical section - let shared = c.shared.shared.lock(|shared| { - *shared += 1; - - *shared - }); - - hprintln!("D - shared = {}", shared); - } - - #[task(priority = 3)] - async fn baz(_: baz::Context) { - hprintln!("C"); - } -} diff --git a/examples/message.no_rs b/examples/message.no_rs deleted file mode 100644 index 76c5675..0000000 --- a/examples/message.no_rs +++ /dev/null @@ -1,52 +0,0 @@ -//! examples/message.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { - foo::spawn(/* no message */).unwrap(); - - (Shared {}, Local {}, init::Monotonics()) - } - - #[task(local = [count: u32 = 0])] - fn foo(cx: foo::Context) { - hprintln!("foo").unwrap(); - - bar::spawn(*cx.local.count).unwrap(); - *cx.local.count += 1; - } - - #[task] - fn bar(_: bar::Context, x: u32) { - hprintln!("bar({})", x).unwrap(); - - baz::spawn(x + 1, x + 2).unwrap(); - } - - #[task] - fn baz(_: baz::Context, x: u32, y: u32) { - hprintln!("baz({}, {})", x, y).unwrap(); - - if x + y > 4 { - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } - - foo::spawn().unwrap(); - } -} diff --git a/examples/message_passing.no_rs b/examples/message_passing.no_rs deleted file mode 100644 index ffa9537..0000000 --- a/examples/message_passing.no_rs +++ /dev/null @@ -1,37 +0,0 @@ -//! examples/message_passing.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { - foo::spawn(1, 1).unwrap(); - foo::spawn(1, 2).unwrap(); - foo::spawn(2, 3).unwrap(); - assert!(foo::spawn(1, 4).is_err()); // The capacity of `foo` is reached - - (Shared {}, Local {}, init::Monotonics()) - } - - #[task(capacity = 3)] - fn foo(_c: foo::Context, x: i32, y: u32) { - hprintln!("foo {}, {}", x, y).unwrap(); - if x == 2 { - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } - } -} diff --git a/examples/multilock.rs b/examples/multilock.rs deleted file mode 100644 index 6208cac..0000000 --- a/examples/multilock.rs +++ /dev/null @@ -1,57 +0,0 @@ -//! examples/mutlilock.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![deny(missing_docs)] -#![no_main] -#![no_std] -#![feature(type_alias_impl_trait)] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [GPIOA])] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - - #[shared] - struct Shared { - shared1: u32, - shared2: u32, - shared3: u32, - } - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) { - locks::spawn().unwrap(); - - ( - Shared { - shared1: 0, - shared2: 0, - shared3: 0, - }, - Local {}, - ) - } - - // when omitted priority is assumed to be `1` - #[task(shared = [shared1, shared2, shared3])] - async fn locks(c: locks::Context) { - let s1 = c.shared.shared1; - let s2 = c.shared.shared2; - let s3 = c.shared.shared3; - - (s1, s2, s3).lock(|s1, s2, s3| { - *s1 += 1; - *s2 += 1; - *s3 += 1; - - hprintln!("Multiple locks, s1: {}, s2: {}, s3: {}", *s1, *s2, *s3); - }); - - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } -} diff --git a/examples/not-sync.rs b/examples/not-sync.rs deleted file mode 100644 index 6d1ddae..0000000 --- a/examples/not-sync.rs +++ /dev/null @@ -1,69 +0,0 @@ -//! `examples/not-sync.rs` - -// #![deny(unsafe_code)] -#![deny(warnings)] -#![deny(missing_docs)] -#![no_main] -#![no_std] -#![feature(type_alias_impl_trait)] - -use core::marker::PhantomData; -use panic_semihosting as _; - -/// Not sync -pub struct NotSync { - _0: PhantomData<*const ()>, - data: u32, -} - -unsafe impl Send for NotSync {} - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] -mod app { - use super::NotSync; - use core::marker::PhantomData; - use cortex_m_semihosting::{debug, hprintln}; - - #[shared] - struct Shared { - shared: NotSync, - } - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) { - hprintln!("init"); - - foo::spawn().unwrap(); - bar::spawn().unwrap(); - ( - Shared { - shared: NotSync { - _0: PhantomData, - data: 13, - }, - }, - Local {}, - ) - } - - #[idle] - fn idle(_: idle::Context) -> ! { - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - loop {} - } - - #[task(shared = [&shared])] - async fn foo(c: foo::Context) { - let shared: &NotSync = c.shared.shared; - hprintln!("foo a {}", shared.data); - } - - #[task(shared = [&shared])] - async fn bar(c: bar::Context) { - let shared: &NotSync = c.shared.shared; - hprintln!("bar a {}", shared.data); - } -} diff --git a/examples/only-shared-access.rs b/examples/only-shared-access.rs deleted file mode 100644 index 1d006e6..0000000 --- a/examples/only-shared-access.rs +++ /dev/null @@ -1,44 +0,0 @@ -//! examples/only-shared-access.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![deny(missing_docs)] -#![no_main] -#![no_std] -#![feature(type_alias_impl_trait)] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [UART0, UART1])] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - - #[shared] - struct Shared { - key: u32, - } - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) { - foo::spawn().unwrap(); - bar::spawn().unwrap(); - - (Shared { key: 0xdeadbeef }, Local {}) - } - - #[task(shared = [&key])] - async fn foo(cx: foo::Context) { - let key: &u32 = cx.shared.key; - hprintln!("foo(key = {:#x})", key); - - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } - - #[task(priority = 2, shared = [&key])] - async fn bar(cx: bar::Context) { - hprintln!("bar(key = {:#x})", cx.shared.key); - } -} diff --git a/examples/periodic-at.no_rs b/examples/periodic-at.no_rs deleted file mode 100644 index ca68ed5..0000000 --- a/examples/periodic-at.no_rs +++ /dev/null @@ -1,49 +0,0 @@ -//! examples/periodic-at.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - use systick_monotonic::*; - - #[monotonic(binds = SysTick, default = true)] - type MyMono = Systick<100>; // 100 Hz / 10 ms granularity - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { - let systick = cx.core.SYST; - - // Initialize the monotonic (SysTick rate in QEMU is 12 MHz) - let mut mono = Systick::new(systick, 12_000_000); - - foo::spawn_after(1.secs(), mono.now()).unwrap(); - - (Shared {}, Local {}, init::Monotonics(mono)) - } - - #[task(local = [cnt: u32 = 0])] - fn foo(cx: foo::Context, instant: fugit::TimerInstantU64<100>) { - hprintln!("foo {:?}", instant).ok(); - *cx.local.cnt += 1; - - if *cx.local.cnt == 4 { - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } - - // Periodic every 100 milliseconds - let next_instant = instant + 100.millis(); - foo::spawn_at(next_instant, next_instant).unwrap(); - } -} diff --git a/examples/periodic-at2.no_rs b/examples/periodic-at2.no_rs deleted file mode 100644 index ec9adcc..0000000 --- a/examples/periodic-at2.no_rs +++ /dev/null @@ -1,61 +0,0 @@ -//! examples/periodic-at2.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - use systick_monotonic::*; - - #[monotonic(binds = SysTick, default = true)] - type MyMono = Systick<100>; // 100 Hz / 10 ms granularity - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { - let systick = cx.core.SYST; - - // Initialize the monotonic (SysTick rate in QEMU is 12 MHz) - let mut mono = Systick::new(systick, 12_000_000); - - foo::spawn_after(200.millis(), mono.now()).unwrap(); - - (Shared {}, Local {}, init::Monotonics(mono)) - } - - // Using the explicit type of the timer implementation - #[task(local = [cnt: u32 = 0])] - fn foo(cx: foo::Context, instant: fugit::TimerInstantU64<100>) { - hprintln!("foo {:?}", instant).ok(); - *cx.local.cnt += 1; - - if *cx.local.cnt == 4 { - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } - - // Spawn a new message with 100 ms offset to spawned time - let next_instant = instant + 100.millis(); - bar::spawn_at(next_instant, next_instant).unwrap(); - } - - // Using the Instant from the Monotonic trait - // This remains agnostic to the timer implementation - #[task(local = [cnt: u32 = 0])] - fn bar(_cx: bar::Context, instant: ::Instant) { - hprintln!("bar {:?}", instant).ok(); - - // Spawn a new message with 200ms offset to spawned time - let next_instant = instant + 200.millis(); - foo::spawn_at(next_instant, next_instant).unwrap(); - } -} diff --git a/examples/periodic.no_rs b/examples/periodic.no_rs deleted file mode 100644 index 2f9e8e6..0000000 --- a/examples/periodic.no_rs +++ /dev/null @@ -1,48 +0,0 @@ -//! examples/periodic.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - use systick_monotonic::*; - - #[monotonic(binds = SysTick, default = true)] - type MyMono = Systick<100>; // 100 Hz / 10 ms granularity - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { - let systick = cx.core.SYST; - - // Initialize the monotonic (SysTick rate in QEMU is 12 MHz) - let mono = Systick::new(systick, 12_000_000); - - foo::spawn_after(100.millis()).unwrap(); - - (Shared {}, Local {}, init::Monotonics(mono)) - } - - #[task(local = [cnt: u32 = 0])] - fn foo(cx: foo::Context) { - hprintln!("foo").ok(); - *cx.local.cnt += 1; - - if *cx.local.cnt == 4 { - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } - - // Periodic every 100ms - foo::spawn_after(100.millis()).unwrap(); - } -} diff --git a/examples/peripherals-taken.rs b/examples/peripherals-taken.rs deleted file mode 100644 index 2f710e9..0000000 --- a/examples/peripherals-taken.rs +++ /dev/null @@ -1,28 +0,0 @@ -//! examples/peripherals-taken.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![deny(missing_docs)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965)] -mod app { - use cortex_m_semihosting::debug; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) { - assert!(cortex_m::Peripherals::take().is_none()); - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - - (Shared {}, Local {}) - } -} diff --git a/examples/pool.no_rs b/examples/pool.no_rs deleted file mode 100644 index fb8589a..0000000 --- a/examples/pool.no_rs +++ /dev/null @@ -1,70 +0,0 @@ -//! examples/pool.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] - -use heapless::{ - pool, - pool::singleton::{Box, Pool}, -}; -use panic_semihosting as _; -use rtic::app; - -// Declare a pool of 128-byte memory blocks -pool!(P: [u8; 128]); - -#[app(device = lm3s6965, dispatchers = [SSI0, QEI0])] -mod app { - use crate::{Box, Pool}; - use cortex_m_semihosting::debug; - use lm3s6965::Interrupt; - - // Import the memory pool into scope - use super::P; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init(local = [memory: [u8; 512] = [0; 512]])] - fn init(cx: init::Context) -> (Shared, Local) { - // Increase the capacity of the memory pool by ~4 - P::grow(cx.local.memory); - - rtic::pend(Interrupt::I2C0); - - (Shared {}, Local {}) - } - - #[task(binds = I2C0, priority = 2)] - async fn i2c0(_: i2c0::Context) { - // claim a memory block, initialize it and .. - let x = P::alloc().unwrap().init([0u8; 128]); - - // .. send it to the `foo` task - foo::spawn(x).ok().unwrap(); - - // send another block to the task `bar` - bar::spawn(P::alloc().unwrap().init([0u8; 128])) - .ok() - .unwrap(); - } - - #[task] - async fn foo(_: foo::Context, _x: Box

) { - // explicitly return the block to the pool - drop(_x); - - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } - - #[task(priority = 2)] - async fn bar(_: bar::Context, _x: Box

) { - // this is done automatically so we can omit the call to `drop` - // drop(_x); - } -} diff --git a/examples/preempt.rs b/examples/preempt.rs deleted file mode 100644 index 4b11907..0000000 --- a/examples/preempt.rs +++ /dev/null @@ -1,47 +0,0 @@ -//! examples/preempt.rs - -#![no_main] -#![no_std] -#![feature(type_alias_impl_trait)] -#![deny(missing_docs)] - -use panic_semihosting as _; -use rtic::app; - -#[app(device = lm3s6965, dispatchers = [SSI0, QEI0])] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) { - foo::spawn().unwrap(); - - (Shared {}, Local {}) - } - - #[task(priority = 1)] - async fn foo(_: foo::Context) { - hprintln!("foo - start"); - baz::spawn().unwrap(); - hprintln!("foo - end"); - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } - - #[task(priority = 2)] - async fn bar(_: bar::Context) { - hprintln!(" bar"); - } - - #[task(priority = 2)] - async fn baz(_: baz::Context) { - hprintln!(" baz - start"); - bar::spawn().unwrap(); - hprintln!(" baz - end"); - } -} diff --git a/examples/ramfunc.rs b/examples/ramfunc.rs deleted file mode 100644 index e2e7f67..0000000 --- a/examples/ramfunc.rs +++ /dev/null @@ -1,50 +0,0 @@ -//! examples/ramfunc.rs - -#![deny(warnings)] -#![deny(missing_docs)] -#![no_main] -#![no_std] -#![feature(type_alias_impl_trait)] - -use panic_semihosting as _; - -#[rtic::app( - device = lm3s6965, - dispatchers = [ - UART0, - #[link_section = ".data.UART1"] - UART1 - ]) -] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) { - foo::spawn().unwrap(); - - (Shared {}, Local {}) - } - - #[inline(never)] - #[task] - async fn foo(_: foo::Context) { - hprintln!("foo"); - - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } - - // run this task from RAM - #[inline(never)] - #[link_section = ".data.bar"] - #[task(priority = 2)] - async fn bar(_: bar::Context) { - foo::spawn().unwrap(); - } -} diff --git a/examples/resource-user-struct.rs b/examples/resource-user-struct.rs deleted file mode 100644 index fcbacae..0000000 --- a/examples/resource-user-struct.rs +++ /dev/null @@ -1,72 +0,0 @@ -//! examples/resource.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![deny(missing_docs)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965)] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - use lm3s6965::Interrupt; - - #[shared] - struct Shared { - // A resource - shared: u32, - } - - // Should not collide with the struct above - #[allow(dead_code)] - struct Shared2 { - // A resource - shared: u32, - } - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) { - rtic::pend(Interrupt::UART0); - rtic::pend(Interrupt::UART1); - - (Shared { shared: 0 }, Local {}) - } - - // `shared` cannot be accessed from this context - #[idle] - fn idle(_cx: idle::Context) -> ! { - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - - // error: no `shared` field in `idle::Context` - // _cx.shared.shared += 1; - - loop {} - } - - // `shared` can be accessed from this context - #[task(binds = UART0, shared = [shared])] - fn uart0(mut cx: uart0::Context) { - let shared = cx.shared.shared.lock(|shared| { - *shared += 1; - *shared - }); - - hprintln!("UART0: shared = {}", shared); - } - - // `shared` can be accessed from this context - #[task(binds = UART1, shared = [shared])] - fn uart1(mut cx: uart1::Context) { - let shared = cx.shared.shared.lock(|shared| { - *shared += 1; - *shared - }); - - hprintln!("UART1: shared = {}", shared); - } -} diff --git a/examples/schedule.no_rs b/examples/schedule.no_rs deleted file mode 100644 index 5bad5a3..0000000 --- a/examples/schedule.no_rs +++ /dev/null @@ -1,64 +0,0 @@ -//! examples/schedule.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - use systick_monotonic::*; - - #[monotonic(binds = SysTick, default = true)] - type MyMono = Systick<100>; // 100 Hz / 10 ms granularity - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { - let systick = cx.core.SYST; - - // Initialize the monotonic (SysTick rate in QEMU is 12 MHz) - let mono = Systick::new(systick, 12_000_000); - - hprintln!("init").ok(); - - // Schedule `foo` to run 1 second in the future - foo::spawn_after(1.secs()).unwrap(); - - ( - Shared {}, - Local {}, - init::Monotonics(mono), // Give the monotonic to RTIC - ) - } - - #[task] - fn foo(_: foo::Context) { - hprintln!("foo").ok(); - - // Schedule `bar` to run 2 seconds in the future (1 second after foo runs) - bar::spawn_after(1.secs()).unwrap(); - } - - #[task] - fn bar(_: bar::Context) { - hprintln!("bar").ok(); - - // Schedule `baz` to run 1 seconds from now, but with a specific time instant. - baz::spawn_at(monotonics::now() + 1.secs()).unwrap(); - } - - #[task] - fn baz(_: baz::Context) { - hprintln!("baz").ok(); - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } -} diff --git a/examples/shared.rs b/examples/shared.rs deleted file mode 100644 index d0633fb..0000000 --- a/examples/shared.rs +++ /dev/null @@ -1,51 +0,0 @@ -//! examples/late.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![deny(missing_docs)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965)] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - use heapless::spsc::{Consumer, Producer, Queue}; - use lm3s6965::Interrupt; - - #[shared] - struct Shared { - p: Producer<'static, u32, 5>, - c: Consumer<'static, u32, 5>, - } - - #[local] - struct Local {} - - #[init(local = [q: Queue = Queue::new()])] - fn init(cx: init::Context) -> (Shared, Local) { - let (p, c) = cx.local.q.split(); - - // Initialization of shared resources - (Shared { p, c }, Local {}) - } - - #[idle(shared = [c])] - fn idle(mut c: idle::Context) -> ! { - loop { - if let Some(byte) = c.shared.c.lock(|c| c.dequeue()) { - hprintln!("received message: {}", byte); - - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } else { - rtic::pend(Interrupt::UART0); - } - } - } - - #[task(binds = UART0, shared = [p])] - fn uart0(mut c: uart0::Context) { - c.shared.p.lock(|p| p.enqueue(42).unwrap()); - } -} diff --git a/examples/smallest.rs b/examples/smallest.rs deleted file mode 100644 index e54ae44..0000000 --- a/examples/smallest.rs +++ /dev/null @@ -1,25 +0,0 @@ -//! examples/smallest.rs - -#![no_main] -#![no_std] -#![deny(missing_docs)] - -use panic_semihosting as _; // panic handler -use rtic::app; - -#[app(device = lm3s6965)] -mod app { - use cortex_m_semihosting::debug; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) { - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - (Shared {}, Local {}) - } -} diff --git a/examples/spawn.rs b/examples/spawn.rs deleted file mode 100644 index d30ecf1..0000000 --- a/examples/spawn.rs +++ /dev/null @@ -1,36 +0,0 @@ -//! examples/spawn.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![deny(missing_docs)] -#![no_main] -#![no_std] -#![feature(type_alias_impl_trait)] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) { - hprintln!("init"); - foo::spawn().unwrap(); - - (Shared {}, Local {}) - } - - #[task] - async fn foo(_: foo::Context) { - hprintln!("foo"); - - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } -} diff --git a/examples/static.rs b/examples/static.rs deleted file mode 100644 index 7f656f4..0000000 --- a/examples/static.rs +++ /dev/null @@ -1,61 +0,0 @@ -//! examples/static.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![deny(missing_docs)] -#![no_main] -#![no_std] -#![feature(type_alias_impl_trait)] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [UART0])] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - use heapless::spsc::{Consumer, Producer, Queue}; - - #[shared] - struct Shared {} - - #[local] - struct Local { - p: Producer<'static, u32, 5>, - c: Consumer<'static, u32, 5>, - } - - #[init(local = [q: Queue = Queue::new()])] - fn init(cx: init::Context) -> (Shared, Local) { - // q has 'static life-time so after the split and return of `init` - // it will continue to exist and be allocated - let (p, c) = cx.local.q.split(); - - foo::spawn().unwrap(); - - (Shared {}, Local { p, c }) - } - - #[idle(local = [c])] - fn idle(c: idle::Context) -> ! { - loop { - // Lock-free access to the same underlying queue! - if let Some(data) = c.local.c.dequeue() { - hprintln!("received message: {}", data); - - // Run foo until data - if data == 3 { - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } else { - foo::spawn().unwrap(); - } - } - } - } - - #[task(local = [p, state: u32 = 0])] - async fn foo(c: foo::Context) { - *c.local.state += 1; - - // Lock-free access to the same underlying queue! - c.local.p.enqueue(*c.local.state).unwrap(); - } -} diff --git a/examples/t-binds.rs b/examples/t-binds.rs deleted file mode 100644 index bdeb391..0000000 --- a/examples/t-binds.rs +++ /dev/null @@ -1,45 +0,0 @@ -//! [compile-pass] Check that `binds` works as advertised - -#![deny(unsafe_code)] -#![deny(warnings)] -#![deny(missing_docs)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965)] -mod app { - use cortex_m_semihosting::debug; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) { - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - - (Shared {}, Local {}) - } - - // Cortex-M exception - #[task(binds = SVCall)] - fn foo(c: foo::Context) { - crate::foo_trampoline(c) - } - - // LM3S6965 interrupt - #[task(binds = UART0)] - fn bar(c: bar::Context) { - crate::bar_trampoline(c) - } -} - -#[allow(dead_code)] -fn foo_trampoline(_: app::foo::Context) {} - -#[allow(dead_code)] -fn bar_trampoline(_: app::bar::Context) {} diff --git a/examples/t-cfg-resources.rs b/examples/t-cfg-resources.rs deleted file mode 100644 index 0328700..0000000 --- a/examples/t-cfg-resources.rs +++ /dev/null @@ -1,42 +0,0 @@ -//! [compile-pass] check that `#[cfg]` attributes applied on resources work - -#![no_main] -#![no_std] -#![deny(missing_docs)] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965)] -mod app { - use cortex_m_semihosting::debug; - - #[shared] - struct Shared { - // A conditionally compiled resource behind feature_x - #[cfg(feature = "feature_x")] - x: u32, - } - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) { - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - - ( - Shared { - #[cfg(feature = "feature_x")] - x: 0, - }, - Local {}, - ) - } - - #[idle] - fn idle(_cx: idle::Context) -> ! { - loop { - cortex_m::asm::nop(); - } - } -} diff --git a/examples/t-htask-main.rs b/examples/t-htask-main.rs deleted file mode 100644 index 8f885bc..0000000 --- a/examples/t-htask-main.rs +++ /dev/null @@ -1,32 +0,0 @@ -//! examples/h-task-main.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![deny(missing_docs)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965)] -mod app { - use cortex_m_semihosting::debug; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) { - rtic::pend(lm3s6965::Interrupt::UART0); - - (Shared {}, Local {}) - } - - #[task(binds = UART0)] - fn taskmain(_: taskmain::Context) { - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } -} diff --git a/examples/t-idle-main.rs b/examples/t-idle-main.rs deleted file mode 100644 index 43215cf..0000000 --- a/examples/t-idle-main.rs +++ /dev/null @@ -1,33 +0,0 @@ -//! examples/t-idle-main.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![deny(missing_docs)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965)] -mod app { - use cortex_m_semihosting::debug; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) { - (Shared {}, Local {}) - } - - #[idle] - fn taskmain(_: taskmain::Context) -> ! { - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - loop { - cortex_m::asm::nop(); - } - } -} diff --git a/examples/t-late-not-send.rs b/examples/t-late-not-send.rs deleted file mode 100644 index 44d1d85..0000000 --- a/examples/t-late-not-send.rs +++ /dev/null @@ -1,48 +0,0 @@ -//! [compile-pass] shared resources don't need to be `Send` if they are owned by `idle` - -#![no_main] -#![no_std] -#![deny(missing_docs)] - -use core::marker::PhantomData; -use panic_semihosting as _; - -/// Not send -pub struct NotSend { - _0: PhantomData<*const ()>, -} - -#[rtic::app(device = lm3s6965)] -mod app { - use super::NotSend; - use core::marker::PhantomData; - use cortex_m_semihosting::debug; - - #[shared] - struct Shared { - x: NotSend, - y: Option, - } - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) { - ( - Shared { - x: NotSend { _0: PhantomData }, - y: None, - }, - Local {}, - ) - } - - #[idle(shared = [x, y])] - fn idle(_: idle::Context) -> ! { - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - loop { - cortex_m::asm::nop(); - } - } -} diff --git a/examples/t-schedule.no_rs b/examples/t-schedule.no_rs deleted file mode 100644 index 5ec4208..0000000 --- a/examples/t-schedule.no_rs +++ /dev/null @@ -1,136 +0,0 @@ -//! [compile-pass] Check `schedule` code generation - -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] -mod app { - use cortex_m_semihosting::debug; - use systick_monotonic::*; - - #[monotonic(binds = SysTick, default = true)] - type MyMono = Systick<100>; // 100 Hz / 10 ms granularity - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { - let systick = cx.core.SYST; - - // Initialize the monotonic (SysTick rate in QEMU is 12 MHz) - let mono = Systick::new(systick, 12_000_000); - - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - - (Shared {}, Local {}, init::Monotonics(mono)) - } - - #[idle] - fn idle(_: idle::Context) -> ! { - // Task without message passing - - // Not default - let _: Result = - foo::MyMono::spawn_at(monotonics::MyMono::now()); - let handle: Result = foo::MyMono::spawn_after(1.secs()); - let _: Result = handle.unwrap().reschedule_after(1.secs()); - - let handle: Result = foo::MyMono::spawn_after(1.secs()); - let _: Result = - handle.unwrap().reschedule_at(monotonics::MyMono::now()); - - let handle: Result = foo::MyMono::spawn_after(1.secs()); - let _: Result<(), ()> = handle.unwrap().cancel(); - - // Using default - let _: Result = foo::spawn_at(monotonics::now()); - let handle: Result = foo::spawn_after(1.secs()); - let _: Result = handle.unwrap().reschedule_after(1.secs()); - - let handle: Result = foo::spawn_after(1.secs()); - let _: Result = - handle.unwrap().reschedule_at(monotonics::MyMono::now()); - - let handle: Result = foo::spawn_after(1.secs()); - let _: Result<(), ()> = handle.unwrap().cancel(); - - // Task with single message passing - - // Not default - let _: Result = - bar::MyMono::spawn_at(monotonics::MyMono::now(), 0); - let handle: Result = bar::MyMono::spawn_after(1.secs(), 1); - let _: Result = handle.unwrap().reschedule_after(1.secs()); - - let handle: Result = bar::MyMono::spawn_after(1.secs(), 1); - let _: Result = - handle.unwrap().reschedule_at(monotonics::MyMono::now()); - - let handle: Result = bar::MyMono::spawn_after(1.secs(), 1); - let _: Result = handle.unwrap().cancel(); - - // Using default - let _: Result = bar::spawn_at(monotonics::MyMono::now(), 0); - let handle: Result = bar::spawn_after(1.secs(), 1); - let _: Result = handle.unwrap().reschedule_after(1.secs()); - - let handle: Result = bar::spawn_after(1.secs(), 1); - let _: Result = - handle.unwrap().reschedule_at(monotonics::MyMono::now()); - - let handle: Result = bar::spawn_after(1.secs(), 1); - let _: Result = handle.unwrap().cancel(); - - // Task with multiple message passing - - // Not default - let _: Result = - baz::MyMono::spawn_at(monotonics::MyMono::now(), 0, 1); - let handle: Result = - baz::MyMono::spawn_after(1.secs(), 1, 2); - let _: Result = handle.unwrap().reschedule_after(1.secs()); - - let handle: Result = - baz::MyMono::spawn_after(1.secs(), 1, 2); - let _: Result = - handle.unwrap().reschedule_at(monotonics::MyMono::now()); - - let handle: Result = - baz::MyMono::spawn_after(1.secs(), 1, 2); - let _: Result<(u32, u32), ()> = handle.unwrap().cancel(); - - // Using default - let _: Result = - baz::spawn_at(monotonics::MyMono::now(), 0, 1); - let handle: Result = baz::spawn_after(1.secs(), 1, 2); - let _: Result = handle.unwrap().reschedule_after(1.secs()); - - let handle: Result = baz::spawn_after(1.secs(), 1, 2); - let _: Result = - handle.unwrap().reschedule_at(monotonics::MyMono::now()); - - let handle: Result = baz::spawn_after(1.secs(), 1, 2); - let _: Result<(u32, u32), ()> = handle.unwrap().cancel(); - - loop { - cortex_m::asm::nop(); - } - } - - #[task] - fn foo(_: foo::Context) {} - - #[task] - fn bar(_: bar::Context, _x: u32) {} - - #[task] - fn baz(_: baz::Context, _x: u32, _y: u32) {} -} diff --git a/examples/t-spawn.no_rs b/examples/t-spawn.no_rs deleted file mode 100644 index dad0c83..0000000 --- a/examples/t-spawn.no_rs +++ /dev/null @@ -1,69 +0,0 @@ -//! [compile-pass] Check code generation of `spawn` - -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] -#![feature(type_alias_impl_trait)] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] -mod app { - use cortex_m_semihosting::debug; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) { - let _: Result<(), ()> = foo::spawn(); - let _: Result<(), u32> = bar::spawn(0); - let _: Result<(), (u32, u32)> = baz::spawn(0, 1); - - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - - (Shared {}, Local {}) - } - - #[idle] - fn idle(_: idle::Context) -> ! { - let _: Result<(), ()> = foo::spawn(); - let _: Result<(), u32> = bar::spawn(0); - let _: Result<(), (u32, u32)> = baz::spawn(0, 1); - - loop { - cortex_m::asm::nop(); - } - } - - #[task(binds = SVCall)] - fn svcall(_: svcall::Context) { - let _: Result<(), ()> = foo::spawn(); - let _: Result<(), u32> = bar::spawn(0); - let _: Result<(), (u32, u32)> = baz::spawn(0, 1); - } - - #[task(binds = UART0)] - fn uart0(_: uart0::Context) { - let _: Result<(), ()> = foo::spawn(); - let _: Result<(), u32> = bar::spawn(0); - let _: Result<(), (u32, u32)> = baz::spawn(0, 1); - } - - #[task] - async fn foo(_: foo::Context) { - let _: Result<(), ()> = foo::spawn(); - let _: Result<(), u32> = bar::spawn(0); - let _: Result<(), (u32, u32)> = baz::spawn(0, 1); - } - - #[task] - async fn bar(_: bar::Context, _x: u32) {} - - #[task] - async fn baz(_: baz::Context, _x: u32, _y: u32) {} -} diff --git a/examples/task.rs b/examples/task.rs deleted file mode 100644 index ab6a1e0..0000000 --- a/examples/task.rs +++ /dev/null @@ -1,58 +0,0 @@ -//! examples/task.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![deny(missing_docs)] -#![no_main] -#![no_std] -#![feature(type_alias_impl_trait)] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0, QEI0])] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) { - foo::spawn().unwrap(); - - (Shared {}, Local {}) - } - - #[task] - async fn foo(_: foo::Context) { - hprintln!("foo - start"); - - // spawns `bar` onto the task scheduler - // `foo` and `bar` have the same priority so `bar` will not run until - // after `foo` terminates - bar::spawn().unwrap(); - - hprintln!("foo - middle"); - - // spawns `baz` onto the task scheduler - // `baz` has higher priority than `foo` so it immediately preempts `foo` - baz::spawn().unwrap(); - - hprintln!("foo - end"); - } - - #[task] - async fn bar(_: bar::Context) { - hprintln!("bar"); - - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } - - #[task(priority = 2)] - async fn baz(_: baz::Context) { - hprintln!("baz"); - } -} diff --git a/examples/zero-prio-task.rs b/examples/zero-prio-task.rs deleted file mode 100644 index c810e8f..0000000 --- a/examples/zero-prio-task.rs +++ /dev/null @@ -1,60 +0,0 @@ -//! examples/zero-prio-task.rs - -#![no_main] -#![no_std] -#![feature(type_alias_impl_trait)] -#![deny(missing_docs)] - -use core::marker::PhantomData; -use panic_semihosting as _; - -/// Does not impl send -pub struct NotSend { - _0: PhantomData<*const ()>, -} - -#[rtic::app(device = lm3s6965, peripherals = true)] -mod app { - use super::NotSend; - use core::marker::PhantomData; - use cortex_m_semihosting::{debug, hprintln}; - - #[shared] - struct Shared { - x: NotSend, - } - - #[local] - struct Local { - y: NotSend, - } - - #[init] - fn init(_cx: init::Context) -> (Shared, Local) { - hprintln!("init"); - - async_task::spawn().unwrap(); - async_task2::spawn().unwrap(); - - ( - Shared { - x: NotSend { _0: PhantomData }, - }, - Local { - y: NotSend { _0: PhantomData }, - }, - ) - } - - #[task(priority = 0, shared = [x], local = [y])] - async fn async_task(_: async_task::Context) { - hprintln!("hello from async"); - } - - #[task(priority = 0, shared = [x])] - async fn async_task2(_: async_task2::Context) { - hprintln!("hello from async2"); - - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } -} diff --git a/macros/.gitignore b/macros/.gitignore deleted file mode 100644 index 4fffb2f..0000000 --- a/macros/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/target -/Cargo.lock diff --git a/macros/Cargo.toml b/macros/Cargo.toml deleted file mode 100644 index 1cc9556..0000000 --- a/macros/Cargo.toml +++ /dev/null @@ -1,41 +0,0 @@ -[package] -authors = [ - "The Real-Time Interrupt-driven Concurrency developers", - "Emil Fresk ", - "Henrik Tjäder ", - "Jorge Aparicio ", - "Per Lindgren ", -] -categories = ["concurrency", "embedded", "no-std", "asynchronous"] -description = "Procedural macros, syntax parsing, and codegen of the RTIC crate" -documentation = "https://rtic-rs.github.io/rtic/api/rtic" -edition = "2021" -keywords = ["arm", "cortex-m", "risc-v", "embedded", "async", "runtime", "futures", "await", "no-std", "rtos", "bare-metal"] -license = "MIT OR Apache-2.0" -name = "rtic-macros" -readme = "../README.md" -repository = "https://github.com/rtic-rs/rtic" - -version = "2.0.0-alpha.0" - -[lib] -proc-macro = true - -[feature] -default = [] -debugprint = [] -# list of supported codegen backends -thumbv6 = [] -thumbv7 = [] -# riscv-clic = [] -# riscv-ch32 = [] - -[dependencies] -indexmap = "1.9.2" -proc-macro2 = "1.0.49" -proc-macro-error = "1.0.4" -quote = "1.0.23" -syn = { version = "1.0.107", features = ["extra-traits", "full"] } - -[dev-dependencies] -trybuild = "1.0.73" diff --git a/macros/src/analyze.rs b/macros/src/analyze.rs deleted file mode 100644 index 65774f6..0000000 --- a/macros/src/analyze.rs +++ /dev/null @@ -1,49 +0,0 @@ -use core::ops; -use std::collections::{BTreeMap, BTreeSet}; - -use crate::syntax::{ - analyze::{self, Priority}, - ast::{App, Dispatcher}, -}; -use syn::Ident; - -/// Extend the upstream `Analysis` struct with our field -pub struct Analysis { - parent: analyze::Analysis, - pub interrupts: BTreeMap, -} - -impl ops::Deref for Analysis { - type Target = analyze::Analysis; - - fn deref(&self) -> &Self::Target { - &self.parent - } -} - -// Assign an interrupt to each priority level -pub fn app(analysis: analyze::Analysis, app: &App) -> Analysis { - let mut available_interrupt = app.args.dispatchers.clone(); - - // the set of priorities (each priority only once) - let priorities = app - .software_tasks - .values() - .map(|task| task.args.priority) - .collect::>(); - - // map from priorities to interrupts (holding name and attributes) - - let interrupts: BTreeMap = priorities - .iter() - .filter(|prio| **prio > 0) // 0 prio tasks are run in main - .copied() - .rev() - .map(|p| (p, available_interrupt.pop().expect("UNREACHABLE"))) - .collect(); - - Analysis { - parent: analysis, - interrupts, - } -} diff --git a/macros/src/bindings.rs b/macros/src/bindings.rs deleted file mode 100644 index 8b13789..0000000 --- a/macros/src/bindings.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/macros/src/check.rs b/macros/src/check.rs deleted file mode 100644 index a05c82e..0000000 --- a/macros/src/check.rs +++ /dev/null @@ -1,70 +0,0 @@ -use std::collections::HashSet; - -use crate::syntax::ast::App; -use syn::parse; - -pub fn app(app: &App) -> parse::Result<()> { - // Check that external (device-specific) interrupts are not named after known (Cortex-M) - // exceptions - for name in app.args.dispatchers.keys() { - let name_s = name.to_string(); - - match &*name_s { - "NonMaskableInt" | "HardFault" | "MemoryManagement" | "BusFault" | "UsageFault" - | "SecureFault" | "SVCall" | "DebugMonitor" | "PendSV" | "SysTick" => { - return Err(parse::Error::new( - name.span(), - "Cortex-M exceptions can't be used as `extern` interrupts", - )); - } - - _ => {} - } - } - - // Check that there are enough external interrupts to dispatch the software tasks and the timer - // queue handler - let mut first = None; - let priorities = app - .software_tasks - .iter() - .map(|(name, task)| { - first = Some(name); - task.args.priority - }) - .filter(|prio| *prio > 0) - .collect::>(); - - let need = priorities.len(); - let given = app.args.dispatchers.len(); - if need > given { - let s = { - format!( - "not enough interrupts to dispatch \ - all software tasks (need: {need}; given: {given})" - ) - }; - - // If not enough tasks and first still is None, may cause - // "custom attribute panicked" due to unwrap on None - return Err(parse::Error::new(first.unwrap().span(), s)); - } - - // Check that all exceptions are valid; only exceptions with configurable priorities are - // accepted - for (name, task) in &app.hardware_tasks { - let name_s = task.args.binds.to_string(); - match &*name_s { - "NonMaskableInt" | "HardFault" => { - return Err(parse::Error::new( - name.span(), - "only exceptions with configurable priority can be used as hardware tasks", - )); - } - - _ => {} - } - } - - Ok(()) -} diff --git a/macros/src/codegen.rs b/macros/src/codegen.rs deleted file mode 100644 index 24e98ce..0000000 --- a/macros/src/codegen.rs +++ /dev/null @@ -1,75 +0,0 @@ -use proc_macro2::TokenStream as TokenStream2; -use quote::quote; - -use crate::analyze::Analysis; -use crate::syntax::ast::App; - -mod assertions; -mod async_dispatchers; -mod hardware_tasks; -mod idle; -mod init; -mod local_resources; -mod local_resources_struct; -mod module; -mod post_init; -mod pre_init; -mod shared_resources; -mod shared_resources_struct; -mod software_tasks; -mod util; - -mod main; - -// TODO: organize codegen to actual parts of code -// so `main::codegen` generates ALL the code for `fn main`, -// `software_tasks::codegen` generates ALL the code for software tasks etc... - -#[allow(clippy::too_many_lines)] -pub fn app(app: &App, analysis: &Analysis) -> TokenStream2 { - // Generate the `main` function - let main = main::codegen(app, analysis); - let init_codegen = init::codegen(app, analysis); - let idle_codegen = idle::codegen(app, analysis); - let shared_resources_codegen = shared_resources::codegen(app, analysis); - let local_resources_codegen = local_resources::codegen(app, analysis); - let hardware_tasks_codegen = hardware_tasks::codegen(app, analysis); - let software_tasks_codegen = software_tasks::codegen(app, analysis); - let async_dispatchers_codegen = async_dispatchers::codegen(app, analysis); - - let user_imports = &app.user_imports; - let user_code = &app.user_code; - let name = &app.name; - let device = &app.args.device; - - let rt_err = util::rt_err_ident(); - - quote!( - /// The RTIC application module - pub mod #name { - /// Always include the device crate which contains the vector table - use #device as #rt_err; - - #(#user_imports)* - - #(#user_code)* - /// User code end - - #init_codegen - - #idle_codegen - - #hardware_tasks_codegen - - #software_tasks_codegen - - #shared_resources_codegen - - #local_resources_codegen - - #async_dispatchers_codegen - - #main - } - ) -} diff --git a/macros/src/codegen/assertions.rs b/macros/src/codegen/assertions.rs deleted file mode 100644 index dd94aa6..0000000 --- a/macros/src/codegen/assertions.rs +++ /dev/null @@ -1,53 +0,0 @@ -use proc_macro2::TokenStream as TokenStream2; -use quote::quote; - -use crate::syntax::ast::App; -use crate::{analyze::Analysis, codegen::util}; - -/// Generates compile-time assertions that check that types implement the `Send` / `Sync` traits -pub fn codegen(app: &App, analysis: &Analysis) -> Vec { - let mut stmts = vec![]; - - for ty in &analysis.send_types { - stmts.push(quote!(rtic::export::assert_send::<#ty>();)); - } - - for ty in &analysis.sync_types { - stmts.push(quote!(rtic::export::assert_sync::<#ty>();)); - } - - let device = &app.args.device; - let chunks_name = util::priority_mask_chunks_ident(); - let no_basepri_checks: Vec<_> = app - .hardware_tasks - .iter() - .filter_map(|(_, task)| { - if !util::is_exception(&task.args.binds) { - let interrupt_name = &task.args.binds; - Some(quote!( - if (#device::Interrupt::#interrupt_name as usize) >= (#chunks_name * 32) { - ::core::panic!("An interrupt out of range is used while in armv6 or armv8m.base"); - } - )) - } else { - None - } - }) - .collect(); - - let const_check = quote! { - const _CONST_CHECK: () = { - if !rtic::export::have_basepri() { - #(#no_basepri_checks)* - } else { - // TODO: Add armv7 checks here - } - }; - - let _ = _CONST_CHECK; - }; - - stmts.push(const_check); - - stmts -} diff --git a/macros/src/codegen/async_dispatchers.rs b/macros/src/codegen/async_dispatchers.rs deleted file mode 100644 index a12ad32..0000000 --- a/macros/src/codegen/async_dispatchers.rs +++ /dev/null @@ -1,89 +0,0 @@ -use crate::syntax::ast::App; -use crate::{analyze::Analysis, codegen::util}; -use proc_macro2::TokenStream as TokenStream2; -use quote::quote; - -/// Generates task dispatchers -pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { - let mut items = vec![]; - - let interrupts = &analysis.interrupts; - - // Generate executor definition and priority in global scope - for (name, _) in app.software_tasks.iter() { - let type_name = util::internal_task_ident(name, "F"); - let exec_name = util::internal_task_ident(name, "EXEC"); - - items.push(quote!( - #[allow(non_camel_case_types)] - type #type_name = impl core::future::Future; - #[allow(non_upper_case_globals)] - static #exec_name: rtic::export::executor::AsyncTaskExecutor<#type_name> = - rtic::export::executor::AsyncTaskExecutor::new(); - )); - } - - for (&level, channel) in &analysis.channels { - let mut stmts = vec![]; - - let dispatcher_name = if level > 0 { - util::suffixed(&interrupts.get(&level).expect("UNREACHABLE").0.to_string()) - } else { - util::zero_prio_dispatcher_ident() - }; - - let pend_interrupt = if level > 0 { - let device = &app.args.device; - let enum_ = util::interrupt_ident(); - - quote!(rtic::pend(#device::#enum_::#dispatcher_name);) - } else { - // For 0 priority tasks we don't need to pend anything - quote!() - }; - - for name in channel.tasks.iter() { - let exec_name = util::internal_task_ident(name, "EXEC"); - // TODO: Fix cfg - // let task = &app.software_tasks[name]; - // let cfgs = &task.cfgs; - - stmts.push(quote!( - #exec_name.poll(|| { - #exec_name.set_pending(); - #pend_interrupt - }); - )); - } - - if level > 0 { - let doc = format!("Interrupt handler to dispatch async tasks at priority {level}"); - let attribute = &interrupts.get(&level).expect("UNREACHABLE").1.attrs; - items.push(quote!( - #[allow(non_snake_case)] - #[doc = #doc] - #[no_mangle] - #(#attribute)* - unsafe fn #dispatcher_name() { - /// The priority of this interrupt handler - const PRIORITY: u8 = #level; - - rtic::export::run(PRIORITY, || { - #(#stmts)* - }); - } - )); - } else { - items.push(quote!( - #[allow(non_snake_case)] - unsafe fn #dispatcher_name() -> ! { - loop { - #(#stmts)* - } - } - )); - } - } - - quote!(#(#items)*) -} diff --git a/macros/src/codegen/hardware_tasks.rs b/macros/src/codegen/hardware_tasks.rs deleted file mode 100644 index 8a5a8f6..0000000 --- a/macros/src/codegen/hardware_tasks.rs +++ /dev/null @@ -1,87 +0,0 @@ -use crate::syntax::{ast::App, Context}; -use crate::{ - analyze::Analysis, - codegen::{local_resources_struct, module, shared_resources_struct}, -}; -use proc_macro2::TokenStream as TokenStream2; -use quote::quote; - -/// Generate support code for hardware tasks (`#[exception]`s and `#[interrupt]`s) -pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { - let mut mod_app = vec![]; - let mut root = vec![]; - let mut user_tasks = vec![]; - - for (name, task) in &app.hardware_tasks { - let symbol = task.args.binds.clone(); - let priority = task.args.priority; - let cfgs = &task.cfgs; - let attrs = &task.attrs; - - mod_app.push(quote!( - #[allow(non_snake_case)] - #[no_mangle] - #(#attrs)* - #(#cfgs)* - unsafe fn #symbol() { - const PRIORITY: u8 = #priority; - - rtic::export::run(PRIORITY, || { - #name( - #name::Context::new() - ) - }); - } - )); - - // `${task}Locals` - if !task.args.local_resources.is_empty() { - let (item, constructor) = - local_resources_struct::codegen(Context::HardwareTask(name), app); - - root.push(item); - - mod_app.push(constructor); - } - - // `${task}Resources` - if !task.args.shared_resources.is_empty() { - let (item, constructor) = - shared_resources_struct::codegen(Context::HardwareTask(name), app); - - root.push(item); - - mod_app.push(constructor); - } - - // Module generation... - - root.push(module::codegen(Context::HardwareTask(name), app, analysis)); - - // End module generation - - if !task.is_extern { - let attrs = &task.attrs; - let context = &task.context; - let stmts = &task.stmts; - user_tasks.push(quote!( - #(#attrs)* - #[allow(non_snake_case)] - fn #name(#context: #name::Context) { - use rtic::Mutex as _; - use rtic::mutex::prelude::*; - - #(#stmts)* - } - )); - } - } - - quote!( - #(#mod_app)* - - #(#root)* - - #(#user_tasks)* - ) -} diff --git a/macros/src/codegen/idle.rs b/macros/src/codegen/idle.rs deleted file mode 100644 index 0c833ef..0000000 --- a/macros/src/codegen/idle.rs +++ /dev/null @@ -1,58 +0,0 @@ -use crate::syntax::{ast::App, Context}; -use crate::{ - analyze::Analysis, - codegen::{local_resources_struct, module, shared_resources_struct}, -}; -use proc_macro2::TokenStream as TokenStream2; -use quote::quote; - -/// Generates support code for `#[idle]` functions -pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { - if let Some(idle) = &app.idle { - let mut mod_app = vec![]; - let mut root_idle = vec![]; - - let name = &idle.name; - - if !idle.args.shared_resources.is_empty() { - let (item, constructor) = shared_resources_struct::codegen(Context::Idle, app); - - root_idle.push(item); - mod_app.push(constructor); - } - - if !idle.args.local_resources.is_empty() { - let (item, constructor) = local_resources_struct::codegen(Context::Idle, app); - - root_idle.push(item); - - mod_app.push(constructor); - } - - root_idle.push(module::codegen(Context::Idle, app, analysis)); - - let attrs = &idle.attrs; - let context = &idle.context; - let stmts = &idle.stmts; - let user_idle = Some(quote!( - #(#attrs)* - #[allow(non_snake_case)] - fn #name(#context: #name::Context) -> ! { - use rtic::Mutex as _; - use rtic::mutex::prelude::*; - - #(#stmts)* - } - )); - - quote!( - #(#mod_app)* - - #(#root_idle)* - - #user_idle - ) - } else { - quote!() - } -} diff --git a/macros/src/codegen/init.rs b/macros/src/codegen/init.rs deleted file mode 100644 index 6e1059f..0000000 --- a/macros/src/codegen/init.rs +++ /dev/null @@ -1,95 +0,0 @@ -use proc_macro2::TokenStream as TokenStream2; -use quote::quote; - -use crate::{ - analyze::Analysis, - codegen::{local_resources_struct, module}, - syntax::{ast::App, Context}, -}; - -/// Generates support code for `#[init]` functions -pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { - let init = &app.init; - let name = &init.name; - - let mut root_init = vec![]; - - let context = &init.context; - let attrs = &init.attrs; - let stmts = &init.stmts; - let shared = &init.user_shared_struct; - let local = &init.user_local_struct; - - let shared_resources: Vec<_> = app - .shared_resources - .iter() - .map(|(k, v)| { - let ty = &v.ty; - let cfgs = &v.cfgs; - let docs = &v.docs; - quote!( - #(#cfgs)* - #(#docs)* - #k: #ty, - ) - }) - .collect(); - let local_resources: Vec<_> = app - .local_resources - .iter() - .map(|(k, v)| { - let ty = &v.ty; - let cfgs = &v.cfgs; - let docs = &v.docs; - quote!( - #(#cfgs)* - #(#docs)* - #k: #ty, - ) - }) - .collect(); - - root_init.push(quote! { - struct #shared { - #(#shared_resources)* - } - - struct #local { - #(#local_resources)* - } - }); - - // let locals_pat = locals_pat.iter(); - - let user_init_return = quote! {#shared, #local}; - - let user_init = quote!( - #(#attrs)* - #[inline(always)] - #[allow(non_snake_case)] - fn #name(#context: #name::Context) -> (#user_init_return) { - #(#stmts)* - } - ); - - let mut mod_app = None; - - // `${task}Locals` - if !init.args.local_resources.is_empty() { - let (item, constructor) = local_resources_struct::codegen(Context::Init, app); - - root_init.push(item); - - mod_app = Some(constructor); - } - - root_init.push(module::codegen(Context::Init, app, analysis)); - - quote!( - #mod_app - - #(#root_init)* - - #user_init - ) -} diff --git a/macros/src/codegen/local_resources.rs b/macros/src/codegen/local_resources.rs deleted file mode 100644 index e6d1553..0000000 --- a/macros/src/codegen/local_resources.rs +++ /dev/null @@ -1,65 +0,0 @@ -use crate::syntax::ast::App; -use crate::{analyze::Analysis, codegen::util}; -use proc_macro2::TokenStream as TokenStream2; -use quote::quote; - -/// Generates `local` variables and local resource proxies -/// -/// I.e. the `static` variables and theirs proxies. -pub fn codegen(app: &App, _analysis: &Analysis) -> TokenStream2 { - let mut mod_app = vec![]; - - // All local resources declared in the `#[local]' struct - for (name, res) in &app.local_resources { - let cfgs = &res.cfgs; - let ty = &res.ty; - let mangled_name = util::static_local_resource_ident(name); - - let attrs = &res.attrs; - - // late resources in `util::link_section_uninit` - // unless user specifies custom link section - let section = if attrs.iter().any(|attr| attr.path.is_ident("link_section")) { - None - } else { - Some(util::link_section_uninit()) - }; - - // For future use - // let doc = format!(" RTIC internal: {}:{}", file!(), line!()); - mod_app.push(quote!( - #[allow(non_camel_case_types)] - #[allow(non_upper_case_globals)] - // #[doc = #doc] - #[doc(hidden)] - #(#attrs)* - #(#cfgs)* - #section - static #mangled_name: rtic::RacyCell> = rtic::RacyCell::new(core::mem::MaybeUninit::uninit()); - )); - } - - // All declared `local = [NAME: TY = EXPR]` local resources - for (task_name, resource_name, task_local) in app.declared_local_resources() { - let cfgs = &task_local.cfgs; - let ty = &task_local.ty; - let expr = &task_local.expr; - let attrs = &task_local.attrs; - - let mangled_name = util::declared_static_local_resource_ident(resource_name, task_name); - - // For future use - // let doc = format!(" RTIC internal: {}:{}", file!(), line!()); - mod_app.push(quote!( - #[allow(non_camel_case_types)] - #[allow(non_upper_case_globals)] - // #[doc = #doc] - #[doc(hidden)] - #(#attrs)* - #(#cfgs)* - static #mangled_name: rtic::RacyCell<#ty> = rtic::RacyCell::new(#expr); - )); - } - - quote!(#(#mod_app)*) -} diff --git a/macros/src/codegen/local_resources_struct.rs b/macros/src/codegen/local_resources_struct.rs deleted file mode 100644 index 100c3eb..0000000 --- a/macros/src/codegen/local_resources_struct.rs +++ /dev/null @@ -1,102 +0,0 @@ -use crate::syntax::{ - ast::{App, TaskLocal}, - Context, -}; -use proc_macro2::TokenStream as TokenStream2; -use quote::quote; - -use crate::codegen::util; - -/// Generates local resources structs -pub fn codegen(ctxt: Context, app: &App) -> (TokenStream2, TokenStream2) { - let resources = match ctxt { - Context::Init => &app.init.args.local_resources, - Context::Idle => { - &app.idle - .as_ref() - .expect("RTIC-ICE: unable to get idle name") - .args - .local_resources - } - Context::HardwareTask(name) => &app.hardware_tasks[name].args.local_resources, - Context::SoftwareTask(name) => &app.software_tasks[name].args.local_resources, - }; - - let task_name = util::get_task_name(ctxt, app); - - let mut fields = vec![]; - let mut values = vec![]; - - for (name, task_local) in resources { - let (cfgs, ty, is_declared) = match task_local { - TaskLocal::External => { - let r = app.local_resources.get(name).expect("UNREACHABLE"); - (&r.cfgs, &r.ty, false) - } - TaskLocal::Declared(r) => (&r.cfgs, &r.ty, true), - }; - - let lt = if ctxt.runs_once() { - quote!('static) - } else { - quote!('a) - }; - - let mangled_name = if matches!(task_local, TaskLocal::External) { - util::static_local_resource_ident(name) - } else { - util::declared_static_local_resource_ident(name, &task_name) - }; - - fields.push(quote!( - #(#cfgs)* - #[allow(missing_docs)] - pub #name: &#lt mut #ty - )); - - let expr = if is_declared { - // If the local resources is already initialized, we only need to access its value and - // not go through an `MaybeUninit` - quote!(&mut *#mangled_name.get_mut()) - } else { - quote!(&mut *(&mut *#mangled_name.get_mut()).as_mut_ptr()) - }; - - values.push(quote!( - #(#cfgs)* - #name: #expr - )); - } - - fields.push(quote!( - #[doc(hidden)] - pub __rtic_internal_marker: ::core::marker::PhantomData<&'a ()> - )); - - values.push(quote!(__rtic_internal_marker: ::core::marker::PhantomData)); - - let doc = format!("Local resources `{}` has access to", ctxt.ident(app)); - let ident = util::local_resources_ident(ctxt, app); - let item = quote!( - #[allow(non_snake_case)] - #[allow(non_camel_case_types)] - #[doc = #doc] - pub struct #ident<'a> { - #(#fields,)* - } - ); - - let constructor = quote!( - impl<'a> #ident<'a> { - #[inline(always)] - #[allow(missing_docs)] - pub unsafe fn new() -> Self { - #ident { - #(#values,)* - } - } - } - ); - - (item, constructor) -} diff --git a/macros/src/codegen/main.rs b/macros/src/codegen/main.rs deleted file mode 100644 index 2775d25..0000000 --- a/macros/src/codegen/main.rs +++ /dev/null @@ -1,52 +0,0 @@ -use crate::{analyze::Analysis, codegen::util, syntax::ast::App}; -use proc_macro2::TokenStream as TokenStream2; -use quote::quote; - -use super::{assertions, post_init, pre_init}; - -/// Generates code for `fn main` -pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { - let assertion_stmts = assertions::codegen(app, analysis); - - let pre_init_stmts = pre_init::codegen(app, analysis); - - let post_init_stmts = post_init::codegen(app, analysis); - - let call_idle = if let Some(idle) = &app.idle { - let name = &idle.name; - quote!(#name(#name::Context::new())) - } else if analysis.channels.get(&0).is_some() { - let dispatcher = util::zero_prio_dispatcher_ident(); - quote!(#dispatcher();) - } else { - quote!(loop { - rtic::export::nop() - }) - }; - - let main = util::suffixed("main"); - let init_name = &app.init.name; - quote!( - #[doc(hidden)] - #[no_mangle] - unsafe extern "C" fn #main() -> ! { - #(#assertion_stmts)* - - #(#pre_init_stmts)* - - #[inline(never)] - fn __rtic_init_resources(f: F) where F: FnOnce() { - f(); - } - - // Wrap late_init_stmts in a function to ensure that stack space is reclaimed. - __rtic_init_resources(||{ - let (shared_resources, local_resources) = #init_name(#init_name::Context::new(core.into())); - - #(#post_init_stmts)* - }); - - #call_idle - } - ) -} diff --git a/macros/src/codegen/module.rs b/macros/src/codegen/module.rs deleted file mode 100644 index 4725b9a..0000000 --- a/macros/src/codegen/module.rs +++ /dev/null @@ -1,194 +0,0 @@ -use crate::syntax::{ast::App, Context}; -use crate::{analyze::Analysis, codegen::util}; -use proc_macro2::TokenStream as TokenStream2; -use quote::quote; - -#[allow(clippy::too_many_lines)] -pub fn codegen(ctxt: Context, app: &App, analysis: &Analysis) -> TokenStream2 { - let mut items = vec![]; - let mut module_items = vec![]; - let mut fields = vec![]; - let mut values = vec![]; - // Used to copy task cfgs to the whole module - let mut task_cfgs = vec![]; - - let name = ctxt.ident(app); - - match ctxt { - Context::Init => { - fields.push(quote!( - /// Core (Cortex-M) peripherals - pub core: rtic::export::Peripherals - )); - - if app.args.peripherals { - let device = &app.args.device; - - fields.push(quote!( - /// Device peripherals - pub device: #device::Peripherals - )); - - values.push(quote!(device: #device::Peripherals::steal())); - } - - fields.push(quote!( - /// Critical section token for init - pub cs: rtic::export::CriticalSection<'a> - )); - - values.push(quote!(cs: rtic::export::CriticalSection::new())); - - values.push(quote!(core)); - } - - Context::Idle | Context::HardwareTask(_) | Context::SoftwareTask(_) => {} - } - - if ctxt.has_local_resources(app) { - let ident = util::local_resources_ident(ctxt, app); - - module_items.push(quote!( - #[doc(inline)] - pub use super::#ident as LocalResources; - )); - - fields.push(quote!( - /// Local Resources this task has access to - pub local: #name::LocalResources<'a> - )); - - values.push(quote!(local: #name::LocalResources::new())); - } - - if ctxt.has_shared_resources(app) { - let ident = util::shared_resources_ident(ctxt, app); - - module_items.push(quote!( - #[doc(inline)] - pub use super::#ident as SharedResources; - )); - - fields.push(quote!( - /// Shared Resources this task has access to - pub shared: #name::SharedResources<'a> - )); - - values.push(quote!(shared: #name::SharedResources::new())); - } - - let doc = match ctxt { - Context::Idle => "Idle loop", - Context::Init => "Initialization function", - Context::HardwareTask(_) => "Hardware task", - Context::SoftwareTask(_) => "Software task", - }; - - let v = Vec::new(); - let cfgs = match ctxt { - Context::HardwareTask(t) => &app.hardware_tasks[t].cfgs, - Context::SoftwareTask(t) => &app.software_tasks[t].cfgs, - _ => &v, - }; - - let core = if ctxt.is_init() { - Some(quote!(core: rtic::export::Peripherals,)) - } else { - None - }; - - let internal_context_name = util::internal_task_ident(name, "Context"); - let exec_name = util::internal_task_ident(name, "EXEC"); - - items.push(quote!( - #(#cfgs)* - /// Execution context - #[allow(non_snake_case)] - #[allow(non_camel_case_types)] - pub struct #internal_context_name<'a> { - #[doc(hidden)] - __rtic_internal_p: ::core::marker::PhantomData<&'a ()>, - #(#fields,)* - } - - #(#cfgs)* - impl<'a> #internal_context_name<'a> { - #[inline(always)] - #[allow(missing_docs)] - pub unsafe fn new(#core) -> Self { - #internal_context_name { - __rtic_internal_p: ::core::marker::PhantomData, - #(#values,)* - } - } - } - )); - - module_items.push(quote!( - #(#cfgs)* - #[doc(inline)] - pub use super::#internal_context_name as Context; - )); - - if let Context::SoftwareTask(..) = ctxt { - let spawnee = &app.software_tasks[name]; - let priority = spawnee.args.priority; - let cfgs = &spawnee.cfgs; - // Store a copy of the task cfgs - task_cfgs = cfgs.clone(); - - let pend_interrupt = if priority > 0 { - let device = &app.args.device; - let enum_ = util::interrupt_ident(); - let interrupt = &analysis.interrupts.get(&priority).expect("UREACHABLE").0; - quote!(rtic::pend(#device::#enum_::#interrupt);) - } else { - quote!() - }; - - let internal_spawn_ident = util::internal_task_ident(name, "spawn"); - let (input_args, input_tupled, input_untupled, input_ty) = - util::regroup_inputs(&spawnee.inputs); - - // Spawn caller - items.push(quote!( - #(#cfgs)* - /// Spawns the task directly - #[allow(non_snake_case)] - #[doc(hidden)] - pub fn #internal_spawn_ident(#(#input_args,)*) -> Result<(), #input_ty> { - - if #exec_name.spawn(|| #name(unsafe { #name::Context::new() } #(,#input_untupled)*) ) { - - #pend_interrupt - - Ok(()) - } else { - Err(#input_tupled) - } - - } - )); - - module_items.push(quote!( - #(#cfgs)* - #[doc(inline)] - pub use super::#internal_spawn_ident as spawn; - )); - } - - if items.is_empty() { - quote!() - } else { - quote!( - #(#items)* - - #[allow(non_snake_case)] - #(#task_cfgs)* - #[doc = #doc] - pub mod #name { - #(#module_items)* - } - ) - } -} diff --git a/macros/src/codegen/post_init.rs b/macros/src/codegen/post_init.rs deleted file mode 100644 index c4e5383..0000000 --- a/macros/src/codegen/post_init.rs +++ /dev/null @@ -1,47 +0,0 @@ -use crate::{analyze::Analysis, codegen::util, syntax::ast::App}; -use proc_macro2::TokenStream as TokenStream2; -use quote::quote; - -/// Generates code that runs after `#[init]` returns -pub fn codegen(app: &App, analysis: &Analysis) -> Vec { - let mut stmts = vec![]; - - // Initialize shared resources - for (name, res) in &app.shared_resources { - let mangled_name = util::static_shared_resource_ident(name); - // If it's live - let cfgs = res.cfgs.clone(); - if analysis.shared_resources.get(name).is_some() { - stmts.push(quote!( - // We include the cfgs - #(#cfgs)* - // Resource is a RacyCell> - // - `get_mut` to obtain a raw pointer to `MaybeUninit` - // - `write` the defined value for the late resource T - #mangled_name.get_mut().write(core::mem::MaybeUninit::new(shared_resources.#name)); - )); - } - } - - // Initialize local resources - for (name, res) in &app.local_resources { - let mangled_name = util::static_local_resource_ident(name); - // If it's live - let cfgs = res.cfgs.clone(); - if analysis.local_resources.get(name).is_some() { - stmts.push(quote!( - // We include the cfgs - #(#cfgs)* - // Resource is a RacyCell> - // - `get_mut` to obtain a raw pointer to `MaybeUninit` - // - `write` the defined value for the late resource T - #mangled_name.get_mut().write(core::mem::MaybeUninit::new(local_resources.#name)); - )); - } - } - - // Enable the interrupts -- this completes the `init`-ialization phase - stmts.push(quote!(rtic::export::interrupt::enable();)); - - stmts -} diff --git a/macros/src/codegen/pre_init.rs b/macros/src/codegen/pre_init.rs deleted file mode 100644 index 28ba29c..0000000 --- a/macros/src/codegen/pre_init.rs +++ /dev/null @@ -1,85 +0,0 @@ -use crate::syntax::ast::App; -use proc_macro2::TokenStream as TokenStream2; -use quote::quote; - -use crate::{analyze::Analysis, codegen::util}; - -/// Generates code that runs before `#[init]` -pub fn codegen(app: &App, analysis: &Analysis) -> Vec { - let mut stmts = vec![]; - - let rt_err = util::rt_err_ident(); - - // Disable interrupts -- `init` must run with interrupts disabled - stmts.push(quote!(rtic::export::interrupt::disable();)); - - stmts.push(quote!( - // To set the variable in cortex_m so the peripherals cannot be taken multiple times - let mut core: rtic::export::Peripherals = rtic::export::Peripherals::steal().into(); - )); - - let device = &app.args.device; - let nvic_prio_bits = quote!(#device::NVIC_PRIO_BITS); - - // check that all dispatchers exists in the `Interrupt` enumeration regardless of whether - // they are used or not - let interrupt = util::interrupt_ident(); - for name in app.args.dispatchers.keys() { - stmts.push(quote!(let _ = #rt_err::#interrupt::#name;)); - } - - let interrupt_ids = analysis.interrupts.iter().map(|(p, (id, _))| (p, id)); - - // Unmask interrupts and set their priorities - for (&priority, name) in interrupt_ids.chain(app.hardware_tasks.values().filter_map(|task| { - if util::is_exception(&task.args.binds) { - // We do exceptions in another pass - None - } else { - Some((&task.args.priority, &task.args.binds)) - } - })) { - let es = format!( - "Maximum priority used by interrupt vector '{name}' is more than supported by hardware" - ); - // Compile time assert that this priority is supported by the device - stmts.push(quote!( - const _: () = if (1 << #nvic_prio_bits) < #priority as usize { ::core::panic!(#es); }; - )); - - stmts.push(quote!( - core.NVIC.set_priority( - #rt_err::#interrupt::#name, - rtic::export::logical2hw(#priority, #nvic_prio_bits), - ); - )); - - // NOTE unmask the interrupt *after* setting its priority: changing the priority of a pended - // interrupt is implementation defined - stmts.push(quote!(rtic::export::NVIC::unmask(#rt_err::#interrupt::#name);)); - } - - // Set exception priorities - for (name, priority) in app.hardware_tasks.values().filter_map(|task| { - if util::is_exception(&task.args.binds) { - Some((&task.args.binds, task.args.priority)) - } else { - None - } - }) { - let es = format!( - "Maximum priority used by interrupt vector '{name}' is more than supported by hardware" - ); - // Compile time assert that this priority is supported by the device - stmts.push(quote!( - const _: () = if (1 << #nvic_prio_bits) < #priority as usize { ::core::panic!(#es); }; - )); - - stmts.push(quote!(core.SCB.set_priority( - rtic::export::SystemHandler::#name, - rtic::export::logical2hw(#priority, #nvic_prio_bits), - );)); - } - - stmts -} diff --git a/macros/src/codegen/shared_resources.rs b/macros/src/codegen/shared_resources.rs deleted file mode 100644 index 19fd13f..0000000 --- a/macros/src/codegen/shared_resources.rs +++ /dev/null @@ -1,183 +0,0 @@ -use crate::syntax::{analyze::Ownership, ast::App}; -use crate::{analyze::Analysis, codegen::util}; -use proc_macro2::TokenStream as TokenStream2; -use quote::quote; -use std::collections::HashMap; - -/// Generates `static` variables and shared resource proxies -pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { - let mut mod_app = vec![]; - let mut mod_resources = vec![]; - - for (name, res) in &app.shared_resources { - let cfgs = &res.cfgs; - let ty = &res.ty; - let mangled_name = &util::static_shared_resource_ident(name); - - let attrs = &res.attrs; - - // late resources in `util::link_section_uninit` - // unless user specifies custom link section - let section = if attrs.iter().any(|attr| attr.path.is_ident("link_section")) { - None - } else { - Some(util::link_section_uninit()) - }; - - // For future use - // let doc = format!(" RTIC internal: {}:{}", file!(), line!()); - mod_app.push(quote!( - #[allow(non_camel_case_types)] - #[allow(non_upper_case_globals)] - // #[doc = #doc] - #[doc(hidden)] - #(#attrs)* - #(#cfgs)* - #section - static #mangled_name: rtic::RacyCell> = rtic::RacyCell::new(core::mem::MaybeUninit::uninit()); - )); - - // For future use - // let doc = format!(" RTIC internal: {}:{}", file!(), line!()); - - let shared_name = util::need_to_lock_ident(name); - - if !res.properties.lock_free { - mod_resources.push(quote!( - // #[doc = #doc] - #[doc(hidden)] - #[allow(non_camel_case_types)] - #(#cfgs)* - pub struct #shared_name<'a> { - __rtic_internal_p: ::core::marker::PhantomData<&'a ()>, - } - - #(#cfgs)* - impl<'a> #shared_name<'a> { - #[inline(always)] - pub unsafe fn new() -> Self { - #shared_name { __rtic_internal_p: ::core::marker::PhantomData } - } - } - )); - - let ptr = quote!( - #(#cfgs)* - #mangled_name.get_mut() as *mut _ - ); - - let ceiling = match analysis.ownerships.get(name) { - Some(Ownership::Owned { priority } | Ownership::CoOwned { priority }) => *priority, - Some(Ownership::Contended { ceiling }) => *ceiling, - None => 0, - }; - - // For future use - // let doc = format!(" RTIC internal ({} resource): {}:{}", doc, file!(), line!()); - - mod_app.push(util::impl_mutex( - app, - cfgs, - true, - &shared_name, - "e!(#ty), - ceiling, - &ptr, - )); - } - } - - let mod_resources = if mod_resources.is_empty() { - quote!() - } else { - quote!(mod shared_resources { - #(#mod_resources)* - }) - }; - - // Computing mapping of used interrupts to masks - let interrupt_ids = analysis.interrupts.iter().map(|(p, (id, _))| (p, id)); - - let mut prio_to_masks = HashMap::new(); - let device = &app.args.device; - let mut uses_exceptions_with_resources = false; - - let mut mask_ids = Vec::new(); - - for (&priority, name) in interrupt_ids.chain(app.hardware_tasks.values().flat_map(|task| { - if !util::is_exception(&task.args.binds) { - Some((&task.args.priority, &task.args.binds)) - } else { - // If any resource to the exception uses non-lock-free or non-local resources this is - // not allwed on thumbv6. - uses_exceptions_with_resources = uses_exceptions_with_resources - || task - .args - .shared_resources - .iter() - .map(|(ident, access)| { - if access.is_exclusive() { - if let Some(r) = app.shared_resources.get(ident) { - !r.properties.lock_free - } else { - false - } - } else { - false - } - }) - .any(|v| v); - - None - } - })) { - let v: &mut Vec<_> = prio_to_masks.entry(priority - 1).or_default(); - v.push(quote!(#device::Interrupt::#name as u32)); - mask_ids.push(quote!(#device::Interrupt::#name as u32)); - } - - // Call rtic::export::create_mask([Mask; N]), where the array is the list of shifts - - let mut mask_arr = Vec::new(); - // NOTE: 0..3 assumes max 4 priority levels according to M0, M23 spec - for i in 0..3 { - let v = if let Some(v) = prio_to_masks.get(&i) { - v.clone() - } else { - Vec::new() - }; - - mask_arr.push(quote!( - rtic::export::create_mask([#(#v),*]) - )); - } - - // Generate a constant for the number of chunks needed by Mask. - let chunks_name = util::priority_mask_chunks_ident(); - mod_app.push(quote!( - #[doc(hidden)] - #[allow(non_upper_case_globals)] - const #chunks_name: usize = rtic::export::compute_mask_chunks([#(#mask_ids),*]); - )); - - let masks_name = util::priority_masks_ident(); - mod_app.push(quote!( - #[doc(hidden)] - #[allow(non_upper_case_globals)] - const #masks_name: [rtic::export::Mask<#chunks_name>; 3] = [#(#mask_arr),*]; - )); - - if uses_exceptions_with_resources { - mod_app.push(quote!( - #[doc(hidden)] - #[allow(non_upper_case_globals)] - const __rtic_internal_V6_ERROR: () = rtic::export::no_basepri_panic(); - )); - } - - quote!( - #(#mod_app)* - - #mod_resources - ) -} diff --git a/macros/src/codegen/shared_resources_struct.rs b/macros/src/codegen/shared_resources_struct.rs deleted file mode 100644 index fa6f0fc..0000000 --- a/macros/src/codegen/shared_resources_struct.rs +++ /dev/null @@ -1,119 +0,0 @@ -use crate::syntax::{ast::App, Context}; -use proc_macro2::TokenStream as TokenStream2; -use quote::quote; - -use crate::codegen::util; - -/// Generate shared resources structs -pub fn codegen(ctxt: Context, app: &App) -> (TokenStream2, TokenStream2) { - let resources = match ctxt { - Context::Init => unreachable!("Tried to generate shared resources struct for init"), - Context::Idle => { - &app.idle - .as_ref() - .expect("RTIC-ICE: unable to get idle name") - .args - .shared_resources - } - Context::HardwareTask(name) => &app.hardware_tasks[name].args.shared_resources, - Context::SoftwareTask(name) => &app.software_tasks[name].args.shared_resources, - }; - - let mut fields = vec![]; - let mut values = vec![]; - - for (name, access) in resources { - let res = app.shared_resources.get(name).expect("UNREACHABLE"); - - let cfgs = &res.cfgs; - - // access hold if the resource is [x] (exclusive) or [&x] (shared) - let mut_ = if access.is_exclusive() { - Some(quote!(mut)) - } else { - None - }; - let ty = &res.ty; - let mangled_name = util::static_shared_resource_ident(name); - let shared_name = util::need_to_lock_ident(name); - - if res.properties.lock_free { - // Lock free resources of `idle` and `init` get 'static lifetime - let lt = if ctxt.runs_once() { - quote!('static) - } else { - quote!('a) - }; - - fields.push(quote!( - #(#cfgs)* - #[allow(missing_docs)] - pub #name: &#lt #mut_ #ty - )); - } else if access.is_shared() { - fields.push(quote!( - #(#cfgs)* - #[allow(missing_docs)] - pub #name: &'a #ty - )); - } else { - fields.push(quote!( - #(#cfgs)* - #[allow(missing_docs)] - pub #name: shared_resources::#shared_name<'a> - )); - - values.push(quote!( - #(#cfgs)* - #name: shared_resources::#shared_name::new() - - )); - - // continue as the value has been filled, - continue; - } - - let expr = if access.is_exclusive() { - quote!(&mut *(&mut *#mangled_name.get_mut()).as_mut_ptr()) - } else { - quote!(&*(&*#mangled_name.get()).as_ptr()) - }; - - values.push(quote!( - #(#cfgs)* - #name: #expr - )); - } - - fields.push(quote!( - #[doc(hidden)] - pub __rtic_internal_marker: core::marker::PhantomData<&'a ()> - )); - - values.push(quote!(__rtic_internal_marker: core::marker::PhantomData)); - - let doc = format!("Shared resources `{}` has access to", ctxt.ident(app)); - let ident = util::shared_resources_ident(ctxt, app); - let item = quote!( - #[allow(non_snake_case)] - #[allow(non_camel_case_types)] - #[doc = #doc] - pub struct #ident<'a> { - #(#fields,)* - } - ); - - let constructor = quote!( - impl<'a> #ident<'a> { - #[inline(always)] - #[allow(missing_docs)] - pub unsafe fn new() -> Self { - #ident { - #(#values,)* - } - } - } - ); - - (item, constructor) -} diff --git a/macros/src/codegen/software_tasks.rs b/macros/src/codegen/software_tasks.rs deleted file mode 100644 index 34fc851..0000000 --- a/macros/src/codegen/software_tasks.rs +++ /dev/null @@ -1,64 +0,0 @@ -use crate::syntax::{ast::App, Context}; -use crate::{ - analyze::Analysis, - codegen::{local_resources_struct, module, shared_resources_struct}, -}; -use proc_macro2::TokenStream as TokenStream2; -use quote::quote; - -pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { - let mut mod_app = vec![]; - let mut root = vec![]; - let mut user_tasks = vec![]; - - // Any task - for (name, task) in app.software_tasks.iter() { - if !task.args.local_resources.is_empty() { - let (item, constructor) = - local_resources_struct::codegen(Context::SoftwareTask(name), app); - - root.push(item); - - mod_app.push(constructor); - } - - if !task.args.shared_resources.is_empty() { - let (item, constructor) = - shared_resources_struct::codegen(Context::SoftwareTask(name), app); - - root.push(item); - - mod_app.push(constructor); - } - - if !&task.is_extern { - let context = &task.context; - let attrs = &task.attrs; - let cfgs = &task.cfgs; - let stmts = &task.stmts; - let inputs = &task.inputs; - - user_tasks.push(quote!( - #(#attrs)* - #(#cfgs)* - #[allow(non_snake_case)] - async fn #name<'a>(#context: #name::Context<'a> #(,#inputs)*) { - use rtic::Mutex as _; - use rtic::mutex::prelude::*; - - #(#stmts)* - } - )); - } - - root.push(module::codegen(Context::SoftwareTask(name), app, analysis)); - } - - quote!( - #(#mod_app)* - - #(#root)* - - #(#user_tasks)* - ) -} diff --git a/macros/src/codegen/util.rs b/macros/src/codegen/util.rs deleted file mode 100644 index e121487..0000000 --- a/macros/src/codegen/util.rs +++ /dev/null @@ -1,238 +0,0 @@ -use crate::syntax::{ast::App, Context}; -use core::sync::atomic::{AtomicUsize, Ordering}; -use proc_macro2::{Span, TokenStream as TokenStream2}; -use quote::quote; -use syn::{Attribute, Ident, PatType}; - -const RTIC_INTERNAL: &str = "__rtic_internal"; - -/// Generates a `Mutex` implementation -pub fn impl_mutex( - app: &App, - cfgs: &[Attribute], - resources_prefix: bool, - name: &Ident, - ty: &TokenStream2, - ceiling: u8, - ptr: &TokenStream2, -) -> TokenStream2 { - let path = if resources_prefix { - quote!(shared_resources::#name) - } else { - quote!(#name) - }; - - let device = &app.args.device; - let masks_name = priority_masks_ident(); - quote!( - #(#cfgs)* - impl<'a> rtic::Mutex for #path<'a> { - type T = #ty; - - #[inline(always)] - fn lock(&mut self, f: impl FnOnce(&mut #ty) -> RTIC_INTERNAL_R) -> RTIC_INTERNAL_R { - /// Priority ceiling - const CEILING: u8 = #ceiling; - - unsafe { - rtic::export::lock( - #ptr, - CEILING, - #device::NVIC_PRIO_BITS, - &#masks_name, - f, - ) - } - } - } - ) -} - -pub fn interrupt_ident() -> Ident { - let span = Span::call_site(); - Ident::new("interrupt", span) -} - -/// Whether `name` is an exception with configurable priority -pub fn is_exception(name: &Ident) -> bool { - let s = name.to_string(); - - matches!( - &*s, - "MemoryManagement" - | "BusFault" - | "UsageFault" - | "SecureFault" - | "SVCall" - | "DebugMonitor" - | "PendSV" - | "SysTick" - ) -} - -/// Mark a name as internal -pub fn mark_internal_name(name: &str) -> Ident { - Ident::new(&format!("{RTIC_INTERNAL}_{name}"), Span::call_site()) -} - -/// Generate an internal identifier for tasks -pub fn internal_task_ident(task: &Ident, ident_name: &str) -> Ident { - mark_internal_name(&format!("{task}_{ident_name}")) -} - -fn link_section_index() -> usize { - static INDEX: AtomicUsize = AtomicUsize::new(0); - - INDEX.fetch_add(1, Ordering::Relaxed) -} - -/// Add `link_section` attribute -pub fn link_section_uninit() -> TokenStream2 { - let section = format!(".uninit.rtic{}", link_section_index()); - - quote!(#[link_section = #section]) -} - -/// Regroups the inputs of a task -/// -/// `inputs` could be &[`input: Foo`] OR &[`mut x: i32`, `ref y: i64`] -pub fn regroup_inputs( - inputs: &[PatType], -) -> ( - // args e.g. &[`_0`], &[`_0: i32`, `_1: i64`] - Vec, - // tupled e.g. `_0`, `(_0, _1)` - TokenStream2, - // untupled e.g. &[`_0`], &[`_0`, `_1`] - Vec, - // ty e.g. `Foo`, `(i32, i64)` - TokenStream2, -) { - if inputs.len() == 1 { - let ty = &inputs[0].ty; - - ( - vec![quote!(_0: #ty)], - quote!(_0), - vec![quote!(_0)], - quote!(#ty), - ) - } else { - let mut args = vec![]; - let mut pats = vec![]; - let mut tys = vec![]; - - for (i, input) in inputs.iter().enumerate() { - let i = Ident::new(&format!("_{}", i), Span::call_site()); - let ty = &input.ty; - - args.push(quote!(#i: #ty)); - - pats.push(quote!(#i)); - - tys.push(quote!(#ty)); - } - - let tupled = { - let pats = pats.clone(); - quote!((#(#pats,)*)) - }; - let ty = quote!((#(#tys,)*)); - (args, tupled, pats, ty) - } -} - -/// Get the ident for the name of the task -pub fn get_task_name(ctxt: Context, app: &App) -> Ident { - let s = match ctxt { - Context::Init => app.init.name.to_string(), - Context::Idle => app - .idle - .as_ref() - .expect("RTIC-ICE: unable to find idle name") - .name - .to_string(), - Context::HardwareTask(ident) | Context::SoftwareTask(ident) => ident.to_string(), - }; - - Ident::new(&s, Span::call_site()) -} - -/// Generates a pre-reexport identifier for the "shared resources" struct -pub fn shared_resources_ident(ctxt: Context, app: &App) -> Ident { - let mut s = match ctxt { - Context::Init => app.init.name.to_string(), - Context::Idle => app - .idle - .as_ref() - .expect("RTIC-ICE: unable to find idle name") - .name - .to_string(), - Context::HardwareTask(ident) | Context::SoftwareTask(ident) => ident.to_string(), - }; - - s.push_str("SharedResources"); - - mark_internal_name(&s) -} - -/// Generates a pre-reexport identifier for the "local resources" struct -pub fn local_resources_ident(ctxt: Context, app: &App) -> Ident { - let mut s = match ctxt { - Context::Init => app.init.name.to_string(), - Context::Idle => app - .idle - .as_ref() - .expect("RTIC-ICE: unable to find idle name") - .name - .to_string(), - Context::HardwareTask(ident) | Context::SoftwareTask(ident) => ident.to_string(), - }; - - s.push_str("LocalResources"); - - mark_internal_name(&s) -} - -/// Suffixed identifier -pub fn suffixed(name: &str) -> Ident { - let span = Span::call_site(); - Ident::new(name, span) -} - -pub fn static_shared_resource_ident(name: &Ident) -> Ident { - mark_internal_name(&format!("shared_resource_{name}")) -} - -/// Generates an Ident for the number of 32 bit chunks used for Mask storage. -pub fn priority_mask_chunks_ident() -> Ident { - mark_internal_name("MASK_CHUNKS") -} - -pub fn priority_masks_ident() -> Ident { - mark_internal_name("MASKS") -} - -pub fn static_local_resource_ident(name: &Ident) -> Ident { - mark_internal_name(&format!("local_resource_{name}")) -} - -pub fn declared_static_local_resource_ident(name: &Ident, task_name: &Ident) -> Ident { - mark_internal_name(&format!("local_{task_name}_{name}")) -} - -pub fn need_to_lock_ident(name: &Ident) -> Ident { - Ident::new(&format!("{name}_that_needs_to_be_locked"), name.span()) -} - -pub fn zero_prio_dispatcher_ident() -> Ident { - Ident::new("__rtic_internal_async_0_prio_dispatcher", Span::call_site()) -} - -/// The name to get better RT flag errors -pub fn rt_err_ident() -> Ident { - Ident::new( - "you_must_enable_the_rt_feature_for_the_pac_in_your_cargo_toml", - Span::call_site(), - ) -} diff --git a/macros/src/lib.rs b/macros/src/lib.rs deleted file mode 100644 index a8422d0..0000000 --- a/macros/src/lib.rs +++ /dev/null @@ -1,92 +0,0 @@ -#![doc( - html_logo_url = "https://raw.githubusercontent.com/rtic-rs/rtic/master/book/en/src/RTIC.svg", - html_favicon_url = "https://raw.githubusercontent.com/rtic-rs/rtic/master/book/en/src/RTIC.svg" -)] - -//deny_warnings_placeholder_for_ci - -use proc_macro::TokenStream; -use std::{env, fs, path::Path}; - -mod analyze; -mod bindings; -mod check; -mod codegen; -mod syntax; - -// Used for mocking the API in testing -#[doc(hidden)] -#[proc_macro_attribute] -pub fn mock_app(args: TokenStream, input: TokenStream) -> TokenStream { - if let Err(e) = syntax::parse(args, input) { - e.to_compile_error().into() - } else { - "fn main() {}".parse().unwrap() - } -} - -/// Attribute used to declare a RTIC application -/// -/// For user documentation see the [RTIC book](https://rtic.rs) -/// -/// # Panics -/// -/// Should never panic, cargo feeds a path which is later converted to a string -#[proc_macro_attribute] -pub fn app(args: TokenStream, input: TokenStream) -> TokenStream { - let (app, analysis) = match syntax::parse(args, input) { - Err(e) => return e.to_compile_error().into(), - Ok(x) => x, - }; - - if let Err(e) = check::app(&app) { - return e.to_compile_error().into(); - } - - let analysis = analyze::app(analysis, &app); - - let ts = codegen::app(&app, &analysis); - - // Default output path: /target/ - let mut out_dir = Path::new("target"); - - // Get output directory from Cargo environment - // TODO don't want to break builds if OUT_DIR is not set, is this ever the case? - let out_str = env::var("OUT_DIR").unwrap_or_else(|_| "".to_string()); - - if !out_dir.exists() { - // Set out_dir to OUT_DIR - out_dir = Path::new(&out_str); - - // Default build path, annotated below: - // $(pwd)/target/thumbv7em-none-eabihf/debug/build/cortex-m-rtic-/out/ - // ///debug/build/cortex-m-rtic-/out/ - // - // traverse up to first occurrence of TARGET, approximated with starts_with("thumbv") - // and use the parent() of this path - // - // If no "target" directory is found, / is used - for path in out_dir.ancestors() { - if let Some(dir) = path.components().last() { - let dir = dir.as_os_str().to_str().unwrap(); - - if dir.starts_with("thumbv") || dir.starts_with("riscv") { - if let Some(out) = path.parent() { - out_dir = out; - break; - } - // If no parent, just use it - out_dir = path; - break; - } - } - } - } - - // Try to write the expanded code to disk - if let Some(out_str) = out_dir.to_str() { - fs::write(format!("{out_str}/rtic-expansion.rs"), ts.to_string()).ok(); - } - - ts.into() -} diff --git a/macros/src/syntax.rs b/macros/src/syntax.rs deleted file mode 100644 index d6f5a47..0000000 --- a/macros/src/syntax.rs +++ /dev/null @@ -1,121 +0,0 @@ -#[allow(unused_extern_crates)] -extern crate proc_macro; - -use proc_macro::TokenStream; - -use indexmap::{IndexMap, IndexSet}; -use proc_macro2::TokenStream as TokenStream2; -use syn::Ident; - -use crate::syntax::ast::App; - -mod accessors; -pub mod analyze; -pub mod ast; -mod check; -mod parse; - -/// An ordered map keyed by identifier -pub type Map = IndexMap; - -/// An order set -pub type Set = IndexSet; - -/// Execution context -#[derive(Clone, Copy)] -pub enum Context<'a> { - /// The `idle` context - Idle, - - /// The `init`-ialization function - Init, - - /// A async software task - SoftwareTask(&'a Ident), - - /// A hardware task - HardwareTask(&'a Ident), -} - -impl<'a> Context<'a> { - /// The identifier of this context - pub fn ident(&self, app: &'a App) -> &'a Ident { - match self { - Context::HardwareTask(ident) => ident, - Context::Idle => &app.idle.as_ref().unwrap().name, - Context::Init => &app.init.name, - Context::SoftwareTask(ident) => ident, - } - } - - /// Is this the `idle` context? - pub fn is_idle(&self) -> bool { - matches!(self, Context::Idle) - } - - /// Is this the `init`-ialization context? - pub fn is_init(&self) -> bool { - matches!(self, Context::Init) - } - - /// Whether this context runs only once - pub fn runs_once(&self) -> bool { - self.is_init() || self.is_idle() - } - - /// Whether this context has shared resources - pub fn has_shared_resources(&self, app: &App) -> bool { - match *self { - Context::HardwareTask(name) => { - !app.hardware_tasks[name].args.shared_resources.is_empty() - } - Context::Idle => !app.idle.as_ref().unwrap().args.shared_resources.is_empty(), - Context::Init => false, - Context::SoftwareTask(name) => { - !app.software_tasks[name].args.shared_resources.is_empty() - } - } - } - - /// Whether this context has local resources - pub fn has_local_resources(&self, app: &App) -> bool { - match *self { - Context::HardwareTask(name) => { - !app.hardware_tasks[name].args.local_resources.is_empty() - } - Context::Idle => !app.idle.as_ref().unwrap().args.local_resources.is_empty(), - Context::Init => !app.init.args.local_resources.is_empty(), - Context::SoftwareTask(name) => { - !app.software_tasks[name].args.local_resources.is_empty() - } - } - } -} - -/// Parses the input of the `#[app]` attribute -pub fn parse( - args: TokenStream, - input: TokenStream, -) -> Result<(ast::App, analyze::Analysis), syn::parse::Error> { - parse2(args.into(), input.into()) -} - -/// `proc_macro2::TokenStream` version of `parse` -pub fn parse2( - args: TokenStream2, - input: TokenStream2, -) -> Result<(ast::App, analyze::Analysis), syn::parse::Error> { - let app = parse::app(args, input)?; - check::app(&app)?; - - match analyze::app(&app) { - Err(e) => Err(e), - // If no errors, return the app and analysis results - Ok(analysis) => Ok((app, analysis)), - } -} - -enum Either { - Left(A), - Right(B), -} diff --git a/macros/src/syntax/.github/bors.toml b/macros/src/syntax/.github/bors.toml deleted file mode 100644 index aee6042..0000000 --- a/macros/src/syntax/.github/bors.toml +++ /dev/null @@ -1,3 +0,0 @@ -block_labels = ["S-blocked"] -delete_merged_branches = true -status = ["ci"] diff --git a/macros/src/syntax/.github/workflows/build.yml b/macros/src/syntax/.github/workflows/build.yml deleted file mode 100644 index 29971b1..0000000 --- a/macros/src/syntax/.github/workflows/build.yml +++ /dev/null @@ -1,213 +0,0 @@ -name: Build -on: - pull_request: - push: - branches: - - master - - staging - - trying - - bors/staging - - bors/trying - -env: - CARGO_TERM_COLOR: always - -jobs: - # Run cargo fmt --check - style: - name: style - runs-on: ubuntu-20.04 - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - override: true - components: rustfmt - - - name: Fail on warnings - run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs - - - name: cargo fmt --check - uses: actions-rs/cargo@v1 - with: - command: fmt - args: --all -- --check - - # Compilation check - check: - name: check - runs-on: ubuntu-20.04 - strategy: - matrix: - target: - - x86_64-unknown-linux-gnu - toolchain: - - stable - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Install Rust ${{ matrix.toolchain }} with target (${{ matrix.target }}) - uses: actions-rs/toolchain@v1 - with: - toolchain: ${{ matrix.toolchain }} - target: ${{ matrix.target }} - override: true - - - name: Fail on warnings - run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs - - - name: Cache Dependencies - uses: Swatinem/rust-cache@v1 - - - name: cargo check - uses: actions-rs/cargo@v1 - with: - use-cross: false - command: check - args: --target=${{ matrix.target }} - - # Clippy - clippy: - name: Cargo clippy - runs-on: ubuntu-20.04 - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Install Rust stable - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - target: x86_64-unknown-linux-gnu - override: true - - - name: Fail on warnings - run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs - - - name: Cache Dependencies - uses: Swatinem/rust-cache@v1 - - - name: cargo clippy - uses: actions-rs/cargo@v1 - with: - use-cross: false - command: clippy - - # Verify all examples - testexamples: - name: testexamples - runs-on: ubuntu-20.04 - strategy: - matrix: - target: - - x86_64-unknown-linux-gnu - toolchain: - - stable - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Install Rust ${{ matrix.toolchain }} with target (${{ matrix.target }}) - uses: actions-rs/toolchain@v1 - with: - toolchain: ${{ matrix.toolchain }} - target: ${{ matrix.target }} - override: true - - - name: Cache Dependencies - uses: Swatinem/rust-cache@v1 - - - name: Fail on warnings - run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs - - - uses: actions-rs/cargo@v1 - with: - use-cross: false - command: test - args: --examples - - # Run test suite for UI - testui: - name: testui - runs-on: ubuntu-20.04 - strategy: - matrix: - target: - - x86_64-unknown-linux-gnu - toolchain: - - stable - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Install Rust ${{ matrix.toolchain }} with target (${{ matrix.target }}) - uses: actions-rs/toolchain@v1 - with: - toolchain: ${{ matrix.toolchain }} - target: ${{ matrix.target }} - override: true - - - name: Cache Dependencies - uses: Swatinem/rust-cache@v1 - - - name: Fail on warnings - run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs - - - - uses: actions-rs/cargo@v1 - with: - use-cross: false - command: test - args: --test ui - - # Run test suite - test: - name: test - runs-on: ubuntu-20.04 - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - target: thumbv7m-none-eabi - override: true - - - name: Cache Dependencies - uses: Swatinem/rust-cache@v1 - - - name: Fail on warnings - run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs - - - uses: actions-rs/cargo@v1 - with: - use-cross: false - command: test - args: --lib - - # Refs: https://github.com/rust-lang/crater/blob/9ab6f9697c901c4a44025cf0a39b73ad5b37d198/.github/workflows/bors.yml#L125-L149 - # - # ALL THE PREVIOUS JOBS NEEDS TO BE ADDED TO THE `needs` SECTION OF THIS JOB! - - ci-success: - name: ci - if: github.event_name == 'push' && success() - needs: - - style - - check - - clippy - - testexamples - - test - - testui - runs-on: ubuntu-20.04 - steps: - - name: Mark the job as a success - run: exit 0 diff --git a/macros/src/syntax/.github/workflows/changelog.yml b/macros/src/syntax/.github/workflows/changelog.yml deleted file mode 100644 index ccf6eb9..0000000 --- a/macros/src/syntax/.github/workflows/changelog.yml +++ /dev/null @@ -1,28 +0,0 @@ -# Check that the changelog is updated for all changes. -# -# This is only run for PRs. - -on: - pull_request: - # opened, reopened, synchronize are the default types for pull_request. - # labeled, unlabeled ensure this check is also run if a label is added or removed. - types: [opened, reopened, labeled, unlabeled, synchronize] - -name: Changelog - -jobs: - changelog: - name: Changelog - runs-on: ubuntu-latest - steps: - - name: Checkout sources - uses: actions/checkout@v2 - - - name: Check that changelog updated - uses: dangoslen/changelog-enforcer@v3 - with: - changeLogPath: CHANGELOG.md - skipLabels: 'needs-changelog, skip-changelog' - missingUpdateErrorMessage: 'Please add a changelog entry in the CHANGELOG.md file.' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/macros/src/syntax/.github/workflows/properties/build.properties.json b/macros/src/syntax/.github/workflows/properties/build.properties.json deleted file mode 100644 index fd3eed3..0000000 --- a/macros/src/syntax/.github/workflows/properties/build.properties.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "Build", - "description": "RTIC Test Suite", - "iconName": "rust", - "categories": ["Rust"] -} diff --git a/macros/src/syntax/.gitignore b/macros/src/syntax/.gitignore deleted file mode 100644 index f8d7c8b..0000000 --- a/macros/src/syntax/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -**/*.rs.bk -.#* -/target/ -Cargo.lock diff --git a/macros/src/syntax/.travis.yml b/macros/src/syntax/.travis.yml deleted file mode 100644 index 52d1ffd..0000000 --- a/macros/src/syntax/.travis.yml +++ /dev/null @@ -1,31 +0,0 @@ -language: rust - -matrix: - include: - # MSRV - - env: TARGET=x86_64-unknown-linux-gnu - rust: 1.36.0 - - - env: TARGET=x86_64-unknown-linux-gnu - rust: stable - -before_install: set -e - -script: - - bash ci/script.sh - -after_script: set +e - -cache: cargo - -before_cache: - - chmod -R a+r $HOME/.cargo; - -branches: - only: - - staging - - trying - -notifications: - email: - on_success: never diff --git a/macros/src/syntax/accessors.rs b/macros/src/syntax/accessors.rs deleted file mode 100644 index e75dde6..0000000 --- a/macros/src/syntax/accessors.rs +++ /dev/null @@ -1,113 +0,0 @@ -use syn::Ident; - -use crate::syntax::{ - analyze::Priority, - ast::{Access, App, Local, TaskLocal}, -}; - -impl App { - pub(crate) fn shared_resource_accesses( - &self, - ) -> impl Iterator, &Ident, Access)> { - self.idle - .iter() - .flat_map(|idle| { - idle.args - .shared_resources - .iter() - .map(move |(name, access)| (Some(0), name, *access)) - }) - .chain(self.hardware_tasks.values().flat_map(|task| { - task.args - .shared_resources - .iter() - .map(move |(name, access)| (Some(task.args.priority), name, *access)) - })) - .chain(self.software_tasks.values().flat_map(|task| { - task.args - .shared_resources - .iter() - .map(move |(name, access)| (Some(task.args.priority), name, *access)) - })) - } - - fn is_external(task_local: &TaskLocal) -> bool { - matches!(task_local, TaskLocal::External) - } - - pub(crate) fn local_resource_accesses(&self) -> impl Iterator { - self.init - .args - .local_resources - .iter() - .filter(|(_, task_local)| Self::is_external(task_local)) // Only check the resources declared in `#[local]` - .map(move |(name, _)| name) - .chain(self.idle.iter().flat_map(|idle| { - idle.args - .local_resources - .iter() - .filter(|(_, task_local)| Self::is_external(task_local)) // Only check the resources declared in `#[local]` - .map(move |(name, _)| name) - })) - .chain(self.hardware_tasks.values().flat_map(|task| { - task.args - .local_resources - .iter() - .filter(|(_, task_local)| Self::is_external(task_local)) // Only check the resources declared in `#[local]` - .map(move |(name, _)| name) - })) - .chain(self.software_tasks.values().flat_map(|task| { - task.args - .local_resources - .iter() - .filter(|(_, task_local)| Self::is_external(task_local)) // Only check the resources declared in `#[local]` - .map(move |(name, _)| name) - })) - } - - fn get_declared_local(tl: &TaskLocal) -> Option<&Local> { - match tl { - TaskLocal::External => None, - TaskLocal::Declared(l) => Some(l), - } - } - - /// Get all declared local resources, i.e. `local = [NAME: TYPE = EXPR]`. - /// - /// Returns a vector of (task name, resource name, `Local` struct) - pub fn declared_local_resources(&self) -> Vec<(&Ident, &Ident, &Local)> { - self.init - .args - .local_resources - .iter() - .filter_map(move |(name, tl)| { - Self::get_declared_local(tl).map(|l| (&self.init.name, name, l)) - }) - .chain(self.idle.iter().flat_map(|idle| { - idle.args - .local_resources - .iter() - .filter_map(move |(name, tl)| { - Self::get_declared_local(tl) - .map(|l| (&self.idle.as_ref().unwrap().name, name, l)) - }) - })) - .chain(self.hardware_tasks.iter().flat_map(|(task_name, task)| { - task.args - .local_resources - .iter() - .filter_map(move |(name, tl)| { - Self::get_declared_local(tl).map(|l| (task_name, name, l)) - }) - })) - .chain(self.software_tasks.iter().flat_map(|(task_name, task)| { - task.args - .local_resources - .iter() - .filter_map(move |(name, tl)| { - Self::get_declared_local(tl).map(|l| (task_name, name, l)) - }) - })) - .collect() - } -} diff --git a/macros/src/syntax/analyze.rs b/macros/src/syntax/analyze.rs deleted file mode 100644 index 3ed1487..0000000 --- a/macros/src/syntax/analyze.rs +++ /dev/null @@ -1,417 +0,0 @@ -//! RTIC application analysis - -use core::cmp; -use std::collections::{BTreeMap, BTreeSet, HashMap}; - -use indexmap::{IndexMap, IndexSet}; -use syn::{Ident, Type}; - -use crate::syntax::{ - ast::{App, LocalResources, TaskLocal}, - Set, -}; - -pub(crate) fn app(app: &App) -> Result { - // Collect all tasks into a vector - type TaskName = Ident; - type Priority = u8; - - // The task list is a Tuple (Name, Shared Resources, Local Resources, Priority) - let task_resources_list: Vec<(TaskName, Vec<&Ident>, &LocalResources, Priority)> = - Some(&app.init) - .iter() - .map(|ht| (ht.name.clone(), Vec::new(), &ht.args.local_resources, 0)) - .chain(app.idle.iter().map(|ht| { - ( - ht.name.clone(), - ht.args - .shared_resources - .iter() - .map(|(v, _)| v) - .collect::>(), - &ht.args.local_resources, - 0, - ) - })) - .chain(app.software_tasks.iter().map(|(name, ht)| { - ( - name.clone(), - ht.args - .shared_resources - .iter() - .map(|(v, _)| v) - .collect::>(), - &ht.args.local_resources, - ht.args.priority, - ) - })) - .chain(app.hardware_tasks.iter().map(|(name, ht)| { - ( - name.clone(), - ht.args - .shared_resources - .iter() - .map(|(v, _)| v) - .collect::>(), - &ht.args.local_resources, - ht.args.priority, - ) - })) - .collect(); - - let mut error = vec![]; - let mut lf_res_with_error = vec![]; - let mut lf_hash = HashMap::new(); - - // Collect lock free resources - let lock_free: Vec<&Ident> = app - .shared_resources - .iter() - .filter(|(_, r)| r.properties.lock_free) - .map(|(i, _)| i) - .collect(); - - // Check that lock_free resources are correct - for lf_res in lock_free.iter() { - for (task, tr, _, priority) in task_resources_list.iter() { - for r in tr { - // Get all uses of resources annotated lock_free - if lf_res == r { - // Check so async tasks do not use lock free resources - if app.software_tasks.get(task).is_some() { - error.push(syn::Error::new( - r.span(), - format!( - "Lock free shared resource {:?} is used by an async tasks, which is forbidden", - r.to_string(), - ), - )); - } - - // HashMap returns the previous existing object if old.key == new.key - if let Some(lf_res) = lf_hash.insert(r.to_string(), (task, r, priority)) { - // Check if priority differ, if it does, append to - // list of resources which will be annotated with errors - if priority != lf_res.2 { - lf_res_with_error.push(lf_res.1); - lf_res_with_error.push(r); - } - - // If the resource already violates lock free properties - if lf_res_with_error.contains(&r) { - lf_res_with_error.push(lf_res.1); - lf_res_with_error.push(r); - } - } - } - } - } - } - - // Add error message in the resource struct - for r in lock_free { - if lf_res_with_error.contains(&&r) { - error.push(syn::Error::new( - r.span(), - format!( - "Lock free shared resource {:?} is used by tasks at different priorities", - r.to_string(), - ), - )); - } - } - - // Add error message for each use of the shared resource - for resource in lf_res_with_error.clone() { - error.push(syn::Error::new( - resource.span(), - format!( - "Shared resource {:?} is declared lock free but used by tasks at different priorities", - resource.to_string(), - ), - )); - } - - // Collect local resources - let local: Vec<&Ident> = app.local_resources.iter().map(|(i, _)| i).collect(); - - let mut lr_with_error = vec![]; - let mut lr_hash = HashMap::new(); - - // Check that local resources are not shared - for lr in local { - for (task, _, local_resources, _) in task_resources_list.iter() { - for (name, res) in local_resources.iter() { - // Get all uses of resources annotated lock_free - if lr == name { - match res { - TaskLocal::External => { - // HashMap returns the previous existing object if old.key == new.key - if let Some(lr) = lr_hash.insert(name.to_string(), (task, name)) { - lr_with_error.push(lr.1); - lr_with_error.push(name); - } - } - // If a declared local has the same name as the `#[local]` struct, it's an - // direct error - TaskLocal::Declared(_) => { - lr_with_error.push(lr); - lr_with_error.push(name); - } - } - } - } - } - } - - // Add error message for each use of the local resource - for resource in lr_with_error.clone() { - error.push(syn::Error::new( - resource.span(), - format!( - "Local resource {:?} is used by multiple tasks or collides with multiple definitions", - resource.to_string(), - ), - )); - } - - // Check 0-priority async software tasks and idle dependency - for (name, task) in &app.software_tasks { - if task.args.priority == 0 { - // If there is a 0-priority task, there must be no idle - if app.idle.is_some() { - error.push(syn::Error::new( - name.span(), - format!( - "Async task {:?} has priority 0, but `#[idle]` is defined. 0-priority async tasks are only allowed if there is no `#[idle]`.", - name.to_string(), - ) - )); - } - } - } - - // Collect errors if any and return/halt - if !error.is_empty() { - let mut err = error.get(0).unwrap().clone(); - error.iter().for_each(|e| err.combine(e.clone())); - return Err(err); - } - - // e. Location of resources - let mut used_shared_resource = IndexSet::new(); - let mut ownerships = Ownerships::new(); - let mut sync_types = SyncTypes::new(); - for (prio, name, access) in app.shared_resource_accesses() { - let res = app.shared_resources.get(name).expect("UNREACHABLE"); - - // (e) - // This shared resource is used - used_shared_resource.insert(name.clone()); - - // (c) - if let Some(priority) = prio { - if let Some(ownership) = ownerships.get_mut(name) { - match *ownership { - Ownership::Owned { priority: ceiling } - | Ownership::CoOwned { priority: ceiling } - | Ownership::Contended { ceiling } - if priority != ceiling => - { - *ownership = Ownership::Contended { - ceiling: cmp::max(ceiling, priority), - }; - - if access.is_shared() { - sync_types.insert(res.ty.clone()); - } - } - - Ownership::Owned { priority: ceil } if ceil == priority => { - *ownership = Ownership::CoOwned { priority }; - } - - _ => {} - } - } else { - ownerships.insert(name.clone(), Ownership::Owned { priority }); - } - } - } - - // Create the list of used local resource Idents - let mut used_local_resource = IndexSet::new(); - - for (_, _, locals, _) in task_resources_list { - for (local, _) in locals { - used_local_resource.insert(local.clone()); - } - } - - // Most shared resources need to be `Send`, only 0 prio does not need it - let mut send_types = SendTypes::new(); - - for (name, res) in app.shared_resources.iter() { - if ownerships - .get(name) - .map(|ownership| match *ownership { - Ownership::Owned { priority: ceiling } - | Ownership::CoOwned { priority: ceiling } - | Ownership::Contended { ceiling } => ceiling != 0, - }) - .unwrap_or(false) - { - send_types.insert(res.ty.clone()); - } - } - - // Most local resources need to be `Send` as well, only 0 prio does not need it - for (name, res) in app.local_resources.iter() { - if ownerships - .get(name) - .map(|ownership| match *ownership { - Ownership::Owned { priority: ceiling } - | Ownership::CoOwned { priority: ceiling } - | Ownership::Contended { ceiling } => ceiling != 0, - }) - .unwrap_or(false) - { - send_types.insert(res.ty.clone()); - } - } - - let mut channels = Channels::new(); - - for (name, spawnee) in &app.software_tasks { - let spawnee_prio = spawnee.args.priority; - - let channel = channels.entry(spawnee_prio).or_default(); - channel.tasks.insert(name.clone()); - - // All inputs are send as we do not know from where they may be spawned. - spawnee.inputs.iter().for_each(|input| { - send_types.insert(input.ty.clone()); - }); - } - - // No channel should ever be empty - debug_assert!(channels.values().all(|channel| !channel.tasks.is_empty())); - - Ok(Analysis { - channels, - shared_resources: used_shared_resource, - local_resources: used_local_resource, - ownerships, - send_types, - sync_types, - }) -} - -// /// Priority ceiling -// pub type Ceiling = Option; - -/// Task priority -pub type Priority = u8; - -/// Resource name -pub type Resource = Ident; - -/// Task name -pub type Task = Ident; - -/// The result of analyzing an RTIC application -pub struct Analysis { - /// SPSC message channels - pub channels: Channels, - - /// Shared resources - /// - /// If a resource is not listed here it means that's a "dead" (never - /// accessed) resource and the backend should not generate code for it - pub shared_resources: UsedSharedResource, - - /// Local resources - /// - /// If a resource is not listed here it means that's a "dead" (never - /// accessed) resource and the backend should not generate code for it - pub local_resources: UsedLocalResource, - - /// Resource ownership - pub ownerships: Ownerships, - - /// These types must implement the `Send` trait - pub send_types: SendTypes, - - /// These types must implement the `Sync` trait - pub sync_types: SyncTypes, -} - -/// All channels, keyed by dispatch priority -pub type Channels = BTreeMap; - -/// Location of all *used* shared resources -pub type UsedSharedResource = IndexSet; - -/// Location of all *used* local resources -pub type UsedLocalResource = IndexSet; - -/// Resource ownership -pub type Ownerships = IndexMap; - -/// These types must implement the `Send` trait -pub type SendTypes = Set>; - -/// These types must implement the `Sync` trait -pub type SyncTypes = Set>; - -/// A channel used to send messages -#[derive(Debug, Default)] -pub struct Channel { - /// The channel capacity - pub capacity: u8, - - /// Tasks that can be spawned on this channel - pub tasks: BTreeSet, -} - -/// Resource ownership -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum Ownership { - /// Owned by a single task - Owned { - /// Priority of the task that owns this resource - priority: u8, - }, - - /// "Co-owned" by more than one task; all of them have the same priority - CoOwned { - /// Priority of the tasks that co-own this resource - priority: u8, - }, - - /// Contended by more than one task; the tasks have different priorities - Contended { - /// Priority ceiling - ceiling: u8, - }, -} - -// impl Ownership { -// /// Whether this resource needs to a lock at this priority level -// pub fn needs_lock(&self, priority: u8) -> bool { -// match self { -// Ownership::Owned { .. } | Ownership::CoOwned { .. } => false, -// -// Ownership::Contended { ceiling } => { -// debug_assert!(*ceiling >= priority); -// -// priority < *ceiling -// } -// } -// } -// -// /// Whether this resource is exclusively owned -// pub fn is_owned(&self) -> bool { -// matches!(self, Ownership::Owned { .. }) -// } -// } diff --git a/macros/src/syntax/ast.rs b/macros/src/syntax/ast.rs deleted file mode 100644 index 27e6773..0000000 --- a/macros/src/syntax/ast.rs +++ /dev/null @@ -1,335 +0,0 @@ -//! Abstract Syntax Tree - -use syn::{Attribute, Expr, Ident, Item, ItemUse, Pat, PatType, Path, Stmt, Type}; - -use crate::syntax::Map; - -/// The `#[app]` attribute -#[derive(Debug)] -#[non_exhaustive] -pub struct App { - /// The arguments to the `#[app]` attribute - pub args: AppArgs, - - /// The name of the `const` item on which the `#[app]` attribute has been placed - pub name: Ident, - - /// The `#[init]` function - pub init: Init, - - /// The `#[idle]` function - pub idle: Option, - - /// Resources shared between tasks defined in `#[shared]` - pub shared_resources: Map, - - /// Task local resources defined in `#[local]` - pub local_resources: Map, - - /// User imports - pub user_imports: Vec, - - /// User code - pub user_code: Vec, - - /// Hardware tasks: `#[task(binds = ..)]`s - pub hardware_tasks: Map, - - /// Async software tasks: `#[task]` - pub software_tasks: Map, -} - -/// Interrupts used to dispatch software tasks -pub type Dispatchers = Map; - -/// Interrupt that could be used to dispatch software tasks -#[derive(Debug, Clone)] -#[non_exhaustive] -pub struct Dispatcher { - /// Attributes that will apply to this interrupt handler - pub attrs: Vec, -} - -/// The arguments of the `#[app]` attribute -#[derive(Debug)] -pub struct AppArgs { - /// Device - pub device: Path, - - /// Peripherals - pub peripherals: bool, - - /// Interrupts used to dispatch software tasks - pub dispatchers: Dispatchers, -} - -/// The `init`-ialization function -#[derive(Debug)] -#[non_exhaustive] -pub struct Init { - /// `init` context metadata - pub args: InitArgs, - - /// Attributes that will apply to this `init` function - pub attrs: Vec, - - /// The name of the `#[init]` function - pub name: Ident, - - /// The context argument - pub context: Box, - - /// The statements that make up this `init` function - pub stmts: Vec, - - /// The name of the user provided shared resources struct - pub user_shared_struct: Ident, - - /// The name of the user provided local resources struct - pub user_local_struct: Ident, -} - -/// `init` context metadata -#[derive(Debug)] -#[non_exhaustive] -pub struct InitArgs { - /// Local resources that can be accessed from this context - pub local_resources: LocalResources, -} - -impl Default for InitArgs { - fn default() -> Self { - Self { - local_resources: LocalResources::new(), - } - } -} - -/// The `idle` context -#[derive(Debug)] -#[non_exhaustive] -pub struct Idle { - /// `idle` context metadata - pub args: IdleArgs, - - /// Attributes that will apply to this `idle` function - pub attrs: Vec, - - /// The name of the `#[idle]` function - pub name: Ident, - - /// The context argument - pub context: Box, - - /// The statements that make up this `idle` function - pub stmts: Vec, -} - -/// `idle` context metadata -#[derive(Debug)] -#[non_exhaustive] -pub struct IdleArgs { - /// Local resources that can be accessed from this context - pub local_resources: LocalResources, - - /// Shared resources that can be accessed from this context - pub shared_resources: SharedResources, -} - -impl Default for IdleArgs { - fn default() -> Self { - Self { - local_resources: LocalResources::new(), - shared_resources: SharedResources::new(), - } - } -} - -/// Shared resource properties -#[derive(Debug)] -pub struct SharedResourceProperties { - /// A lock free (exclusive resource) - pub lock_free: bool, -} - -/// A shared resource, defined in `#[shared]` -#[derive(Debug)] -#[non_exhaustive] -pub struct SharedResource { - /// `#[cfg]` attributes like `#[cfg(debug_assertions)]` - pub cfgs: Vec, - - /// `#[doc]` attributes like `/// this is a docstring` - pub docs: Vec, - - /// Attributes that will apply to this resource - pub attrs: Vec, - - /// The type of this resource - pub ty: Box, - - /// Shared resource properties - pub properties: SharedResourceProperties, -} - -/// A local resource, defined in `#[local]` -#[derive(Debug)] -#[non_exhaustive] -pub struct LocalResource { - /// `#[cfg]` attributes like `#[cfg(debug_assertions)]` - pub cfgs: Vec, - - /// `#[doc]` attributes like `/// this is a docstring` - pub docs: Vec, - - /// Attributes that will apply to this resource - pub attrs: Vec, - - /// The type of this resource - pub ty: Box, -} - -/// An async software task -#[derive(Debug)] -#[non_exhaustive] -pub struct SoftwareTask { - /// Software task metadata - pub args: SoftwareTaskArgs, - - /// `#[cfg]` attributes like `#[cfg(debug_assertions)]` - pub cfgs: Vec, - - /// Attributes that will apply to this interrupt handler - pub attrs: Vec, - - /// The context argument - pub context: Box, - - /// The inputs of this software task - pub inputs: Vec, - - /// The statements that make up the task handler - pub stmts: Vec, - - /// The task is declared externally - pub is_extern: bool, -} - -/// Software task metadata -#[derive(Debug)] -#[non_exhaustive] -pub struct SoftwareTaskArgs { - /// The priority of this task - pub priority: u8, - - /// Local resources that can be accessed from this context - pub local_resources: LocalResources, - - /// Shared resources that can be accessed from this context - pub shared_resources: SharedResources, -} - -impl Default for SoftwareTaskArgs { - fn default() -> Self { - Self { - priority: 1, - local_resources: LocalResources::new(), - shared_resources: SharedResources::new(), - } - } -} - -/// A hardware task -#[derive(Debug)] -#[non_exhaustive] -pub struct HardwareTask { - /// Hardware task metadata - pub args: HardwareTaskArgs, - - /// `#[cfg]` attributes like `#[cfg(debug_assertions)]` - pub cfgs: Vec, - - /// Attributes that will apply to this interrupt handler - pub attrs: Vec, - - /// The context argument - pub context: Box, - - /// The statements that make up the task handler - pub stmts: Vec, - - /// The task is declared externally - pub is_extern: bool, -} - -/// Hardware task metadata -#[derive(Debug)] -#[non_exhaustive] -pub struct HardwareTaskArgs { - /// The interrupt or exception that this task is bound to - pub binds: Ident, - - /// The priority of this task - pub priority: u8, - - /// Local resources that can be accessed from this context - pub local_resources: LocalResources, - - /// Shared resources that can be accessed from this context - pub shared_resources: SharedResources, -} - -/// A `static mut` variable local to and owned by a context -#[derive(Debug)] -#[non_exhaustive] -pub struct Local { - /// Attributes like `#[link_section]` - pub attrs: Vec, - - /// `#[cfg]` attributes like `#[cfg(debug_assertions)]` - pub cfgs: Vec, - - /// Type - pub ty: Box, - - /// Initial value - pub expr: Box, -} - -/// A wrapper of the 2 kinds of locals that tasks can have -#[derive(Debug)] -#[non_exhaustive] -pub enum TaskLocal { - /// The local is declared externally (i.e. `#[local]` struct) - External, - /// The local is declared in the task - Declared(Local), -} - -/// Resource access -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum Access { - /// `[x]`, a mutable resource - Exclusive, - - /// `[&x]`, a static non-mutable resource - Shared, -} - -impl Access { - /// Is this enum in the `Exclusive` variant? - pub fn is_exclusive(&self) -> bool { - *self == Access::Exclusive - } - - /// Is this enum in the `Shared` variant? - pub fn is_shared(&self) -> bool { - *self == Access::Shared - } -} - -/// Shared resource access list in task attribute -pub type SharedResources = Map; - -/// Local resource access/declaration list in task attribute -pub type LocalResources = Map; diff --git a/macros/src/syntax/check.rs b/macros/src/syntax/check.rs deleted file mode 100644 index 989d418..0000000 --- a/macros/src/syntax/check.rs +++ /dev/null @@ -1,66 +0,0 @@ -use std::collections::HashSet; - -use syn::parse; - -use crate::syntax::ast::App; - -pub fn app(app: &App) -> parse::Result<()> { - // Check that all referenced resources have been declared - // Check that resources are NOT `Exclusive`-ly shared - let mut owners = HashSet::new(); - for (_, name, access) in app.shared_resource_accesses() { - if app.shared_resources.get(name).is_none() { - return Err(parse::Error::new( - name.span(), - "this shared resource has NOT been declared", - )); - } - - if access.is_exclusive() { - owners.insert(name); - } - } - - for name in app.local_resource_accesses() { - if app.local_resources.get(name).is_none() { - return Err(parse::Error::new( - name.span(), - "this local resource has NOT been declared", - )); - } - } - - // Check that no resource has both types of access (`Exclusive` & `Shared`) - let exclusive_accesses = app - .shared_resource_accesses() - .filter_map(|(priority, name, access)| { - if priority.is_some() && access.is_exclusive() { - Some(name) - } else { - None - } - }) - .collect::>(); - for (_, name, access) in app.shared_resource_accesses() { - if access.is_shared() && exclusive_accesses.contains(name) { - return Err(parse::Error::new( - name.span(), - "this implementation doesn't support shared (`&-`) - exclusive (`&mut-`) locks; use `x` instead of `&x`", - )); - } - } - - // check that dispatchers are not used as hardware tasks - for task in app.hardware_tasks.values() { - let binds = &task.args.binds; - - if app.args.dispatchers.contains_key(binds) { - return Err(parse::Error::new( - binds.span(), - "dispatcher interrupts can't be used as hardware tasks", - )); - } - } - - Ok(()) -} diff --git a/macros/src/syntax/optimize.rs b/macros/src/syntax/optimize.rs deleted file mode 100644 index e83ba31..0000000 --- a/macros/src/syntax/optimize.rs +++ /dev/null @@ -1,36 +0,0 @@ -use std::collections::{BTreeSet, HashMap}; - -use crate::syntax::ast::App; - -pub fn app(app: &mut App, settings: &Settings) { - // "compress" priorities - // If the user specified, for example, task priorities of "1, 3, 6", - // compress them into "1, 2, 3" as to leave no gaps - if settings.optimize_priorities { - // all task priorities ordered in ascending order - let priorities = app - .hardware_tasks - .values() - .map(|task| Some(task.args.priority)) - .chain( - app.software_tasks - .values() - .map(|task| Some(task.args.priority)), - ) - .collect::>(); - - let map = priorities - .iter() - .cloned() - .zip(1..) - .collect::>(); - - for task in app.hardware_tasks.values_mut() { - task.args.priority = map[&Some(task.args.priority)]; - } - - for task in app.software_tasks.values_mut() { - task.args.priority = map[&Some(task.args.priority)]; - } - } -} diff --git a/macros/src/syntax/parse.rs b/macros/src/syntax/parse.rs deleted file mode 100644 index c78453a..0000000 --- a/macros/src/syntax/parse.rs +++ /dev/null @@ -1,363 +0,0 @@ -mod app; -mod hardware_task; -mod idle; -mod init; -mod resource; -mod software_task; -mod util; - -use proc_macro2::TokenStream as TokenStream2; -use syn::{ - braced, parenthesized, - parse::{self, Parse, ParseStream, Parser}, - token::Brace, - Ident, Item, LitInt, Token, -}; - -use crate::syntax::{ - ast::{App, AppArgs, HardwareTaskArgs, IdleArgs, InitArgs, SoftwareTaskArgs, TaskLocal}, - Either, -}; - -// Parse the app, both app arguments and body (input) -pub fn app(args: TokenStream2, input: TokenStream2) -> parse::Result { - let args = AppArgs::parse(args)?; - let input: Input = syn::parse2(input)?; - - App::parse(args, input) -} - -pub(crate) struct Input { - _mod_token: Token![mod], - pub ident: Ident, - _brace_token: Brace, - pub items: Vec, -} - -impl Parse for Input { - fn parse(input: ParseStream<'_>) -> parse::Result { - fn parse_items(input: ParseStream<'_>) -> parse::Result> { - let mut items = vec![]; - - while !input.is_empty() { - items.push(input.parse()?); - } - - Ok(items) - } - - let content; - - let _mod_token = input.parse()?; - let ident = input.parse()?; - let _brace_token = braced!(content in input); - let items = content.call(parse_items)?; - - Ok(Input { - _mod_token, - ident, - _brace_token, - items, - }) - } -} - -fn init_args(tokens: TokenStream2) -> parse::Result { - (|input: ParseStream<'_>| -> parse::Result { - if input.is_empty() { - return Ok(InitArgs::default()); - } - - let mut local_resources = None; - - let content; - parenthesized!(content in input); - - if !content.is_empty() { - loop { - // Parse identifier name - let ident: Ident = content.parse()?; - // Handle equal sign - let _: Token![=] = content.parse()?; - - match &*ident.to_string() { - "local" => { - if local_resources.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - local_resources = Some(util::parse_local_resources(&content)?); - } - _ => { - return Err(parse::Error::new(ident.span(), "unexpected argument")); - } - } - - if content.is_empty() { - break; - } - // Handle comma: , - let _: Token![,] = content.parse()?; - } - } - - if let Some(locals) = &local_resources { - for (ident, task_local) in locals { - if let TaskLocal::External = task_local { - return Err(parse::Error::new( - ident.span(), - "only declared local resources are allowed in init", - )); - } - } - } - - Ok(InitArgs { - local_resources: local_resources.unwrap_or_default(), - }) - }) - .parse2(tokens) -} - -fn idle_args(tokens: TokenStream2) -> parse::Result { - (|input: ParseStream<'_>| -> parse::Result { - if input.is_empty() { - return Ok(IdleArgs::default()); - } - - let mut shared_resources = None; - let mut local_resources = None; - - let content; - parenthesized!(content in input); - if !content.is_empty() { - loop { - // Parse identifier name - let ident: Ident = content.parse()?; - // Handle equal sign - let _: Token![=] = content.parse()?; - - match &*ident.to_string() { - "shared" => { - if shared_resources.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - shared_resources = Some(util::parse_shared_resources(&content)?); - } - - "local" => { - if local_resources.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - local_resources = Some(util::parse_local_resources(&content)?); - } - - _ => { - return Err(parse::Error::new(ident.span(), "unexpected argument")); - } - } - if content.is_empty() { - break; - } - - // Handle comma: , - let _: Token![,] = content.parse()?; - } - } - - Ok(IdleArgs { - shared_resources: shared_resources.unwrap_or_default(), - local_resources: local_resources.unwrap_or_default(), - }) - }) - .parse2(tokens) -} - -fn task_args(tokens: TokenStream2) -> parse::Result> { - (|input: ParseStream<'_>| -> parse::Result> { - if input.is_empty() { - return Ok(Either::Right(SoftwareTaskArgs::default())); - } - - let mut binds = None; - let mut capacity = None; - let mut priority = None; - let mut shared_resources = None; - let mut local_resources = None; - let mut prio_span = None; - - let content; - parenthesized!(content in input); - loop { - if content.is_empty() { - break; - } - - // Parse identifier name - let ident: Ident = content.parse()?; - let ident_s = ident.to_string(); - - // Handle equal sign - let _: Token![=] = content.parse()?; - - match &*ident_s { - "binds" => { - if binds.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - if capacity.is_some() { - return Err(parse::Error::new( - ident.span(), - "hardware tasks can't use the `capacity` argument", - )); - } - - // Parse identifier name - let ident = content.parse()?; - - binds = Some(ident); - } - - "capacity" => { - if capacity.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - if binds.is_some() { - return Err(parse::Error::new( - ident.span(), - "hardware tasks can't use the `capacity` argument", - )); - } - - // #lit - let lit: LitInt = content.parse()?; - - if !lit.suffix().is_empty() { - return Err(parse::Error::new( - lit.span(), - "this literal must be unsuffixed", - )); - } - - let value = lit.base10_parse::().ok(); - if value.is_none() || value == Some(0) { - return Err(parse::Error::new( - lit.span(), - "this literal must be in the range 1...255", - )); - } - - capacity = Some(value.unwrap()); - } - - "priority" => { - if priority.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - // #lit - let lit: LitInt = content.parse()?; - - if !lit.suffix().is_empty() { - return Err(parse::Error::new( - lit.span(), - "this literal must be unsuffixed", - )); - } - - let value = lit.base10_parse::().ok(); - if value.is_none() { - return Err(parse::Error::new( - lit.span(), - "this literal must be in the range 0...255", - )); - } - - prio_span = Some(lit.span()); - priority = Some(value.unwrap()); - } - - "shared" => { - if shared_resources.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - shared_resources = Some(util::parse_shared_resources(&content)?); - } - - "local" => { - if local_resources.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - local_resources = Some(util::parse_local_resources(&content)?); - } - - _ => { - return Err(parse::Error::new(ident.span(), "unexpected argument")); - } - } - - if content.is_empty() { - break; - } - - // Handle comma: , - let _: Token![,] = content.parse()?; - } - let priority = priority.unwrap_or(1); - let shared_resources = shared_resources.unwrap_or_default(); - let local_resources = local_resources.unwrap_or_default(); - - Ok(if let Some(binds) = binds { - if priority == 0 { - return Err(parse::Error::new( - prio_span.unwrap(), - "hardware tasks are not allowed to be at priority 0", - )); - } - - Either::Left(HardwareTaskArgs { - binds, - priority, - shared_resources, - local_resources, - }) - } else { - Either::Right(SoftwareTaskArgs { - priority, - shared_resources, - local_resources, - }) - }) - }) - .parse2(tokens) -} diff --git a/macros/src/syntax/parse/app.rs b/macros/src/syntax/parse/app.rs deleted file mode 100644 index e797f75..0000000 --- a/macros/src/syntax/parse/app.rs +++ /dev/null @@ -1,480 +0,0 @@ -use std::collections::HashSet; - -// use indexmap::map::Entry; -use proc_macro2::TokenStream as TokenStream2; -use syn::{ - parse::{self, ParseStream, Parser}, - spanned::Spanned, - Expr, ExprArray, Fields, ForeignItem, Ident, Item, LitBool, Path, Token, Visibility, -}; - -use super::Input; -use crate::syntax::{ - ast::{ - App, AppArgs, Dispatcher, Dispatchers, HardwareTask, Idle, IdleArgs, Init, InitArgs, - LocalResource, SharedResource, SoftwareTask, - }, - parse::{self as syntax_parse, util}, - Either, Map, Set, -}; - -impl AppArgs { - pub(crate) fn parse(tokens: TokenStream2) -> parse::Result { - (|input: ParseStream<'_>| -> parse::Result { - let mut custom = Set::new(); - let mut device = None; - let mut peripherals = true; - let mut dispatchers = Dispatchers::new(); - - loop { - if input.is_empty() { - break; - } - - // #ident = .. - let ident: Ident = input.parse()?; - let _eq_token: Token![=] = input.parse()?; - - if custom.contains(&ident) { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - custom.insert(ident.clone()); - - let ks = ident.to_string(); - - match &*ks { - "device" => { - if let Ok(p) = input.parse::() { - device = Some(p); - } else { - return Err(parse::Error::new( - ident.span(), - "unexpected argument value; this should be a path", - )); - } - } - - "peripherals" => { - if let Ok(p) = input.parse::() { - peripherals = p.value; - } else { - return Err(parse::Error::new( - ident.span(), - "unexpected argument value; this should be a boolean", - )); - } - } - - "dispatchers" => { - if let Ok(p) = input.parse::() { - for e in p.elems { - match e { - Expr::Path(ep) => { - let path = ep.path; - let ident = if path.leading_colon.is_some() - || path.segments.len() != 1 - { - return Err(parse::Error::new( - path.span(), - "interrupt must be an identifier, not a path", - )); - } else { - path.segments[0].ident.clone() - }; - let span = ident.span(); - if dispatchers.contains_key(&ident) { - return Err(parse::Error::new( - span, - "this extern interrupt is listed more than once", - )); - } else { - dispatchers - .insert(ident, Dispatcher { attrs: ep.attrs }); - } - } - _ => { - return Err(parse::Error::new( - e.span(), - "interrupt must be an identifier", - )); - } - } - } - } else { - return Err(parse::Error::new( - ident.span(), - // increasing the length of the error message will break rustfmt - "unexpected argument value; expected an array", - )); - } - } - _ => { - return Err(parse::Error::new(ident.span(), "unexpected argument")); - } - } - - if input.is_empty() { - break; - } - - // , - let _: Token![,] = input.parse()?; - } - - let device = if let Some(device) = device { - device - } else { - return Err(parse::Error::new(input.span(), "missing `device = ...`")); - }; - - Ok(AppArgs { - device, - peripherals, - dispatchers, - }) - }) - .parse2(tokens) - } -} - -impl App { - pub(crate) fn parse(args: AppArgs, input: Input) -> parse::Result { - let mut init = None; - let mut idle = None; - - let mut shared_resources_ident = None; - let mut shared_resources = Map::new(); - let mut local_resources_ident = None; - let mut local_resources = Map::new(); - let mut hardware_tasks = Map::new(); - let mut software_tasks = Map::new(); - let mut user_imports = vec![]; - let mut user_code = vec![]; - - let mut seen_idents = HashSet::::new(); - let mut bindings = HashSet::::new(); - - let mut check_binding = |ident: &Ident| { - if bindings.contains(ident) { - return Err(parse::Error::new( - ident.span(), - "this interrupt is already bound", - )); - } else { - bindings.insert(ident.clone()); - } - - Ok(()) - }; - - let mut check_ident = |ident: &Ident| { - if seen_idents.contains(ident) { - return Err(parse::Error::new( - ident.span(), - "this identifier has already been used", - )); - } else { - seen_idents.insert(ident.clone()); - } - - Ok(()) - }; - - for mut item in input.items { - match item { - Item::Fn(mut item) => { - let span = item.sig.ident.span(); - if let Some(pos) = item - .attrs - .iter() - .position(|attr| util::attr_eq(attr, "init")) - { - let args = InitArgs::parse(item.attrs.remove(pos).tokens)?; - - // If an init function already exists, error - if init.is_some() { - return Err(parse::Error::new( - span, - "`#[init]` function must appear at most once", - )); - } - - check_ident(&item.sig.ident)?; - - init = Some(Init::parse(args, item)?); - } else if let Some(pos) = item - .attrs - .iter() - .position(|attr| util::attr_eq(attr, "idle")) - { - let args = IdleArgs::parse(item.attrs.remove(pos).tokens)?; - - // If an idle function already exists, error - if idle.is_some() { - return Err(parse::Error::new( - span, - "`#[idle]` function must appear at most once", - )); - } - - check_ident(&item.sig.ident)?; - - idle = Some(Idle::parse(args, item)?); - } else if let Some(pos) = item - .attrs - .iter() - .position(|attr| util::attr_eq(attr, "task")) - { - if hardware_tasks.contains_key(&item.sig.ident) - || software_tasks.contains_key(&item.sig.ident) - { - return Err(parse::Error::new( - span, - "this task is defined multiple times", - )); - } - - match syntax_parse::task_args(item.attrs.remove(pos).tokens)? { - Either::Left(args) => { - check_binding(&args.binds)?; - check_ident(&item.sig.ident)?; - - hardware_tasks.insert( - item.sig.ident.clone(), - HardwareTask::parse(args, item)?, - ); - } - - Either::Right(args) => { - check_ident(&item.sig.ident)?; - - software_tasks.insert( - item.sig.ident.clone(), - SoftwareTask::parse(args, item)?, - ); - } - } - } else { - // Forward normal functions - user_code.push(Item::Fn(item.clone())); - } - } - - Item::Struct(ref mut struct_item) => { - // Match structures with the attribute #[shared], name of structure is not - // important - if let Some(_pos) = struct_item - .attrs - .iter() - .position(|attr| util::attr_eq(attr, "shared")) - { - let span = struct_item.ident.span(); - - shared_resources_ident = Some(struct_item.ident.clone()); - - if !shared_resources.is_empty() { - return Err(parse::Error::new( - span, - "`#[shared]` struct must appear at most once", - )); - } - - if struct_item.vis != Visibility::Inherited { - return Err(parse::Error::new( - struct_item.span(), - "this item must have inherited / private visibility", - )); - } - - if let Fields::Named(fields) = &mut struct_item.fields { - for field in &mut fields.named { - let ident = field.ident.as_ref().expect("UNREACHABLE"); - - if shared_resources.contains_key(ident) { - return Err(parse::Error::new( - ident.span(), - "this resource is listed more than once", - )); - } - - shared_resources.insert( - ident.clone(), - SharedResource::parse(field, ident.span())?, - ); - } - } else { - return Err(parse::Error::new( - struct_item.span(), - "this `struct` must have named fields", - )); - } - } else if let Some(_pos) = struct_item - .attrs - .iter() - .position(|attr| util::attr_eq(attr, "local")) - { - let span = struct_item.ident.span(); - - local_resources_ident = Some(struct_item.ident.clone()); - - if !local_resources.is_empty() { - return Err(parse::Error::new( - span, - "`#[local]` struct must appear at most once", - )); - } - - if struct_item.vis != Visibility::Inherited { - return Err(parse::Error::new( - struct_item.span(), - "this item must have inherited / private visibility", - )); - } - - if let Fields::Named(fields) = &mut struct_item.fields { - for field in &mut fields.named { - let ident = field.ident.as_ref().expect("UNREACHABLE"); - - if local_resources.contains_key(ident) { - return Err(parse::Error::new( - ident.span(), - "this resource is listed more than once", - )); - } - - local_resources.insert( - ident.clone(), - LocalResource::parse(field, ident.span())?, - ); - } - } else { - return Err(parse::Error::new( - struct_item.span(), - "this `struct` must have named fields", - )); - } - } else { - // Structure without the #[resources] attribute should just be passed along - user_code.push(item.clone()); - } - } - - Item::ForeignMod(mod_) => { - if !util::abi_is_rust(&mod_.abi) { - return Err(parse::Error::new( - mod_.abi.extern_token.span(), - "this `extern` block must use the \"Rust\" ABI", - )); - } - - for item in mod_.items { - if let ForeignItem::Fn(mut item) = item { - let span = item.sig.ident.span(); - if let Some(pos) = item - .attrs - .iter() - .position(|attr| util::attr_eq(attr, "task")) - { - if hardware_tasks.contains_key(&item.sig.ident) - || software_tasks.contains_key(&item.sig.ident) - { - return Err(parse::Error::new( - span, - "this task is defined multiple times", - )); - } - - if item.attrs.len() != 1 { - return Err(parse::Error::new( - span, - "`extern` task required `#[task(..)]` attribute", - )); - } - - match syntax_parse::task_args(item.attrs.remove(pos).tokens)? { - Either::Left(args) => { - check_binding(&args.binds)?; - check_ident(&item.sig.ident)?; - - hardware_tasks.insert( - item.sig.ident.clone(), - HardwareTask::parse_foreign(args, item)?, - ); - } - - Either::Right(args) => { - check_ident(&item.sig.ident)?; - - software_tasks.insert( - item.sig.ident.clone(), - SoftwareTask::parse_foreign(args, item)?, - ); - } - } - } else { - return Err(parse::Error::new( - span, - "`extern` task required `#[task(..)]` attribute", - )); - } - } else { - return Err(parse::Error::new( - item.span(), - "this item must live outside the `#[app]` module", - )); - } - } - } - Item::Use(itemuse_) => { - // Store the user provided use-statements - user_imports.push(itemuse_.clone()); - } - _ => { - // Anything else within the module should not make any difference - user_code.push(item.clone()); - } - } - } - - let shared_resources_ident = - shared_resources_ident.expect("No `#[shared]` resource struct defined"); - let local_resources_ident = - local_resources_ident.expect("No `#[local]` resource struct defined"); - let init = init.expect("No `#[init]` function defined"); - - if shared_resources_ident != init.user_shared_struct { - return Err(parse::Error::new( - init.user_shared_struct.span(), - format!( - "This name and the one defined on `#[shared]` are not the same. Should this be `{shared_resources_ident}`?" - ), - )); - } - - if local_resources_ident != init.user_local_struct { - return Err(parse::Error::new( - init.user_local_struct.span(), - format!( - "This name and the one defined on `#[local]` are not the same. Should this be `{local_resources_ident}`?" - ), - )); - } - - Ok(App { - args, - name: input.ident, - init, - idle, - shared_resources, - local_resources, - user_imports, - user_code, - hardware_tasks, - software_tasks, - }) - } -} diff --git a/macros/src/syntax/parse/hardware_task.rs b/macros/src/syntax/parse/hardware_task.rs deleted file mode 100644 index 7f6dfbe..0000000 --- a/macros/src/syntax/parse/hardware_task.rs +++ /dev/null @@ -1,76 +0,0 @@ -use syn::{parse, ForeignItemFn, ItemFn, Stmt}; - -use crate::syntax::parse::util::FilterAttrs; -use crate::syntax::{ - ast::{HardwareTask, HardwareTaskArgs}, - parse::util, -}; - -impl HardwareTask { - pub(crate) fn parse(args: HardwareTaskArgs, item: ItemFn) -> parse::Result { - let span = item.sig.ident.span(); - let valid_signature = util::check_fn_signature(&item, false) - && item.sig.inputs.len() == 1 - && util::type_is_unit(&item.sig.output); - - let name = item.sig.ident.to_string(); - - if valid_signature { - if let Some((context, Ok(rest))) = util::parse_inputs(item.sig.inputs, &name) { - if rest.is_empty() { - let FilterAttrs { cfgs, attrs, .. } = util::filter_attributes(item.attrs); - - return Ok(HardwareTask { - args, - cfgs, - attrs, - context, - stmts: item.block.stmts, - is_extern: false, - }); - } - } - } - - Err(parse::Error::new( - span, - format!("this task handler must have type signature `fn({name}::Context)`"), - )) - } -} - -impl HardwareTask { - pub(crate) fn parse_foreign( - args: HardwareTaskArgs, - item: ForeignItemFn, - ) -> parse::Result { - let span = item.sig.ident.span(); - let valid_signature = util::check_foreign_fn_signature(&item, false) - && item.sig.inputs.len() == 1 - && util::type_is_unit(&item.sig.output); - - let name = item.sig.ident.to_string(); - - if valid_signature { - if let Some((context, Ok(rest))) = util::parse_inputs(item.sig.inputs, &name) { - if rest.is_empty() { - let FilterAttrs { cfgs, attrs, .. } = util::filter_attributes(item.attrs); - - return Ok(HardwareTask { - args, - cfgs, - attrs, - context, - stmts: Vec::::new(), - is_extern: true, - }); - } - } - } - - Err(parse::Error::new( - span, - format!("this task handler must have type signature `fn({name}::Context)`"), - )) - } -} diff --git a/macros/src/syntax/parse/idle.rs b/macros/src/syntax/parse/idle.rs deleted file mode 100644 index 124c136..0000000 --- a/macros/src/syntax/parse/idle.rs +++ /dev/null @@ -1,42 +0,0 @@ -use proc_macro2::TokenStream as TokenStream2; -use syn::{parse, ItemFn}; - -use crate::syntax::{ - ast::{Idle, IdleArgs}, - parse::util, -}; - -impl IdleArgs { - pub(crate) fn parse(tokens: TokenStream2) -> parse::Result { - crate::syntax::parse::idle_args(tokens) - } -} - -impl Idle { - pub(crate) fn parse(args: IdleArgs, item: ItemFn) -> parse::Result { - let valid_signature = util::check_fn_signature(&item, false) - && item.sig.inputs.len() == 1 - && util::type_is_bottom(&item.sig.output); - - let name = item.sig.ident.to_string(); - - if valid_signature { - if let Some((context, Ok(rest))) = util::parse_inputs(item.sig.inputs, &name) { - if rest.is_empty() { - return Ok(Idle { - args, - attrs: item.attrs, - context, - name: item.sig.ident, - stmts: item.block.stmts, - }); - } - } - } - - Err(parse::Error::new( - item.sig.ident.span(), - format!("this `#[idle]` function must have signature `fn({name}::Context) -> !`"), - )) - } -} diff --git a/macros/src/syntax/parse/init.rs b/macros/src/syntax/parse/init.rs deleted file mode 100644 index 0aea20b..0000000 --- a/macros/src/syntax/parse/init.rs +++ /dev/null @@ -1,51 +0,0 @@ -use proc_macro2::TokenStream as TokenStream2; - -use syn::{parse, ItemFn}; - -use crate::syntax::{ - ast::{Init, InitArgs}, - parse::{self as syntax_parse, util}, -}; - -impl InitArgs { - pub(crate) fn parse(tokens: TokenStream2) -> parse::Result { - syntax_parse::init_args(tokens) - } -} - -impl Init { - pub(crate) fn parse(args: InitArgs, item: ItemFn) -> parse::Result { - let valid_signature = util::check_fn_signature(&item, false) && item.sig.inputs.len() == 1; - - let span = item.sig.ident.span(); - - let name = item.sig.ident.to_string(); - - if valid_signature { - if let Ok((user_shared_struct, user_local_struct)) = - util::type_is_init_return(&item.sig.output) - { - if let Some((context, Ok(rest))) = util::parse_inputs(item.sig.inputs, &name) { - if rest.is_empty() { - return Ok(Init { - args, - attrs: item.attrs, - context, - name: item.sig.ident, - stmts: item.block.stmts, - user_shared_struct, - user_local_struct, - }); - } - } - } - } - - Err(parse::Error::new( - span, - format!( - "the `#[init]` function must have signature `fn({name}::Context) -> (Shared resources struct, Local resources struct)`" - ), - )) - } -} diff --git a/macros/src/syntax/parse/resource.rs b/macros/src/syntax/parse/resource.rs deleted file mode 100644 index ff10057..0000000 --- a/macros/src/syntax/parse/resource.rs +++ /dev/null @@ -1,55 +0,0 @@ -use proc_macro2::Span; -use syn::{parse, Field, Visibility}; - -use crate::syntax::parse::util::FilterAttrs; -use crate::syntax::{ - ast::{LocalResource, SharedResource, SharedResourceProperties}, - parse::util, -}; - -impl SharedResource { - pub(crate) fn parse(item: &Field, span: Span) -> parse::Result { - if item.vis != Visibility::Inherited { - return Err(parse::Error::new( - span, - "this field must have inherited / private visibility", - )); - } - - let FilterAttrs { - cfgs, - mut attrs, - docs, - } = util::filter_attributes(item.attrs.clone()); - - let lock_free = util::extract_lock_free(&mut attrs)?; - - Ok(SharedResource { - cfgs, - attrs, - docs, - ty: Box::new(item.ty.clone()), - properties: SharedResourceProperties { lock_free }, - }) - } -} - -impl LocalResource { - pub(crate) fn parse(item: &Field, span: Span) -> parse::Result { - if item.vis != Visibility::Inherited { - return Err(parse::Error::new( - span, - "this field must have inherited / private visibility", - )); - } - - let FilterAttrs { cfgs, attrs, docs } = util::filter_attributes(item.attrs.clone()); - - Ok(LocalResource { - cfgs, - attrs, - docs, - ty: Box::new(item.ty.clone()), - }) - } -} diff --git a/macros/src/syntax/parse/software_task.rs b/macros/src/syntax/parse/software_task.rs deleted file mode 100644 index 769aa65..0000000 --- a/macros/src/syntax/parse/software_task.rs +++ /dev/null @@ -1,76 +0,0 @@ -use syn::{parse, ForeignItemFn, ItemFn, Stmt}; - -use crate::syntax::parse::util::FilterAttrs; -use crate::syntax::{ - ast::{SoftwareTask, SoftwareTaskArgs}, - parse::util, -}; - -impl SoftwareTask { - pub(crate) fn parse(args: SoftwareTaskArgs, item: ItemFn) -> parse::Result { - let valid_signature = util::check_fn_signature(&item, true) - && util::type_is_unit(&item.sig.output) - && item.sig.asyncness.is_some(); - - let span = item.sig.ident.span(); - - let name = item.sig.ident.to_string(); - - if valid_signature { - if let Some((context, Ok(inputs))) = util::parse_inputs(item.sig.inputs, &name) { - let FilterAttrs { cfgs, attrs, .. } = util::filter_attributes(item.attrs); - - return Ok(SoftwareTask { - args, - attrs, - cfgs, - context, - inputs, - stmts: item.block.stmts, - is_extern: false, - }); - } - } - - Err(parse::Error::new( - span, - format!("this task handler must have type signature `async fn({name}::Context, ..)`"), - )) - } -} - -impl SoftwareTask { - pub(crate) fn parse_foreign( - args: SoftwareTaskArgs, - item: ForeignItemFn, - ) -> parse::Result { - let valid_signature = util::check_foreign_fn_signature(&item, true) - && util::type_is_unit(&item.sig.output) - && item.sig.asyncness.is_some(); - - let span = item.sig.ident.span(); - - let name = item.sig.ident.to_string(); - - if valid_signature { - if let Some((context, Ok(inputs))) = util::parse_inputs(item.sig.inputs, &name) { - let FilterAttrs { cfgs, attrs, .. } = util::filter_attributes(item.attrs); - - return Ok(SoftwareTask { - args, - attrs, - cfgs, - context, - inputs, - stmts: Vec::::new(), - is_extern: true, - }); - } - } - - Err(parse::Error::new( - span, - format!("this task handler must have type signature `async fn({name}::Context, ..)`"), - )) - } -} diff --git a/macros/src/syntax/parse/util.rs b/macros/src/syntax/parse/util.rs deleted file mode 100644 index 5a5e0c0..0000000 --- a/macros/src/syntax/parse/util.rs +++ /dev/null @@ -1,338 +0,0 @@ -use syn::{ - bracketed, - parse::{self, ParseStream}, - punctuated::Punctuated, - spanned::Spanned, - Abi, AttrStyle, Attribute, Expr, FnArg, ForeignItemFn, Ident, ItemFn, Pat, PatType, Path, - PathArguments, ReturnType, Token, Type, Visibility, -}; - -use crate::syntax::{ - ast::{Access, Local, LocalResources, SharedResources, TaskLocal}, - Map, -}; - -pub fn abi_is_rust(abi: &Abi) -> bool { - match &abi.name { - None => true, - Some(s) => s.value() == "Rust", - } -} - -pub fn attr_eq(attr: &Attribute, name: &str) -> bool { - attr.style == AttrStyle::Outer && attr.path.segments.len() == 1 && { - let segment = attr.path.segments.first().unwrap(); - segment.arguments == PathArguments::None && *segment.ident.to_string() == *name - } -} - -/// checks that a function signature -/// -/// - has no bounds (like where clauses) -/// - is not `async` -/// - is not `const` -/// - is not `unsafe` -/// - is not generic (has no type parameters) -/// - is not variadic -/// - uses the Rust ABI (and not e.g. "C") -pub fn check_fn_signature(item: &ItemFn, allow_async: bool) -> bool { - item.vis == Visibility::Inherited - && item.sig.constness.is_none() - && (item.sig.asyncness.is_none() || allow_async) - && item.sig.abi.is_none() - && item.sig.unsafety.is_none() - && item.sig.generics.params.is_empty() - && item.sig.generics.where_clause.is_none() - && item.sig.variadic.is_none() -} - -#[allow(dead_code)] -pub fn check_foreign_fn_signature(item: &ForeignItemFn, allow_async: bool) -> bool { - item.vis == Visibility::Inherited - && item.sig.constness.is_none() - && (item.sig.asyncness.is_none() || allow_async) - && item.sig.abi.is_none() - && item.sig.unsafety.is_none() - && item.sig.generics.params.is_empty() - && item.sig.generics.where_clause.is_none() - && item.sig.variadic.is_none() -} - -pub struct FilterAttrs { - pub cfgs: Vec, - pub docs: Vec, - pub attrs: Vec, -} - -pub fn filter_attributes(input_attrs: Vec) -> FilterAttrs { - let mut cfgs = vec![]; - let mut docs = vec![]; - let mut attrs = vec![]; - - for attr in input_attrs { - if attr_eq(&attr, "cfg") { - cfgs.push(attr); - } else if attr_eq(&attr, "doc") { - docs.push(attr); - } else { - attrs.push(attr); - } - } - - FilterAttrs { cfgs, docs, attrs } -} - -pub fn extract_lock_free(attrs: &mut Vec) -> parse::Result { - if let Some(pos) = attrs.iter().position(|attr| attr_eq(attr, "lock_free")) { - attrs.remove(pos); - Ok(true) - } else { - Ok(false) - } -} - -pub fn parse_shared_resources(content: ParseStream<'_>) -> parse::Result { - let inner; - bracketed!(inner in content); - - let mut resources = Map::new(); - for e in inner.call(Punctuated::::parse_terminated)? { - let err = Err(parse::Error::new( - e.span(), - "identifier appears more than once in list", - )); - let (access, path) = match e { - Expr::Path(e) => (Access::Exclusive, e.path), - - Expr::Reference(ref r) if r.mutability.is_none() => match &*r.expr { - Expr::Path(e) => (Access::Shared, e.path.clone()), - - _ => return err, - }, - - _ => return err, - }; - - let ident = extract_resource_name_ident(path)?; - - if resources.contains_key(&ident) { - return Err(parse::Error::new( - ident.span(), - "resource appears more than once in list", - )); - } - - resources.insert(ident, access); - } - - Ok(resources) -} - -fn extract_resource_name_ident(path: Path) -> parse::Result { - if path.leading_colon.is_some() - || path.segments.len() != 1 - || path.segments[0].arguments != PathArguments::None - { - Err(parse::Error::new( - path.span(), - "resource must be an identifier, not a path", - )) - } else { - Ok(path.segments[0].ident.clone()) - } -} - -pub fn parse_local_resources(content: ParseStream<'_>) -> parse::Result { - let inner; - bracketed!(inner in content); - - let mut resources = Map::new(); - - for e in inner.call(Punctuated::::parse_terminated)? { - let err = Err(parse::Error::new( - e.span(), - "identifier appears more than once in list", - )); - - let (name, local) = match e { - // local = [IDENT], - Expr::Path(path) => { - if !path.attrs.is_empty() { - return Err(parse::Error::new( - path.span(), - "attributes are not supported here", - )); - } - - let ident = extract_resource_name_ident(path.path)?; - // let (cfgs, attrs) = extract_cfgs(path.attrs); - - (ident, TaskLocal::External) - } - - // local = [IDENT: TYPE = EXPR] - Expr::Assign(e) => { - let (name, ty, cfgs, attrs) = match *e.left { - Expr::Type(t) => { - // Extract name and attributes - let (name, cfgs, attrs) = match *t.expr { - Expr::Path(path) => { - let name = extract_resource_name_ident(path.path)?; - let FilterAttrs { cfgs, attrs, .. } = filter_attributes(path.attrs); - - (name, cfgs, attrs) - } - _ => return err, - }; - - let ty = t.ty; - - // Error check - match &*ty { - Type::Array(_) => {} - Type::Path(_) => {} - Type::Ptr(_) => {} - Type::Tuple(_) => {} - _ => return Err(parse::Error::new( - ty.span(), - "unsupported type, must be an array, tuple, pointer or type path", - )), - }; - - (name, ty, cfgs, attrs) - } - e => return Err(parse::Error::new(e.span(), "malformed, expected a type")), - }; - - let expr = e.right; // Expr - - ( - name, - TaskLocal::Declared(Local { - attrs, - cfgs, - ty, - expr, - }), - ) - } - - expr => { - return Err(parse::Error::new( - expr.span(), - "malformed, expected 'IDENT: TYPE = EXPR'", - )) - } - }; - - resources.insert(name, local); - } - - Ok(resources) -} - -type ParseInputResult = Option<(Box, Result, FnArg>)>; - -pub fn parse_inputs(inputs: Punctuated, name: &str) -> ParseInputResult { - let mut inputs = inputs.into_iter(); - - match inputs.next() { - Some(FnArg::Typed(first)) => { - if type_is_path(&first.ty, &[name, "Context"]) { - let rest = inputs - .map(|arg| match arg { - FnArg::Typed(arg) => Ok(arg), - _ => Err(arg), - }) - .collect::, _>>(); - - Some((first.pat, rest)) - } else { - None - } - } - - _ => None, - } -} - -pub fn type_is_bottom(ty: &ReturnType) -> bool { - if let ReturnType::Type(_, ty) = ty { - matches!(**ty, Type::Never(_)) - } else { - false - } -} - -fn extract_init_resource_name_ident(ty: Type) -> Result { - match ty { - Type::Path(path) => { - let path = path.path; - - if path.leading_colon.is_some() - || path.segments.len() != 1 - || path.segments[0].arguments != PathArguments::None - { - Err(()) - } else { - Ok(path.segments[0].ident.clone()) - } - } - _ => Err(()), - } -} - -/// Checks Init's return type, return the user provided types for analysis -pub fn type_is_init_return(ty: &ReturnType) -> Result<(Ident, Ident), ()> { - match ty { - ReturnType::Default => Err(()), - - ReturnType::Type(_, ty) => match &**ty { - Type::Tuple(t) => { - // return should be: - // fn -> (User's #[shared] struct, User's #[local] struct) - // - // We check the length and the last one here, analysis checks that the user - // provided structs are correct. - if t.elems.len() == 2 { - return Ok(( - extract_init_resource_name_ident(t.elems[0].clone())?, - extract_init_resource_name_ident(t.elems[1].clone())?, - )); - } - - Err(()) - } - - _ => Err(()), - }, - } -} - -pub fn type_is_path(ty: &Type, segments: &[&str]) -> bool { - match ty { - Type::Path(tpath) if tpath.qself.is_none() => { - tpath.path.segments.len() == segments.len() - && tpath - .path - .segments - .iter() - .zip(segments) - .all(|(lhs, rhs)| lhs.ident == **rhs) - } - - _ => false, - } -} - -pub fn type_is_unit(ty: &ReturnType) -> bool { - if let ReturnType::Type(_, ty) = ty { - if let Type::Tuple(ref tuple) = **ty { - tuple.elems.is_empty() - } else { - false - } - } else { - true - } -} diff --git a/macros/src/tests.rs b/macros/src/tests.rs deleted file mode 100644 index e9e3326..0000000 --- a/macros/src/tests.rs +++ /dev/null @@ -1,4 +0,0 @@ -// NOTE these tests are specific to the Cortex-M port; `rtic-syntax` has a more extensive test suite -// that tests functionality common to all the RTIC ports - -mod single; diff --git a/macros/src/tests/single.rs b/macros/src/tests/single.rs deleted file mode 100644 index f20c9cc..0000000 --- a/macros/src/tests/single.rs +++ /dev/null @@ -1,40 +0,0 @@ -use quote::quote; -use rtic_syntax::Settings; - -#[test] -fn analyze() { - let mut settings = Settings::default(); - settings.parse_extern_interrupt = true; - let (app, analysis) = rtic_syntax::parse2( - // First interrupt is assigned to the highest priority dispatcher - quote!(device = pac, dispatchers = [B, A]), - quote!( - mod app { - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { - (Shared {}, Local {}, init::Monotonics()) - } - - #[task(priority = 1)] - fn a(_: a::Context) {} - - #[task(priority = 2)] - fn b(_: b::Context) {} - } - ), - settings, - ) - .unwrap(); - - let analysis = crate::analyze::app(analysis, &app); - let interrupts = &analysis.interrupts; - assert_eq!(interrupts.len(), 2); - assert_eq!(interrupts[&2].0.to_string(), "B"); - assert_eq!(interrupts[&1].0.to_string(), "A"); -} diff --git a/macros/tests/ui.rs b/macros/tests/ui.rs deleted file mode 100644 index 9fb88a1..0000000 --- a/macros/tests/ui.rs +++ /dev/null @@ -1,7 +0,0 @@ -use trybuild::TestCases; - -#[test] -fn ui() { - let t = TestCases::new(); - t.compile_fail("ui/*.rs"); -} diff --git a/macros/ui/extern-interrupt-used.rs b/macros/ui/extern-interrupt-used.rs deleted file mode 100644 index 6346a7d..0000000 --- a/macros/ui/extern-interrupt-used.rs +++ /dev/null @@ -1,16 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock, dispatchers = [EXTI0])] -mod app { - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) {} - - #[task(binds = EXTI0)] - fn foo(_: foo::Context) {} -} diff --git a/macros/ui/extern-interrupt-used.stderr b/macros/ui/extern-interrupt-used.stderr deleted file mode 100644 index 970d39b..0000000 --- a/macros/ui/extern-interrupt-used.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: dispatcher interrupts can't be used as hardware tasks - --> ui/extern-interrupt-used.rs:14:20 - | -14 | #[task(binds = EXTI0)] - | ^^^^^ diff --git a/macros/ui/idle-double-local.rs b/macros/ui/idle-double-local.rs deleted file mode 100644 index 54e67d3..0000000 --- a/macros/ui/idle-double-local.rs +++ /dev/null @@ -1,9 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[idle(local = [A], local = [B])] - fn idle(_: idle::Context) -> ! { - loop {} - } -} diff --git a/macros/ui/idle-double-local.stderr b/macros/ui/idle-double-local.stderr deleted file mode 100644 index b558136..0000000 --- a/macros/ui/idle-double-local.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: argument appears more than once - --> ui/idle-double-local.rs:5:25 - | -5 | #[idle(local = [A], local = [B])] - | ^^^^^ diff --git a/macros/ui/idle-double-shared.rs b/macros/ui/idle-double-shared.rs deleted file mode 100644 index f66cb93..0000000 --- a/macros/ui/idle-double-shared.rs +++ /dev/null @@ -1,9 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[idle(shared = [A], shared = [B])] - fn idle(_: idle::Context) -> ! { - loop {} - } -} diff --git a/macros/ui/idle-double-shared.stderr b/macros/ui/idle-double-shared.stderr deleted file mode 100644 index 6f62ad2..0000000 --- a/macros/ui/idle-double-shared.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: argument appears more than once - --> ui/idle-double-shared.rs:5:26 - | -5 | #[idle(shared = [A], shared = [B])] - | ^^^^^^ diff --git a/macros/ui/idle-input.rs b/macros/ui/idle-input.rs deleted file mode 100644 index c896b1c..0000000 --- a/macros/ui/idle-input.rs +++ /dev/null @@ -1,9 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[idle] - fn idle(_: idle::Context, _undef: u32) -> ! { - loop {} - } -} diff --git a/macros/ui/idle-input.stderr b/macros/ui/idle-input.stderr deleted file mode 100644 index 34c38fc..0000000 --- a/macros/ui/idle-input.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: this `#[idle]` function must have signature `fn(idle::Context) -> !` - --> ui/idle-input.rs:6:8 - | -6 | fn idle(_: idle::Context, _undef: u32) -> ! { - | ^^^^ diff --git a/macros/ui/idle-no-context.rs b/macros/ui/idle-no-context.rs deleted file mode 100644 index bab4680..0000000 --- a/macros/ui/idle-no-context.rs +++ /dev/null @@ -1,9 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[idle] - fn idle() -> ! { - loop {} - } -} diff --git a/macros/ui/idle-no-context.stderr b/macros/ui/idle-no-context.stderr deleted file mode 100644 index c9f4b3d..0000000 --- a/macros/ui/idle-no-context.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: this `#[idle]` function must have signature `fn(idle::Context) -> !` - --> ui/idle-no-context.rs:6:8 - | -6 | fn idle() -> ! { - | ^^^^ diff --git a/macros/ui/idle-not-divergent.rs b/macros/ui/idle-not-divergent.rs deleted file mode 100644 index d1ae8b1..0000000 --- a/macros/ui/idle-not-divergent.rs +++ /dev/null @@ -1,7 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[idle] - fn idle(_: idle::Context) {} -} diff --git a/macros/ui/idle-not-divergent.stderr b/macros/ui/idle-not-divergent.stderr deleted file mode 100644 index e318f58..0000000 --- a/macros/ui/idle-not-divergent.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: this `#[idle]` function must have signature `fn(idle::Context) -> !` - --> ui/idle-not-divergent.rs:6:8 - | -6 | fn idle(_: idle::Context) {} - | ^^^^ diff --git a/macros/ui/idle-output.rs b/macros/ui/idle-output.rs deleted file mode 100644 index 1662157..0000000 --- a/macros/ui/idle-output.rs +++ /dev/null @@ -1,9 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[idle] - fn idle(_: idle::Context) -> u32 { - 0 - } -} diff --git a/macros/ui/idle-output.stderr b/macros/ui/idle-output.stderr deleted file mode 100644 index 7070e25..0000000 --- a/macros/ui/idle-output.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: this `#[idle]` function must have signature `fn(idle::Context) -> !` - --> ui/idle-output.rs:6:8 - | -6 | fn idle(_: idle::Context) -> u32 { - | ^^^^ diff --git a/macros/ui/idle-pub.rs b/macros/ui/idle-pub.rs deleted file mode 100644 index 0d8dd01..0000000 --- a/macros/ui/idle-pub.rs +++ /dev/null @@ -1,9 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[idle] - pub fn idle(_: idle::Context) -> ! { - loop {} - } -} diff --git a/macros/ui/idle-pub.stderr b/macros/ui/idle-pub.stderr deleted file mode 100644 index aa46ac3..0000000 --- a/macros/ui/idle-pub.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: this `#[idle]` function must have signature `fn(idle::Context) -> !` - --> ui/idle-pub.rs:6:12 - | -6 | pub fn idle(_: idle::Context) -> ! { - | ^^^^ diff --git a/macros/ui/idle-unsafe.rs b/macros/ui/idle-unsafe.rs deleted file mode 100644 index 3422ef2..0000000 --- a/macros/ui/idle-unsafe.rs +++ /dev/null @@ -1,9 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[idle] - unsafe fn idle(_: idle::Context) -> ! { - loop {} - } -} diff --git a/macros/ui/idle-unsafe.stderr b/macros/ui/idle-unsafe.stderr deleted file mode 100644 index a416800..0000000 --- a/macros/ui/idle-unsafe.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: this `#[idle]` function must have signature `fn(idle::Context) -> !` - --> ui/idle-unsafe.rs:6:15 - | -6 | unsafe fn idle(_: idle::Context) -> ! { - | ^^^^ diff --git a/macros/ui/init-divergent.rs b/macros/ui/init-divergent.rs deleted file mode 100644 index 5e4e96a..0000000 --- a/macros/ui/init-divergent.rs +++ /dev/null @@ -1,13 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> ! {} -} diff --git a/macros/ui/init-divergent.stderr b/macros/ui/init-divergent.stderr deleted file mode 100644 index 9f6acf6..0000000 --- a/macros/ui/init-divergent.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: the `#[init]` function must have signature `fn(init::Context) -> (Shared resources struct, Local resources struct)` - --> ui/init-divergent.rs:12:8 - | -12 | fn init(_: init::Context) -> ! {} - | ^^^^ diff --git a/macros/ui/init-double-local.rs b/macros/ui/init-double-local.rs deleted file mode 100644 index 5f6d7ac..0000000 --- a/macros/ui/init-double-local.rs +++ /dev/null @@ -1,7 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[init(local = [A], local = [B])] - fn init(_: init::Context) {} -} diff --git a/macros/ui/init-double-local.stderr b/macros/ui/init-double-local.stderr deleted file mode 100644 index 07c3b50..0000000 --- a/macros/ui/init-double-local.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: argument appears more than once - --> ui/init-double-local.rs:5:25 - | -5 | #[init(local = [A], local = [B])] - | ^^^^^ diff --git a/macros/ui/init-double-shared.rs b/macros/ui/init-double-shared.rs deleted file mode 100644 index 4503c87..0000000 --- a/macros/ui/init-double-shared.rs +++ /dev/null @@ -1,7 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[init(shared = [A], shared = [B])] - fn init(_: init::Context) {} -} diff --git a/macros/ui/init-double-shared.stderr b/macros/ui/init-double-shared.stderr deleted file mode 100644 index af2a97b..0000000 --- a/macros/ui/init-double-shared.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: unexpected argument - --> ui/init-double-shared.rs:5:12 - | -5 | #[init(shared = [A], shared = [B])] - | ^^^^^^ diff --git a/macros/ui/init-input.rs b/macros/ui/init-input.rs deleted file mode 100644 index d41a503..0000000 --- a/macros/ui/init-input.rs +++ /dev/null @@ -1,13 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context, _undef: u32) -> (Shared, Local) {} -} diff --git a/macros/ui/init-input.stderr b/macros/ui/init-input.stderr deleted file mode 100644 index e236043..0000000 --- a/macros/ui/init-input.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: the `#[init]` function must have signature `fn(init::Context) -> (Shared resources struct, Local resources struct)` - --> ui/init-input.rs:12:8 - | -12 | fn init(_: init::Context, _undef: u32) -> (Shared, Local) {} - | ^^^^ diff --git a/macros/ui/init-no-context.rs b/macros/ui/init-no-context.rs deleted file mode 100644 index cdce4c5..0000000 --- a/macros/ui/init-no-context.rs +++ /dev/null @@ -1,13 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init() -> (Shared, Local) {} -} diff --git a/macros/ui/init-no-context.stderr b/macros/ui/init-no-context.stderr deleted file mode 100644 index 28e1fd4..0000000 --- a/macros/ui/init-no-context.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: the `#[init]` function must have signature `fn(init::Context) -> (Shared resources struct, Local resources struct)` - --> ui/init-no-context.rs:12:8 - | -12 | fn init() -> (Shared, Local) {} - | ^^^^ diff --git a/macros/ui/init-output.rs b/macros/ui/init-output.rs deleted file mode 100644 index 7057c95..0000000 --- a/macros/ui/init-output.rs +++ /dev/null @@ -1,9 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[init] - fn init(_: init::Context) -> u32 { - 0 - } -} diff --git a/macros/ui/init-output.stderr b/macros/ui/init-output.stderr deleted file mode 100644 index 8bc3c83..0000000 --- a/macros/ui/init-output.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: the `#[init]` function must have signature `fn(init::Context) -> (Shared resources struct, Local resources struct)` - --> ui/init-output.rs:6:8 - | -6 | fn init(_: init::Context) -> u32 { - | ^^^^ diff --git a/macros/ui/init-pub.rs b/macros/ui/init-pub.rs deleted file mode 100644 index dd59aa1..0000000 --- a/macros/ui/init-pub.rs +++ /dev/null @@ -1,13 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - pub fn init(_: init::Context) -> (Shared, Local) {} -} diff --git a/macros/ui/init-pub.stderr b/macros/ui/init-pub.stderr deleted file mode 100644 index b1610ed..0000000 --- a/macros/ui/init-pub.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: the `#[init]` function must have signature `fn(init::Context) -> (Shared resources struct, Local resources struct)` - --> ui/init-pub.rs:12:12 - | -12 | pub fn init(_: init::Context) -> (Shared, Local) {} - | ^^^^ diff --git a/macros/ui/init-unsafe.rs b/macros/ui/init-unsafe.rs deleted file mode 100644 index 4f89baf..0000000 --- a/macros/ui/init-unsafe.rs +++ /dev/null @@ -1,7 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[init] - unsafe fn init(_: init::Context) -> (Shared, Local) {} -} diff --git a/macros/ui/init-unsafe.stderr b/macros/ui/init-unsafe.stderr deleted file mode 100644 index fd0b8f3..0000000 --- a/macros/ui/init-unsafe.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: the `#[init]` function must have signature `fn(init::Context) -> (Shared resources struct, Local resources struct)` - --> ui/init-unsafe.rs:6:15 - | -6 | unsafe fn init(_: init::Context) -> (Shared, Local) {} - | ^^^^ diff --git a/macros/ui/interrupt-double.rs b/macros/ui/interrupt-double.rs deleted file mode 100644 index e2addc7..0000000 --- a/macros/ui/interrupt-double.rs +++ /dev/null @@ -1,10 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[task(binds = UART0)] - fn foo(_: foo::Context) {} - - #[task(binds = UART0)] - fn bar(_: bar::Context) {} -} diff --git a/macros/ui/interrupt-double.stderr b/macros/ui/interrupt-double.stderr deleted file mode 100644 index 8db34e2..0000000 --- a/macros/ui/interrupt-double.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: this interrupt is already bound - --> ui/interrupt-double.rs:8:20 - | -8 | #[task(binds = UART0)] - | ^^^^^ diff --git a/macros/ui/local-collision-2.rs b/macros/ui/local-collision-2.rs deleted file mode 100644 index 08bc8e5..0000000 --- a/macros/ui/local-collision-2.rs +++ /dev/null @@ -1,18 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[shared] - struct Shared {} - - #[local] - struct Local { - a: u32, - } - - #[task(local = [a: u8 = 3])] - async fn bar(_: bar::Context) {} - - #[init(local = [a: u16 = 2])] - fn init(_: init::Context) -> (Shared, Local) {} -} diff --git a/macros/ui/local-collision-2.stderr b/macros/ui/local-collision-2.stderr deleted file mode 100644 index 47dbbe3..0000000 --- a/macros/ui/local-collision-2.stderr +++ /dev/null @@ -1,17 +0,0 @@ -error: Local resource "a" is used by multiple tasks or collides with multiple definitions - --> ui/local-collision-2.rs:10:9 - | -10 | a: u32, - | ^ - -error: Local resource "a" is used by multiple tasks or collides with multiple definitions - --> ui/local-collision-2.rs:16:21 - | -16 | #[init(local = [a: u16 = 2])] - | ^ - -error: Local resource "a" is used by multiple tasks or collides with multiple definitions - --> ui/local-collision-2.rs:13:21 - | -13 | #[task(local = [a: u8 = 3])] - | ^ diff --git a/macros/ui/local-collision.rs b/macros/ui/local-collision.rs deleted file mode 100644 index 0e4eef7..0000000 --- a/macros/ui/local-collision.rs +++ /dev/null @@ -1,21 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[shared] - struct Shared {} - - #[local] - struct Local { - a: u32, - } - - #[task(local = [a])] - async fn foo(_: foo::Context) {} - - #[task(local = [a: u8 = 3])] - async fn bar(_: bar::Context) {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) {} -} diff --git a/macros/ui/local-collision.stderr b/macros/ui/local-collision.stderr deleted file mode 100644 index 47fbb6e..0000000 --- a/macros/ui/local-collision.stderr +++ /dev/null @@ -1,11 +0,0 @@ -error: Local resource "a" is used by multiple tasks or collides with multiple definitions - --> ui/local-collision.rs:10:9 - | -10 | a: u32, - | ^ - -error: Local resource "a" is used by multiple tasks or collides with multiple definitions - --> ui/local-collision.rs:16:21 - | -16 | #[task(local = [a: u8 = 3])] - | ^ diff --git a/macros/ui/local-malformed-1.rs b/macros/ui/local-malformed-1.rs deleted file mode 100644 index 219eef5..0000000 --- a/macros/ui/local-malformed-1.rs +++ /dev/null @@ -1,16 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[task(local = [a:])] - async fn foo(_: foo::Context) {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) {} -} diff --git a/macros/ui/local-malformed-1.stderr b/macros/ui/local-malformed-1.stderr deleted file mode 100644 index d15c324..0000000 --- a/macros/ui/local-malformed-1.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: unexpected end of input, expected one of: `for`, parentheses, `fn`, `unsafe`, `extern`, identifier, `::`, `<`, square brackets, `*`, `&`, `!`, `impl`, `_`, lifetime - --> ui/local-malformed-1.rs:11:23 - | -11 | #[task(local = [a:])] - | ^ diff --git a/macros/ui/local-malformed-2.rs b/macros/ui/local-malformed-2.rs deleted file mode 100644 index d691453..0000000 --- a/macros/ui/local-malformed-2.rs +++ /dev/null @@ -1,16 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[task(local = [a: u32])] - async fn foo(_: foo::Context) {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) {} -} diff --git a/macros/ui/local-malformed-2.stderr b/macros/ui/local-malformed-2.stderr deleted file mode 100644 index 0b448f0..0000000 --- a/macros/ui/local-malformed-2.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: malformed, expected 'IDENT: TYPE = EXPR' - --> ui/local-malformed-2.rs:11:21 - | -11 | #[task(local = [a: u32])] - | ^^^^^^ diff --git a/macros/ui/local-malformed-3.rs b/macros/ui/local-malformed-3.rs deleted file mode 100644 index 7eddfa4..0000000 --- a/macros/ui/local-malformed-3.rs +++ /dev/null @@ -1,16 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[task(local = [a: u32 =])] - async fn foo(_: foo::Context) {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) {} -} diff --git a/macros/ui/local-malformed-3.stderr b/macros/ui/local-malformed-3.stderr deleted file mode 100644 index 61af4f3..0000000 --- a/macros/ui/local-malformed-3.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: unexpected end of input, expected expression - --> ui/local-malformed-3.rs:11:29 - | -11 | #[task(local = [a: u32 =])] - | ^ diff --git a/macros/ui/local-malformed-4.rs b/macros/ui/local-malformed-4.rs deleted file mode 100644 index b913947..0000000 --- a/macros/ui/local-malformed-4.rs +++ /dev/null @@ -1,16 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[task(local = [a = u32])] - async fn foo(_: foo::Context) {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) {} -} diff --git a/macros/ui/local-malformed-4.stderr b/macros/ui/local-malformed-4.stderr deleted file mode 100644 index 0f7d9e7..0000000 --- a/macros/ui/local-malformed-4.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: malformed, expected a type - --> ui/local-malformed-4.rs:11:21 - | -11 | #[task(local = [a = u32])] - | ^ diff --git a/macros/ui/local-not-declared.rs b/macros/ui/local-not-declared.rs deleted file mode 100644 index 7c087e4..0000000 --- a/macros/ui/local-not-declared.rs +++ /dev/null @@ -1,16 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[task(local = [A])] - async fn foo(_: foo::Context) {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) {} -} diff --git a/macros/ui/local-not-declared.stderr b/macros/ui/local-not-declared.stderr deleted file mode 100644 index 10d4b04..0000000 --- a/macros/ui/local-not-declared.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: this local resource has NOT been declared - --> ui/local-not-declared.rs:11:21 - | -11 | #[task(local = [A])] - | ^ diff --git a/macros/ui/local-pub.rs b/macros/ui/local-pub.rs deleted file mode 100644 index 42da4f4..0000000 --- a/macros/ui/local-pub.rs +++ /dev/null @@ -1,15 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[shared] - struct Shared {} - - #[local] - struct Local { - pub x: u32, - } - - #[init] - fn init(_: init::Context) -> (Shared, Local) {} -} diff --git a/macros/ui/local-pub.stderr b/macros/ui/local-pub.stderr deleted file mode 100644 index e4814ca..0000000 --- a/macros/ui/local-pub.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: this field must have inherited / private visibility - --> ui/local-pub.rs:10:13 - | -10 | pub x: u32, - | ^ diff --git a/macros/ui/local-shared-attribute.rs b/macros/ui/local-shared-attribute.rs deleted file mode 100644 index c594b5f..0000000 --- a/macros/ui/local-shared-attribute.rs +++ /dev/null @@ -1,21 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) {} - - #[task(local = [ - #[test] - a: u32 = 0, // Ok - #[test] - b, // Error - ])] - fn foo(_: foo::Context) {} -} diff --git a/macros/ui/local-shared-attribute.stderr b/macros/ui/local-shared-attribute.stderr deleted file mode 100644 index a8130e8..0000000 --- a/macros/ui/local-shared-attribute.stderr +++ /dev/null @@ -1,6 +0,0 @@ -error: attributes are not supported here - --> ui/local-shared-attribute.rs:17:9 - | -17 | / #[test] -18 | | b, // Error - | |_________^ diff --git a/macros/ui/local-shared.rs b/macros/ui/local-shared.rs deleted file mode 100644 index 4e8f9f4..0000000 --- a/macros/ui/local-shared.rs +++ /dev/null @@ -1,28 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[shared] - struct Shared {} - - #[local] - struct Local { - l1: u32, - l2: u32, - } - - #[init] - fn init(_: init::Context) -> (Shared, Local) {} - - // l2 ok - #[idle(local = [l2])] - fn idle(cx: idle::Context) -> ! {} - - // l1 rejected (not local) - #[task(priority = 1, local = [l1])] - async fn uart0(cx: uart0::Context) {} - - // l1 rejected (not lock_free) - #[task(priority = 2, local = [l1])] - async fn uart1(cx: uart1::Context) {} -} diff --git a/macros/ui/local-shared.stderr b/macros/ui/local-shared.stderr deleted file mode 100644 index fceb763..0000000 --- a/macros/ui/local-shared.stderr +++ /dev/null @@ -1,11 +0,0 @@ -error: Local resource "l1" is used by multiple tasks or collides with multiple definitions - --> ui/local-shared.rs:22:35 - | -22 | #[task(priority = 1, local = [l1])] - | ^^ - -error: Local resource "l1" is used by multiple tasks or collides with multiple definitions - --> ui/local-shared.rs:26:35 - | -26 | #[task(priority = 2, local = [l1])] - | ^^ diff --git a/macros/ui/shared-lock-free.rs b/macros/ui/shared-lock-free.rs deleted file mode 100644 index b3a4b9c..0000000 --- a/macros/ui/shared-lock-free.rs +++ /dev/null @@ -1,38 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[shared] - struct Shared { - // An exclusive, early resource - #[lock_free] - e1: u32, - - // An exclusive, late resource - #[lock_free] - e2: u32, - } - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) {} - - // e2 ok - #[idle(shared = [e2])] - fn idle(cx: idle::Context) -> ! { - debug::exit(debug::EXIT_SUCCESS); - loop {} - } - - // e1 rejected (not lock_free) - #[task(binds = UART0, priority = 1, shared = [e1])] - fn uart0(cx: uart0::Context) { - *cx.resources.e1 += 10; - } - - // e1 rejected (not lock_free) - #[task(binds = UART1, priority = 2, shared = [e1])] - fn uart1(cx: uart1::Context) {} -} diff --git a/macros/ui/shared-lock-free.stderr b/macros/ui/shared-lock-free.stderr deleted file mode 100644 index 51e99a0..0000000 --- a/macros/ui/shared-lock-free.stderr +++ /dev/null @@ -1,17 +0,0 @@ -error: Lock free shared resource "e1" is used by tasks at different priorities - --> ui/shared-lock-free.rs:9:9 - | -9 | e1: u32, - | ^^ - -error: Shared resource "e1" is declared lock free but used by tasks at different priorities - --> ui/shared-lock-free.rs:30:51 - | -30 | #[task(binds = UART0, priority = 1, shared = [e1])] - | ^^ - -error: Shared resource "e1" is declared lock free but used by tasks at different priorities - --> ui/shared-lock-free.rs:36:51 - | -36 | #[task(binds = UART1, priority = 2, shared = [e1])] - | ^^ diff --git a/macros/ui/shared-not-declared.rs b/macros/ui/shared-not-declared.rs deleted file mode 100644 index 5fef534..0000000 --- a/macros/ui/shared-not-declared.rs +++ /dev/null @@ -1,16 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[task(shared = [A])] - async fn foo(_: foo::Context) {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) {} -} diff --git a/macros/ui/shared-not-declared.stderr b/macros/ui/shared-not-declared.stderr deleted file mode 100644 index 7c5fb32..0000000 --- a/macros/ui/shared-not-declared.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: this shared resource has NOT been declared - --> ui/shared-not-declared.rs:11:22 - | -11 | #[task(shared = [A])] - | ^ diff --git a/macros/ui/shared-pub.rs b/macros/ui/shared-pub.rs deleted file mode 100644 index 10351fd..0000000 --- a/macros/ui/shared-pub.rs +++ /dev/null @@ -1,9 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[shared] - struct Shared { - pub x: u32, - } -} diff --git a/macros/ui/shared-pub.stderr b/macros/ui/shared-pub.stderr deleted file mode 100644 index 7148893..0000000 --- a/macros/ui/shared-pub.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: this field must have inherited / private visibility - --> ui/shared-pub.rs:7:13 - | -7 | pub x: u32, - | ^ diff --git a/macros/ui/task-divergent.rs b/macros/ui/task-divergent.rs deleted file mode 100644 index ffe2dc0..0000000 --- a/macros/ui/task-divergent.rs +++ /dev/null @@ -1,9 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[task] - async fn foo(_: foo::Context) -> ! { - loop {} - } -} diff --git a/macros/ui/task-divergent.stderr b/macros/ui/task-divergent.stderr deleted file mode 100644 index dd00208..0000000 --- a/macros/ui/task-divergent.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: this task handler must have type signature `async fn(foo::Context, ..)` - --> ui/task-divergent.rs:6:14 - | -6 | async fn foo(_: foo::Context) -> ! { - | ^^^ diff --git a/macros/ui/task-double-local.rs b/macros/ui/task-double-local.rs deleted file mode 100644 index c5277e2..0000000 --- a/macros/ui/task-double-local.rs +++ /dev/null @@ -1,7 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[task(local = [A], local = [B])] - async fn foo(_: foo::Context) {} -} diff --git a/macros/ui/task-double-local.stderr b/macros/ui/task-double-local.stderr deleted file mode 100644 index 91ed844..0000000 --- a/macros/ui/task-double-local.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: argument appears more than once - --> ui/task-double-local.rs:5:25 - | -5 | #[task(local = [A], local = [B])] - | ^^^^^ diff --git a/macros/ui/task-double-priority.rs b/macros/ui/task-double-priority.rs deleted file mode 100644 index 5c8bd5b..0000000 --- a/macros/ui/task-double-priority.rs +++ /dev/null @@ -1,7 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[task(priority = 1, priority = 2)] - async fn foo(_: foo::Context) {} -} diff --git a/macros/ui/task-double-priority.stderr b/macros/ui/task-double-priority.stderr deleted file mode 100644 index b3c814a..0000000 --- a/macros/ui/task-double-priority.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: argument appears more than once - --> ui/task-double-priority.rs:5:26 - | -5 | #[task(priority = 1, priority = 2)] - | ^^^^^^^^ diff --git a/macros/ui/task-double-shared.rs b/macros/ui/task-double-shared.rs deleted file mode 100644 index f9812d3..0000000 --- a/macros/ui/task-double-shared.rs +++ /dev/null @@ -1,7 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[task(shared = [A], shared = [B])] - async fn foo(_: foo::Context) {} -} diff --git a/macros/ui/task-double-shared.stderr b/macros/ui/task-double-shared.stderr deleted file mode 100644 index bb90212..0000000 --- a/macros/ui/task-double-shared.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: argument appears more than once - --> ui/task-double-shared.rs:5:26 - | -5 | #[task(shared = [A], shared = [B])] - | ^^^^^^ diff --git a/macros/ui/task-idle.rs b/macros/ui/task-idle.rs deleted file mode 100644 index 353c782..0000000 --- a/macros/ui/task-idle.rs +++ /dev/null @@ -1,13 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[idle] - fn foo(_: foo::Context) -> ! { - loop {} - } - - // name collides with `#[idle]` function - #[task] - async fn foo(_: foo::Context) {} -} diff --git a/macros/ui/task-idle.stderr b/macros/ui/task-idle.stderr deleted file mode 100644 index 4ccc113..0000000 --- a/macros/ui/task-idle.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: this identifier has already been used - --> ui/task-idle.rs:12:14 - | -12 | async fn foo(_: foo::Context) {} - | ^^^ diff --git a/macros/ui/task-init.rs b/macros/ui/task-init.rs deleted file mode 100644 index e58fdce..0000000 --- a/macros/ui/task-init.rs +++ /dev/null @@ -1,17 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn foo(_: foo::Context) -> (Shared, Local) {} - - // name collides with `#[idle]` function - #[task] - async fn foo(_: foo::Context) {} -} diff --git a/macros/ui/task-init.stderr b/macros/ui/task-init.stderr deleted file mode 100644 index 161e194..0000000 --- a/macros/ui/task-init.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: this identifier has already been used - --> ui/task-init.rs:16:14 - | -16 | async fn foo(_: foo::Context) {} - | ^^^ diff --git a/macros/ui/task-interrupt.rs b/macros/ui/task-interrupt.rs deleted file mode 100644 index 3d50bd8..0000000 --- a/macros/ui/task-interrupt.rs +++ /dev/null @@ -1,10 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[task(binds = SysTick)] - fn foo(_: foo::Context) {} - - #[task] - async fn foo(_: foo::Context) {} -} diff --git a/macros/ui/task-interrupt.stderr b/macros/ui/task-interrupt.stderr deleted file mode 100644 index 087b6c6..0000000 --- a/macros/ui/task-interrupt.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: this task is defined multiple times - --> ui/task-interrupt.rs:9:14 - | -9 | async fn foo(_: foo::Context) {} - | ^^^ diff --git a/macros/ui/task-no-context.rs b/macros/ui/task-no-context.rs deleted file mode 100644 index 55e8c3b..0000000 --- a/macros/ui/task-no-context.rs +++ /dev/null @@ -1,7 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[task] - async fn foo() {} -} diff --git a/macros/ui/task-no-context.stderr b/macros/ui/task-no-context.stderr deleted file mode 100644 index 62147aa..0000000 --- a/macros/ui/task-no-context.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: this task handler must have type signature `async fn(foo::Context, ..)` - --> ui/task-no-context.rs:6:14 - | -6 | async fn foo() {} - | ^^^ diff --git a/macros/ui/task-priority-too-high.rs b/macros/ui/task-priority-too-high.rs deleted file mode 100644 index f33ba56..0000000 --- a/macros/ui/task-priority-too-high.rs +++ /dev/null @@ -1,7 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[task(priority = 256)] - async fn foo(_: foo::Context) {} -} diff --git a/macros/ui/task-priority-too-high.stderr b/macros/ui/task-priority-too-high.stderr deleted file mode 100644 index 5790c88..0000000 --- a/macros/ui/task-priority-too-high.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: this literal must be in the range 0...255 - --> ui/task-priority-too-high.rs:5:23 - | -5 | #[task(priority = 256)] - | ^^^ diff --git a/macros/ui/task-priority-too-low.rs b/macros/ui/task-priority-too-low.rs deleted file mode 100644 index 16e0557..0000000 --- a/macros/ui/task-priority-too-low.rs +++ /dev/null @@ -1,7 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[task(binds = UART0, priority = 0)] - fn foo(_: foo::Context) {} -} diff --git a/macros/ui/task-priority-too-low.stderr b/macros/ui/task-priority-too-low.stderr deleted file mode 100644 index 85c8660..0000000 --- a/macros/ui/task-priority-too-low.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: hardware tasks are not allowed to be at priority 0 - --> ui/task-priority-too-low.rs:5:38 - | -5 | #[task(binds = UART0, priority = 0)] - | ^ diff --git a/macros/ui/task-pub.rs b/macros/ui/task-pub.rs deleted file mode 100644 index 1ae533f..0000000 --- a/macros/ui/task-pub.rs +++ /dev/null @@ -1,7 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[task] - pub async fn foo(_: foo::Context) {} -} diff --git a/macros/ui/task-pub.stderr b/macros/ui/task-pub.stderr deleted file mode 100644 index 7b9813d..0000000 --- a/macros/ui/task-pub.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: this task handler must have type signature `async fn(foo::Context, ..)` - --> ui/task-pub.rs:6:18 - | -6 | pub async fn foo(_: foo::Context) {} - | ^^^ diff --git a/macros/ui/task-unsafe.rs b/macros/ui/task-unsafe.rs deleted file mode 100644 index a8383ef..0000000 --- a/macros/ui/task-unsafe.rs +++ /dev/null @@ -1,7 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[task] - async unsafe fn foo(_: foo::Context) {} -} diff --git a/macros/ui/task-unsafe.stderr b/macros/ui/task-unsafe.stderr deleted file mode 100644 index 90ac76f..0000000 --- a/macros/ui/task-unsafe.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: this task handler must have type signature `async fn(foo::Context, ..)` - --> ui/task-unsafe.rs:6:21 - | -6 | async unsafe fn foo(_: foo::Context) {} - | ^^^ diff --git a/macros/ui/task-zero-prio.rs b/macros/ui/task-zero-prio.rs deleted file mode 100644 index de3c86f..0000000 --- a/macros/ui/task-zero-prio.rs +++ /dev/null @@ -1,19 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) {} - - #[task(priority = 0)] - fn foo(_: foo::Context) {} - - #[idle] - fn idle(_: idle::Context) -> ! {} -} diff --git a/macros/ui/task-zero-prio.stderr b/macros/ui/task-zero-prio.stderr deleted file mode 100644 index 1ab9aab..0000000 --- a/macros/ui/task-zero-prio.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: this task handler must have type signature `async fn(foo::Context, ..)` - --> ui/task-zero-prio.rs:15:8 - | -15 | fn foo(_: foo::Context) {} - | ^^^ diff --git a/rtic-monotonics/.gitignore b/rtic-monotonics/.gitignore new file mode 100644 index 0000000..c400256 --- /dev/null +++ b/rtic-monotonics/.gitignore @@ -0,0 +1,6 @@ +**/*.rs.bk +.#* +.gdb_history +/target +Cargo.lock +*.hex diff --git a/rtic-monotonics/Cargo.toml b/rtic-monotonics/Cargo.toml new file mode 100644 index 0000000..24448fb --- /dev/null +++ b/rtic-monotonics/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "rtic-timer" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +cortex-m = { version = "0.7.6" } +embedded-hal-async = "0.2.0-alpha.0" +fugit = { version = "0.3.6", features = ["defmt"] } +rtic-timer = { version = "1.0.0", path = "../rtic-timer" } diff --git a/rtic-monotonics/rust-toolchain.toml b/rtic-monotonics/rust-toolchain.toml new file mode 100644 index 0000000..e28b55d --- /dev/null +++ b/rtic-monotonics/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "nightly" +components = [ "rust-src", "rustfmt", "llvm-tools-preview" ] +targets = [ "thumbv6m-none-eabi", "thumbv7m-none-eabi" ] diff --git a/rtic-monotonics/src/lib.rs b/rtic-monotonics/src/lib.rs new file mode 100644 index 0000000..88398ca --- /dev/null +++ b/rtic-monotonics/src/lib.rs @@ -0,0 +1,11 @@ +//! Crate + +#![no_std] +#![no_main] +#![deny(missing_docs)] +#![allow(incomplete_features)] +#![feature(async_fn_in_trait)] + +pub use rtic_timer::{Monotonic, TimeoutError, TimerQueue}; + +pub mod systick_monotonic; diff --git a/rtic-monotonics/src/systick_monotonic.rs b/rtic-monotonics/src/systick_monotonic.rs new file mode 100644 index 0000000..491cf81 --- /dev/null +++ b/rtic-monotonics/src/systick_monotonic.rs @@ -0,0 +1 @@ +//! ... diff --git a/rtic-timer/.gitignore b/rtic-timer/.gitignore new file mode 100644 index 0000000..c400256 --- /dev/null +++ b/rtic-timer/.gitignore @@ -0,0 +1,6 @@ +**/*.rs.bk +.#* +.gdb_history +/target +Cargo.lock +*.hex diff --git a/rtic-timer/Cargo.toml b/rtic-timer/Cargo.toml index 8e2e2ad..b7b3a5f 100644 --- a/rtic-timer/Cargo.toml +++ b/rtic-timer/Cargo.toml @@ -1,11 +1,10 @@ [package] name = "rtic-timer" -version = "0.1.0" +version = "1.0.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -cortex-m = "0.7.6" -rtic-monotonic = "1.0.0" -fugit = "0.3.6" \ No newline at end of file +critical-section = "1" +futures-util = { version = "0.3.25", default-features = false } diff --git a/rtic-timer/rust-toolchain.toml b/rtic-timer/rust-toolchain.toml new file mode 100644 index 0000000..e28b55d --- /dev/null +++ b/rtic-timer/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "nightly" +components = [ "rust-src", "rustfmt", "llvm-tools-preview" ] +targets = [ "thumbv6m-none-eabi", "thumbv7m-none-eabi" ] diff --git a/rtic-timer/src/lib.rs b/rtic-timer/src/lib.rs index e7051d2..d7faa07 100644 --- a/rtic-timer/src/lib.rs +++ b/rtic-timer/src/lib.rs @@ -1,138 +1,336 @@ +//! Crate + #![no_std] +#![no_main] +#![deny(missing_docs)] +#![allow(incomplete_features)] +#![feature(async_fn_in_trait)] + +pub mod monotonic; + +use core::future::{poll_fn, Future}; +use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; +use core::task::{Poll, Waker}; +use futures_util::{ + future::{select, Either}, + pin_mut, +}; +pub use monotonic::Monotonic; -use core::sync::atomic::{AtomicU32, Ordering}; -use core::{cmp::Ordering, task::Waker}; -use cortex_m::peripheral::{syst::SystClkSource, SYST}; -pub use fugit::{self, ExtU64}; -pub use rtic_monotonic::Monotonic; +mod linked_list; -mod sll; -use sll::{IntrusiveSortedLinkedList, Min as IsslMin, Node as IntrusiveNode}; +use linked_list::{Link, LinkedList}; -pub struct Timer { - cnt: AtomicU32, - // queue: IntrusiveSortedLinkedList<'static, WakerNotReady, IsslMin>, +/// Holds a waker and at which time instant this waker shall be awoken. +struct WaitingWaker { + waker: Waker, + release_at: Mono::Instant, +} + +impl Clone for WaitingWaker { + fn clone(&self) -> Self { + Self { + waker: self.waker.clone(), + release_at: self.release_at, + } + } } -#[allow(non_snake_case)] -#[no_mangle] -fn SysTick() { - // .. - let cnt = unsafe { - static mut CNT: u32 = 0; - &mut CNT - }; +impl PartialEq for WaitingWaker { + fn eq(&self, other: &Self) -> bool { + self.release_at == other.release_at + } +} - *cnt = cnt.wrapping_add(1); +impl PartialOrd for WaitingWaker { + fn partial_cmp(&self, other: &Self) -> Option { + self.release_at.partial_cmp(&other.release_at) + } } -/// Systick implementing `rtic_monotonic::Monotonic` which runs at a -/// settable rate using the `TIMER_HZ` parameter. -pub struct Systick { - systick: SYST, - cnt: u64, +/// A generic timer queue for async executors. +/// +/// # Blocking +/// +/// The internal priority queue uses global critical sections to manage access. This means that +/// `await`ing a delay will cause a lock of the entire system for O(n) time. In practice the lock +/// duration is ~10 clock cycles per element in the queue. +/// +/// # Safety +/// +/// This timer queue is based on an intrusive linked list, and by extension the links are strored +/// on the async stacks of callers. The links are deallocated on `drop` or when the wait is +/// complete. +/// +/// Do not call `mem::forget` on an awaited future, or there will be dragons! +pub struct TimerQueue { + queue: LinkedList>, + initialized: AtomicBool, } -impl Systick { - /// Provide a new `Monotonic` based on SysTick. +/// This indicates that there was a timeout. +pub struct TimeoutError; + +impl TimerQueue { + /// Make a new queue. + pub const fn new() -> Self { + Self { + queue: LinkedList::new(), + initialized: AtomicBool::new(false), + } + } + + /// Forwards the `Monotonic::now()` method. + #[inline(always)] + pub fn now(&self) -> Mono::Instant { + Mono::now() + } + + /// Takes the initialized monotonic to initialize the TimerQueue. + pub fn initialize(&self, monotonic: Mono) { + self.initialized.store(true, Ordering::SeqCst); + + // Don't run drop on `Mono` + core::mem::forget(monotonic); + } + + /// Call this in the interrupt handler of the hardware timer supporting the `Monotonic` /// - /// The `sysclk` parameter is the speed at which SysTick runs at. This value should come from - /// the clock generation function of the used HAL. + /// # Safety /// - /// Notice that the actual rate of the timer is a best approximation based on the given - /// `sysclk` and `TIMER_HZ`. - pub fn new(mut systick: SYST, sysclk: u32) -> Self { - // + TIMER_HZ / 2 provides round to nearest instead of round to 0. - // - 1 as the counter range is inclusive [0, reload] - let reload = (sysclk + TIMER_HZ / 2) / TIMER_HZ - 1; + /// It's always safe to call, but it must only be called from the interrupt of the + /// monotonic timer for correct operation. + pub unsafe fn on_monotonic_interrupt(&self) { + Mono::clear_compare_flag(); + Mono::on_interrupt(); - assert!(reload <= 0x00ff_ffff); - assert!(reload > 0); + loop { + let mut release_at = None; + let head = self.queue.pop_if(|head| { + release_at = Some(head.release_at); - systick.disable_counter(); - systick.set_clock_source(SystClkSource::Core); - systick.set_reload(reload); + Mono::now() >= head.release_at + }); - Systick { systick, cnt: 0 } - } -} + match (head, release_at) { + (Some(link), _) => { + link.waker.wake(); + } + (None, Some(instant)) => { + Mono::enable_timer(); + Mono::set_compare(instant); -impl Monotonic for Systick { - const DISABLE_INTERRUPT_ON_EMPTY_QUEUE: bool = false; + if Mono::now() >= instant { + // The time for the next instant passed while handling it, + // continue dequeueing + continue; + } - type Instant = fugit::TimerInstantU64; - type Duration = fugit::TimerDurationU64; + break; + } + (None, None) => { + // Queue is empty + Mono::disable_timer(); - fn now(&mut self) -> Self::Instant { - if self.systick.has_wrapped() { - self.cnt = self.cnt.wrapping_add(1); + break; + } + } } - - Self::Instant::from_ticks(self.cnt) } - unsafe fn reset(&mut self) { - self.systick.clear_current(); - self.systick.enable_counter(); - } + /// Timeout at a specific time. + pub async fn timeout_at( + &self, + instant: Mono::Instant, + future: F, + ) -> Result { + let delay = self.delay_until(instant); - #[inline(always)] - fn set_compare(&mut self, _val: Self::Instant) { - // No need to do something here, we get interrupts anyway. + pin_mut!(future); + pin_mut!(delay); + + match select(future, delay).await { + Either::Left((r, _)) => Ok(r), + Either::Right(_) => Err(TimeoutError), + } } - #[inline(always)] - fn clear_compare_flag(&mut self) { - // NOOP with SysTick interrupt + /// Timeout after a specific duration. + #[inline] + pub async fn timeout_after( + &self, + duration: Mono::Duration, + future: F, + ) -> Result { + self.timeout_at(Mono::now() + duration, future).await } - #[inline(always)] - fn zero() -> Self::Instant { - Self::Instant::from_ticks(0) + /// Delay for some duration of time. + #[inline] + pub async fn delay(&self, duration: Mono::Duration) { + let now = Mono::now(); + + self.delay_until(now + duration).await; } - #[inline(always)] - fn on_interrupt(&mut self) { - if self.systick.has_wrapped() { - self.cnt = self.cnt.wrapping_add(1); + /// Delay to some specific time instant. + pub async fn delay_until(&self, instant: Mono::Instant) { + if !self.initialized.load(Ordering::Relaxed) { + panic!( + "The timer queue is not initialized with a monotonic, you need to run `initialize`" + ); } + + let mut first_run = true; + let queue = &self.queue; + let mut link = Link::new(WaitingWaker { + waker: poll_fn(|cx| Poll::Ready(cx.waker().clone())).await, + release_at: instant, + }); + + let marker = &AtomicUsize::new(0); + + let dropper = OnDrop::new(|| { + queue.delete(marker.load(Ordering::Relaxed)); + }); + + poll_fn(|_| { + if Mono::now() >= instant { + return Poll::Ready(()); + } + + if first_run { + first_run = false; + let (was_empty, addr) = queue.insert(&mut link); + marker.store(addr, Ordering::Relaxed); + + if was_empty { + // Pend the monotonic handler if the queue was empty to setup the timer. + Mono::pend_interrupt(); + } + } + + Poll::Pending + }) + .await; + + // Make sure that our link is deleted from the list before we drop this stack + drop(dropper); } } -struct WakerNotReady -where - Mono: Monotonic, -{ - pub waker: Waker, - pub instant: Mono::Instant, - pub marker: u32, +struct OnDrop { + f: core::mem::MaybeUninit, } -impl Eq for WakerNotReady where Mono: Monotonic {} - -impl Ord for WakerNotReady -where - Mono: Monotonic, -{ - fn cmp(&self, other: &Self) -> Ordering { - self.instant.cmp(&other.instant) +impl OnDrop { + pub fn new(f: F) -> Self { + Self { + f: core::mem::MaybeUninit::new(f), + } } -} -impl PartialEq for WakerNotReady -where - Mono: Monotonic, -{ - fn eq(&self, other: &Self) -> bool { - self.instant == other.instant + #[allow(unused)] + pub fn defuse(self) { + core::mem::forget(self) } } -impl PartialOrd for WakerNotReady -where - Mono: Monotonic, -{ - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) +impl Drop for OnDrop { + fn drop(&mut self) { + unsafe { self.f.as_ptr().read()() } } } + +// -------- Test program --------- +// +// +// use systick_monotonic::{Systick, TimerQueue}; +// +// // same panicking *behavior* as `panic-probe` but doesn't print a panic message +// // this prevents the panic message being printed *twice* when `defmt::panic` is invoked +// #[defmt::panic_handler] +// fn panic() -> ! { +// cortex_m::asm::udf() +// } +// +// /// Terminates the application and makes `probe-run` exit with exit-code = 0 +// pub fn exit() -> ! { +// loop { +// cortex_m::asm::bkpt(); +// } +// } +// +// defmt::timestamp!("{=u64:us}", { +// let time_us: fugit::MicrosDurationU32 = MONO.now().duration_since_epoch().convert(); +// +// time_us.ticks() as u64 +// }); +// +// make_systick_timer_queue!(MONO, Systick<1_000>); +// +// #[rtic::app( +// device = nrf52832_hal::pac, +// dispatchers = [SWI0_EGU0, SWI1_EGU1, SWI2_EGU2, SWI3_EGU3, SWI4_EGU4, SWI5_EGU5], +// )] +// mod app { +// use super::{Systick, MONO}; +// use fugit::ExtU32; +// +// #[shared] +// struct Shared {} +// +// #[local] +// struct Local {} +// +// #[init] +// fn init(cx: init::Context) -> (Shared, Local) { +// defmt::println!("init"); +// +// let systick = Systick::start(cx.core.SYST, 64_000_000); +// +// defmt::println!("initializing monotonic"); +// +// MONO.initialize(systick); +// +// async_task::spawn().ok(); +// async_task2::spawn().ok(); +// async_task3::spawn().ok(); +// +// (Shared {}, Local {}) +// } +// +// #[idle] +// fn idle(_: idle::Context) -> ! { +// defmt::println!("idle"); +// +// loop { +// core::hint::spin_loop(); +// } +// } +// +// #[task] +// async fn async_task(_: async_task::Context) { +// loop { +// defmt::println!("async task waiting for 1 second"); +// MONO.delay(1.secs()).await; +// } +// } +// +// #[task] +// async fn async_task2(_: async_task2::Context) { +// loop { +// defmt::println!(" async task 2 waiting for 0.5 second"); +// MONO.delay(500.millis()).await; +// } +// } +// +// #[task] +// async fn async_task3(_: async_task3::Context) { +// loop { +// defmt::println!(" async task 3 waiting for 0.2 second"); +// MONO.delay(200.millis()).await; +// } +// } +// } +// diff --git a/rtic-timer/src/linked_list.rs b/rtic-timer/src/linked_list.rs new file mode 100644 index 0000000..42ff8cb --- /dev/null +++ b/rtic-timer/src/linked_list.rs @@ -0,0 +1,173 @@ +//! ... + +use core::marker::PhantomPinned; +use core::sync::atomic::{AtomicPtr, Ordering}; +use critical_section as cs; + +/// A sorted linked list for the timer queue. +pub struct LinkedList { + head: AtomicPtr>, +} + +impl LinkedList { + /// Create a new linked list. + pub const fn new() -> Self { + Self { + head: AtomicPtr::new(core::ptr::null_mut()), + } + } +} + +impl LinkedList { + /// Pop the first element in the queue if the closure returns true. + pub fn pop_if bool>(&self, f: F) -> Option { + cs::with(|_| { + // Make sure all previous writes are visible + core::sync::atomic::fence(Ordering::SeqCst); + + let head = self.head.load(Ordering::Relaxed); + + // SAFETY: `as_ref` is safe as `insert` requires a valid reference to a link + if let Some(head) = unsafe { head.as_ref() } { + if f(&head.val) { + // Move head to the next element + self.head + .store(head.next.load(Ordering::Relaxed), Ordering::Relaxed); + + // We read the value at head + let head_val = head.val.clone(); + + return Some(head_val); + } + } + None + }) + } + + /// Delete a link at an address. + pub fn delete(&self, addr: usize) { + cs::with(|_| { + // Make sure all previous writes are visible + core::sync::atomic::fence(Ordering::SeqCst); + + let head = self.head.load(Ordering::Relaxed); + + // SAFETY: `as_ref` is safe as `insert` requires a valid reference to a link + let head_ref = if let Some(head_ref) = unsafe { head.as_ref() } { + head_ref + } else { + // 1. List is empty, do nothing + return; + }; + + if head as *const _ as usize == addr { + // 2. Replace head with head.next + self.head + .store(head_ref.next.load(Ordering::Relaxed), Ordering::Relaxed); + + return; + } + + // 3. search list for correct node + let mut curr = head_ref; + let mut next = head_ref.next.load(Ordering::Relaxed); + + // SAFETY: `as_ref` is safe as `insert` requires a valid reference to a link + while let Some(next_link) = unsafe { next.as_ref() } { + // Next is not null + + if next as *const _ as usize == addr { + curr.next + .store(next_link.next.load(Ordering::Relaxed), Ordering::Relaxed); + + return; + } + + // Continue searching + curr = next_link; + next = next_link.next.load(Ordering::Relaxed); + } + }) + } + + /// Insert a new link into the linked list. + /// The return is (was_empty, address), where the address of the link is for use with `delete`. + pub fn insert(&self, val: &mut Link) -> (bool, usize) { + cs::with(|_| { + let addr = val as *const _ as usize; + + // Make sure all previous writes are visible + core::sync::atomic::fence(Ordering::SeqCst); + + let head = self.head.load(Ordering::Relaxed); + + // 3 cases to handle + + // 1. List is empty, write to head + // SAFETY: `as_ref` is safe as `insert` requires a valid reference to a link + let head_ref = if let Some(head_ref) = unsafe { head.as_ref() } { + head_ref + } else { + self.head.store(val, Ordering::Relaxed); + return (true, addr); + }; + + // 2. val needs to go in first + if val.val < head_ref.val { + // Set current head as next of `val` + val.next.store(head, Ordering::Relaxed); + + // `val` is now first in the queue + self.head.store(val, Ordering::Relaxed); + + return (false, addr); + } + + // 3. search list for correct place + let mut curr = head_ref; + let mut next = head_ref.next.load(Ordering::Relaxed); + + // SAFETY: `as_ref` is safe as `insert` requires a valid reference to a link + while let Some(next_link) = unsafe { next.as_ref() } { + // Next is not null + + if val.val < next_link.val { + // Replace next with `val` + val.next.store(next, Ordering::Relaxed); + + // Insert `val` + curr.next.store(val, Ordering::Relaxed); + + return (false, addr); + } + + // Continue searching + curr = next_link; + next = next_link.next.load(Ordering::Relaxed); + } + + // No next, write link to last position in list + curr.next.store(val, Ordering::Relaxed); + + (false, addr) + }) + } +} + +/// A link in the linked list. +pub struct Link { + val: T, + next: AtomicPtr>, + _up: PhantomPinned, +} + +impl Link { + /// Create a new link. + pub const fn new(val: T) -> Self { + Self { + val, + next: AtomicPtr::new(core::ptr::null_mut()), + _up: PhantomPinned, + } + } +} diff --git a/rtic-timer/src/monotonic.rs b/rtic-timer/src/monotonic.rs new file mode 100644 index 0000000..9b3742f --- /dev/null +++ b/rtic-timer/src/monotonic.rs @@ -0,0 +1,60 @@ +//! ... + +/// # A monotonic clock / counter definition. +/// +/// ## Correctness +/// +/// The trait enforces that proper time-math is implemented between `Instant` and `Duration`. This +/// is a requirement on the time library that the user chooses to use. +pub trait Monotonic { + /// The time at time zero. + const ZERO: Self::Instant; + + /// The type for instant, defining an instant in time. + /// + /// **Note:** In all APIs in RTIC that use instants from this monotonic, this type will be used. + type Instant: Ord + + Copy + + core::ops::Add + + core::ops::Sub + + core::ops::Sub; + + /// The type for duration, defining an duration of time. + /// + /// **Note:** In all APIs in RTIC that use duration from this monotonic, this type will be used. + type Duration; + + /// Get the current time. + fn now() -> Self::Instant; + + /// Set the compare value of the timer interrupt. + /// + /// **Note:** This method does not need to handle race conditions of the monotonic, the timer + /// queue in RTIC checks this. + fn set_compare(instant: Self::Instant); + + /// Clear the compare interrupt flag. + fn clear_compare_flag(); + + /// Pend the timer's interrupt. + fn pend_interrupt(); + + /// Optional. Runs on interrupt before any timer queue handling. + fn on_interrupt() {} + + /// Optional. This is used to save power, this is called when the timer queue is not empty. + /// + /// Enabling and disabling the monotonic needs to propagate to `now` so that an instant + /// based of `now()` is still valid. + /// + /// NOTE: This may be called more than once. + fn enable_timer() {} + + /// Optional. This is used to save power, this is called when the timer queue is empty. + /// + /// Enabling and disabling the monotonic needs to propagate to `now` so that an instant + /// based of `now()` is still valid. + /// + /// NOTE: This may be called more than once. + fn disable_timer() {} +} diff --git a/rtic-timer/src/sll.rs b/rtic-timer/src/sll.rs deleted file mode 100644 index 43b53c1..0000000 --- a/rtic-timer/src/sll.rs +++ /dev/null @@ -1,421 +0,0 @@ -//! An intrusive sorted priority linked list, designed for use in `Future`s in RTIC. -use core::cmp::Ordering; -use core::fmt; -use core::marker::PhantomData; -use core::ops::{Deref, DerefMut}; -use core::ptr::NonNull; - -/// Marker for Min sorted [`IntrusiveSortedLinkedList`]. -pub struct Min; - -/// Marker for Max sorted [`IntrusiveSortedLinkedList`]. -pub struct Max; - -/// The linked list kind: min-list or max-list -pub trait Kind: private::Sealed { - #[doc(hidden)] - fn ordering() -> Ordering; -} - -impl Kind for Min { - fn ordering() -> Ordering { - Ordering::Less - } -} - -impl Kind for Max { - fn ordering() -> Ordering { - Ordering::Greater - } -} - -/// Sealed traits -mod private { - pub trait Sealed {} -} - -impl private::Sealed for Max {} -impl private::Sealed for Min {} - -/// A node in the [`IntrusiveSortedLinkedList`]. -pub struct Node { - pub val: T, - next: Option>>, -} - -impl Node { - pub fn new(val: T) -> Self { - Self { val, next: None } - } -} - -/// The linked list. -pub struct IntrusiveSortedLinkedList<'a, T, K> { - head: Option>>, - _kind: PhantomData, - _lt: PhantomData<&'a ()>, -} - -impl<'a, T, K> fmt::Debug for IntrusiveSortedLinkedList<'a, T, K> -where - T: Ord + core::fmt::Debug, - K: Kind, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut l = f.debug_list(); - let mut current = self.head; - - while let Some(head) = current { - let head = unsafe { head.as_ref() }; - current = head.next; - - l.entry(&head.val); - } - - l.finish() - } -} - -impl<'a, T, K> IntrusiveSortedLinkedList<'a, T, K> -where - T: Ord, - K: Kind, -{ - pub const fn new() -> Self { - Self { - head: None, - _kind: PhantomData, - _lt: PhantomData, - } - } - - // Push to the list. - pub fn push(&mut self, new: &'a mut Node) { - unsafe { - if let Some(head) = self.head { - if head.as_ref().val.cmp(&new.val) != K::ordering() { - // This is newer than head, replace head - new.next = self.head; - self.head = Some(NonNull::new_unchecked(new)); - } else { - // It's not head, search the list for the correct placement - let mut current = head; - - while let Some(next) = current.as_ref().next { - if next.as_ref().val.cmp(&new.val) != K::ordering() { - break; - } - - current = next; - } - - new.next = current.as_ref().next; - current.as_mut().next = Some(NonNull::new_unchecked(new)); - } - } else { - // List is empty, place at head - self.head = Some(NonNull::new_unchecked(new)) - } - } - } - - /// Get an iterator over the sorted list. - pub fn iter(&self) -> Iter<'_, T, K> { - Iter { - _list: self, - index: self.head, - } - } - - /// Find an element in the list that can be changed and resorted. - pub fn find_mut(&mut self, mut f: F) -> Option> - where - F: FnMut(&T) -> bool, - { - let head = self.head?; - - // Special-case, first element - if f(&unsafe { head.as_ref() }.val) { - return Some(FindMut { - is_head: true, - prev_index: None, - index: self.head, - list: self, - maybe_changed: false, - }); - } - - let mut current = head; - - while let Some(next) = unsafe { current.as_ref() }.next { - if f(&unsafe { next.as_ref() }.val) { - return Some(FindMut { - is_head: false, - prev_index: Some(current), - index: Some(next), - list: self, - maybe_changed: false, - }); - } - - current = next; - } - - None - } - - /// Peek at the first element. - pub fn peek(&self) -> Option<&T> { - self.head.map(|head| unsafe { &head.as_ref().val }) - } - - /// Pops the first element in the list. - /// - /// Complexity is worst-case `O(1)`. - pub fn pop(&mut self) -> Option<&'a Node> { - if let Some(head) = self.head { - let v = unsafe { head.as_ref() }; - self.head = v.next; - Some(v) - } else { - None - } - } - - /// Checks if the linked list is empty. - #[inline] - pub fn is_empty(&self) -> bool { - self.head.is_none() - } -} - -/// Iterator for the linked list. -pub struct Iter<'a, T, K> -where - T: Ord, - K: Kind, -{ - _list: &'a IntrusiveSortedLinkedList<'a, T, K>, - index: Option>>, -} - -impl<'a, T, K> Iterator for Iter<'a, T, K> -where - T: Ord, - K: Kind, -{ - type Item = &'a T; - - fn next(&mut self) -> Option { - let index = self.index?; - - let node = unsafe { index.as_ref() }; - self.index = node.next; - - Some(&node.val) - } -} - -/// Comes from [`IntrusiveSortedLinkedList::find_mut`]. -pub struct FindMut<'a, 'b, T, K> -where - T: Ord + 'b, - K: Kind, -{ - list: &'a mut IntrusiveSortedLinkedList<'b, T, K>, - is_head: bool, - prev_index: Option>>, - index: Option>>, - maybe_changed: bool, -} - -impl<'a, 'b, T, K> FindMut<'a, 'b, T, K> -where - T: Ord, - K: Kind, -{ - unsafe fn pop_internal(&mut self) -> &'b mut Node { - if self.is_head { - // If it is the head element, we can do a normal pop - let mut head = self.list.head.unwrap_unchecked(); - let v = head.as_mut(); - self.list.head = v.next; - v - } else { - // Somewhere in the list - let mut prev = self.prev_index.unwrap_unchecked(); - let mut curr = self.index.unwrap_unchecked(); - - // Re-point the previous index - prev.as_mut().next = curr.as_ref().next; - - curr.as_mut() - } - } - - /// This will pop the element from the list. - /// - /// Complexity is worst-case `O(1)`. - #[inline] - pub fn pop(mut self) -> &'b mut Node { - unsafe { self.pop_internal() } - } - - /// This will resort the element into the correct position in the list if needed. The resorting - /// will only happen if the element has been accessed mutably. - /// - /// Same as calling `drop`. - /// - /// Complexity is worst-case `O(N)`. - #[inline] - pub fn finish(self) { - drop(self) - } -} - -impl<'b, T, K> Drop for FindMut<'_, 'b, T, K> -where - T: Ord + 'b, - K: Kind, -{ - fn drop(&mut self) { - // Only resort the list if the element has changed - if self.maybe_changed { - unsafe { - let val = self.pop_internal(); - self.list.push(val); - } - } - } -} - -impl Deref for FindMut<'_, '_, T, K> -where - T: Ord, - K: Kind, -{ - type Target = T; - - fn deref(&self) -> &Self::Target { - unsafe { &self.index.unwrap_unchecked().as_ref().val } - } -} - -impl DerefMut for FindMut<'_, '_, T, K> -where - T: Ord, - K: Kind, -{ - fn deref_mut(&mut self) -> &mut Self::Target { - self.maybe_changed = true; - unsafe { &mut self.index.unwrap_unchecked().as_mut().val } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn const_new() { - static mut _V1: IntrusiveSortedLinkedList = IntrusiveSortedLinkedList::new(); - } - - #[test] - fn test_peek() { - let mut ll: IntrusiveSortedLinkedList = IntrusiveSortedLinkedList::new(); - - let mut a = Node { val: 1, next: None }; - ll.push(&mut a); - assert_eq!(ll.peek().unwrap(), &1); - - let mut a = Node { val: 2, next: None }; - ll.push(&mut a); - assert_eq!(ll.peek().unwrap(), &2); - - let mut a = Node { val: 3, next: None }; - ll.push(&mut a); - assert_eq!(ll.peek().unwrap(), &3); - - let mut ll: IntrusiveSortedLinkedList = IntrusiveSortedLinkedList::new(); - - let mut a = Node { val: 2, next: None }; - ll.push(&mut a); - assert_eq!(ll.peek().unwrap(), &2); - - let mut a = Node { val: 1, next: None }; - ll.push(&mut a); - assert_eq!(ll.peek().unwrap(), &1); - - let mut a = Node { val: 3, next: None }; - ll.push(&mut a); - assert_eq!(ll.peek().unwrap(), &1); - } - - #[test] - fn test_empty() { - let ll: IntrusiveSortedLinkedList = IntrusiveSortedLinkedList::new(); - - assert!(ll.is_empty()) - } - - #[test] - fn test_updating() { - let mut ll: IntrusiveSortedLinkedList = IntrusiveSortedLinkedList::new(); - - let mut a = Node { val: 1, next: None }; - ll.push(&mut a); - - let mut a = Node { val: 2, next: None }; - ll.push(&mut a); - - let mut a = Node { val: 3, next: None }; - ll.push(&mut a); - - let mut find = ll.find_mut(|v| *v == 2).unwrap(); - - *find += 1000; - find.finish(); - - assert_eq!(ll.peek().unwrap(), &1002); - - let mut find = ll.find_mut(|v| *v == 3).unwrap(); - - *find += 1000; - find.finish(); - - assert_eq!(ll.peek().unwrap(), &1003); - - // Remove largest element - ll.find_mut(|v| *v == 1003).unwrap().pop(); - - assert_eq!(ll.peek().unwrap(), &1002); - } - - #[test] - fn test_updating_1() { - let mut ll: IntrusiveSortedLinkedList = IntrusiveSortedLinkedList::new(); - - let mut a = Node { val: 1, next: None }; - ll.push(&mut a); - - let v = ll.pop().unwrap(); - - assert_eq!(v.val, 1); - } - - #[test] - fn test_updating_2() { - let mut ll: IntrusiveSortedLinkedList = IntrusiveSortedLinkedList::new(); - - let mut a = Node { val: 1, next: None }; - ll.push(&mut a); - - let mut find = ll.find_mut(|v| *v == 1).unwrap(); - - *find += 1000; - find.finish(); - - assert_eq!(ll.peek().unwrap(), &1001); - } -} diff --git a/rtic/.cargo/config.toml b/rtic/.cargo/config.toml new file mode 100644 index 0000000..d70faef --- /dev/null +++ b/rtic/.cargo/config.toml @@ -0,0 +1,13 @@ +[alias] +xtask = "run --package xtask --" + +[target.thumbv6m-none-eabi] +runner = "qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb -nographic -semihosting-config enable=on,target=native -kernel" + +[target.thumbv7m-none-eabi] +runner = "qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb -nographic -semihosting-config enable=on,target=native -kernel" + +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +rustflags = [ + "-C", "link-arg=-Tlink.x", +] \ No newline at end of file diff --git a/rtic/.gitignore b/rtic/.gitignore new file mode 100644 index 0000000..c400256 --- /dev/null +++ b/rtic/.gitignore @@ -0,0 +1,6 @@ +**/*.rs.bk +.#* +.gdb_history +/target +Cargo.lock +*.hex diff --git a/rtic/CHANGELOG.md b/rtic/CHANGELOG.md new file mode 100644 index 0000000..d172278 --- /dev/null +++ b/rtic/CHANGELOG.md @@ -0,0 +1,603 @@ +# Change Log + +All notable changes to this project will be documented in this file. +This project adheres to [Semantic Versioning](http://semver.org/). + +For each category, *Added*, *Changed*, *Fixed* add new entries at the top! + +## [Unreleased] + +### Added + +### Fixed + +### Changed + +## [v1.1.4] - 2023-02-26 + +### Added + +- CFG: Support #[cfg] on HW task, cleanup for SW tasks +- CFG: Slightly improved support for #[cfg] on Monotonics +- CI: Check examples also for thumbv8.{base,main} +- Allow custom `link_section` attributes for late resources + +### Fixed + +- Attempt to handle docs generation enabling `deny(missing_docs)` +- Book: Editorial review +- Use native GHA rustup and cargo +- Distinguish between thumbv8m.base and thumbv8m.main for basepri usage. + +### Changed + +- Updated dev-dependency cortex-m-semihosting to v0.5 +- CI: Updated to setup-python@v4 +- CI: Updated to checkout@v3 +- Tuned redirect message for rtic.rs/meeting + +## [v1.1.3] - 2022-06-23 + +### Added + +### Fixed + +- Bump cortex-m-rtic-macros to 1.1.5 +- fix ci: use SYST::PTR + +#### cortex-m-rtic-macros v1.1.5 - 2022-06-23 + +- Bump rtic-syntax to 1.0.2 + +#### cortex-m-rtic-macros v1.1.4 - 2022-05-24 + +- Fix macros to Rust 2021 + +#### cortex-m-rtic-macros v1.1.3 - 2022-05-24 + +- Fix clash with defmt + +### Changed + +## [v1.1.2] - 2022-05-09 + +### Added + +### Fixed + +- Generation of masks for the source masking scheduling for thumbv6 + +### Changed + +## [v1.1.1] - 2022-04-13 - YANKED + +### Added + +### Fixed + +- Fixed `marcro` version + +### Changed + +## [v1.1.0] - 2022-04-13 - YANKED + +### Added + +- Improve how CHANGELOG.md merges are handled +- If current $stable and master version matches, dev-book redirects to $stable book +- During deploy stage, merge master branch into current stable IFF cargo package version matches +- Rework branch structure, release/vVERSION +- Cargo clippy in CI +- Use rust-cache Github Action +- Support for NVIC based SPR based scheduling for armv6m. +- CI changelog entry enforcer +- `examples/periodic-at.rs`, an example of a periodic timer without accumulated drift. +- `examples/periodic-at2.rs`, an example of a periodic process with two tasks, with offset timing. + Here we depict two alternative usages of the timer type, explicit and trait based. +- book: Update `Monotonic` tips. + +### Fixed + +- Re-export `rtic_core::prelude` as `rtic::mutex::prelude` to allow glob imports + Clippy +- Fix all except `must_use` lints from clippy::pedantic +- Fix dated migration docs for spawn +- Remove obsolete action-rs tool-cache +- Force mdBook to return error codes +- Readded missing ramfunc output to book + +### Changed + +- Try to detect `target-dir` for rtic-expansion.rs + +## [v1.0.0] - 2021-12-25 + +### Changed + +- Bump RTIC dependencies also updated to v1.0.0 +- Edition 2021 +- Change default `idle` behaviour to be `NOP` instead of `WFI` + +## [v0.6.0-rc.4] - 2021-11-09 + +- Updated to use the new generic `Monotonic` trait + +## [v0.6.0-rc.3] - 2021-11-08 + +### Fixed + +- Match rtic-syntax Analysis-struct updates from https://github.com/rtic-rs/rtic-syntax/pull/61 + +## [v0.6.0-rc.2] - 2021-09-28 + +- Fixed issue with `cortex_m` being used by the codegen instead of using the `rtic::export::...` which could make an app not compile if Systick is used and the user did not have the cortex-m crate as a dependency + +## [v0.6.0-rc.1] - 2021-09-27 + +- Documentation updates +- Monotonic handlers default to maximum priority instead of minimum (to follow RTIC 0.5) +- Better support for `rust-analyzer` + +## [v0.5.9] - 2021-09-27 + +- Removed the `cortex-m-rt` dependency +- Docs updates + +## [v0.5.8] - 2021-08-19 + +- Feature flag was added to support `cortex-m v0.7.x` +- MSRV raised to 1.38. + +## [v0.6.0-alpha.5] - 2021-07-09 + +### Changed + +- The new resources syntax is implemented. + +## [v0.5.7] - 2021-07-05 + +- Backport: "you must enable the rt feature" compile time detection + +## [v0.6.0-alpha.4] - 2021-05-27 + +### Fixed + +- Fixed codegen structure to not have issues with local paths +- Default paths for monotonics now work properly +- New `embedded-time` version to `0.11` + +## [v0.6.0-alpha.3] - 2021-0X-XX + +- Lost in the ether... + +## [v0.6.0-alpha.2] - 2021-04-08 + +### Added + +- Cancel and reschedule support to the monotonics + +### Fixed + +- UB in `spawn_at` +- `#[cfg]` and other attributes now work on hardware tasks +- Type aliases now work in `mod app` + +### Changed + +- The access to monotonic static methods was for example `MyMono::now()`, and is now `monotonics::MyMono::now()` + +## [v0.6.0-alpha.1] - 2021-03-04 + +### Added + +- Support for multi-locks, see `examples/multilock.rs` for syntax. +- New monotonic syntax and support, see `#[monotonic]` + +## [v0.5.6] - 2021-03-03 + +- **Security** Use latest security patched heapless + +## [v0.6.0-alpha.0] - 2020-11-14 + +### Added + +- Allow annotating resources to activate special resource locking behaviour. + - `#[lock_free]`, there might be several tasks with the same priority accessing + the resource without critical section. + - `#[task_local]`, there must be only one task, similar to a task local + resource, but (optionally) set-up by init. This is similar to move. + +- Improved ergonomics allowing separation of task signatures to actual implementation in extern block `extern "Rust" { #[task(..)] fn t(..); }`. + +### Changed + +- [breaking-change] [PR 400] Move dispatchers from extern block to app argument. + +[PR 400]: https://github.com/rtic-rs/cortex-m-rtic/pull/400 + +- [breaking-change] [PR 399] Locking resources are now always required to achieve a symmetric UI. + +[PR 399]: https://github.com/rtic-rs/cortex-m-rtic/pull/399 + +- [breaking-change] [PR 390] Rework whole spawn/schedule, support `foo::spawn( ... )`, + `foo::schedule( ... )`. + +[PR 390]: https://github.com/rtic-rs/cortex-m-rtic/pull/390 + +- [breaking-change] [PR 368] `struct Resources` changed to attribute `#[resources]` on a struct. + +- [breaking-change] [PR 368] Mod over const, instead of `const APP: () = {` use `mod app {`. + +- [breaking-change] [PR 372] Init function always return `LateResources` for a symmetric API. + +- [PR 355] Multi-core support was removed to reduce overall complexity. + +[PR 368]: https://github.com/rtic-rs/cortex-m-rtic/pull/368 +[PR 372]: https://github.com/rtic-rs/cortex-m-rtic/pull/372 +[PR 355]: https://github.com/rtic-rs/cortex-m-rtic/pull/355 + +## [v0.5.5] - 2020-08-27 + +- Includes the previous soundness fix. +- Fixes wrong use of the `cortex_m` crate which can cause some projects to stop compiling. + +## [v0.5.4] - 2020-08-26 - YANKED + +- **Soundness fix in RTIC**, it was previously possible to get the `cortex_m::Peripherals` more than once, causing UB. + +## [v0.5.3] - 2020-06-12 + +- Added migration guide from `cortex-m-rtfm` to `cortex-m-rtic` +- No code changes, only a version compatibility release with `cortex-m-rtfm` to ease the transition +for users. + +## [v0.5.2] - 2020-06-11 + +- Using safe `DWT` interface +- Using GitHub Actions now +- Improved CI speed +- Now `main` can be used as function name +- Fixed so one can `cfg`-out resources when using a newer compiler + +## [v0.5.1] - 2019-11-19 + +- Fixed arithmetic wrapping bug in src/cyccntr.rs + elapsed and duration could cause an internal overflow trap + on subtraction in debug mode. + +- Fixed bug in SysTick implementation where the SysTick could be disabled by + accident + +## [v0.5.0] - 2019-11-14 + +### Added + +- Experimental support for homogeneous and heterogeneous multi-core + microcontrollers has been added. Support is gated behind the `homogeneous` and + `heterogeneous` Cargo features. + +### Changed + +- [breaking-change] [RFC 155] "explicit `Context` parameter" has been + implemented. + +[RFC 155]: https://github.com/rtic-rs/cortex-m-rtic/issues/155 + +- [breaking-change] [RFC 147] "all functions must be safe" has been + implemented. + +[RFC 147]: https://github.com/rtic-rs/cortex-m-rtic/issues/147 + +- All the queues internally used by the framework now use `AtomicU8` indices + instead of `AtomicUsize`; this reduces the static memory used by the + framework. + +- [breaking-change] when the `capacity` argument is omitted, the capacity of + the task is assumed to be `1`. Before, a reasonable (but hard to predict) + capacity was computed based on the number of `spawn` references the task had. + +- [breaking-change] resources that are appear as exclusive references + (`&mut-`) no longer appear behind the `Exclusive` newtype. + +- [breaking-change] the `timer-queue` Cargo feature has been removed. The + `schedule` API can be used without enabling any Cargo feature. + +- [breaking-change] when the `schedule` API is used the type of + `init::Context.core` changes from `cortex_m::Peripherals` to + `rtic::Peripherals`. The fields of `rtic::Peripherals` do not change when + Cargo features are enabled. + +- [breaking-change] the monotonic timer used to implement the `schedule` API + is now user configurable via the `#[app(monotonic = ..)]` argument. IMPORTANT: + it is now the responsibility of the application author to configure and + initialize the chosen `monotonic` timer during the `#[init]` phase. + +- [breaking-change] the `peripherals` field is not include in `init::Context` + by default. One must opt-in using the `#[app(peripherals = ..)]` argument. + +- [breaking-change] the `#[exception]` and `#[interrupt]` attributes have been + removed. Hardware tasks are now declared using the `#[task(binds = ..)]` + attribute. + +- [breaking-change] the syntax to declare resources has changed. Instead of + using a `static [mut]` variable for each resource, all resources must be + declared in a `Resources` structure. + +### Removed + +- [breaking-change] the integration with the `owned_singleton` crate has been + removed. You can use `heapless::Pool` instead of `alloc_singleton`. + +- [breaking-change] late resources can no longer be initialized using the assign + syntax. `init::LateResources` is the only method to initialize late resources. + See [PR #140] for more details. + +[PR #140]: https://github.com/rtic-rs/cortex-m-rtic/pull/140 + +## [v0.4.3] - 2019-04-21 + +### Changed + +- Checking that the specified priorities are supported by the target device is + now done at compile time. + +### Fixed + +- Building this crate with the "nightly" feature and a recent compiler has been + fixed. + +## [v0.4.2] - 2019-02-27 + +### Added + +- `Duration` now has an `as_cycles` method to get the number of clock cycles + contained in it. + +- An opt-in "nightly" feature that reduces static memory usage, shortens + initialization time and reduces runtime overhead has been added. To use this + feature you need a nightly compiler! + +- [RFC 128] has been implemented. The `exception` and `interrupt` have gained a + `binds` argument that lets you give the handler an arbitrary name. For + example: + +[RFC 128]: https://github.com/rtic-rs/cortex-m-rtic/issues/128 + +``` rust +// on v0.4.1 you had to write +#[interrupt] +fn USART0() { .. } + +// on v0.4.2 you can write +#[interrupt(binds = USART0)] +fn on_new_frame() { .. } +``` + +### Changed + +- Builds are now reproducible. `cargo build; cargo clean; cargo build` will + produce binaries that are exactly the same (after `objcopy -O ihex`). This + wasn't the case before because we used randomly generated identifiers for + memory safety but now all the randomness is gone. + +### Fixed + +- Fixed a `non_camel_case_types` warning that showed up when using a recent + nightly. + +- Fixed a bug that allowed you to enter the `capacity` and `priority` arguments + in the `task` attribute more than once. Now all arguments can only be stated + once in the list, as it should be. + +## [v0.4.1] - 2019-02-12 + +### Added + +- The RTIC book has been translated to Russian. You can find the translation + online at https://japaric.github.io/cortex-m-rtic/book/ru/ + +- `Duration` now implements the `Default` trait. + +### Changed + +- [breaking-change] [soundness-fix] `init` can not contain any early return as + that would result in late resources not being initialized and thus undefined + behavior. + +- Use an absolute link to the book so it works when landing from crates.io + documentation page + +- The initialization function can now be written as `fn init() -> + init::LateResources` when late resources are used. This is preferred over the + old `fn init()` form. See the section on late resources (resources chapter) in + the book for more details. + +### Fixed + +- `#[interrupt]` and `#[exception]` no longer produce warnings on recent nightlies. + +## [v0.4.0] - 2018-11-03 - YANKED + +Yanked due to a soundness issue in `init`; the issue has been mostly fixed in v0.4.1. + +### Changed + +- This crate now compiles on stable 1.31. + +- [breaking-change] The `app!` macro has been transformed into an attribute. See + the documentation for details. + +- [breaking-change] Applications that use this library must be written using the + 2018 edition. + +- [breaking-change] The `Resource` trait has been renamed to `Mutex`. + `Resource.claim_mut` has been renamed to `Mutex.lock` and its signature has + changed (no `Threshold` token is required). + +- [breaking-change] The name of the library has changed to `rtic`. The package + name is still `cortex-m-rtic`. + +- [breaking-change] `cortex_m_rtic::set_pending` has been renamed to + `rtic::pend`. + +### Added + +- Software tasks, which can be immediately spawn and scheduled to run in the + future. + +- `Instant` and `Duration` API. + +- Integration with the [`Singleton`] abstraction. + +[`Singleton`]: https://docs.rs/owned-singleton/0.1.0/owned_singleton/ + +### Removed + +- [breaking-change] The `Threshold` token has been removed. + +- [breaking-change] The `bkpt` and `wfi` re-exports have been removed. + +- [breaking-change] `rtic::atomic` has been removed. + +## [v0.3.4] - 2018-08-27 + +### Changed + +- The documentation link to point to GH pages. + +## [v0.3.3] - 2018-08-24 + +### Fixed + +- Compilation with latest nightly + +## [v0.3.2] - 2018-04-16 + +### Added + +- Span information to error messages + +### Changed + +- Some non fatal error messages have become warning messages. For example, specifying an empty list + of resources now produces a warning instead of a hard error. + +## [v0.3.1] - 2018-01-16 + +### Fixed + +- Documentation link + +## [v0.3.0] - 2018-01-15 + +### Added + +- [feat] `&'static mut` references can be safely created by assigning resources to `init`. See the + `init.resources` section of the `app!` macro documentation and the `safe-static-mut-ref` example + for details. + +### Changed + +- [breaking-change] svd2rust dependency has been bumped to v0.12.0 + +- [breaking-change] resources assigned to tasks, or to idle, that were not declared in the top + `resources` field generate compiler errors. Before these were assumed to be peripherals, that's no + longer the case. + +- [breaking-change] the layout of `init::Peripherals` has changed. This struct now has two fields: + `core` and `device`. The value of the `core` field is a struct that owns all the core peripherals + of the device and the value of the `device` field is a struct that owns all the device specific + peripherals of the device. + +## [v0.2.2] - 2017-11-22 + +### Added + +- Support for runtime initialized resources ("late" resources). + +## [v0.2.1] - 2017-07-29 + +### Fixed + +- Link to `app!` macro documentation. + +## [v0.2.0] - 2017-07-29 + +### Added + +- The `app!` macro, a macro to declare the tasks and resources of an + application. + +- The `Resource` trait, which is used to write generic code that deals with + resources. + +- Support for system handlers like SYS_TICK. + +### Changed + +- [breaking-change] The signature of the `atomic` function has changed. + +- [breaking-change] The threshold token has become a concrete type and lost its + `raise` method. + +### Removed + +- [breaking-change] The `tasks!` and `peripherals!` macros. + +- [breaking-change] The ceiling and priority tokens. + +- [breaking-change] The `Local`, `Resource` and `Peripheral` structs. + +- [breaking-change] The traits related to type level integers. + +## [v0.1.1] - 2017-06-05 + +### Changed + +- `peripherals!`: The `register_block` field is now optional + +## v0.1.0 - 2017-05-09 + +- Initial release + +[Unreleased]: https://github.com/rtic-rs/cortex-m-rtic/compare/v1.1.4...HEAD +[v1.1.4]: https://github.com/rtic-rs/cortex-m-rtic/compare/v1.1.3...v1.1.4 +[v1.1.3]: https://github.com/rtic-rs/cortex-m-rtic/compare/v1.1.2...v1.1.3 +[v1.1.2]: https://github.com/rtic-rs/cortex-m-rtic/compare/v1.1.1...v1.1.2 +[v1.1.1]: https://github.com/rtic-rs/cortex-m-rtic/compare/v1.1.0...v1.1.1 +[v1.1.0]: https://github.com/rtic-rs/cortex-m-rtic/compare/v1.0.0...v1.1.0 +[v1.0.0]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.6.0-rc.4...v1.0.0 +[v0.6.0-rc.4]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.6.0-rc.3...v0.6.0-rc.4 +[v0.6.0-rc.3]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.6.0-rc.2...v0.6.0-rc.3 +[v0.6.0-rc.2]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.6.0-rc.1...v0.6.0-rc.2 +[v0.6.0-rc.1]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.6.0-rc.0...v0.6.0-rc.1 +[v0.6.0-rc.0]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.6.0-alpha.5...v0.6.0-rc.0 +[v0.6.0-alpha.5]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.6.0-alpha.4...v0.6.0-alpha.5 +[v0.6.0-alpha.4]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.6.0-alpha.3...v0.6.0-alpha.4 +[v0.6.0-alpha.3]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.6.0-alpha.2...v0.6.0-alpha.3 +[v0.6.0-alpha.2]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.6.0-alpha.1...v0.6.0-alpha.2 +[v0.6.0-alpha.1]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.6.0-alpha.0...v0.6.0-alpha.1 +[v0.6.0-alpha.0]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.5.5...v0.6.0-alpha.0 +[v0.5.x unreleased]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.5.8...v0.5.x +[v0.5.9]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.5.8...v0.5.9 +[v0.5.8]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.5.7...v0.5.8 +[v0.5.7]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.5.6...v0.5.7 +[v0.5.6]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.5.5...v0.5.6 +[v0.5.5]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.5.4...v0.5.5 +[v0.5.4]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.5.3...v0.5.4 +[v0.5.3]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.5.2...v0.5.3 +[v0.5.2]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.5.1...v0.5.2 +[v0.5.1]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.5.0...v0.5.1 +[v0.5.0]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.4.3...v0.5.0 +[v0.4.3]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.4.2...v0.4.3 +[v0.4.2]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.4.1...v0.4.2 +[v0.4.1]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.4.0...v0.4.1 +[v0.4.0]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.3.4...v0.4.0 +[v0.3.4]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.3.3...v0.3.4 +[v0.3.3]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.3.2...v0.3.3 +[v0.3.2]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.3.1...v0.3.2 +[v0.3.1]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.3.0...v0.3.1 +[v0.3.0]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.2.2...v0.3.0 +[v0.2.2]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.2.1...v0.2.2 +[v0.2.1]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.2.0...v0.2.1 +[v0.2.0]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.1.1...v0.2.0 +[v0.1.1]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.1.0...v0.1.1 diff --git a/rtic/Cargo.toml b/rtic/Cargo.toml new file mode 100644 index 0000000..c22d023 --- /dev/null +++ b/rtic/Cargo.toml @@ -0,0 +1,80 @@ +[package] +authors = [ + "The Real-Time Interrupt-driven Concurrency developers", + "Emil Fresk ", + "Henrik Tjäder ", + "Jorge Aparicio ", + "Per Lindgren ", +] +categories = ["concurrency", "embedded", "no-std", "asynchronous"] +description = "Real-Time Interrupt-driven Concurrency (RTIC): a concurrency framework for building real-time systems" +documentation = "https://rtic.rs/" +edition = "2021" +keywords = ["arm", "cortex-m", "risc-v", "embedded", "async", "runtime", "futures", "await", "no-std", "rtos", "bare-metal"] +license = "MIT OR Apache-2.0" +name = "rtic" +readme = "README.md" +repository = "https://github.com/rtic-rs/rtic" + +version = "2.0.0-alpha.0" + +[lib] +name = "rtic" + +[dependencies] +cortex-m = "0.7.0" +rtic-macros = { path = "macros", version = "2.0.0-alpha.0" } +rtic-monotonic = "1.0.0" +rtic-core = "1.0.0" +heapless = "0.7.7" +bare-metal = "1.0.0" +#portable-atomic = { version = "0.3.19" } +atomic-polyfill = "1" + +[build-dependencies] +version_check = "0.9" + +[dev-dependencies] +lm3s6965 = "0.1.3" +cortex-m-semihosting = "0.5.0" +systick-monotonic = "1.0.0" + +[dev-dependencies.panic-semihosting] +features = ["exit"] +version = "0.6.0" + +[target.x86_64-unknown-linux-gnu.dev-dependencies] +trybuild = "1" + +[profile.release] +codegen-units = 1 +lto = true + +[workspace] +members = ["macros", "xtask", "rtic-timer"] + +# do not optimize proc-macro deps or build scripts +[profile.dev.build-override] +codegen-units = 16 +debug = false +debug-assertions = false +opt-level = 0 +overflow-checks = false + + +[profile.release.build-override] +codegen-units = 16 +debug = false +debug-assertions = false +opt-level = 0 +overflow-checks = false + +[patch.crates-io] +lm3s6965 = { git = "https://github.com/japaric/lm3s6965" } + +[features] +test-critical-section = ["cortex-m/critical-section-single-core"] + +# [[example]] +# name = "pool" +# required-features = ["test-critical-section"] diff --git a/rtic/build.rs b/rtic/build.rs new file mode 100644 index 0000000..35f8303 --- /dev/null +++ b/rtic/build.rs @@ -0,0 +1,25 @@ +use std::env; + +fn main() { + let target = env::var("TARGET").unwrap(); + + if version_check::Channel::read().unwrap().is_nightly() { + println!("cargo:rustc-cfg=rustc_is_nightly"); + } + + // These targets all have know support for the BASEPRI register. + if target.starts_with("thumbv7m") + | target.starts_with("thumbv7em") + | target.starts_with("thumbv8m.main") + { + println!("cargo:rustc-cfg=have_basepri"); + + // These targets are all known to _not_ have the BASEPRI register. + } else if target.starts_with("thumb") + && !(target.starts_with("thumbv6m") | target.starts_with("thumbv8m.base")) + { + panic!("Unknown target '{target}'. Need to update BASEPRI logic in build.rs."); + } + + println!("cargo:rerun-if-changed=build.rs"); +} diff --git a/rtic/ci/expected/async-delay.run b/rtic/ci/expected/async-delay.run new file mode 100644 index 0000000..61852ab --- /dev/null +++ b/rtic/ci/expected/async-delay.run @@ -0,0 +1,7 @@ +init +hello from bar +hello from baz +hello from foo +bye from foo +bye from bar +bye from baz diff --git a/rtic/ci/expected/async-infinite-loop.run b/rtic/ci/expected/async-infinite-loop.run new file mode 100644 index 0000000..f9fd4e4 --- /dev/null +++ b/rtic/ci/expected/async-infinite-loop.run @@ -0,0 +1,6 @@ +init +hello from async 0 +hello from async 1 +hello from async 2 +hello from async 3 +hello from async 4 diff --git a/rtic/ci/expected/async-task-multiple-prios.run b/rtic/ci/expected/async-task-multiple-prios.run new file mode 100644 index 0000000..0b42df0 --- /dev/null +++ b/rtic/ci/expected/async-task-multiple-prios.run @@ -0,0 +1,6 @@ +init +hello from async 3 a 1 +hello from async 4 a 2 +hello from async 1 a 3 +hello from async 2 a 4 +idle diff --git a/rtic/ci/expected/async-task.run b/rtic/ci/expected/async-task.run new file mode 100644 index 0000000..1f93a4c --- /dev/null +++ b/rtic/ci/expected/async-task.run @@ -0,0 +1,5 @@ +init +hello from async2 +hello from async +hello from async with args a: 1, b: 2 +idle diff --git a/rtic/ci/expected/async-timeout.run b/rtic/ci/expected/async-timeout.run new file mode 100644 index 0000000..a807423 --- /dev/null +++ b/rtic/ci/expected/async-timeout.run @@ -0,0 +1,5 @@ +init +hello from bar +hello from foo +foo no timeout +bar timeout diff --git a/rtic/ci/expected/big-struct-opt.run b/rtic/ci/expected/big-struct-opt.run new file mode 100644 index 0000000..7fdef35 --- /dev/null +++ b/rtic/ci/expected/big-struct-opt.run @@ -0,0 +1,3 @@ +async_task data:[22, 22, 22, 22, 22] +uart0 data:[22, 22, 22, 22, 22] +idle diff --git a/rtic/ci/expected/binds.run b/rtic/ci/expected/binds.run new file mode 100644 index 0000000..f84cff0 --- /dev/null +++ b/rtic/ci/expected/binds.run @@ -0,0 +1,4 @@ +init +foo called 1 time +idle +foo called 2 times diff --git a/rtic/ci/expected/cancel-reschedule.run b/rtic/ci/expected/cancel-reschedule.run new file mode 100644 index 0000000..5a94752 --- /dev/null +++ b/rtic/ci/expected/cancel-reschedule.run @@ -0,0 +1,3 @@ +init +foo +bar diff --git a/rtic/ci/expected/capacity.run b/rtic/ci/expected/capacity.run new file mode 100644 index 0000000..f96815d --- /dev/null +++ b/rtic/ci/expected/capacity.run @@ -0,0 +1,5 @@ +foo(0) +foo(1) +foo(2) +foo(3) +bar diff --git a/rtic/ci/expected/cfg-whole-task.run b/rtic/ci/expected/cfg-whole-task.run new file mode 100644 index 0000000..e69de29 diff --git a/rtic/ci/expected/common.run b/rtic/ci/expected/common.run new file mode 100644 index 0000000..e69de29 diff --git a/rtic/ci/expected/complex.run b/rtic/ci/expected/complex.run new file mode 100644 index 0000000..5df884d --- /dev/null +++ b/rtic/ci/expected/complex.run @@ -0,0 +1,47 @@ +init +idle p0 started +t2 p4 called 1 time +enter lock s4 0 +t3 p4 exit +idle enter lock s3 0 +idle pend t0 +idle pend t1 +idle pend t2 +t2 p4 called 2 times +enter lock s4 1 +t3 p4 exit +idle still in lock s3 0 +t1 p3 called 1 time +t1 enter lock s4 2 +t1 pend t0 +t1 pend t2 +t1 still in lock s4 2 +t2 p4 called 3 times +enter lock s4 2 +t3 p4 exit +t1 p3 exit +t0 p2 called 1 time +t0 p2 exit + +back in idle +enter lock s2 0 +idle pend t0 +idle pend t1 +t1 p3 called 2 times +t1 enter lock s4 3 +t1 pend t0 +t1 pend t2 +t1 still in lock s4 3 +t2 p4 called 4 times +enter lock s4 3 +t3 p4 exit +t1 p3 exit +idle pend t2 +t2 p4 called 5 times +enter lock s4 4 +t3 p4 exit +idle still in lock s2 0 +t0 p2 called 2 times +t0 p2 exit + +idle exit diff --git a/rtic/ci/expected/declared_locals.run b/rtic/ci/expected/declared_locals.run new file mode 100644 index 0000000..e69de29 diff --git a/rtic/ci/expected/destructure.run b/rtic/ci/expected/destructure.run new file mode 100644 index 0000000..25a4b1b --- /dev/null +++ b/rtic/ci/expected/destructure.run @@ -0,0 +1,2 @@ +bar: a = 0, b = 1, c = 2 +foo: a = 0, b = 1, c = 2 diff --git a/rtic/ci/expected/extern_binds.run b/rtic/ci/expected/extern_binds.run new file mode 100644 index 0000000..9d925d5 --- /dev/null +++ b/rtic/ci/expected/extern_binds.run @@ -0,0 +1,4 @@ +init +foo called +idle +foo called diff --git a/rtic/ci/expected/extern_spawn.run b/rtic/ci/expected/extern_spawn.run new file mode 100644 index 0000000..257cc56 --- /dev/null +++ b/rtic/ci/expected/extern_spawn.run @@ -0,0 +1 @@ +foo diff --git a/rtic/ci/expected/generics.run b/rtic/ci/expected/generics.run new file mode 100644 index 0000000..fb31731 --- /dev/null +++ b/rtic/ci/expected/generics.run @@ -0,0 +1,6 @@ +UART1(STATE = 0) +shared: 0 -> 1 +UART0(STATE = 0) +shared: 1 -> 2 +UART1(STATE = 1) +shared: 2 -> 4 diff --git a/rtic/ci/expected/hardware.run b/rtic/ci/expected/hardware.run new file mode 100644 index 0000000..ef00864 --- /dev/null +++ b/rtic/ci/expected/hardware.run @@ -0,0 +1,4 @@ +init +UART0 called 1 time +idle +UART0 called 2 times diff --git a/rtic/ci/expected/idle-wfi.run b/rtic/ci/expected/idle-wfi.run new file mode 100644 index 0000000..4307776 --- /dev/null +++ b/rtic/ci/expected/idle-wfi.run @@ -0,0 +1,2 @@ +init +idle diff --git a/rtic/ci/expected/idle.run b/rtic/ci/expected/idle.run new file mode 100644 index 0000000..4307776 --- /dev/null +++ b/rtic/ci/expected/idle.run @@ -0,0 +1,2 @@ +init +idle diff --git a/rtic/ci/expected/init.run b/rtic/ci/expected/init.run new file mode 100644 index 0000000..b1b7161 --- /dev/null +++ b/rtic/ci/expected/init.run @@ -0,0 +1 @@ +init diff --git a/rtic/ci/expected/locals.run b/rtic/ci/expected/locals.run new file mode 100644 index 0000000..4f1d350 --- /dev/null +++ b/rtic/ci/expected/locals.run @@ -0,0 +1,3 @@ +bar: local_to_bar = 1 +foo: local_to_foo = 1 +idle: local_to_idle = 1 diff --git a/rtic/ci/expected/lock-free.run b/rtic/ci/expected/lock-free.run new file mode 100644 index 0000000..18de0ec --- /dev/null +++ b/rtic/ci/expected/lock-free.run @@ -0,0 +1,2 @@ + foo = 1 + bar = 2 diff --git a/rtic/ci/expected/lock.run b/rtic/ci/expected/lock.run new file mode 100644 index 0000000..a987b37 --- /dev/null +++ b/rtic/ci/expected/lock.run @@ -0,0 +1,5 @@ +A +B - shared = 1 +C +D - shared = 2 +E diff --git a/rtic/ci/expected/message.run b/rtic/ci/expected/message.run new file mode 100644 index 0000000..11814db --- /dev/null +++ b/rtic/ci/expected/message.run @@ -0,0 +1,6 @@ +foo +bar(0) +baz(1, 2) +foo +bar(1) +baz(2, 3) diff --git a/rtic/ci/expected/message_passing.run b/rtic/ci/expected/message_passing.run new file mode 100644 index 0000000..a1448d8 --- /dev/null +++ b/rtic/ci/expected/message_passing.run @@ -0,0 +1,3 @@ +foo 1, 1 +foo 1, 2 +foo 2, 3 diff --git a/rtic/ci/expected/multilock.run b/rtic/ci/expected/multilock.run new file mode 100644 index 0000000..dd8c1f2 --- /dev/null +++ b/rtic/ci/expected/multilock.run @@ -0,0 +1 @@ +Multiple locks, s1: 1, s2: 1, s3: 1 diff --git a/rtic/ci/expected/not-sync.run b/rtic/ci/expected/not-sync.run new file mode 100644 index 0000000..cd91476 --- /dev/null +++ b/rtic/ci/expected/not-sync.run @@ -0,0 +1,3 @@ +init +bar a 13 +foo a 13 diff --git a/rtic/ci/expected/only-shared-access.run b/rtic/ci/expected/only-shared-access.run new file mode 100644 index 0000000..dcc73e6 --- /dev/null +++ b/rtic/ci/expected/only-shared-access.run @@ -0,0 +1,2 @@ +bar(key = 0xdeadbeef) +foo(key = 0xdeadbeef) diff --git a/rtic/ci/expected/periodic-at.run b/rtic/ci/expected/periodic-at.run new file mode 100644 index 0000000..bf5bb06 --- /dev/null +++ b/rtic/ci/expected/periodic-at.run @@ -0,0 +1,4 @@ +foo Instant { ticks: 0 } +foo Instant { ticks: 10 } +foo Instant { ticks: 20 } +foo Instant { ticks: 30 } diff --git a/rtic/ci/expected/periodic-at2.run b/rtic/ci/expected/periodic-at2.run new file mode 100644 index 0000000..6e56421 --- /dev/null +++ b/rtic/ci/expected/periodic-at2.run @@ -0,0 +1,7 @@ +foo Instant { ticks: 0 } +bar Instant { ticks: 10 } +foo Instant { ticks: 30 } +bar Instant { ticks: 40 } +foo Instant { ticks: 60 } +bar Instant { ticks: 70 } +foo Instant { ticks: 90 } diff --git a/rtic/ci/expected/periodic.run b/rtic/ci/expected/periodic.run new file mode 100644 index 0000000..a1f8944 --- /dev/null +++ b/rtic/ci/expected/periodic.run @@ -0,0 +1,4 @@ +foo +foo +foo +foo diff --git a/rtic/ci/expected/peripherals-taken.run b/rtic/ci/expected/peripherals-taken.run new file mode 100644 index 0000000..e69de29 diff --git a/rtic/ci/expected/pool.run b/rtic/ci/expected/pool.run new file mode 100644 index 0000000..e69de29 diff --git a/rtic/ci/expected/preempt.run b/rtic/ci/expected/preempt.run new file mode 100644 index 0000000..932b2b3 --- /dev/null +++ b/rtic/ci/expected/preempt.run @@ -0,0 +1,5 @@ +foo - start + baz - start + baz - end + bar +foo - end diff --git a/rtic/ci/expected/ramfunc.run b/rtic/ci/expected/ramfunc.run new file mode 100644 index 0000000..257cc56 --- /dev/null +++ b/rtic/ci/expected/ramfunc.run @@ -0,0 +1 @@ +foo diff --git a/rtic/ci/expected/ramfunc.run.grep.bar b/rtic/ci/expected/ramfunc.run.grep.bar new file mode 100644 index 0000000..33e002f --- /dev/null +++ b/rtic/ci/expected/ramfunc.run.grep.bar @@ -0,0 +1 @@ +20000000 t ramfunc::bar::h9d6714fe5a3b0c89 diff --git a/rtic/ci/expected/ramfunc.run.grep.foo b/rtic/ci/expected/ramfunc.run.grep.foo new file mode 100644 index 0000000..44e8822 --- /dev/null +++ b/rtic/ci/expected/ramfunc.run.grep.foo @@ -0,0 +1 @@ +00000162 t ramfunc::foo::h30e7789b08c08e19 diff --git a/rtic/ci/expected/resource-user-struct.run b/rtic/ci/expected/resource-user-struct.run new file mode 100644 index 0000000..a587a94 --- /dev/null +++ b/rtic/ci/expected/resource-user-struct.run @@ -0,0 +1,2 @@ +UART0: shared = 1 +UART1: shared = 2 diff --git a/rtic/ci/expected/schedule.run b/rtic/ci/expected/schedule.run new file mode 100644 index 0000000..1dbd445 --- /dev/null +++ b/rtic/ci/expected/schedule.run @@ -0,0 +1,4 @@ +init +foo +bar +baz diff --git a/rtic/ci/expected/shared.run b/rtic/ci/expected/shared.run new file mode 100644 index 0000000..6d3d3e4 --- /dev/null +++ b/rtic/ci/expected/shared.run @@ -0,0 +1 @@ +received message: 42 diff --git a/rtic/ci/expected/smallest.run b/rtic/ci/expected/smallest.run new file mode 100644 index 0000000..e69de29 diff --git a/rtic/ci/expected/spawn.run b/rtic/ci/expected/spawn.run new file mode 100644 index 0000000..240cd18 --- /dev/null +++ b/rtic/ci/expected/spawn.run @@ -0,0 +1,2 @@ +init +foo diff --git a/rtic/ci/expected/static.run b/rtic/ci/expected/static.run new file mode 100644 index 0000000..3d3f46f --- /dev/null +++ b/rtic/ci/expected/static.run @@ -0,0 +1,3 @@ +received message: 1 +received message: 2 +received message: 3 diff --git a/rtic/ci/expected/t-binds.run b/rtic/ci/expected/t-binds.run new file mode 100644 index 0000000..e69de29 diff --git a/rtic/ci/expected/t-cfg-resources.run b/rtic/ci/expected/t-cfg-resources.run new file mode 100644 index 0000000..e69de29 diff --git a/rtic/ci/expected/t-htask-main.run b/rtic/ci/expected/t-htask-main.run new file mode 100644 index 0000000..e69de29 diff --git a/rtic/ci/expected/t-idle-main.run b/rtic/ci/expected/t-idle-main.run new file mode 100644 index 0000000..e69de29 diff --git a/rtic/ci/expected/t-late-not-send.run b/rtic/ci/expected/t-late-not-send.run new file mode 100644 index 0000000..e69de29 diff --git a/rtic/ci/expected/t-schedule.run b/rtic/ci/expected/t-schedule.run new file mode 100644 index 0000000..e69de29 diff --git a/rtic/ci/expected/t-spawn.run b/rtic/ci/expected/t-spawn.run new file mode 100644 index 0000000..e69de29 diff --git a/rtic/ci/expected/task.run b/rtic/ci/expected/task.run new file mode 100644 index 0000000..de45dce --- /dev/null +++ b/rtic/ci/expected/task.run @@ -0,0 +1,5 @@ +foo - start +foo - middle +baz +foo - end +bar diff --git a/rtic/ci/expected/zero-prio-task.run b/rtic/ci/expected/zero-prio-task.run new file mode 100644 index 0000000..123b0f2 --- /dev/null +++ b/rtic/ci/expected/zero-prio-task.run @@ -0,0 +1,3 @@ +init +hello from async +hello from async2 diff --git a/rtic/examples/async-delay.no_rs b/rtic/examples/async-delay.no_rs new file mode 100644 index 0000000..fb478c3 --- /dev/null +++ b/rtic/examples/async-delay.no_rs @@ -0,0 +1,63 @@ +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0, UART0], peripherals = true)] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use systick_monotonic::*; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[monotonic(binds = SysTick, default = true)] + type MyMono = Systick<100>; + + #[init] + fn init(cx: init::Context) -> (Shared, Local) { + hprintln!("init").unwrap(); + + foo::spawn().ok(); + bar::spawn().ok(); + baz::spawn().ok(); + + (Shared {}, Local {}) + } + + #[idle] + fn idle(_: idle::Context) -> ! { + // debug::exit(debug::EXIT_SUCCESS); + loop { + // hprintln!("idle"); + cortex_m::asm::wfi(); // put the MCU in sleep mode until interrupt occurs + } + } + + #[task] + async fn foo(_cx: foo::Context) { + hprintln!("hello from foo").ok(); + monotonics::delay(100.millis()).await; + hprintln!("bye from foo").ok(); + } + + #[task] + async fn bar(_cx: bar::Context) { + hprintln!("hello from bar").ok(); + monotonics::delay(200.millis()).await; + hprintln!("bye from bar").ok(); + } + + #[task] + async fn baz(_cx: baz::Context) { + hprintln!("hello from baz").ok(); + monotonics::delay(300.millis()).await; + hprintln!("bye from baz").ok(); + + debug::exit(debug::EXIT_SUCCESS); + } +} diff --git a/rtic/examples/async-infinite-loop.no_rs b/rtic/examples/async-infinite-loop.no_rs new file mode 100644 index 0000000..a95f998 --- /dev/null +++ b/rtic/examples/async-infinite-loop.no_rs @@ -0,0 +1,53 @@ +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0, UART0], peripherals = true)] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use systick_monotonic::*; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[monotonic(binds = SysTick, default = true)] + type MyMono = Systick<100>; + + #[init] + fn init(cx: init::Context) -> (Shared, Local) { + hprintln!("init").unwrap(); + + foo::spawn().ok(); + + (Shared {}, Local {}) + } + + #[idle] + fn idle(_: idle::Context) -> ! { + loop { + cortex_m::asm::wfi(); // put the MCU in sleep mode until interrupt occurs + } + } + + // Infinite loops are not allowed in RTIC, however in async tasks they are - if there is an + // await inside the loop. + #[task] + async fn foo(_cx: foo::Context) { + let mut i = 0; + loop { + if i == 5 { + debug::exit(debug::EXIT_SUCCESS); + } + + hprintln!("hello from async {}", i).ok(); + monotonics::delay(100.millis()).await; // This makes it okey! + + i += 1; + } + } +} diff --git a/rtic/examples/async-task-multiple-prios.rs b/rtic/examples/async-task-multiple-prios.rs new file mode 100644 index 0000000..5c9674d --- /dev/null +++ b/rtic/examples/async-task-multiple-prios.rs @@ -0,0 +1,92 @@ +//! examples/async-task-multiple-prios.rs + +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] +#![deny(missing_docs)] + +use panic_semihosting as _; + +// NOTES: +// +// - Async tasks cannot have `#[lock_free]` resources, as they can interleave and each async +// task can have a mutable reference stored. +// - Spawning an async task equates to it being polled once. + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0, QEI0])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + + #[shared] + struct Shared { + a: u32, + b: u32, + } + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + hprintln!("init"); + + async_task1::spawn().ok(); + async_task2::spawn().ok(); + async_task3::spawn().ok(); + async_task4::spawn().ok(); + + (Shared { a: 0, b: 0 }, Local {}) + } + + #[idle] + fn idle(_: idle::Context) -> ! { + loop { + hprintln!("idle"); + debug::exit(debug::EXIT_SUCCESS); + } + } + + #[task(priority = 1, shared = [a, b])] + async fn async_task1(mut cx: async_task1::Context) { + hprintln!( + "hello from async 1 a {}", + cx.shared.a.lock(|a| { + *a += 1; + *a + }) + ); + } + + #[task(priority = 1, shared = [a, b])] + async fn async_task2(mut cx: async_task2::Context) { + hprintln!( + "hello from async 2 a {}", + cx.shared.a.lock(|a| { + *a += 1; + *a + }) + ); + } + + #[task(priority = 2, shared = [a, b])] + async fn async_task3(mut cx: async_task3::Context) { + hprintln!( + "hello from async 3 a {}", + cx.shared.a.lock(|a| { + *a += 1; + *a + }) + ); + } + + #[task(priority = 2, shared = [a, b])] + async fn async_task4(mut cx: async_task4::Context) { + hprintln!( + "hello from async 4 a {}", + cx.shared.a.lock(|a| { + *a += 1; + *a + }) + ); + } +} diff --git a/rtic/examples/async-task.rs b/rtic/examples/async-task.rs new file mode 100644 index 0000000..7730c54 --- /dev/null +++ b/rtic/examples/async-task.rs @@ -0,0 +1,70 @@ +//! examples/async-task.rs + +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] +#![deny(missing_docs)] + +use panic_semihosting as _; + +// NOTES: +// +// - Async tasks cannot have `#[lock_free]` resources, as they can interleave and each async +// task can have a mutable reference stored. +// - Spawning an async task equates to it being polled once. + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0, UART0], peripherals = true)] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + + #[shared] + struct Shared { + a: u32, + } + + #[local] + struct Local {} + + #[init] + fn init(_cx: init::Context) -> (Shared, Local) { + hprintln!("init"); + + async_task::spawn().unwrap(); + async_task_args::spawn(1, 2).unwrap(); + async_task2::spawn().unwrap(); + + (Shared { a: 0 }, Local {}) + } + + #[idle(shared = [a])] + fn idle(_: idle::Context) -> ! { + loop { + hprintln!("idle"); + debug::exit(debug::EXIT_SUCCESS); + cortex_m::asm::wfi(); // put the MCU in sleep mode until interrupt occurs + } + } + + #[task(binds = UART1, shared = [a])] + fn hw_task(cx: hw_task::Context) { + let hw_task::SharedResources { a: _, .. } = cx.shared; + hprintln!("hello from hw"); + } + + #[task(shared = [a])] + async fn async_task(cx: async_task::Context) { + let async_task::SharedResources { a: _, .. } = cx.shared; + hprintln!("hello from async"); + } + + #[task] + async fn async_task_args(_cx: async_task_args::Context, a: u32, b: i32) { + hprintln!("hello from async with args a: {}, b: {}", a, b); + } + + #[task(priority = 2, shared = [a])] + async fn async_task2(cx: async_task2::Context) { + let async_task2::SharedResources { a: _, .. } = cx.shared; + hprintln!("hello from async2"); + } +} diff --git a/rtic/examples/async-timeout.no_rs b/rtic/examples/async-timeout.no_rs new file mode 100644 index 0000000..3f68df7 --- /dev/null +++ b/rtic/examples/async-timeout.no_rs @@ -0,0 +1,87 @@ +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use panic_semihosting as _; + +// NOTES: +// +// - Async tasks cannot have `#[lock_free]` resources, as they can interleve and each async +// task can have a mutable reference stored. +// - Spawning an async task equates to it being polled once. + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0, UART0], peripherals = true)] +mod app { + use core::{ + future::Future, + pin::Pin, + task::{Context, Poll}, + }; + use cortex_m_semihosting::{debug, hprintln}; + use systick_monotonic::*; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[monotonic(binds = SysTick, default = true)] + type MyMono = Systick<100>; + + #[init] + fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { + hprintln!("init").unwrap(); + + foo::spawn().ok(); + bar::spawn().ok(); + + ( + Shared {}, + Local {}, + init::Monotonics(Systick::new(cx.core.SYST, 12_000_000)), + ) + } + + #[idle] + fn idle(_: idle::Context) -> ! { + loop { + cortex_m::asm::wfi(); // put the MCU in sleep mode until interrupt occurs + } + } + + #[task] + async fn foo(_cx: foo::Context) { + hprintln!("hello from foo").ok(); + + // This will not timeout + match monotonics::timeout_after(monotonics::delay(100.millis()), 200.millis()).await { + Ok(_) => hprintln!("foo no timeout").ok(), + Err(_) => hprintln!("foo timeout").ok(), + }; + } + + #[task] + async fn bar(_cx: bar::Context) { + hprintln!("hello from bar").ok(); + + // This will timeout + match monotonics::timeout_after(NeverEndingFuture {}, 300.millis()).await { + Ok(_) => hprintln!("bar no timeout").ok(), + Err(_) => hprintln!("bar timeout").ok(), + }; + + debug::exit(debug::EXIT_SUCCESS); + } + + pub struct NeverEndingFuture {} + + impl Future for NeverEndingFuture { + type Output = (); + + fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll { + // Never finish + Poll::Pending + } + } +} diff --git a/rtic/examples/big-struct-opt.rs b/rtic/examples/big-struct-opt.rs new file mode 100644 index 0000000..408a2de --- /dev/null +++ b/rtic/examples/big-struct-opt.rs @@ -0,0 +1,80 @@ +//! examples/big-struct-opt.rs +//! +//! Example on how to initialize a large struct without needing to copy it via `LateResources`, +//! effectively saving stack space needed for the copies. + +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] +#![deny(missing_docs)] + +use panic_semihosting as _; + +/// Some big struct +pub struct BigStruct { + /// Big content + pub data: [u8; 2048], +} + +impl BigStruct { + fn new() -> Self { + BigStruct { data: [22; 2048] } + } +} + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] +mod app { + use super::BigStruct; + use core::mem::MaybeUninit; + use cortex_m_semihosting::{debug, hprintln}; + use lm3s6965::Interrupt; + + #[shared] + struct Shared { + big_struct: &'static mut BigStruct, + } + + #[local] + struct Local {} + + #[init(local = [bs: MaybeUninit = MaybeUninit::uninit()])] + fn init(cx: init::Context) -> (Shared, Local) { + let big_struct = unsafe { + // write directly into the static storage + cx.local.bs.as_mut_ptr().write(BigStruct::new()); + &mut *cx.local.bs.as_mut_ptr() + }; + + rtic::pend(Interrupt::UART0); + async_task::spawn().unwrap(); + ( + Shared { + // assign the reference so we can use the resource + big_struct, + }, + Local {}, + ) + } + + #[idle] + fn idle(_: idle::Context) -> ! { + loop { + hprintln!("idle"); + debug::exit(debug::EXIT_SUCCESS); + } + } + + #[task(binds = UART0, shared = [big_struct])] + fn uart0(mut cx: uart0::Context) { + cx.shared + .big_struct + .lock(|b| hprintln!("uart0 data:{:?}", &b.data[0..5])); + } + + #[task(shared = [big_struct], priority = 2)] + async fn async_task(mut cx: async_task::Context) { + cx.shared + .big_struct + .lock(|b| hprintln!("async_task data:{:?}", &b.data[0..5])); + } +} diff --git a/rtic/examples/binds.rs b/rtic/examples/binds.rs new file mode 100644 index 0000000..cf078ff --- /dev/null +++ b/rtic/examples/binds.rs @@ -0,0 +1,54 @@ +//! examples/binds.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] +#![deny(missing_docs)] + +use panic_semihosting as _; + +// `examples/interrupt.rs` rewritten to use `binds` +#[rtic::app(device = lm3s6965)] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use lm3s6965::Interrupt; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + rtic::pend(Interrupt::UART0); + + hprintln!("init"); + + (Shared {}, Local {}) + } + + #[idle] + fn idle(_: idle::Context) -> ! { + hprintln!("idle"); + + rtic::pend(Interrupt::UART0); + + loop { + cortex_m::asm::nop(); + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } + } + + #[task(binds = UART0, local = [times: u32 = 0])] + fn foo(cx: foo::Context) { + *cx.local.times += 1; + + hprintln!( + "foo called {} time{}", + *cx.local.times, + if *cx.local.times > 1 { "s" } else { "" } + ); + } +} diff --git a/rtic/examples/cancel-reschedule.no_rs b/rtic/examples/cancel-reschedule.no_rs new file mode 100644 index 0000000..a38a9c4 --- /dev/null +++ b/rtic/examples/cancel-reschedule.no_rs @@ -0,0 +1,73 @@ +//! examples/cancel-reschedule.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use systick_monotonic::*; + + #[monotonic(binds = SysTick, default = true)] + type MyMono = Systick<100>; // 100 Hz / 10 ms granularity + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { + let systick = cx.core.SYST; + + // Initialize the monotonic (SysTick rate in QEMU is 12 MHz) + let mono = Systick::new(systick, 12_000_000); + + hprintln!("init").ok(); + + // Schedule `foo` to run 1 second in the future + foo::spawn_after(1.secs()).unwrap(); + + ( + Shared {}, + Local {}, + init::Monotonics(mono), // Give the monotonic to RTIC + ) + } + + #[task] + fn foo(_: foo::Context) { + hprintln!("foo").ok(); + + // Schedule `bar` to run 2 seconds in the future (1 second after foo runs) + let spawn_handle = baz::spawn_after(2.secs()).unwrap(); + bar::spawn_after(1.secs(), spawn_handle, false).unwrap(); // Change to true + } + + #[task] + fn bar(_: bar::Context, baz_handle: baz::SpawnHandle, do_reschedule: bool) { + hprintln!("bar").ok(); + + if do_reschedule { + // Reschedule baz 2 seconds from now, instead of the original 1 second + // from now. + baz_handle.reschedule_after(2.secs()).unwrap(); + // Or baz_handle.reschedule_at(/* time */) + } else { + // Or cancel it + baz_handle.cancel().unwrap(); + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } + } + + #[task] + fn baz(_: baz::Context) { + hprintln!("baz").ok(); + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } +} diff --git a/rtic/examples/capacity.no_rs b/rtic/examples/capacity.no_rs new file mode 100644 index 0000000..a617269 --- /dev/null +++ b/rtic/examples/capacity.no_rs @@ -0,0 +1,49 @@ +//! examples/capacity.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use lm3s6965::Interrupt; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { + rtic::pend(Interrupt::UART0); + + (Shared {}, Local {}, init::Monotonics()) + } + + #[task(binds = UART0)] + fn uart0(_: uart0::Context) { + foo::spawn(0).unwrap(); + foo::spawn(1).unwrap(); + foo::spawn(2).unwrap(); + foo::spawn(3).unwrap(); + + bar::spawn().unwrap(); + } + + #[task(capacity = 4)] + fn foo(_: foo::Context, x: u32) { + hprintln!("foo({})", x).unwrap(); + } + + #[task] + fn bar(_: bar::Context) { + hprintln!("bar").unwrap(); + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } +} diff --git a/rtic/examples/cfg-whole-task.no_rs b/rtic/examples/cfg-whole-task.no_rs new file mode 100644 index 0000000..f41866d --- /dev/null +++ b/rtic/examples/cfg-whole-task.no_rs @@ -0,0 +1,94 @@ +//! examples/cfg-whole-task.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0, QEI0])] +mod app { + use cortex_m_semihosting::debug; + #[cfg(debug_assertions)] + use cortex_m_semihosting::hprintln; + + #[shared] + struct Shared { + count: u32, + #[cfg(never)] + unused: u32, + } + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { + foo::spawn().unwrap(); + foo::spawn().unwrap(); + + ( + Shared { + count: 0, + #[cfg(never)] + unused: 1, + }, + Local {}, + init::Monotonics(), + ) + } + + #[idle] + fn idle(_: idle::Context) -> ! { + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + + loop { + cortex_m::asm::nop(); + } + } + + #[task(capacity = 2, shared = [count])] + fn foo(mut _cx: foo::Context) { + #[cfg(debug_assertions)] + { + _cx.shared.count.lock(|count| *count += 1); + + log::spawn(_cx.shared.count.lock(|count| *count)).unwrap(); + } + + // this wouldn't compile in `release` mode + // *_cx.shared.count += 1; + + // .. + } + + // The whole task should disappear, + // currently still present in the Tasks enum + #[cfg(never)] + #[task(capacity = 2, shared = [count])] + fn foo2(mut _cx: foo2::Context) { + #[cfg(debug_assertions)] + { + _cx.shared.count.lock(|count| *count += 10); + + log::spawn(_cx.shared.count.lock(|count| *count)).unwrap(); + } + + // this wouldn't compile in `release` mode + // *_cx.shared.count += 1; + + // .. + } + + #[cfg(debug_assertions)] + #[task(capacity = 2)] + fn log(_: log::Context, n: u32) { + hprintln!( + "foo has been called {} time{}", + n, + if n == 1 { "" } else { "s" } + ) + .ok(); + } +} diff --git a/rtic/examples/common.no_rs b/rtic/examples/common.no_rs new file mode 100644 index 0000000..1fe671e --- /dev/null +++ b/rtic/examples/common.no_rs @@ -0,0 +1,102 @@ +//! examples/common.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0, QEI0])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use systick_monotonic::*; // Implements the `Monotonic` trait + + // A monotonic timer to enable scheduling in RTIC + #[monotonic(binds = SysTick, default = true)] + type MyMono = Systick<100>; // 100 Hz / 10 ms granularity + + // Resources shared between tasks + #[shared] + struct Shared { + s1: u32, + s2: i32, + } + + // Local resources to specific tasks (cannot be shared) + #[local] + struct Local { + l1: u8, + l2: i8, + } + + #[init] + fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { + let systick = cx.core.SYST; + + // Initialize the monotonic (SysTick rate in QEMU is 12 MHz) + let mono = Systick::new(systick, 12_000_000); + + // Spawn the task `foo` directly after `init` finishes + foo::spawn().unwrap(); + + // Spawn the task `bar` 1 second after `init` finishes, this is enabled + // by the `#[monotonic(..)]` above + bar::spawn_after(1.secs()).unwrap(); + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + + ( + // Initialization of shared resources + Shared { s1: 0, s2: 1 }, + // Initialization of task local resources + Local { l1: 2, l2: 3 }, + // Move the monotonic timer to the RTIC run-time, this enables + // scheduling + init::Monotonics(mono), + ) + } + + // Background task, runs whenever no other tasks are running + #[idle] + fn idle(_: idle::Context) -> ! { + loop { + continue; + } + } + + // Software task, not bound to a hardware interrupt. + // This task takes the task local resource `l1` + // The resources `s1` and `s2` are shared between all other tasks. + #[task(shared = [s1, s2], local = [l1])] + fn foo(_: foo::Context) { + // This task is only spawned once in `init`, hence this task will run + // only once + + hprintln!("foo").ok(); + } + + // Software task, also not bound to a hardware interrupt + // This task takes the task local resource `l2` + // The resources `s1` and `s2` are shared between all other tasks. + #[task(shared = [s1, s2], local = [l2])] + fn bar(_: bar::Context) { + hprintln!("bar").ok(); + + // Run `bar` once per second + bar::spawn_after(1.secs()).unwrap(); + } + + // Hardware task, bound to a hardware interrupt + // The resources `s1` and `s2` are shared between all other tasks. + #[task(binds = UART0, priority = 3, shared = [s1, s2])] + fn uart0_interrupt(_: uart0_interrupt::Context) { + // This task is bound to the interrupt `UART0` and will run + // whenever the interrupt fires + + // Note that RTIC does NOT clear the interrupt flag, this is up to the + // user + + hprintln!("UART0 interrupt!").ok(); + } +} diff --git a/rtic/examples/complex.rs b/rtic/examples/complex.rs new file mode 100644 index 0000000..c1e9c6c --- /dev/null +++ b/rtic/examples/complex.rs @@ -0,0 +1,129 @@ +//! examples/complex.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![deny(missing_docs)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965)] +mod app { + + use cortex_m_semihosting::{debug, hprintln}; + use lm3s6965::Interrupt; + + #[shared] + struct Shared { + s2: u32, // shared with ceiling 2 + s3: u32, // shared with ceiling 3 + s4: u32, // shared with ceiling 4 + } + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + hprintln!("init"); + + ( + Shared { + s2: 0, + s3: 0, + s4: 0, + }, + Local {}, + ) + } + + #[idle(shared = [s2, s3])] + fn idle(mut cx: idle::Context) -> ! { + hprintln!("idle p0 started"); + rtic::pend(Interrupt::GPIOC); + cx.shared.s3.lock(|s| { + hprintln!("idle enter lock s3 {}", s); + hprintln!("idle pend t0"); + rtic::pend(Interrupt::GPIOA); // t0 p2, with shared ceiling 3 + hprintln!("idle pend t1"); + rtic::pend(Interrupt::GPIOB); // t1 p3, with shared ceiling 3 + hprintln!("idle pend t2"); + rtic::pend(Interrupt::GPIOC); // t2 p4, no sharing + hprintln!("idle still in lock s3 {}", s); + }); + hprintln!("\nback in idle"); + + cx.shared.s2.lock(|s| { + hprintln!("enter lock s2 {}", s); + hprintln!("idle pend t0"); + rtic::pend(Interrupt::GPIOA); // t0 p2, with shared ceiling 2 + hprintln!("idle pend t1"); + rtic::pend(Interrupt::GPIOB); // t1 p3, no sharing + hprintln!("idle pend t2"); + rtic::pend(Interrupt::GPIOC); // t2 p4, no sharing + hprintln!("idle still in lock s2 {}", s); + }); + hprintln!("\nidle exit"); + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + + loop { + cortex_m::asm::nop(); + } + } + + #[task(binds = GPIOA, priority = 2, local = [times: u32 = 0], shared = [s2, s3])] + fn t0(cx: t0::Context) { + // Safe access to local `static mut` variable + *cx.local.times += 1; + + hprintln!( + "t0 p2 called {} time{}", + *cx.local.times, + if *cx.local.times > 1 { "s" } else { "" } + ); + hprintln!("t0 p2 exit"); + } + + #[task(binds = GPIOB, priority = 3, local = [times: u32 = 0], shared = [s3, s4])] + fn t1(mut cx: t1::Context) { + // Safe access to local `static mut` variable + *cx.local.times += 1; + + hprintln!( + "t1 p3 called {} time{}", + *cx.local.times, + if *cx.local.times > 1 { "s" } else { "" } + ); + + cx.shared.s4.lock(|s| { + hprintln!("t1 enter lock s4 {}", s); + hprintln!("t1 pend t0"); + rtic::pend(Interrupt::GPIOA); // t0 p2, with shared ceiling 2 + hprintln!("t1 pend t2"); + rtic::pend(Interrupt::GPIOC); // t2 p4, no sharing + hprintln!("t1 still in lock s4 {}", s); + }); + + hprintln!("t1 p3 exit"); + } + + #[task(binds = GPIOC, priority = 4, local = [times: u32 = 0], shared = [s4])] + fn t2(mut cx: t2::Context) { + // Safe access to local `static mut` variable + *cx.local.times += 1; + + hprintln!( + "t2 p4 called {} time{}", + *cx.local.times, + if *cx.local.times > 1 { "s" } else { "" } + ); + + cx.shared.s4.lock(|s| { + hprintln!("enter lock s4 {}", s); + *s += 1; + }); + hprintln!("t3 p4 exit"); + } +} diff --git a/rtic/examples/declared_locals.rs b/rtic/examples/declared_locals.rs new file mode 100644 index 0000000..c845191 --- /dev/null +++ b/rtic/examples/declared_locals.rs @@ -0,0 +1,47 @@ +//! examples/declared_locals.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![deny(missing_docs)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965)] +mod app { + use cortex_m_semihosting::debug; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init(local = [a: u32 = 0])] + fn init(cx: init::Context) -> (Shared, Local) { + // Locals in `#[init]` have 'static lifetime + let _a: &'static mut u32 = cx.local.a; + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + + (Shared {}, Local {}) + } + + #[idle(local = [a: u32 = 0])] + fn idle(cx: idle::Context) -> ! { + // Locals in `#[idle]` have 'static lifetime + let _a: &'static mut u32 = cx.local.a; + + loop {} + } + + #[task(binds = UART0, local = [a: u32 = 0])] + fn foo(cx: foo::Context) { + // Locals in `#[task]`s have a local lifetime + let _a: &mut u32 = cx.local.a; + + // error: explicit lifetime required in the type of `cx` + // let _a: &'static mut u32 = cx.local.a; + } +} diff --git a/rtic/examples/destructure.rs b/rtic/examples/destructure.rs new file mode 100644 index 0000000..81eff3b --- /dev/null +++ b/rtic/examples/destructure.rs @@ -0,0 +1,57 @@ +//! examples/destructure.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![deny(missing_docs)] +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [UART0])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + + #[shared] + struct Shared { + a: u32, + b: u32, + c: u32, + } + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + foo::spawn().unwrap(); + bar::spawn().unwrap(); + + (Shared { a: 0, b: 1, c: 2 }, Local {}) + } + + #[idle] + fn idle(_: idle::Context) -> ! { + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + loop {} + } + + // Direct destructure + #[task(shared = [&a, &b, &c])] + async fn foo(cx: foo::Context) { + let a = cx.shared.a; + let b = cx.shared.b; + let c = cx.shared.c; + + hprintln!("foo: a = {}, b = {}, c = {}", a, b, c); + } + + // De-structure-ing syntax + #[task(shared = [&a, &b, &c])] + async fn bar(cx: bar::Context) { + let bar::SharedResources { a, b, c, .. } = cx.shared; + + hprintln!("bar: a = {}, b = {}, c = {}", a, b, c); + } +} diff --git a/rtic/examples/extern_binds.rs b/rtic/examples/extern_binds.rs new file mode 100644 index 0000000..142a11d --- /dev/null +++ b/rtic/examples/extern_binds.rs @@ -0,0 +1,54 @@ +//! examples/extern_binds.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![deny(missing_docs)] +#![no_main] +#![no_std] + +use cortex_m_semihosting::hprintln; +use panic_semihosting as _; + +// Free function implementing the interrupt bound task `foo`. +fn foo(_: app::foo::Context) { + hprintln!("foo called"); +} + +#[rtic::app(device = lm3s6965)] +mod app { + use crate::foo; + use cortex_m_semihosting::{debug, hprintln}; + use lm3s6965::Interrupt; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + rtic::pend(Interrupt::UART0); + + hprintln!("init"); + + (Shared {}, Local {}) + } + + #[idle] + fn idle(_: idle::Context) -> ! { + hprintln!("idle"); + + rtic::pend(Interrupt::UART0); + + loop { + cortex_m::asm::nop(); + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } + } + + extern "Rust" { + #[task(binds = UART0)] + fn foo(_: foo::Context); + } +} diff --git a/rtic/examples/extern_spawn.rs b/rtic/examples/extern_spawn.rs new file mode 100644 index 0000000..b2b95b9 --- /dev/null +++ b/rtic/examples/extern_spawn.rs @@ -0,0 +1,41 @@ +//! examples/extern_spawn.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![deny(missing_docs)] +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use cortex_m_semihosting::{debug, hprintln}; +use panic_semihosting as _; + +// Free function implementing the spawnable task `foo`. +// Notice, you need to indicate an anonymous lifetime <'a_> +async fn foo(_c: app::foo::Context<'_>) { + hprintln!("foo"); + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator +} + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] +mod app { + use crate::foo; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + foo::spawn().unwrap(); + + (Shared {}, Local {}) + } + + extern "Rust" { + #[task()] + async fn foo(_c: foo::Context); + } +} diff --git a/rtic/examples/generics.rs b/rtic/examples/generics.rs new file mode 100644 index 0000000..2f23cce --- /dev/null +++ b/rtic/examples/generics.rs @@ -0,0 +1,67 @@ +//! examples/generics.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![deny(missing_docs)] +#![no_main] +#![no_std] + +use cortex_m_semihosting::hprintln; +use panic_semihosting as _; +use rtic::Mutex; + +#[rtic::app(device = lm3s6965)] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use lm3s6965::Interrupt; + + #[shared] + struct Shared { + shared: u32, + } + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + rtic::pend(Interrupt::UART0); + rtic::pend(Interrupt::UART1); + + (Shared { shared: 0 }, Local {}) + } + + #[task(binds = UART0, shared = [shared], local = [state: u32 = 0])] + fn uart0(c: uart0::Context) { + hprintln!("UART0(STATE = {})", *c.local.state); + + // second argument has type `shared::shared` + super::advance(c.local.state, c.shared.shared); + + rtic::pend(Interrupt::UART1); + + cortex_m::asm::nop(); + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } + + #[task(binds = UART1, priority = 2, shared = [shared], local = [state: u32 = 0])] + fn uart1(c: uart1::Context) { + hprintln!("UART1(STATE = {})", *c.local.state); + + // second argument has type `shared::shared` + super::advance(c.local.state, c.shared.shared); + } +} + +// the second parameter is generic: it can be any type that implements the `Mutex` trait +fn advance(state: &mut u32, mut shared: impl Mutex) { + *state += 1; + + let (old, new) = shared.lock(|shared: &mut u32| { + let old = *shared; + *shared += *state; + (old, *shared) + }); + + hprintln!("shared: {} -> {}", old, new); +} diff --git a/rtic/examples/hardware.rs b/rtic/examples/hardware.rs new file mode 100644 index 0000000..62ae0d6 --- /dev/null +++ b/rtic/examples/hardware.rs @@ -0,0 +1,58 @@ +//! examples/hardware.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![deny(missing_docs)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965)] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use lm3s6965::Interrupt; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + // Pends the UART0 interrupt but its handler won't run until *after* + // `init` returns because interrupts are disabled + rtic::pend(Interrupt::UART0); // equivalent to NVIC::pend + + hprintln!("init"); + + (Shared {}, Local {}) + } + + #[idle] + fn idle(_: idle::Context) -> ! { + // interrupts are enabled again; the `UART0` handler runs at this point + + hprintln!("idle"); + + rtic::pend(Interrupt::UART0); + + loop { + cortex_m::asm::nop(); + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } + } + + #[task(binds = UART0, local = [times: u32 = 0])] + fn uart0(cx: uart0::Context) { + // Safe access to local `static mut` variable + *cx.local.times += 1; + + hprintln!( + "UART0 called {} time{}", + *cx.local.times, + if *cx.local.times > 1 { "s" } else { "" } + ); + } +} diff --git a/rtic/examples/idle-wfi.rs b/rtic/examples/idle-wfi.rs new file mode 100644 index 0000000..8134ce3 --- /dev/null +++ b/rtic/examples/idle-wfi.rs @@ -0,0 +1,48 @@ +//! examples/idle-wfi.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![deny(missing_docs)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965)] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(mut cx: init::Context) -> (Shared, Local) { + hprintln!("init"); + + // Set the ARM SLEEPONEXIT bit to go to sleep after handling interrupts + // See https://developer.arm.com/docs/100737/0100/power-management/sleep-mode/sleep-on-exit-bit + cx.core.SCB.set_sleepdeep(); + + (Shared {}, Local {}) + } + + #[idle(local = [x: u32 = 0])] + fn idle(cx: idle::Context) -> ! { + // Locals in idle have lifetime 'static + let _x: &'static mut u32 = cx.local.x; + + hprintln!("idle"); + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + + loop { + // Now Wait For Interrupt is used instead of a busy-wait loop + // to allow MCU to sleep between interrupts + // https://developer.arm.com/documentation/ddi0406/c/Application-Level-Architecture/Instruction-Details/Alphabetical-list-of-instructions/WFI + rtic::export::wfi() + } + } +} diff --git a/rtic/examples/idle.rs b/rtic/examples/idle.rs new file mode 100644 index 0000000..0c4bd04 --- /dev/null +++ b/rtic/examples/idle.rs @@ -0,0 +1,41 @@ +//! examples/idle.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![deny(missing_docs)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965)] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + hprintln!("init"); + + (Shared {}, Local {}) + } + + #[idle(local = [x: u32 = 0])] + fn idle(cx: idle::Context) -> ! { + // Locals in idle have lifetime 'static + let _x: &'static mut u32 = cx.local.x; + + hprintln!("idle"); + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + + loop { + cortex_m::asm::nop(); + } + } +} diff --git a/rtic/examples/init.rs b/rtic/examples/init.rs new file mode 100644 index 0000000..c3081bf --- /dev/null +++ b/rtic/examples/init.rs @@ -0,0 +1,42 @@ +//! examples/init.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![deny(missing_docs)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, peripherals = true)] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init(local = [x: u32 = 0])] + fn init(cx: init::Context) -> (Shared, Local) { + // Cortex-M peripherals + let _core: cortex_m::Peripherals = cx.core; + + // Device specific peripherals + let _device: lm3s6965::Peripherals = cx.device; + + // Locals in `init` have 'static lifetime + let _x: &'static mut u32 = cx.local.x; + + // Access to the critical section token, + // to indicate that this is a critical section + let _cs_token: bare_metal::CriticalSection = cx.cs; + + hprintln!("init"); + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + + (Shared {}, Local {}) + } +} diff --git a/rtic/examples/locals.rs b/rtic/examples/locals.rs new file mode 100644 index 0000000..ec3d59d --- /dev/null +++ b/rtic/examples/locals.rs @@ -0,0 +1,87 @@ +//! examples/locals.rs + +#![feature(type_alias_impl_trait)] +#![deny(unsafe_code)] +#![deny(missing_docs)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [UART0, UART1])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + + #[shared] + struct Shared {} + + #[local] + struct Local { + local_to_foo: i64, + local_to_bar: i64, + local_to_idle: i64, + } + + // `#[init]` cannot access locals from the `#[local]` struct as they are initialized here. + #[init] + fn init(_: init::Context) -> (Shared, Local) { + foo::spawn().unwrap(); + bar::spawn().unwrap(); + + ( + Shared {}, + // initial values for the `#[local]` resources + Local { + local_to_foo: 0, + local_to_bar: 0, + local_to_idle: 0, + }, + ) + } + + // `local_to_idle` can only be accessed from this context + #[idle(local = [local_to_idle])] + fn idle(cx: idle::Context) -> ! { + let local_to_idle = cx.local.local_to_idle; + *local_to_idle += 1; + + hprintln!("idle: local_to_idle = {}", local_to_idle); + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + + // error: no `local_to_foo` field in `idle::LocalResources` + // _cx.local.local_to_foo += 1; + + // error: no `local_to_bar` field in `idle::LocalResources` + // _cx.local.local_to_bar += 1; + + loop { + cortex_m::asm::nop(); + } + } + + // `local_to_foo` can only be accessed from this context + #[task(local = [local_to_foo])] + async fn foo(cx: foo::Context) { + let local_to_foo = cx.local.local_to_foo; + *local_to_foo += 1; + + // error: no `local_to_bar` field in `foo::LocalResources` + // cx.local.local_to_bar += 1; + + hprintln!("foo: local_to_foo = {}", local_to_foo); + } + + // `local_to_bar` can only be accessed from this context + #[task(local = [local_to_bar])] + async fn bar(cx: bar::Context) { + let local_to_bar = cx.local.local_to_bar; + *local_to_bar += 1; + + // error: no `local_to_foo` field in `bar::LocalResources` + // cx.local.local_to_foo += 1; + + hprintln!("bar: local_to_bar = {}", local_to_bar); + } +} diff --git a/rtic/examples/lock-free.no_rs b/rtic/examples/lock-free.no_rs new file mode 100644 index 0000000..053307c --- /dev/null +++ b/rtic/examples/lock-free.no_rs @@ -0,0 +1,50 @@ +//! examples/lock-free.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [GPIOA])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + + #[shared] + struct Shared { + #[lock_free] // <- lock-free shared resource + counter: u64, + } + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + foo::spawn().unwrap(); + + (Shared { counter: 0 }, Local {}) + } + + #[task(shared = [counter])] // <- same priority + async fn foo(c: foo::Context) { + bar::spawn().unwrap(); + + *c.shared.counter += 1; // <- no lock API required + let counter = *c.shared.counter; + hprintln!(" foo = {}", counter).unwrap(); + } + + #[task(shared = [counter])] // <- same priority + async fn bar(c: bar::Context) { + foo::spawn().unwrap(); + + *c.shared.counter += 1; // <- no lock API required + let counter = *c.shared.counter; + hprintln!(" bar = {}", counter).unwrap(); + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } +} diff --git a/rtic/examples/lock.rs b/rtic/examples/lock.rs new file mode 100644 index 0000000..203ae6f --- /dev/null +++ b/rtic/examples/lock.rs @@ -0,0 +1,73 @@ +//! examples/lock.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![deny(missing_docs)] +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [GPIOA, GPIOB, GPIOC])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + + #[shared] + struct Shared { + shared: u32, + } + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + foo::spawn().unwrap(); + + (Shared { shared: 0 }, Local {}) + } + + // when omitted priority is assumed to be `1` + #[task(shared = [shared])] + async fn foo(mut c: foo::Context) { + hprintln!("A"); + + // the lower priority task requires a critical section to access the data + c.shared.shared.lock(|shared| { + // data can only be modified within this critical section (closure) + *shared += 1; + + // bar will *not* run right now due to the critical section + bar::spawn().unwrap(); + + hprintln!("B - shared = {}", *shared); + + // baz does not contend for `shared` so it's allowed to run now + baz::spawn().unwrap(); + }); + + // critical section is over: bar can now start + + hprintln!("E"); + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } + + #[task(priority = 2, shared = [shared])] + async fn bar(mut c: bar::Context) { + // the higher priority task does still need a critical section + let shared = c.shared.shared.lock(|shared| { + *shared += 1; + + *shared + }); + + hprintln!("D - shared = {}", shared); + } + + #[task(priority = 3)] + async fn baz(_: baz::Context) { + hprintln!("C"); + } +} diff --git a/rtic/examples/message.no_rs b/rtic/examples/message.no_rs new file mode 100644 index 0000000..76c5675 --- /dev/null +++ b/rtic/examples/message.no_rs @@ -0,0 +1,52 @@ +//! examples/message.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { + foo::spawn(/* no message */).unwrap(); + + (Shared {}, Local {}, init::Monotonics()) + } + + #[task(local = [count: u32 = 0])] + fn foo(cx: foo::Context) { + hprintln!("foo").unwrap(); + + bar::spawn(*cx.local.count).unwrap(); + *cx.local.count += 1; + } + + #[task] + fn bar(_: bar::Context, x: u32) { + hprintln!("bar({})", x).unwrap(); + + baz::spawn(x + 1, x + 2).unwrap(); + } + + #[task] + fn baz(_: baz::Context, x: u32, y: u32) { + hprintln!("baz({}, {})", x, y).unwrap(); + + if x + y > 4 { + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } + + foo::spawn().unwrap(); + } +} diff --git a/rtic/examples/message_passing.no_rs b/rtic/examples/message_passing.no_rs new file mode 100644 index 0000000..ffa9537 --- /dev/null +++ b/rtic/examples/message_passing.no_rs @@ -0,0 +1,37 @@ +//! examples/message_passing.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { + foo::spawn(1, 1).unwrap(); + foo::spawn(1, 2).unwrap(); + foo::spawn(2, 3).unwrap(); + assert!(foo::spawn(1, 4).is_err()); // The capacity of `foo` is reached + + (Shared {}, Local {}, init::Monotonics()) + } + + #[task(capacity = 3)] + fn foo(_c: foo::Context, x: i32, y: u32) { + hprintln!("foo {}, {}", x, y).unwrap(); + if x == 2 { + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } + } +} diff --git a/rtic/examples/multilock.rs b/rtic/examples/multilock.rs new file mode 100644 index 0000000..6208cac --- /dev/null +++ b/rtic/examples/multilock.rs @@ -0,0 +1,57 @@ +//! examples/mutlilock.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![deny(missing_docs)] +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [GPIOA])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + + #[shared] + struct Shared { + shared1: u32, + shared2: u32, + shared3: u32, + } + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + locks::spawn().unwrap(); + + ( + Shared { + shared1: 0, + shared2: 0, + shared3: 0, + }, + Local {}, + ) + } + + // when omitted priority is assumed to be `1` + #[task(shared = [shared1, shared2, shared3])] + async fn locks(c: locks::Context) { + let s1 = c.shared.shared1; + let s2 = c.shared.shared2; + let s3 = c.shared.shared3; + + (s1, s2, s3).lock(|s1, s2, s3| { + *s1 += 1; + *s2 += 1; + *s3 += 1; + + hprintln!("Multiple locks, s1: {}, s2: {}, s3: {}", *s1, *s2, *s3); + }); + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } +} diff --git a/rtic/examples/not-sync.rs b/rtic/examples/not-sync.rs new file mode 100644 index 0000000..6d1ddae --- /dev/null +++ b/rtic/examples/not-sync.rs @@ -0,0 +1,69 @@ +//! `examples/not-sync.rs` + +// #![deny(unsafe_code)] +#![deny(warnings)] +#![deny(missing_docs)] +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use core::marker::PhantomData; +use panic_semihosting as _; + +/// Not sync +pub struct NotSync { + _0: PhantomData<*const ()>, + data: u32, +} + +unsafe impl Send for NotSync {} + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] +mod app { + use super::NotSync; + use core::marker::PhantomData; + use cortex_m_semihosting::{debug, hprintln}; + + #[shared] + struct Shared { + shared: NotSync, + } + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + hprintln!("init"); + + foo::spawn().unwrap(); + bar::spawn().unwrap(); + ( + Shared { + shared: NotSync { + _0: PhantomData, + data: 13, + }, + }, + Local {}, + ) + } + + #[idle] + fn idle(_: idle::Context) -> ! { + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + loop {} + } + + #[task(shared = [&shared])] + async fn foo(c: foo::Context) { + let shared: &NotSync = c.shared.shared; + hprintln!("foo a {}", shared.data); + } + + #[task(shared = [&shared])] + async fn bar(c: bar::Context) { + let shared: &NotSync = c.shared.shared; + hprintln!("bar a {}", shared.data); + } +} diff --git a/rtic/examples/only-shared-access.rs b/rtic/examples/only-shared-access.rs new file mode 100644 index 0000000..1d006e6 --- /dev/null +++ b/rtic/examples/only-shared-access.rs @@ -0,0 +1,44 @@ +//! examples/only-shared-access.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![deny(missing_docs)] +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [UART0, UART1])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + + #[shared] + struct Shared { + key: u32, + } + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + foo::spawn().unwrap(); + bar::spawn().unwrap(); + + (Shared { key: 0xdeadbeef }, Local {}) + } + + #[task(shared = [&key])] + async fn foo(cx: foo::Context) { + let key: &u32 = cx.shared.key; + hprintln!("foo(key = {:#x})", key); + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } + + #[task(priority = 2, shared = [&key])] + async fn bar(cx: bar::Context) { + hprintln!("bar(key = {:#x})", cx.shared.key); + } +} diff --git a/rtic/examples/periodic-at.no_rs b/rtic/examples/periodic-at.no_rs new file mode 100644 index 0000000..ca68ed5 --- /dev/null +++ b/rtic/examples/periodic-at.no_rs @@ -0,0 +1,49 @@ +//! examples/periodic-at.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use systick_monotonic::*; + + #[monotonic(binds = SysTick, default = true)] + type MyMono = Systick<100>; // 100 Hz / 10 ms granularity + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { + let systick = cx.core.SYST; + + // Initialize the monotonic (SysTick rate in QEMU is 12 MHz) + let mut mono = Systick::new(systick, 12_000_000); + + foo::spawn_after(1.secs(), mono.now()).unwrap(); + + (Shared {}, Local {}, init::Monotonics(mono)) + } + + #[task(local = [cnt: u32 = 0])] + fn foo(cx: foo::Context, instant: fugit::TimerInstantU64<100>) { + hprintln!("foo {:?}", instant).ok(); + *cx.local.cnt += 1; + + if *cx.local.cnt == 4 { + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } + + // Periodic every 100 milliseconds + let next_instant = instant + 100.millis(); + foo::spawn_at(next_instant, next_instant).unwrap(); + } +} diff --git a/rtic/examples/periodic-at2.no_rs b/rtic/examples/periodic-at2.no_rs new file mode 100644 index 0000000..ec9adcc --- /dev/null +++ b/rtic/examples/periodic-at2.no_rs @@ -0,0 +1,61 @@ +//! examples/periodic-at2.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use systick_monotonic::*; + + #[monotonic(binds = SysTick, default = true)] + type MyMono = Systick<100>; // 100 Hz / 10 ms granularity + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { + let systick = cx.core.SYST; + + // Initialize the monotonic (SysTick rate in QEMU is 12 MHz) + let mut mono = Systick::new(systick, 12_000_000); + + foo::spawn_after(200.millis(), mono.now()).unwrap(); + + (Shared {}, Local {}, init::Monotonics(mono)) + } + + // Using the explicit type of the timer implementation + #[task(local = [cnt: u32 = 0])] + fn foo(cx: foo::Context, instant: fugit::TimerInstantU64<100>) { + hprintln!("foo {:?}", instant).ok(); + *cx.local.cnt += 1; + + if *cx.local.cnt == 4 { + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } + + // Spawn a new message with 100 ms offset to spawned time + let next_instant = instant + 100.millis(); + bar::spawn_at(next_instant, next_instant).unwrap(); + } + + // Using the Instant from the Monotonic trait + // This remains agnostic to the timer implementation + #[task(local = [cnt: u32 = 0])] + fn bar(_cx: bar::Context, instant: ::Instant) { + hprintln!("bar {:?}", instant).ok(); + + // Spawn a new message with 200ms offset to spawned time + let next_instant = instant + 200.millis(); + foo::spawn_at(next_instant, next_instant).unwrap(); + } +} diff --git a/rtic/examples/periodic.no_rs b/rtic/examples/periodic.no_rs new file mode 100644 index 0000000..2f9e8e6 --- /dev/null +++ b/rtic/examples/periodic.no_rs @@ -0,0 +1,48 @@ +//! examples/periodic.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use systick_monotonic::*; + + #[monotonic(binds = SysTick, default = true)] + type MyMono = Systick<100>; // 100 Hz / 10 ms granularity + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { + let systick = cx.core.SYST; + + // Initialize the monotonic (SysTick rate in QEMU is 12 MHz) + let mono = Systick::new(systick, 12_000_000); + + foo::spawn_after(100.millis()).unwrap(); + + (Shared {}, Local {}, init::Monotonics(mono)) + } + + #[task(local = [cnt: u32 = 0])] + fn foo(cx: foo::Context) { + hprintln!("foo").ok(); + *cx.local.cnt += 1; + + if *cx.local.cnt == 4 { + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } + + // Periodic every 100ms + foo::spawn_after(100.millis()).unwrap(); + } +} diff --git a/rtic/examples/peripherals-taken.rs b/rtic/examples/peripherals-taken.rs new file mode 100644 index 0000000..2f710e9 --- /dev/null +++ b/rtic/examples/peripherals-taken.rs @@ -0,0 +1,28 @@ +//! examples/peripherals-taken.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![deny(missing_docs)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965)] +mod app { + use cortex_m_semihosting::debug; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + assert!(cortex_m::Peripherals::take().is_none()); + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + + (Shared {}, Local {}) + } +} diff --git a/rtic/examples/pool.no_rs b/rtic/examples/pool.no_rs new file mode 100644 index 0000000..fb8589a --- /dev/null +++ b/rtic/examples/pool.no_rs @@ -0,0 +1,70 @@ +//! examples/pool.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use heapless::{ + pool, + pool::singleton::{Box, Pool}, +}; +use panic_semihosting as _; +use rtic::app; + +// Declare a pool of 128-byte memory blocks +pool!(P: [u8; 128]); + +#[app(device = lm3s6965, dispatchers = [SSI0, QEI0])] +mod app { + use crate::{Box, Pool}; + use cortex_m_semihosting::debug; + use lm3s6965::Interrupt; + + // Import the memory pool into scope + use super::P; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init(local = [memory: [u8; 512] = [0; 512]])] + fn init(cx: init::Context) -> (Shared, Local) { + // Increase the capacity of the memory pool by ~4 + P::grow(cx.local.memory); + + rtic::pend(Interrupt::I2C0); + + (Shared {}, Local {}) + } + + #[task(binds = I2C0, priority = 2)] + async fn i2c0(_: i2c0::Context) { + // claim a memory block, initialize it and .. + let x = P::alloc().unwrap().init([0u8; 128]); + + // .. send it to the `foo` task + foo::spawn(x).ok().unwrap(); + + // send another block to the task `bar` + bar::spawn(P::alloc().unwrap().init([0u8; 128])) + .ok() + .unwrap(); + } + + #[task] + async fn foo(_: foo::Context, _x: Box

) { + // explicitly return the block to the pool + drop(_x); + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } + + #[task(priority = 2)] + async fn bar(_: bar::Context, _x: Box

) { + // this is done automatically so we can omit the call to `drop` + // drop(_x); + } +} diff --git a/rtic/examples/preempt.rs b/rtic/examples/preempt.rs new file mode 100644 index 0000000..4b11907 --- /dev/null +++ b/rtic/examples/preempt.rs @@ -0,0 +1,47 @@ +//! examples/preempt.rs + +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] +#![deny(missing_docs)] + +use panic_semihosting as _; +use rtic::app; + +#[app(device = lm3s6965, dispatchers = [SSI0, QEI0])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + foo::spawn().unwrap(); + + (Shared {}, Local {}) + } + + #[task(priority = 1)] + async fn foo(_: foo::Context) { + hprintln!("foo - start"); + baz::spawn().unwrap(); + hprintln!("foo - end"); + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } + + #[task(priority = 2)] + async fn bar(_: bar::Context) { + hprintln!(" bar"); + } + + #[task(priority = 2)] + async fn baz(_: baz::Context) { + hprintln!(" baz - start"); + bar::spawn().unwrap(); + hprintln!(" baz - end"); + } +} diff --git a/rtic/examples/ramfunc.rs b/rtic/examples/ramfunc.rs new file mode 100644 index 0000000..e2e7f67 --- /dev/null +++ b/rtic/examples/ramfunc.rs @@ -0,0 +1,50 @@ +//! examples/ramfunc.rs + +#![deny(warnings)] +#![deny(missing_docs)] +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use panic_semihosting as _; + +#[rtic::app( + device = lm3s6965, + dispatchers = [ + UART0, + #[link_section = ".data.UART1"] + UART1 + ]) +] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + foo::spawn().unwrap(); + + (Shared {}, Local {}) + } + + #[inline(never)] + #[task] + async fn foo(_: foo::Context) { + hprintln!("foo"); + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } + + // run this task from RAM + #[inline(never)] + #[link_section = ".data.bar"] + #[task(priority = 2)] + async fn bar(_: bar::Context) { + foo::spawn().unwrap(); + } +} diff --git a/rtic/examples/resource-user-struct.rs b/rtic/examples/resource-user-struct.rs new file mode 100644 index 0000000..fcbacae --- /dev/null +++ b/rtic/examples/resource-user-struct.rs @@ -0,0 +1,72 @@ +//! examples/resource.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![deny(missing_docs)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965)] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use lm3s6965::Interrupt; + + #[shared] + struct Shared { + // A resource + shared: u32, + } + + // Should not collide with the struct above + #[allow(dead_code)] + struct Shared2 { + // A resource + shared: u32, + } + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + rtic::pend(Interrupt::UART0); + rtic::pend(Interrupt::UART1); + + (Shared { shared: 0 }, Local {}) + } + + // `shared` cannot be accessed from this context + #[idle] + fn idle(_cx: idle::Context) -> ! { + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + + // error: no `shared` field in `idle::Context` + // _cx.shared.shared += 1; + + loop {} + } + + // `shared` can be accessed from this context + #[task(binds = UART0, shared = [shared])] + fn uart0(mut cx: uart0::Context) { + let shared = cx.shared.shared.lock(|shared| { + *shared += 1; + *shared + }); + + hprintln!("UART0: shared = {}", shared); + } + + // `shared` can be accessed from this context + #[task(binds = UART1, shared = [shared])] + fn uart1(mut cx: uart1::Context) { + let shared = cx.shared.shared.lock(|shared| { + *shared += 1; + *shared + }); + + hprintln!("UART1: shared = {}", shared); + } +} diff --git a/rtic/examples/schedule.no_rs b/rtic/examples/schedule.no_rs new file mode 100644 index 0000000..5bad5a3 --- /dev/null +++ b/rtic/examples/schedule.no_rs @@ -0,0 +1,64 @@ +//! examples/schedule.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use systick_monotonic::*; + + #[monotonic(binds = SysTick, default = true)] + type MyMono = Systick<100>; // 100 Hz / 10 ms granularity + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { + let systick = cx.core.SYST; + + // Initialize the monotonic (SysTick rate in QEMU is 12 MHz) + let mono = Systick::new(systick, 12_000_000); + + hprintln!("init").ok(); + + // Schedule `foo` to run 1 second in the future + foo::spawn_after(1.secs()).unwrap(); + + ( + Shared {}, + Local {}, + init::Monotonics(mono), // Give the monotonic to RTIC + ) + } + + #[task] + fn foo(_: foo::Context) { + hprintln!("foo").ok(); + + // Schedule `bar` to run 2 seconds in the future (1 second after foo runs) + bar::spawn_after(1.secs()).unwrap(); + } + + #[task] + fn bar(_: bar::Context) { + hprintln!("bar").ok(); + + // Schedule `baz` to run 1 seconds from now, but with a specific time instant. + baz::spawn_at(monotonics::now() + 1.secs()).unwrap(); + } + + #[task] + fn baz(_: baz::Context) { + hprintln!("baz").ok(); + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } +} diff --git a/rtic/examples/shared.rs b/rtic/examples/shared.rs new file mode 100644 index 0000000..d0633fb --- /dev/null +++ b/rtic/examples/shared.rs @@ -0,0 +1,51 @@ +//! examples/late.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![deny(missing_docs)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965)] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use heapless::spsc::{Consumer, Producer, Queue}; + use lm3s6965::Interrupt; + + #[shared] + struct Shared { + p: Producer<'static, u32, 5>, + c: Consumer<'static, u32, 5>, + } + + #[local] + struct Local {} + + #[init(local = [q: Queue = Queue::new()])] + fn init(cx: init::Context) -> (Shared, Local) { + let (p, c) = cx.local.q.split(); + + // Initialization of shared resources + (Shared { p, c }, Local {}) + } + + #[idle(shared = [c])] + fn idle(mut c: idle::Context) -> ! { + loop { + if let Some(byte) = c.shared.c.lock(|c| c.dequeue()) { + hprintln!("received message: {}", byte); + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } else { + rtic::pend(Interrupt::UART0); + } + } + } + + #[task(binds = UART0, shared = [p])] + fn uart0(mut c: uart0::Context) { + c.shared.p.lock(|p| p.enqueue(42).unwrap()); + } +} diff --git a/rtic/examples/smallest.rs b/rtic/examples/smallest.rs new file mode 100644 index 0000000..e54ae44 --- /dev/null +++ b/rtic/examples/smallest.rs @@ -0,0 +1,25 @@ +//! examples/smallest.rs + +#![no_main] +#![no_std] +#![deny(missing_docs)] + +use panic_semihosting as _; // panic handler +use rtic::app; + +#[app(device = lm3s6965)] +mod app { + use cortex_m_semihosting::debug; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + (Shared {}, Local {}) + } +} diff --git a/rtic/examples/spawn.rs b/rtic/examples/spawn.rs new file mode 100644 index 0000000..d30ecf1 --- /dev/null +++ b/rtic/examples/spawn.rs @@ -0,0 +1,36 @@ +//! examples/spawn.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![deny(missing_docs)] +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + hprintln!("init"); + foo::spawn().unwrap(); + + (Shared {}, Local {}) + } + + #[task] + async fn foo(_: foo::Context) { + hprintln!("foo"); + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } +} diff --git a/rtic/examples/static.rs b/rtic/examples/static.rs new file mode 100644 index 0000000..7f656f4 --- /dev/null +++ b/rtic/examples/static.rs @@ -0,0 +1,61 @@ +//! examples/static.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![deny(missing_docs)] +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [UART0])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use heapless::spsc::{Consumer, Producer, Queue}; + + #[shared] + struct Shared {} + + #[local] + struct Local { + p: Producer<'static, u32, 5>, + c: Consumer<'static, u32, 5>, + } + + #[init(local = [q: Queue = Queue::new()])] + fn init(cx: init::Context) -> (Shared, Local) { + // q has 'static life-time so after the split and return of `init` + // it will continue to exist and be allocated + let (p, c) = cx.local.q.split(); + + foo::spawn().unwrap(); + + (Shared {}, Local { p, c }) + } + + #[idle(local = [c])] + fn idle(c: idle::Context) -> ! { + loop { + // Lock-free access to the same underlying queue! + if let Some(data) = c.local.c.dequeue() { + hprintln!("received message: {}", data); + + // Run foo until data + if data == 3 { + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } else { + foo::spawn().unwrap(); + } + } + } + } + + #[task(local = [p, state: u32 = 0])] + async fn foo(c: foo::Context) { + *c.local.state += 1; + + // Lock-free access to the same underlying queue! + c.local.p.enqueue(*c.local.state).unwrap(); + } +} diff --git a/rtic/examples/t-binds.rs b/rtic/examples/t-binds.rs new file mode 100644 index 0000000..bdeb391 --- /dev/null +++ b/rtic/examples/t-binds.rs @@ -0,0 +1,45 @@ +//! [compile-pass] Check that `binds` works as advertised + +#![deny(unsafe_code)] +#![deny(warnings)] +#![deny(missing_docs)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965)] +mod app { + use cortex_m_semihosting::debug; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + + (Shared {}, Local {}) + } + + // Cortex-M exception + #[task(binds = SVCall)] + fn foo(c: foo::Context) { + crate::foo_trampoline(c) + } + + // LM3S6965 interrupt + #[task(binds = UART0)] + fn bar(c: bar::Context) { + crate::bar_trampoline(c) + } +} + +#[allow(dead_code)] +fn foo_trampoline(_: app::foo::Context) {} + +#[allow(dead_code)] +fn bar_trampoline(_: app::bar::Context) {} diff --git a/rtic/examples/t-cfg-resources.rs b/rtic/examples/t-cfg-resources.rs new file mode 100644 index 0000000..0328700 --- /dev/null +++ b/rtic/examples/t-cfg-resources.rs @@ -0,0 +1,42 @@ +//! [compile-pass] check that `#[cfg]` attributes applied on resources work + +#![no_main] +#![no_std] +#![deny(missing_docs)] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965)] +mod app { + use cortex_m_semihosting::debug; + + #[shared] + struct Shared { + // A conditionally compiled resource behind feature_x + #[cfg(feature = "feature_x")] + x: u32, + } + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + + ( + Shared { + #[cfg(feature = "feature_x")] + x: 0, + }, + Local {}, + ) + } + + #[idle] + fn idle(_cx: idle::Context) -> ! { + loop { + cortex_m::asm::nop(); + } + } +} diff --git a/rtic/examples/t-htask-main.rs b/rtic/examples/t-htask-main.rs new file mode 100644 index 0000000..8f885bc --- /dev/null +++ b/rtic/examples/t-htask-main.rs @@ -0,0 +1,32 @@ +//! examples/h-task-main.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![deny(missing_docs)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965)] +mod app { + use cortex_m_semihosting::debug; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + rtic::pend(lm3s6965::Interrupt::UART0); + + (Shared {}, Local {}) + } + + #[task(binds = UART0)] + fn taskmain(_: taskmain::Context) { + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } +} diff --git a/rtic/examples/t-idle-main.rs b/rtic/examples/t-idle-main.rs new file mode 100644 index 0000000..43215cf --- /dev/null +++ b/rtic/examples/t-idle-main.rs @@ -0,0 +1,33 @@ +//! examples/t-idle-main.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![deny(missing_docs)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965)] +mod app { + use cortex_m_semihosting::debug; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + (Shared {}, Local {}) + } + + #[idle] + fn taskmain(_: taskmain::Context) -> ! { + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + loop { + cortex_m::asm::nop(); + } + } +} diff --git a/rtic/examples/t-late-not-send.rs b/rtic/examples/t-late-not-send.rs new file mode 100644 index 0000000..44d1d85 --- /dev/null +++ b/rtic/examples/t-late-not-send.rs @@ -0,0 +1,48 @@ +//! [compile-pass] shared resources don't need to be `Send` if they are owned by `idle` + +#![no_main] +#![no_std] +#![deny(missing_docs)] + +use core::marker::PhantomData; +use panic_semihosting as _; + +/// Not send +pub struct NotSend { + _0: PhantomData<*const ()>, +} + +#[rtic::app(device = lm3s6965)] +mod app { + use super::NotSend; + use core::marker::PhantomData; + use cortex_m_semihosting::debug; + + #[shared] + struct Shared { + x: NotSend, + y: Option, + } + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + ( + Shared { + x: NotSend { _0: PhantomData }, + y: None, + }, + Local {}, + ) + } + + #[idle(shared = [x, y])] + fn idle(_: idle::Context) -> ! { + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + loop { + cortex_m::asm::nop(); + } + } +} diff --git a/rtic/examples/t-schedule.no_rs b/rtic/examples/t-schedule.no_rs new file mode 100644 index 0000000..5ec4208 --- /dev/null +++ b/rtic/examples/t-schedule.no_rs @@ -0,0 +1,136 @@ +//! [compile-pass] Check `schedule` code generation + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] +mod app { + use cortex_m_semihosting::debug; + use systick_monotonic::*; + + #[monotonic(binds = SysTick, default = true)] + type MyMono = Systick<100>; // 100 Hz / 10 ms granularity + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { + let systick = cx.core.SYST; + + // Initialize the monotonic (SysTick rate in QEMU is 12 MHz) + let mono = Systick::new(systick, 12_000_000); + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + + (Shared {}, Local {}, init::Monotonics(mono)) + } + + #[idle] + fn idle(_: idle::Context) -> ! { + // Task without message passing + + // Not default + let _: Result = + foo::MyMono::spawn_at(monotonics::MyMono::now()); + let handle: Result = foo::MyMono::spawn_after(1.secs()); + let _: Result = handle.unwrap().reschedule_after(1.secs()); + + let handle: Result = foo::MyMono::spawn_after(1.secs()); + let _: Result = + handle.unwrap().reschedule_at(monotonics::MyMono::now()); + + let handle: Result = foo::MyMono::spawn_after(1.secs()); + let _: Result<(), ()> = handle.unwrap().cancel(); + + // Using default + let _: Result = foo::spawn_at(monotonics::now()); + let handle: Result = foo::spawn_after(1.secs()); + let _: Result = handle.unwrap().reschedule_after(1.secs()); + + let handle: Result = foo::spawn_after(1.secs()); + let _: Result = + handle.unwrap().reschedule_at(monotonics::MyMono::now()); + + let handle: Result = foo::spawn_after(1.secs()); + let _: Result<(), ()> = handle.unwrap().cancel(); + + // Task with single message passing + + // Not default + let _: Result = + bar::MyMono::spawn_at(monotonics::MyMono::now(), 0); + let handle: Result = bar::MyMono::spawn_after(1.secs(), 1); + let _: Result = handle.unwrap().reschedule_after(1.secs()); + + let handle: Result = bar::MyMono::spawn_after(1.secs(), 1); + let _: Result = + handle.unwrap().reschedule_at(monotonics::MyMono::now()); + + let handle: Result = bar::MyMono::spawn_after(1.secs(), 1); + let _: Result = handle.unwrap().cancel(); + + // Using default + let _: Result = bar::spawn_at(monotonics::MyMono::now(), 0); + let handle: Result = bar::spawn_after(1.secs(), 1); + let _: Result = handle.unwrap().reschedule_after(1.secs()); + + let handle: Result = bar::spawn_after(1.secs(), 1); + let _: Result = + handle.unwrap().reschedule_at(monotonics::MyMono::now()); + + let handle: Result = bar::spawn_after(1.secs(), 1); + let _: Result = handle.unwrap().cancel(); + + // Task with multiple message passing + + // Not default + let _: Result = + baz::MyMono::spawn_at(monotonics::MyMono::now(), 0, 1); + let handle: Result = + baz::MyMono::spawn_after(1.secs(), 1, 2); + let _: Result = handle.unwrap().reschedule_after(1.secs()); + + let handle: Result = + baz::MyMono::spawn_after(1.secs(), 1, 2); + let _: Result = + handle.unwrap().reschedule_at(monotonics::MyMono::now()); + + let handle: Result = + baz::MyMono::spawn_after(1.secs(), 1, 2); + let _: Result<(u32, u32), ()> = handle.unwrap().cancel(); + + // Using default + let _: Result = + baz::spawn_at(monotonics::MyMono::now(), 0, 1); + let handle: Result = baz::spawn_after(1.secs(), 1, 2); + let _: Result = handle.unwrap().reschedule_after(1.secs()); + + let handle: Result = baz::spawn_after(1.secs(), 1, 2); + let _: Result = + handle.unwrap().reschedule_at(monotonics::MyMono::now()); + + let handle: Result = baz::spawn_after(1.secs(), 1, 2); + let _: Result<(u32, u32), ()> = handle.unwrap().cancel(); + + loop { + cortex_m::asm::nop(); + } + } + + #[task] + fn foo(_: foo::Context) {} + + #[task] + fn bar(_: bar::Context, _x: u32) {} + + #[task] + fn baz(_: baz::Context, _x: u32, _y: u32) {} +} diff --git a/rtic/examples/t-spawn.no_rs b/rtic/examples/t-spawn.no_rs new file mode 100644 index 0000000..dad0c83 --- /dev/null +++ b/rtic/examples/t-spawn.no_rs @@ -0,0 +1,69 @@ +//! [compile-pass] Check code generation of `spawn` + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] +mod app { + use cortex_m_semihosting::debug; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + let _: Result<(), ()> = foo::spawn(); + let _: Result<(), u32> = bar::spawn(0); + let _: Result<(), (u32, u32)> = baz::spawn(0, 1); + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + + (Shared {}, Local {}) + } + + #[idle] + fn idle(_: idle::Context) -> ! { + let _: Result<(), ()> = foo::spawn(); + let _: Result<(), u32> = bar::spawn(0); + let _: Result<(), (u32, u32)> = baz::spawn(0, 1); + + loop { + cortex_m::asm::nop(); + } + } + + #[task(binds = SVCall)] + fn svcall(_: svcall::Context) { + let _: Result<(), ()> = foo::spawn(); + let _: Result<(), u32> = bar::spawn(0); + let _: Result<(), (u32, u32)> = baz::spawn(0, 1); + } + + #[task(binds = UART0)] + fn uart0(_: uart0::Context) { + let _: Result<(), ()> = foo::spawn(); + let _: Result<(), u32> = bar::spawn(0); + let _: Result<(), (u32, u32)> = baz::spawn(0, 1); + } + + #[task] + async fn foo(_: foo::Context) { + let _: Result<(), ()> = foo::spawn(); + let _: Result<(), u32> = bar::spawn(0); + let _: Result<(), (u32, u32)> = baz::spawn(0, 1); + } + + #[task] + async fn bar(_: bar::Context, _x: u32) {} + + #[task] + async fn baz(_: baz::Context, _x: u32, _y: u32) {} +} diff --git a/rtic/examples/task.rs b/rtic/examples/task.rs new file mode 100644 index 0000000..ab6a1e0 --- /dev/null +++ b/rtic/examples/task.rs @@ -0,0 +1,58 @@ +//! examples/task.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![deny(missing_docs)] +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0, QEI0])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + foo::spawn().unwrap(); + + (Shared {}, Local {}) + } + + #[task] + async fn foo(_: foo::Context) { + hprintln!("foo - start"); + + // spawns `bar` onto the task scheduler + // `foo` and `bar` have the same priority so `bar` will not run until + // after `foo` terminates + bar::spawn().unwrap(); + + hprintln!("foo - middle"); + + // spawns `baz` onto the task scheduler + // `baz` has higher priority than `foo` so it immediately preempts `foo` + baz::spawn().unwrap(); + + hprintln!("foo - end"); + } + + #[task] + async fn bar(_: bar::Context) { + hprintln!("bar"); + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } + + #[task(priority = 2)] + async fn baz(_: baz::Context) { + hprintln!("baz"); + } +} diff --git a/rtic/examples/zero-prio-task.rs b/rtic/examples/zero-prio-task.rs new file mode 100644 index 0000000..c810e8f --- /dev/null +++ b/rtic/examples/zero-prio-task.rs @@ -0,0 +1,60 @@ +//! examples/zero-prio-task.rs + +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] +#![deny(missing_docs)] + +use core::marker::PhantomData; +use panic_semihosting as _; + +/// Does not impl send +pub struct NotSend { + _0: PhantomData<*const ()>, +} + +#[rtic::app(device = lm3s6965, peripherals = true)] +mod app { + use super::NotSend; + use core::marker::PhantomData; + use cortex_m_semihosting::{debug, hprintln}; + + #[shared] + struct Shared { + x: NotSend, + } + + #[local] + struct Local { + y: NotSend, + } + + #[init] + fn init(_cx: init::Context) -> (Shared, Local) { + hprintln!("init"); + + async_task::spawn().unwrap(); + async_task2::spawn().unwrap(); + + ( + Shared { + x: NotSend { _0: PhantomData }, + }, + Local { + y: NotSend { _0: PhantomData }, + }, + ) + } + + #[task(priority = 0, shared = [x], local = [y])] + async fn async_task(_: async_task::Context) { + hprintln!("hello from async"); + } + + #[task(priority = 0, shared = [x])] + async fn async_task2(_: async_task2::Context) { + hprintln!("hello from async2"); + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } +} diff --git a/rtic/macros/.gitignore b/rtic/macros/.gitignore new file mode 100644 index 0000000..4fffb2f --- /dev/null +++ b/rtic/macros/.gitignore @@ -0,0 +1,2 @@ +/target +/Cargo.lock diff --git a/rtic/macros/Cargo.toml b/rtic/macros/Cargo.toml new file mode 100644 index 0000000..2041d37 --- /dev/null +++ b/rtic/macros/Cargo.toml @@ -0,0 +1,41 @@ +[package] +authors = [ + "The Real-Time Interrupt-driven Concurrency developers", + "Emil Fresk ", + "Henrik Tjäder ", + "Jorge Aparicio ", + "Per Lindgren ", +] +categories = ["concurrency", "embedded", "no-std", "asynchronous"] +description = "Procedural macros, syntax parsing, and codegen of the RTIC crate" +documentation = "https://rtic-rs.github.io/rtic/api/rtic" +edition = "2021" +keywords = ["arm", "cortex-m", "risc-v", "embedded", "async", "runtime", "futures", "await", "no-std", "rtos", "bare-metal"] +license = "MIT OR Apache-2.0" +name = "rtic-macros" +readme = "../README.md" +repository = "https://github.com/rtic-rs/rtic" + +version = "2.0.0-alpha.0" + +[lib] +proc-macro = true + +[features] +default = [] +debugprint = [] +# list of supported codegen backends +thumbv6 = [] +thumbv7 = [] +# riscv-clic = [] +# riscv-ch32 = [] + +[dependencies] +indexmap = "1.9.2" +proc-macro2 = "1.0.49" +proc-macro-error = "1.0.4" +quote = "1.0.23" +syn = { version = "1.0.107", features = ["extra-traits", "full"] } + +[dev-dependencies] +trybuild = "1.0.73" diff --git a/rtic/macros/src/analyze.rs b/rtic/macros/src/analyze.rs new file mode 100644 index 0000000..65774f6 --- /dev/null +++ b/rtic/macros/src/analyze.rs @@ -0,0 +1,49 @@ +use core::ops; +use std::collections::{BTreeMap, BTreeSet}; + +use crate::syntax::{ + analyze::{self, Priority}, + ast::{App, Dispatcher}, +}; +use syn::Ident; + +/// Extend the upstream `Analysis` struct with our field +pub struct Analysis { + parent: analyze::Analysis, + pub interrupts: BTreeMap, +} + +impl ops::Deref for Analysis { + type Target = analyze::Analysis; + + fn deref(&self) -> &Self::Target { + &self.parent + } +} + +// Assign an interrupt to each priority level +pub fn app(analysis: analyze::Analysis, app: &App) -> Analysis { + let mut available_interrupt = app.args.dispatchers.clone(); + + // the set of priorities (each priority only once) + let priorities = app + .software_tasks + .values() + .map(|task| task.args.priority) + .collect::>(); + + // map from priorities to interrupts (holding name and attributes) + + let interrupts: BTreeMap = priorities + .iter() + .filter(|prio| **prio > 0) // 0 prio tasks are run in main + .copied() + .rev() + .map(|p| (p, available_interrupt.pop().expect("UNREACHABLE"))) + .collect(); + + Analysis { + parent: analysis, + interrupts, + } +} diff --git a/rtic/macros/src/bindings.rs b/rtic/macros/src/bindings.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/rtic/macros/src/bindings.rs @@ -0,0 +1 @@ + diff --git a/rtic/macros/src/check.rs b/rtic/macros/src/check.rs new file mode 100644 index 0000000..a05c82e --- /dev/null +++ b/rtic/macros/src/check.rs @@ -0,0 +1,70 @@ +use std::collections::HashSet; + +use crate::syntax::ast::App; +use syn::parse; + +pub fn app(app: &App) -> parse::Result<()> { + // Check that external (device-specific) interrupts are not named after known (Cortex-M) + // exceptions + for name in app.args.dispatchers.keys() { + let name_s = name.to_string(); + + match &*name_s { + "NonMaskableInt" | "HardFault" | "MemoryManagement" | "BusFault" | "UsageFault" + | "SecureFault" | "SVCall" | "DebugMonitor" | "PendSV" | "SysTick" => { + return Err(parse::Error::new( + name.span(), + "Cortex-M exceptions can't be used as `extern` interrupts", + )); + } + + _ => {} + } + } + + // Check that there are enough external interrupts to dispatch the software tasks and the timer + // queue handler + let mut first = None; + let priorities = app + .software_tasks + .iter() + .map(|(name, task)| { + first = Some(name); + task.args.priority + }) + .filter(|prio| *prio > 0) + .collect::>(); + + let need = priorities.len(); + let given = app.args.dispatchers.len(); + if need > given { + let s = { + format!( + "not enough interrupts to dispatch \ + all software tasks (need: {need}; given: {given})" + ) + }; + + // If not enough tasks and first still is None, may cause + // "custom attribute panicked" due to unwrap on None + return Err(parse::Error::new(first.unwrap().span(), s)); + } + + // Check that all exceptions are valid; only exceptions with configurable priorities are + // accepted + for (name, task) in &app.hardware_tasks { + let name_s = task.args.binds.to_string(); + match &*name_s { + "NonMaskableInt" | "HardFault" => { + return Err(parse::Error::new( + name.span(), + "only exceptions with configurable priority can be used as hardware tasks", + )); + } + + _ => {} + } + } + + Ok(()) +} diff --git a/rtic/macros/src/codegen.rs b/rtic/macros/src/codegen.rs new file mode 100644 index 0000000..24e98ce --- /dev/null +++ b/rtic/macros/src/codegen.rs @@ -0,0 +1,75 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +use crate::analyze::Analysis; +use crate::syntax::ast::App; + +mod assertions; +mod async_dispatchers; +mod hardware_tasks; +mod idle; +mod init; +mod local_resources; +mod local_resources_struct; +mod module; +mod post_init; +mod pre_init; +mod shared_resources; +mod shared_resources_struct; +mod software_tasks; +mod util; + +mod main; + +// TODO: organize codegen to actual parts of code +// so `main::codegen` generates ALL the code for `fn main`, +// `software_tasks::codegen` generates ALL the code for software tasks etc... + +#[allow(clippy::too_many_lines)] +pub fn app(app: &App, analysis: &Analysis) -> TokenStream2 { + // Generate the `main` function + let main = main::codegen(app, analysis); + let init_codegen = init::codegen(app, analysis); + let idle_codegen = idle::codegen(app, analysis); + let shared_resources_codegen = shared_resources::codegen(app, analysis); + let local_resources_codegen = local_resources::codegen(app, analysis); + let hardware_tasks_codegen = hardware_tasks::codegen(app, analysis); + let software_tasks_codegen = software_tasks::codegen(app, analysis); + let async_dispatchers_codegen = async_dispatchers::codegen(app, analysis); + + let user_imports = &app.user_imports; + let user_code = &app.user_code; + let name = &app.name; + let device = &app.args.device; + + let rt_err = util::rt_err_ident(); + + quote!( + /// The RTIC application module + pub mod #name { + /// Always include the device crate which contains the vector table + use #device as #rt_err; + + #(#user_imports)* + + #(#user_code)* + /// User code end + + #init_codegen + + #idle_codegen + + #hardware_tasks_codegen + + #software_tasks_codegen + + #shared_resources_codegen + + #local_resources_codegen + + #async_dispatchers_codegen + + #main + } + ) +} diff --git a/rtic/macros/src/codegen/assertions.rs b/rtic/macros/src/codegen/assertions.rs new file mode 100644 index 0000000..dd94aa6 --- /dev/null +++ b/rtic/macros/src/codegen/assertions.rs @@ -0,0 +1,53 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +use crate::syntax::ast::App; +use crate::{analyze::Analysis, codegen::util}; + +/// Generates compile-time assertions that check that types implement the `Send` / `Sync` traits +pub fn codegen(app: &App, analysis: &Analysis) -> Vec { + let mut stmts = vec![]; + + for ty in &analysis.send_types { + stmts.push(quote!(rtic::export::assert_send::<#ty>();)); + } + + for ty in &analysis.sync_types { + stmts.push(quote!(rtic::export::assert_sync::<#ty>();)); + } + + let device = &app.args.device; + let chunks_name = util::priority_mask_chunks_ident(); + let no_basepri_checks: Vec<_> = app + .hardware_tasks + .iter() + .filter_map(|(_, task)| { + if !util::is_exception(&task.args.binds) { + let interrupt_name = &task.args.binds; + Some(quote!( + if (#device::Interrupt::#interrupt_name as usize) >= (#chunks_name * 32) { + ::core::panic!("An interrupt out of range is used while in armv6 or armv8m.base"); + } + )) + } else { + None + } + }) + .collect(); + + let const_check = quote! { + const _CONST_CHECK: () = { + if !rtic::export::have_basepri() { + #(#no_basepri_checks)* + } else { + // TODO: Add armv7 checks here + } + }; + + let _ = _CONST_CHECK; + }; + + stmts.push(const_check); + + stmts +} diff --git a/rtic/macros/src/codegen/async_dispatchers.rs b/rtic/macros/src/codegen/async_dispatchers.rs new file mode 100644 index 0000000..a12ad32 --- /dev/null +++ b/rtic/macros/src/codegen/async_dispatchers.rs @@ -0,0 +1,89 @@ +use crate::syntax::ast::App; +use crate::{analyze::Analysis, codegen::util}; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +/// Generates task dispatchers +pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { + let mut items = vec![]; + + let interrupts = &analysis.interrupts; + + // Generate executor definition and priority in global scope + for (name, _) in app.software_tasks.iter() { + let type_name = util::internal_task_ident(name, "F"); + let exec_name = util::internal_task_ident(name, "EXEC"); + + items.push(quote!( + #[allow(non_camel_case_types)] + type #type_name = impl core::future::Future; + #[allow(non_upper_case_globals)] + static #exec_name: rtic::export::executor::AsyncTaskExecutor<#type_name> = + rtic::export::executor::AsyncTaskExecutor::new(); + )); + } + + for (&level, channel) in &analysis.channels { + let mut stmts = vec![]; + + let dispatcher_name = if level > 0 { + util::suffixed(&interrupts.get(&level).expect("UNREACHABLE").0.to_string()) + } else { + util::zero_prio_dispatcher_ident() + }; + + let pend_interrupt = if level > 0 { + let device = &app.args.device; + let enum_ = util::interrupt_ident(); + + quote!(rtic::pend(#device::#enum_::#dispatcher_name);) + } else { + // For 0 priority tasks we don't need to pend anything + quote!() + }; + + for name in channel.tasks.iter() { + let exec_name = util::internal_task_ident(name, "EXEC"); + // TODO: Fix cfg + // let task = &app.software_tasks[name]; + // let cfgs = &task.cfgs; + + stmts.push(quote!( + #exec_name.poll(|| { + #exec_name.set_pending(); + #pend_interrupt + }); + )); + } + + if level > 0 { + let doc = format!("Interrupt handler to dispatch async tasks at priority {level}"); + let attribute = &interrupts.get(&level).expect("UNREACHABLE").1.attrs; + items.push(quote!( + #[allow(non_snake_case)] + #[doc = #doc] + #[no_mangle] + #(#attribute)* + unsafe fn #dispatcher_name() { + /// The priority of this interrupt handler + const PRIORITY: u8 = #level; + + rtic::export::run(PRIORITY, || { + #(#stmts)* + }); + } + )); + } else { + items.push(quote!( + #[allow(non_snake_case)] + unsafe fn #dispatcher_name() -> ! { + loop { + #(#stmts)* + } + } + )); + } + } + + quote!(#(#items)*) +} diff --git a/rtic/macros/src/codegen/hardware_tasks.rs b/rtic/macros/src/codegen/hardware_tasks.rs new file mode 100644 index 0000000..8a5a8f6 --- /dev/null +++ b/rtic/macros/src/codegen/hardware_tasks.rs @@ -0,0 +1,87 @@ +use crate::syntax::{ast::App, Context}; +use crate::{ + analyze::Analysis, + codegen::{local_resources_struct, module, shared_resources_struct}, +}; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +/// Generate support code for hardware tasks (`#[exception]`s and `#[interrupt]`s) +pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { + let mut mod_app = vec![]; + let mut root = vec![]; + let mut user_tasks = vec![]; + + for (name, task) in &app.hardware_tasks { + let symbol = task.args.binds.clone(); + let priority = task.args.priority; + let cfgs = &task.cfgs; + let attrs = &task.attrs; + + mod_app.push(quote!( + #[allow(non_snake_case)] + #[no_mangle] + #(#attrs)* + #(#cfgs)* + unsafe fn #symbol() { + const PRIORITY: u8 = #priority; + + rtic::export::run(PRIORITY, || { + #name( + #name::Context::new() + ) + }); + } + )); + + // `${task}Locals` + if !task.args.local_resources.is_empty() { + let (item, constructor) = + local_resources_struct::codegen(Context::HardwareTask(name), app); + + root.push(item); + + mod_app.push(constructor); + } + + // `${task}Resources` + if !task.args.shared_resources.is_empty() { + let (item, constructor) = + shared_resources_struct::codegen(Context::HardwareTask(name), app); + + root.push(item); + + mod_app.push(constructor); + } + + // Module generation... + + root.push(module::codegen(Context::HardwareTask(name), app, analysis)); + + // End module generation + + if !task.is_extern { + let attrs = &task.attrs; + let context = &task.context; + let stmts = &task.stmts; + user_tasks.push(quote!( + #(#attrs)* + #[allow(non_snake_case)] + fn #name(#context: #name::Context) { + use rtic::Mutex as _; + use rtic::mutex::prelude::*; + + #(#stmts)* + } + )); + } + } + + quote!( + #(#mod_app)* + + #(#root)* + + #(#user_tasks)* + ) +} diff --git a/rtic/macros/src/codegen/idle.rs b/rtic/macros/src/codegen/idle.rs new file mode 100644 index 0000000..0c833ef --- /dev/null +++ b/rtic/macros/src/codegen/idle.rs @@ -0,0 +1,58 @@ +use crate::syntax::{ast::App, Context}; +use crate::{ + analyze::Analysis, + codegen::{local_resources_struct, module, shared_resources_struct}, +}; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +/// Generates support code for `#[idle]` functions +pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { + if let Some(idle) = &app.idle { + let mut mod_app = vec![]; + let mut root_idle = vec![]; + + let name = &idle.name; + + if !idle.args.shared_resources.is_empty() { + let (item, constructor) = shared_resources_struct::codegen(Context::Idle, app); + + root_idle.push(item); + mod_app.push(constructor); + } + + if !idle.args.local_resources.is_empty() { + let (item, constructor) = local_resources_struct::codegen(Context::Idle, app); + + root_idle.push(item); + + mod_app.push(constructor); + } + + root_idle.push(module::codegen(Context::Idle, app, analysis)); + + let attrs = &idle.attrs; + let context = &idle.context; + let stmts = &idle.stmts; + let user_idle = Some(quote!( + #(#attrs)* + #[allow(non_snake_case)] + fn #name(#context: #name::Context) -> ! { + use rtic::Mutex as _; + use rtic::mutex::prelude::*; + + #(#stmts)* + } + )); + + quote!( + #(#mod_app)* + + #(#root_idle)* + + #user_idle + ) + } else { + quote!() + } +} diff --git a/rtic/macros/src/codegen/init.rs b/rtic/macros/src/codegen/init.rs new file mode 100644 index 0000000..6e1059f --- /dev/null +++ b/rtic/macros/src/codegen/init.rs @@ -0,0 +1,95 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +use crate::{ + analyze::Analysis, + codegen::{local_resources_struct, module}, + syntax::{ast::App, Context}, +}; + +/// Generates support code for `#[init]` functions +pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { + let init = &app.init; + let name = &init.name; + + let mut root_init = vec![]; + + let context = &init.context; + let attrs = &init.attrs; + let stmts = &init.stmts; + let shared = &init.user_shared_struct; + let local = &init.user_local_struct; + + let shared_resources: Vec<_> = app + .shared_resources + .iter() + .map(|(k, v)| { + let ty = &v.ty; + let cfgs = &v.cfgs; + let docs = &v.docs; + quote!( + #(#cfgs)* + #(#docs)* + #k: #ty, + ) + }) + .collect(); + let local_resources: Vec<_> = app + .local_resources + .iter() + .map(|(k, v)| { + let ty = &v.ty; + let cfgs = &v.cfgs; + let docs = &v.docs; + quote!( + #(#cfgs)* + #(#docs)* + #k: #ty, + ) + }) + .collect(); + + root_init.push(quote! { + struct #shared { + #(#shared_resources)* + } + + struct #local { + #(#local_resources)* + } + }); + + // let locals_pat = locals_pat.iter(); + + let user_init_return = quote! {#shared, #local}; + + let user_init = quote!( + #(#attrs)* + #[inline(always)] + #[allow(non_snake_case)] + fn #name(#context: #name::Context) -> (#user_init_return) { + #(#stmts)* + } + ); + + let mut mod_app = None; + + // `${task}Locals` + if !init.args.local_resources.is_empty() { + let (item, constructor) = local_resources_struct::codegen(Context::Init, app); + + root_init.push(item); + + mod_app = Some(constructor); + } + + root_init.push(module::codegen(Context::Init, app, analysis)); + + quote!( + #mod_app + + #(#root_init)* + + #user_init + ) +} diff --git a/rtic/macros/src/codegen/local_resources.rs b/rtic/macros/src/codegen/local_resources.rs new file mode 100644 index 0000000..e6d1553 --- /dev/null +++ b/rtic/macros/src/codegen/local_resources.rs @@ -0,0 +1,65 @@ +use crate::syntax::ast::App; +use crate::{analyze::Analysis, codegen::util}; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +/// Generates `local` variables and local resource proxies +/// +/// I.e. the `static` variables and theirs proxies. +pub fn codegen(app: &App, _analysis: &Analysis) -> TokenStream2 { + let mut mod_app = vec![]; + + // All local resources declared in the `#[local]' struct + for (name, res) in &app.local_resources { + let cfgs = &res.cfgs; + let ty = &res.ty; + let mangled_name = util::static_local_resource_ident(name); + + let attrs = &res.attrs; + + // late resources in `util::link_section_uninit` + // unless user specifies custom link section + let section = if attrs.iter().any(|attr| attr.path.is_ident("link_section")) { + None + } else { + Some(util::link_section_uninit()) + }; + + // For future use + // let doc = format!(" RTIC internal: {}:{}", file!(), line!()); + mod_app.push(quote!( + #[allow(non_camel_case_types)] + #[allow(non_upper_case_globals)] + // #[doc = #doc] + #[doc(hidden)] + #(#attrs)* + #(#cfgs)* + #section + static #mangled_name: rtic::RacyCell> = rtic::RacyCell::new(core::mem::MaybeUninit::uninit()); + )); + } + + // All declared `local = [NAME: TY = EXPR]` local resources + for (task_name, resource_name, task_local) in app.declared_local_resources() { + let cfgs = &task_local.cfgs; + let ty = &task_local.ty; + let expr = &task_local.expr; + let attrs = &task_local.attrs; + + let mangled_name = util::declared_static_local_resource_ident(resource_name, task_name); + + // For future use + // let doc = format!(" RTIC internal: {}:{}", file!(), line!()); + mod_app.push(quote!( + #[allow(non_camel_case_types)] + #[allow(non_upper_case_globals)] + // #[doc = #doc] + #[doc(hidden)] + #(#attrs)* + #(#cfgs)* + static #mangled_name: rtic::RacyCell<#ty> = rtic::RacyCell::new(#expr); + )); + } + + quote!(#(#mod_app)*) +} diff --git a/rtic/macros/src/codegen/local_resources_struct.rs b/rtic/macros/src/codegen/local_resources_struct.rs new file mode 100644 index 0000000..100c3eb --- /dev/null +++ b/rtic/macros/src/codegen/local_resources_struct.rs @@ -0,0 +1,102 @@ +use crate::syntax::{ + ast::{App, TaskLocal}, + Context, +}; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +use crate::codegen::util; + +/// Generates local resources structs +pub fn codegen(ctxt: Context, app: &App) -> (TokenStream2, TokenStream2) { + let resources = match ctxt { + Context::Init => &app.init.args.local_resources, + Context::Idle => { + &app.idle + .as_ref() + .expect("RTIC-ICE: unable to get idle name") + .args + .local_resources + } + Context::HardwareTask(name) => &app.hardware_tasks[name].args.local_resources, + Context::SoftwareTask(name) => &app.software_tasks[name].args.local_resources, + }; + + let task_name = util::get_task_name(ctxt, app); + + let mut fields = vec![]; + let mut values = vec![]; + + for (name, task_local) in resources { + let (cfgs, ty, is_declared) = match task_local { + TaskLocal::External => { + let r = app.local_resources.get(name).expect("UNREACHABLE"); + (&r.cfgs, &r.ty, false) + } + TaskLocal::Declared(r) => (&r.cfgs, &r.ty, true), + }; + + let lt = if ctxt.runs_once() { + quote!('static) + } else { + quote!('a) + }; + + let mangled_name = if matches!(task_local, TaskLocal::External) { + util::static_local_resource_ident(name) + } else { + util::declared_static_local_resource_ident(name, &task_name) + }; + + fields.push(quote!( + #(#cfgs)* + #[allow(missing_docs)] + pub #name: &#lt mut #ty + )); + + let expr = if is_declared { + // If the local resources is already initialized, we only need to access its value and + // not go through an `MaybeUninit` + quote!(&mut *#mangled_name.get_mut()) + } else { + quote!(&mut *(&mut *#mangled_name.get_mut()).as_mut_ptr()) + }; + + values.push(quote!( + #(#cfgs)* + #name: #expr + )); + } + + fields.push(quote!( + #[doc(hidden)] + pub __rtic_internal_marker: ::core::marker::PhantomData<&'a ()> + )); + + values.push(quote!(__rtic_internal_marker: ::core::marker::PhantomData)); + + let doc = format!("Local resources `{}` has access to", ctxt.ident(app)); + let ident = util::local_resources_ident(ctxt, app); + let item = quote!( + #[allow(non_snake_case)] + #[allow(non_camel_case_types)] + #[doc = #doc] + pub struct #ident<'a> { + #(#fields,)* + } + ); + + let constructor = quote!( + impl<'a> #ident<'a> { + #[inline(always)] + #[allow(missing_docs)] + pub unsafe fn new() -> Self { + #ident { + #(#values,)* + } + } + } + ); + + (item, constructor) +} diff --git a/rtic/macros/src/codegen/main.rs b/rtic/macros/src/codegen/main.rs new file mode 100644 index 0000000..2775d25 --- /dev/null +++ b/rtic/macros/src/codegen/main.rs @@ -0,0 +1,52 @@ +use crate::{analyze::Analysis, codegen::util, syntax::ast::App}; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +use super::{assertions, post_init, pre_init}; + +/// Generates code for `fn main` +pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { + let assertion_stmts = assertions::codegen(app, analysis); + + let pre_init_stmts = pre_init::codegen(app, analysis); + + let post_init_stmts = post_init::codegen(app, analysis); + + let call_idle = if let Some(idle) = &app.idle { + let name = &idle.name; + quote!(#name(#name::Context::new())) + } else if analysis.channels.get(&0).is_some() { + let dispatcher = util::zero_prio_dispatcher_ident(); + quote!(#dispatcher();) + } else { + quote!(loop { + rtic::export::nop() + }) + }; + + let main = util::suffixed("main"); + let init_name = &app.init.name; + quote!( + #[doc(hidden)] + #[no_mangle] + unsafe extern "C" fn #main() -> ! { + #(#assertion_stmts)* + + #(#pre_init_stmts)* + + #[inline(never)] + fn __rtic_init_resources(f: F) where F: FnOnce() { + f(); + } + + // Wrap late_init_stmts in a function to ensure that stack space is reclaimed. + __rtic_init_resources(||{ + let (shared_resources, local_resources) = #init_name(#init_name::Context::new(core.into())); + + #(#post_init_stmts)* + }); + + #call_idle + } + ) +} diff --git a/rtic/macros/src/codegen/module.rs b/rtic/macros/src/codegen/module.rs new file mode 100644 index 0000000..4725b9a --- /dev/null +++ b/rtic/macros/src/codegen/module.rs @@ -0,0 +1,194 @@ +use crate::syntax::{ast::App, Context}; +use crate::{analyze::Analysis, codegen::util}; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +#[allow(clippy::too_many_lines)] +pub fn codegen(ctxt: Context, app: &App, analysis: &Analysis) -> TokenStream2 { + let mut items = vec![]; + let mut module_items = vec![]; + let mut fields = vec![]; + let mut values = vec![]; + // Used to copy task cfgs to the whole module + let mut task_cfgs = vec![]; + + let name = ctxt.ident(app); + + match ctxt { + Context::Init => { + fields.push(quote!( + /// Core (Cortex-M) peripherals + pub core: rtic::export::Peripherals + )); + + if app.args.peripherals { + let device = &app.args.device; + + fields.push(quote!( + /// Device peripherals + pub device: #device::Peripherals + )); + + values.push(quote!(device: #device::Peripherals::steal())); + } + + fields.push(quote!( + /// Critical section token for init + pub cs: rtic::export::CriticalSection<'a> + )); + + values.push(quote!(cs: rtic::export::CriticalSection::new())); + + values.push(quote!(core)); + } + + Context::Idle | Context::HardwareTask(_) | Context::SoftwareTask(_) => {} + } + + if ctxt.has_local_resources(app) { + let ident = util::local_resources_ident(ctxt, app); + + module_items.push(quote!( + #[doc(inline)] + pub use super::#ident as LocalResources; + )); + + fields.push(quote!( + /// Local Resources this task has access to + pub local: #name::LocalResources<'a> + )); + + values.push(quote!(local: #name::LocalResources::new())); + } + + if ctxt.has_shared_resources(app) { + let ident = util::shared_resources_ident(ctxt, app); + + module_items.push(quote!( + #[doc(inline)] + pub use super::#ident as SharedResources; + )); + + fields.push(quote!( + /// Shared Resources this task has access to + pub shared: #name::SharedResources<'a> + )); + + values.push(quote!(shared: #name::SharedResources::new())); + } + + let doc = match ctxt { + Context::Idle => "Idle loop", + Context::Init => "Initialization function", + Context::HardwareTask(_) => "Hardware task", + Context::SoftwareTask(_) => "Software task", + }; + + let v = Vec::new(); + let cfgs = match ctxt { + Context::HardwareTask(t) => &app.hardware_tasks[t].cfgs, + Context::SoftwareTask(t) => &app.software_tasks[t].cfgs, + _ => &v, + }; + + let core = if ctxt.is_init() { + Some(quote!(core: rtic::export::Peripherals,)) + } else { + None + }; + + let internal_context_name = util::internal_task_ident(name, "Context"); + let exec_name = util::internal_task_ident(name, "EXEC"); + + items.push(quote!( + #(#cfgs)* + /// Execution context + #[allow(non_snake_case)] + #[allow(non_camel_case_types)] + pub struct #internal_context_name<'a> { + #[doc(hidden)] + __rtic_internal_p: ::core::marker::PhantomData<&'a ()>, + #(#fields,)* + } + + #(#cfgs)* + impl<'a> #internal_context_name<'a> { + #[inline(always)] + #[allow(missing_docs)] + pub unsafe fn new(#core) -> Self { + #internal_context_name { + __rtic_internal_p: ::core::marker::PhantomData, + #(#values,)* + } + } + } + )); + + module_items.push(quote!( + #(#cfgs)* + #[doc(inline)] + pub use super::#internal_context_name as Context; + )); + + if let Context::SoftwareTask(..) = ctxt { + let spawnee = &app.software_tasks[name]; + let priority = spawnee.args.priority; + let cfgs = &spawnee.cfgs; + // Store a copy of the task cfgs + task_cfgs = cfgs.clone(); + + let pend_interrupt = if priority > 0 { + let device = &app.args.device; + let enum_ = util::interrupt_ident(); + let interrupt = &analysis.interrupts.get(&priority).expect("UREACHABLE").0; + quote!(rtic::pend(#device::#enum_::#interrupt);) + } else { + quote!() + }; + + let internal_spawn_ident = util::internal_task_ident(name, "spawn"); + let (input_args, input_tupled, input_untupled, input_ty) = + util::regroup_inputs(&spawnee.inputs); + + // Spawn caller + items.push(quote!( + #(#cfgs)* + /// Spawns the task directly + #[allow(non_snake_case)] + #[doc(hidden)] + pub fn #internal_spawn_ident(#(#input_args,)*) -> Result<(), #input_ty> { + + if #exec_name.spawn(|| #name(unsafe { #name::Context::new() } #(,#input_untupled)*) ) { + + #pend_interrupt + + Ok(()) + } else { + Err(#input_tupled) + } + + } + )); + + module_items.push(quote!( + #(#cfgs)* + #[doc(inline)] + pub use super::#internal_spawn_ident as spawn; + )); + } + + if items.is_empty() { + quote!() + } else { + quote!( + #(#items)* + + #[allow(non_snake_case)] + #(#task_cfgs)* + #[doc = #doc] + pub mod #name { + #(#module_items)* + } + ) + } +} diff --git a/rtic/macros/src/codegen/post_init.rs b/rtic/macros/src/codegen/post_init.rs new file mode 100644 index 0000000..c4e5383 --- /dev/null +++ b/rtic/macros/src/codegen/post_init.rs @@ -0,0 +1,47 @@ +use crate::{analyze::Analysis, codegen::util, syntax::ast::App}; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +/// Generates code that runs after `#[init]` returns +pub fn codegen(app: &App, analysis: &Analysis) -> Vec { + let mut stmts = vec![]; + + // Initialize shared resources + for (name, res) in &app.shared_resources { + let mangled_name = util::static_shared_resource_ident(name); + // If it's live + let cfgs = res.cfgs.clone(); + if analysis.shared_resources.get(name).is_some() { + stmts.push(quote!( + // We include the cfgs + #(#cfgs)* + // Resource is a RacyCell> + // - `get_mut` to obtain a raw pointer to `MaybeUninit` + // - `write` the defined value for the late resource T + #mangled_name.get_mut().write(core::mem::MaybeUninit::new(shared_resources.#name)); + )); + } + } + + // Initialize local resources + for (name, res) in &app.local_resources { + let mangled_name = util::static_local_resource_ident(name); + // If it's live + let cfgs = res.cfgs.clone(); + if analysis.local_resources.get(name).is_some() { + stmts.push(quote!( + // We include the cfgs + #(#cfgs)* + // Resource is a RacyCell> + // - `get_mut` to obtain a raw pointer to `MaybeUninit` + // - `write` the defined value for the late resource T + #mangled_name.get_mut().write(core::mem::MaybeUninit::new(local_resources.#name)); + )); + } + } + + // Enable the interrupts -- this completes the `init`-ialization phase + stmts.push(quote!(rtic::export::interrupt::enable();)); + + stmts +} diff --git a/rtic/macros/src/codegen/pre_init.rs b/rtic/macros/src/codegen/pre_init.rs new file mode 100644 index 0000000..28ba29c --- /dev/null +++ b/rtic/macros/src/codegen/pre_init.rs @@ -0,0 +1,85 @@ +use crate::syntax::ast::App; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +use crate::{analyze::Analysis, codegen::util}; + +/// Generates code that runs before `#[init]` +pub fn codegen(app: &App, analysis: &Analysis) -> Vec { + let mut stmts = vec![]; + + let rt_err = util::rt_err_ident(); + + // Disable interrupts -- `init` must run with interrupts disabled + stmts.push(quote!(rtic::export::interrupt::disable();)); + + stmts.push(quote!( + // To set the variable in cortex_m so the peripherals cannot be taken multiple times + let mut core: rtic::export::Peripherals = rtic::export::Peripherals::steal().into(); + )); + + let device = &app.args.device; + let nvic_prio_bits = quote!(#device::NVIC_PRIO_BITS); + + // check that all dispatchers exists in the `Interrupt` enumeration regardless of whether + // they are used or not + let interrupt = util::interrupt_ident(); + for name in app.args.dispatchers.keys() { + stmts.push(quote!(let _ = #rt_err::#interrupt::#name;)); + } + + let interrupt_ids = analysis.interrupts.iter().map(|(p, (id, _))| (p, id)); + + // Unmask interrupts and set their priorities + for (&priority, name) in interrupt_ids.chain(app.hardware_tasks.values().filter_map(|task| { + if util::is_exception(&task.args.binds) { + // We do exceptions in another pass + None + } else { + Some((&task.args.priority, &task.args.binds)) + } + })) { + let es = format!( + "Maximum priority used by interrupt vector '{name}' is more than supported by hardware" + ); + // Compile time assert that this priority is supported by the device + stmts.push(quote!( + const _: () = if (1 << #nvic_prio_bits) < #priority as usize { ::core::panic!(#es); }; + )); + + stmts.push(quote!( + core.NVIC.set_priority( + #rt_err::#interrupt::#name, + rtic::export::logical2hw(#priority, #nvic_prio_bits), + ); + )); + + // NOTE unmask the interrupt *after* setting its priority: changing the priority of a pended + // interrupt is implementation defined + stmts.push(quote!(rtic::export::NVIC::unmask(#rt_err::#interrupt::#name);)); + } + + // Set exception priorities + for (name, priority) in app.hardware_tasks.values().filter_map(|task| { + if util::is_exception(&task.args.binds) { + Some((&task.args.binds, task.args.priority)) + } else { + None + } + }) { + let es = format!( + "Maximum priority used by interrupt vector '{name}' is more than supported by hardware" + ); + // Compile time assert that this priority is supported by the device + stmts.push(quote!( + const _: () = if (1 << #nvic_prio_bits) < #priority as usize { ::core::panic!(#es); }; + )); + + stmts.push(quote!(core.SCB.set_priority( + rtic::export::SystemHandler::#name, + rtic::export::logical2hw(#priority, #nvic_prio_bits), + );)); + } + + stmts +} diff --git a/rtic/macros/src/codegen/shared_resources.rs b/rtic/macros/src/codegen/shared_resources.rs new file mode 100644 index 0000000..19fd13f --- /dev/null +++ b/rtic/macros/src/codegen/shared_resources.rs @@ -0,0 +1,183 @@ +use crate::syntax::{analyze::Ownership, ast::App}; +use crate::{analyze::Analysis, codegen::util}; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use std::collections::HashMap; + +/// Generates `static` variables and shared resource proxies +pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { + let mut mod_app = vec![]; + let mut mod_resources = vec![]; + + for (name, res) in &app.shared_resources { + let cfgs = &res.cfgs; + let ty = &res.ty; + let mangled_name = &util::static_shared_resource_ident(name); + + let attrs = &res.attrs; + + // late resources in `util::link_section_uninit` + // unless user specifies custom link section + let section = if attrs.iter().any(|attr| attr.path.is_ident("link_section")) { + None + } else { + Some(util::link_section_uninit()) + }; + + // For future use + // let doc = format!(" RTIC internal: {}:{}", file!(), line!()); + mod_app.push(quote!( + #[allow(non_camel_case_types)] + #[allow(non_upper_case_globals)] + // #[doc = #doc] + #[doc(hidden)] + #(#attrs)* + #(#cfgs)* + #section + static #mangled_name: rtic::RacyCell> = rtic::RacyCell::new(core::mem::MaybeUninit::uninit()); + )); + + // For future use + // let doc = format!(" RTIC internal: {}:{}", file!(), line!()); + + let shared_name = util::need_to_lock_ident(name); + + if !res.properties.lock_free { + mod_resources.push(quote!( + // #[doc = #doc] + #[doc(hidden)] + #[allow(non_camel_case_types)] + #(#cfgs)* + pub struct #shared_name<'a> { + __rtic_internal_p: ::core::marker::PhantomData<&'a ()>, + } + + #(#cfgs)* + impl<'a> #shared_name<'a> { + #[inline(always)] + pub unsafe fn new() -> Self { + #shared_name { __rtic_internal_p: ::core::marker::PhantomData } + } + } + )); + + let ptr = quote!( + #(#cfgs)* + #mangled_name.get_mut() as *mut _ + ); + + let ceiling = match analysis.ownerships.get(name) { + Some(Ownership::Owned { priority } | Ownership::CoOwned { priority }) => *priority, + Some(Ownership::Contended { ceiling }) => *ceiling, + None => 0, + }; + + // For future use + // let doc = format!(" RTIC internal ({} resource): {}:{}", doc, file!(), line!()); + + mod_app.push(util::impl_mutex( + app, + cfgs, + true, + &shared_name, + "e!(#ty), + ceiling, + &ptr, + )); + } + } + + let mod_resources = if mod_resources.is_empty() { + quote!() + } else { + quote!(mod shared_resources { + #(#mod_resources)* + }) + }; + + // Computing mapping of used interrupts to masks + let interrupt_ids = analysis.interrupts.iter().map(|(p, (id, _))| (p, id)); + + let mut prio_to_masks = HashMap::new(); + let device = &app.args.device; + let mut uses_exceptions_with_resources = false; + + let mut mask_ids = Vec::new(); + + for (&priority, name) in interrupt_ids.chain(app.hardware_tasks.values().flat_map(|task| { + if !util::is_exception(&task.args.binds) { + Some((&task.args.priority, &task.args.binds)) + } else { + // If any resource to the exception uses non-lock-free or non-local resources this is + // not allwed on thumbv6. + uses_exceptions_with_resources = uses_exceptions_with_resources + || task + .args + .shared_resources + .iter() + .map(|(ident, access)| { + if access.is_exclusive() { + if let Some(r) = app.shared_resources.get(ident) { + !r.properties.lock_free + } else { + false + } + } else { + false + } + }) + .any(|v| v); + + None + } + })) { + let v: &mut Vec<_> = prio_to_masks.entry(priority - 1).or_default(); + v.push(quote!(#device::Interrupt::#name as u32)); + mask_ids.push(quote!(#device::Interrupt::#name as u32)); + } + + // Call rtic::export::create_mask([Mask; N]), where the array is the list of shifts + + let mut mask_arr = Vec::new(); + // NOTE: 0..3 assumes max 4 priority levels according to M0, M23 spec + for i in 0..3 { + let v = if let Some(v) = prio_to_masks.get(&i) { + v.clone() + } else { + Vec::new() + }; + + mask_arr.push(quote!( + rtic::export::create_mask([#(#v),*]) + )); + } + + // Generate a constant for the number of chunks needed by Mask. + let chunks_name = util::priority_mask_chunks_ident(); + mod_app.push(quote!( + #[doc(hidden)] + #[allow(non_upper_case_globals)] + const #chunks_name: usize = rtic::export::compute_mask_chunks([#(#mask_ids),*]); + )); + + let masks_name = util::priority_masks_ident(); + mod_app.push(quote!( + #[doc(hidden)] + #[allow(non_upper_case_globals)] + const #masks_name: [rtic::export::Mask<#chunks_name>; 3] = [#(#mask_arr),*]; + )); + + if uses_exceptions_with_resources { + mod_app.push(quote!( + #[doc(hidden)] + #[allow(non_upper_case_globals)] + const __rtic_internal_V6_ERROR: () = rtic::export::no_basepri_panic(); + )); + } + + quote!( + #(#mod_app)* + + #mod_resources + ) +} diff --git a/rtic/macros/src/codegen/shared_resources_struct.rs b/rtic/macros/src/codegen/shared_resources_struct.rs new file mode 100644 index 0000000..fa6f0fc --- /dev/null +++ b/rtic/macros/src/codegen/shared_resources_struct.rs @@ -0,0 +1,119 @@ +use crate::syntax::{ast::App, Context}; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +use crate::codegen::util; + +/// Generate shared resources structs +pub fn codegen(ctxt: Context, app: &App) -> (TokenStream2, TokenStream2) { + let resources = match ctxt { + Context::Init => unreachable!("Tried to generate shared resources struct for init"), + Context::Idle => { + &app.idle + .as_ref() + .expect("RTIC-ICE: unable to get idle name") + .args + .shared_resources + } + Context::HardwareTask(name) => &app.hardware_tasks[name].args.shared_resources, + Context::SoftwareTask(name) => &app.software_tasks[name].args.shared_resources, + }; + + let mut fields = vec![]; + let mut values = vec![]; + + for (name, access) in resources { + let res = app.shared_resources.get(name).expect("UNREACHABLE"); + + let cfgs = &res.cfgs; + + // access hold if the resource is [x] (exclusive) or [&x] (shared) + let mut_ = if access.is_exclusive() { + Some(quote!(mut)) + } else { + None + }; + let ty = &res.ty; + let mangled_name = util::static_shared_resource_ident(name); + let shared_name = util::need_to_lock_ident(name); + + if res.properties.lock_free { + // Lock free resources of `idle` and `init` get 'static lifetime + let lt = if ctxt.runs_once() { + quote!('static) + } else { + quote!('a) + }; + + fields.push(quote!( + #(#cfgs)* + #[allow(missing_docs)] + pub #name: &#lt #mut_ #ty + )); + } else if access.is_shared() { + fields.push(quote!( + #(#cfgs)* + #[allow(missing_docs)] + pub #name: &'a #ty + )); + } else { + fields.push(quote!( + #(#cfgs)* + #[allow(missing_docs)] + pub #name: shared_resources::#shared_name<'a> + )); + + values.push(quote!( + #(#cfgs)* + #name: shared_resources::#shared_name::new() + + )); + + // continue as the value has been filled, + continue; + } + + let expr = if access.is_exclusive() { + quote!(&mut *(&mut *#mangled_name.get_mut()).as_mut_ptr()) + } else { + quote!(&*(&*#mangled_name.get()).as_ptr()) + }; + + values.push(quote!( + #(#cfgs)* + #name: #expr + )); + } + + fields.push(quote!( + #[doc(hidden)] + pub __rtic_internal_marker: core::marker::PhantomData<&'a ()> + )); + + values.push(quote!(__rtic_internal_marker: core::marker::PhantomData)); + + let doc = format!("Shared resources `{}` has access to", ctxt.ident(app)); + let ident = util::shared_resources_ident(ctxt, app); + let item = quote!( + #[allow(non_snake_case)] + #[allow(non_camel_case_types)] + #[doc = #doc] + pub struct #ident<'a> { + #(#fields,)* + } + ); + + let constructor = quote!( + impl<'a> #ident<'a> { + #[inline(always)] + #[allow(missing_docs)] + pub unsafe fn new() -> Self { + #ident { + #(#values,)* + } + } + } + ); + + (item, constructor) +} diff --git a/rtic/macros/src/codegen/software_tasks.rs b/rtic/macros/src/codegen/software_tasks.rs new file mode 100644 index 0000000..34fc851 --- /dev/null +++ b/rtic/macros/src/codegen/software_tasks.rs @@ -0,0 +1,64 @@ +use crate::syntax::{ast::App, Context}; +use crate::{ + analyze::Analysis, + codegen::{local_resources_struct, module, shared_resources_struct}, +}; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { + let mut mod_app = vec![]; + let mut root = vec![]; + let mut user_tasks = vec![]; + + // Any task + for (name, task) in app.software_tasks.iter() { + if !task.args.local_resources.is_empty() { + let (item, constructor) = + local_resources_struct::codegen(Context::SoftwareTask(name), app); + + root.push(item); + + mod_app.push(constructor); + } + + if !task.args.shared_resources.is_empty() { + let (item, constructor) = + shared_resources_struct::codegen(Context::SoftwareTask(name), app); + + root.push(item); + + mod_app.push(constructor); + } + + if !&task.is_extern { + let context = &task.context; + let attrs = &task.attrs; + let cfgs = &task.cfgs; + let stmts = &task.stmts; + let inputs = &task.inputs; + + user_tasks.push(quote!( + #(#attrs)* + #(#cfgs)* + #[allow(non_snake_case)] + async fn #name<'a>(#context: #name::Context<'a> #(,#inputs)*) { + use rtic::Mutex as _; + use rtic::mutex::prelude::*; + + #(#stmts)* + } + )); + } + + root.push(module::codegen(Context::SoftwareTask(name), app, analysis)); + } + + quote!( + #(#mod_app)* + + #(#root)* + + #(#user_tasks)* + ) +} diff --git a/rtic/macros/src/codegen/util.rs b/rtic/macros/src/codegen/util.rs new file mode 100644 index 0000000..e121487 --- /dev/null +++ b/rtic/macros/src/codegen/util.rs @@ -0,0 +1,238 @@ +use crate::syntax::{ast::App, Context}; +use core::sync::atomic::{AtomicUsize, Ordering}; +use proc_macro2::{Span, TokenStream as TokenStream2}; +use quote::quote; +use syn::{Attribute, Ident, PatType}; + +const RTIC_INTERNAL: &str = "__rtic_internal"; + +/// Generates a `Mutex` implementation +pub fn impl_mutex( + app: &App, + cfgs: &[Attribute], + resources_prefix: bool, + name: &Ident, + ty: &TokenStream2, + ceiling: u8, + ptr: &TokenStream2, +) -> TokenStream2 { + let path = if resources_prefix { + quote!(shared_resources::#name) + } else { + quote!(#name) + }; + + let device = &app.args.device; + let masks_name = priority_masks_ident(); + quote!( + #(#cfgs)* + impl<'a> rtic::Mutex for #path<'a> { + type T = #ty; + + #[inline(always)] + fn lock(&mut self, f: impl FnOnce(&mut #ty) -> RTIC_INTERNAL_R) -> RTIC_INTERNAL_R { + /// Priority ceiling + const CEILING: u8 = #ceiling; + + unsafe { + rtic::export::lock( + #ptr, + CEILING, + #device::NVIC_PRIO_BITS, + &#masks_name, + f, + ) + } + } + } + ) +} + +pub fn interrupt_ident() -> Ident { + let span = Span::call_site(); + Ident::new("interrupt", span) +} + +/// Whether `name` is an exception with configurable priority +pub fn is_exception(name: &Ident) -> bool { + let s = name.to_string(); + + matches!( + &*s, + "MemoryManagement" + | "BusFault" + | "UsageFault" + | "SecureFault" + | "SVCall" + | "DebugMonitor" + | "PendSV" + | "SysTick" + ) +} + +/// Mark a name as internal +pub fn mark_internal_name(name: &str) -> Ident { + Ident::new(&format!("{RTIC_INTERNAL}_{name}"), Span::call_site()) +} + +/// Generate an internal identifier for tasks +pub fn internal_task_ident(task: &Ident, ident_name: &str) -> Ident { + mark_internal_name(&format!("{task}_{ident_name}")) +} + +fn link_section_index() -> usize { + static INDEX: AtomicUsize = AtomicUsize::new(0); + + INDEX.fetch_add(1, Ordering::Relaxed) +} + +/// Add `link_section` attribute +pub fn link_section_uninit() -> TokenStream2 { + let section = format!(".uninit.rtic{}", link_section_index()); + + quote!(#[link_section = #section]) +} + +/// Regroups the inputs of a task +/// +/// `inputs` could be &[`input: Foo`] OR &[`mut x: i32`, `ref y: i64`] +pub fn regroup_inputs( + inputs: &[PatType], +) -> ( + // args e.g. &[`_0`], &[`_0: i32`, `_1: i64`] + Vec, + // tupled e.g. `_0`, `(_0, _1)` + TokenStream2, + // untupled e.g. &[`_0`], &[`_0`, `_1`] + Vec, + // ty e.g. `Foo`, `(i32, i64)` + TokenStream2, +) { + if inputs.len() == 1 { + let ty = &inputs[0].ty; + + ( + vec![quote!(_0: #ty)], + quote!(_0), + vec![quote!(_0)], + quote!(#ty), + ) + } else { + let mut args = vec![]; + let mut pats = vec![]; + let mut tys = vec![]; + + for (i, input) in inputs.iter().enumerate() { + let i = Ident::new(&format!("_{}", i), Span::call_site()); + let ty = &input.ty; + + args.push(quote!(#i: #ty)); + + pats.push(quote!(#i)); + + tys.push(quote!(#ty)); + } + + let tupled = { + let pats = pats.clone(); + quote!((#(#pats,)*)) + }; + let ty = quote!((#(#tys,)*)); + (args, tupled, pats, ty) + } +} + +/// Get the ident for the name of the task +pub fn get_task_name(ctxt: Context, app: &App) -> Ident { + let s = match ctxt { + Context::Init => app.init.name.to_string(), + Context::Idle => app + .idle + .as_ref() + .expect("RTIC-ICE: unable to find idle name") + .name + .to_string(), + Context::HardwareTask(ident) | Context::SoftwareTask(ident) => ident.to_string(), + }; + + Ident::new(&s, Span::call_site()) +} + +/// Generates a pre-reexport identifier for the "shared resources" struct +pub fn shared_resources_ident(ctxt: Context, app: &App) -> Ident { + let mut s = match ctxt { + Context::Init => app.init.name.to_string(), + Context::Idle => app + .idle + .as_ref() + .expect("RTIC-ICE: unable to find idle name") + .name + .to_string(), + Context::HardwareTask(ident) | Context::SoftwareTask(ident) => ident.to_string(), + }; + + s.push_str("SharedResources"); + + mark_internal_name(&s) +} + +/// Generates a pre-reexport identifier for the "local resources" struct +pub fn local_resources_ident(ctxt: Context, app: &App) -> Ident { + let mut s = match ctxt { + Context::Init => app.init.name.to_string(), + Context::Idle => app + .idle + .as_ref() + .expect("RTIC-ICE: unable to find idle name") + .name + .to_string(), + Context::HardwareTask(ident) | Context::SoftwareTask(ident) => ident.to_string(), + }; + + s.push_str("LocalResources"); + + mark_internal_name(&s) +} + +/// Suffixed identifier +pub fn suffixed(name: &str) -> Ident { + let span = Span::call_site(); + Ident::new(name, span) +} + +pub fn static_shared_resource_ident(name: &Ident) -> Ident { + mark_internal_name(&format!("shared_resource_{name}")) +} + +/// Generates an Ident for the number of 32 bit chunks used for Mask storage. +pub fn priority_mask_chunks_ident() -> Ident { + mark_internal_name("MASK_CHUNKS") +} + +pub fn priority_masks_ident() -> Ident { + mark_internal_name("MASKS") +} + +pub fn static_local_resource_ident(name: &Ident) -> Ident { + mark_internal_name(&format!("local_resource_{name}")) +} + +pub fn declared_static_local_resource_ident(name: &Ident, task_name: &Ident) -> Ident { + mark_internal_name(&format!("local_{task_name}_{name}")) +} + +pub fn need_to_lock_ident(name: &Ident) -> Ident { + Ident::new(&format!("{name}_that_needs_to_be_locked"), name.span()) +} + +pub fn zero_prio_dispatcher_ident() -> Ident { + Ident::new("__rtic_internal_async_0_prio_dispatcher", Span::call_site()) +} + +/// The name to get better RT flag errors +pub fn rt_err_ident() -> Ident { + Ident::new( + "you_must_enable_the_rt_feature_for_the_pac_in_your_cargo_toml", + Span::call_site(), + ) +} diff --git a/rtic/macros/src/lib.rs b/rtic/macros/src/lib.rs new file mode 100644 index 0000000..a8422d0 --- /dev/null +++ b/rtic/macros/src/lib.rs @@ -0,0 +1,92 @@ +#![doc( + html_logo_url = "https://raw.githubusercontent.com/rtic-rs/rtic/master/book/en/src/RTIC.svg", + html_favicon_url = "https://raw.githubusercontent.com/rtic-rs/rtic/master/book/en/src/RTIC.svg" +)] + +//deny_warnings_placeholder_for_ci + +use proc_macro::TokenStream; +use std::{env, fs, path::Path}; + +mod analyze; +mod bindings; +mod check; +mod codegen; +mod syntax; + +// Used for mocking the API in testing +#[doc(hidden)] +#[proc_macro_attribute] +pub fn mock_app(args: TokenStream, input: TokenStream) -> TokenStream { + if let Err(e) = syntax::parse(args, input) { + e.to_compile_error().into() + } else { + "fn main() {}".parse().unwrap() + } +} + +/// Attribute used to declare a RTIC application +/// +/// For user documentation see the [RTIC book](https://rtic.rs) +/// +/// # Panics +/// +/// Should never panic, cargo feeds a path which is later converted to a string +#[proc_macro_attribute] +pub fn app(args: TokenStream, input: TokenStream) -> TokenStream { + let (app, analysis) = match syntax::parse(args, input) { + Err(e) => return e.to_compile_error().into(), + Ok(x) => x, + }; + + if let Err(e) = check::app(&app) { + return e.to_compile_error().into(); + } + + let analysis = analyze::app(analysis, &app); + + let ts = codegen::app(&app, &analysis); + + // Default output path: /target/ + let mut out_dir = Path::new("target"); + + // Get output directory from Cargo environment + // TODO don't want to break builds if OUT_DIR is not set, is this ever the case? + let out_str = env::var("OUT_DIR").unwrap_or_else(|_| "".to_string()); + + if !out_dir.exists() { + // Set out_dir to OUT_DIR + out_dir = Path::new(&out_str); + + // Default build path, annotated below: + // $(pwd)/target/thumbv7em-none-eabihf/debug/build/cortex-m-rtic-/out/ + // ///debug/build/cortex-m-rtic-/out/ + // + // traverse up to first occurrence of TARGET, approximated with starts_with("thumbv") + // and use the parent() of this path + // + // If no "target" directory is found, / is used + for path in out_dir.ancestors() { + if let Some(dir) = path.components().last() { + let dir = dir.as_os_str().to_str().unwrap(); + + if dir.starts_with("thumbv") || dir.starts_with("riscv") { + if let Some(out) = path.parent() { + out_dir = out; + break; + } + // If no parent, just use it + out_dir = path; + break; + } + } + } + } + + // Try to write the expanded code to disk + if let Some(out_str) = out_dir.to_str() { + fs::write(format!("{out_str}/rtic-expansion.rs"), ts.to_string()).ok(); + } + + ts.into() +} diff --git a/rtic/macros/src/syntax.rs b/rtic/macros/src/syntax.rs new file mode 100644 index 0000000..d6f5a47 --- /dev/null +++ b/rtic/macros/src/syntax.rs @@ -0,0 +1,121 @@ +#[allow(unused_extern_crates)] +extern crate proc_macro; + +use proc_macro::TokenStream; + +use indexmap::{IndexMap, IndexSet}; +use proc_macro2::TokenStream as TokenStream2; +use syn::Ident; + +use crate::syntax::ast::App; + +mod accessors; +pub mod analyze; +pub mod ast; +mod check; +mod parse; + +/// An ordered map keyed by identifier +pub type Map = IndexMap; + +/// An order set +pub type Set = IndexSet; + +/// Execution context +#[derive(Clone, Copy)] +pub enum Context<'a> { + /// The `idle` context + Idle, + + /// The `init`-ialization function + Init, + + /// A async software task + SoftwareTask(&'a Ident), + + /// A hardware task + HardwareTask(&'a Ident), +} + +impl<'a> Context<'a> { + /// The identifier of this context + pub fn ident(&self, app: &'a App) -> &'a Ident { + match self { + Context::HardwareTask(ident) => ident, + Context::Idle => &app.idle.as_ref().unwrap().name, + Context::Init => &app.init.name, + Context::SoftwareTask(ident) => ident, + } + } + + /// Is this the `idle` context? + pub fn is_idle(&self) -> bool { + matches!(self, Context::Idle) + } + + /// Is this the `init`-ialization context? + pub fn is_init(&self) -> bool { + matches!(self, Context::Init) + } + + /// Whether this context runs only once + pub fn runs_once(&self) -> bool { + self.is_init() || self.is_idle() + } + + /// Whether this context has shared resources + pub fn has_shared_resources(&self, app: &App) -> bool { + match *self { + Context::HardwareTask(name) => { + !app.hardware_tasks[name].args.shared_resources.is_empty() + } + Context::Idle => !app.idle.as_ref().unwrap().args.shared_resources.is_empty(), + Context::Init => false, + Context::SoftwareTask(name) => { + !app.software_tasks[name].args.shared_resources.is_empty() + } + } + } + + /// Whether this context has local resources + pub fn has_local_resources(&self, app: &App) -> bool { + match *self { + Context::HardwareTask(name) => { + !app.hardware_tasks[name].args.local_resources.is_empty() + } + Context::Idle => !app.idle.as_ref().unwrap().args.local_resources.is_empty(), + Context::Init => !app.init.args.local_resources.is_empty(), + Context::SoftwareTask(name) => { + !app.software_tasks[name].args.local_resources.is_empty() + } + } + } +} + +/// Parses the input of the `#[app]` attribute +pub fn parse( + args: TokenStream, + input: TokenStream, +) -> Result<(ast::App, analyze::Analysis), syn::parse::Error> { + parse2(args.into(), input.into()) +} + +/// `proc_macro2::TokenStream` version of `parse` +pub fn parse2( + args: TokenStream2, + input: TokenStream2, +) -> Result<(ast::App, analyze::Analysis), syn::parse::Error> { + let app = parse::app(args, input)?; + check::app(&app)?; + + match analyze::app(&app) { + Err(e) => Err(e), + // If no errors, return the app and analysis results + Ok(analysis) => Ok((app, analysis)), + } +} + +enum Either { + Left(A), + Right(B), +} diff --git a/rtic/macros/src/syntax/.github/bors.toml b/rtic/macros/src/syntax/.github/bors.toml new file mode 100644 index 0000000..aee6042 --- /dev/null +++ b/rtic/macros/src/syntax/.github/bors.toml @@ -0,0 +1,3 @@ +block_labels = ["S-blocked"] +delete_merged_branches = true +status = ["ci"] diff --git a/rtic/macros/src/syntax/.github/workflows/build.yml b/rtic/macros/src/syntax/.github/workflows/build.yml new file mode 100644 index 0000000..29971b1 --- /dev/null +++ b/rtic/macros/src/syntax/.github/workflows/build.yml @@ -0,0 +1,213 @@ +name: Build +on: + pull_request: + push: + branches: + - master + - staging + - trying + - bors/staging + - bors/trying + +env: + CARGO_TERM_COLOR: always + +jobs: + # Run cargo fmt --check + style: + name: style + runs-on: ubuntu-20.04 + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + components: rustfmt + + - name: Fail on warnings + run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs + + - name: cargo fmt --check + uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check + + # Compilation check + check: + name: check + runs-on: ubuntu-20.04 + strategy: + matrix: + target: + - x86_64-unknown-linux-gnu + toolchain: + - stable + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Install Rust ${{ matrix.toolchain }} with target (${{ matrix.target }}) + uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.toolchain }} + target: ${{ matrix.target }} + override: true + + - name: Fail on warnings + run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs + + - name: Cache Dependencies + uses: Swatinem/rust-cache@v1 + + - name: cargo check + uses: actions-rs/cargo@v1 + with: + use-cross: false + command: check + args: --target=${{ matrix.target }} + + # Clippy + clippy: + name: Cargo clippy + runs-on: ubuntu-20.04 + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Install Rust stable + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + target: x86_64-unknown-linux-gnu + override: true + + - name: Fail on warnings + run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs + + - name: Cache Dependencies + uses: Swatinem/rust-cache@v1 + + - name: cargo clippy + uses: actions-rs/cargo@v1 + with: + use-cross: false + command: clippy + + # Verify all examples + testexamples: + name: testexamples + runs-on: ubuntu-20.04 + strategy: + matrix: + target: + - x86_64-unknown-linux-gnu + toolchain: + - stable + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Install Rust ${{ matrix.toolchain }} with target (${{ matrix.target }}) + uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.toolchain }} + target: ${{ matrix.target }} + override: true + + - name: Cache Dependencies + uses: Swatinem/rust-cache@v1 + + - name: Fail on warnings + run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs + + - uses: actions-rs/cargo@v1 + with: + use-cross: false + command: test + args: --examples + + # Run test suite for UI + testui: + name: testui + runs-on: ubuntu-20.04 + strategy: + matrix: + target: + - x86_64-unknown-linux-gnu + toolchain: + - stable + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Install Rust ${{ matrix.toolchain }} with target (${{ matrix.target }}) + uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.toolchain }} + target: ${{ matrix.target }} + override: true + + - name: Cache Dependencies + uses: Swatinem/rust-cache@v1 + + - name: Fail on warnings + run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs + + + - uses: actions-rs/cargo@v1 + with: + use-cross: false + command: test + args: --test ui + + # Run test suite + test: + name: test + runs-on: ubuntu-20.04 + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + target: thumbv7m-none-eabi + override: true + + - name: Cache Dependencies + uses: Swatinem/rust-cache@v1 + + - name: Fail on warnings + run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs + + - uses: actions-rs/cargo@v1 + with: + use-cross: false + command: test + args: --lib + + # Refs: https://github.com/rust-lang/crater/blob/9ab6f9697c901c4a44025cf0a39b73ad5b37d198/.github/workflows/bors.yml#L125-L149 + # + # ALL THE PREVIOUS JOBS NEEDS TO BE ADDED TO THE `needs` SECTION OF THIS JOB! + + ci-success: + name: ci + if: github.event_name == 'push' && success() + needs: + - style + - check + - clippy + - testexamples + - test + - testui + runs-on: ubuntu-20.04 + steps: + - name: Mark the job as a success + run: exit 0 diff --git a/rtic/macros/src/syntax/.github/workflows/changelog.yml b/rtic/macros/src/syntax/.github/workflows/changelog.yml new file mode 100644 index 0000000..ccf6eb9 --- /dev/null +++ b/rtic/macros/src/syntax/.github/workflows/changelog.yml @@ -0,0 +1,28 @@ +# Check that the changelog is updated for all changes. +# +# This is only run for PRs. + +on: + pull_request: + # opened, reopened, synchronize are the default types for pull_request. + # labeled, unlabeled ensure this check is also run if a label is added or removed. + types: [opened, reopened, labeled, unlabeled, synchronize] + +name: Changelog + +jobs: + changelog: + name: Changelog + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v2 + + - name: Check that changelog updated + uses: dangoslen/changelog-enforcer@v3 + with: + changeLogPath: CHANGELOG.md + skipLabels: 'needs-changelog, skip-changelog' + missingUpdateErrorMessage: 'Please add a changelog entry in the CHANGELOG.md file.' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/rtic/macros/src/syntax/.github/workflows/properties/build.properties.json b/rtic/macros/src/syntax/.github/workflows/properties/build.properties.json new file mode 100644 index 0000000..fd3eed3 --- /dev/null +++ b/rtic/macros/src/syntax/.github/workflows/properties/build.properties.json @@ -0,0 +1,6 @@ +{ + "name": "Build", + "description": "RTIC Test Suite", + "iconName": "rust", + "categories": ["Rust"] +} diff --git a/rtic/macros/src/syntax/.gitignore b/rtic/macros/src/syntax/.gitignore new file mode 100644 index 0000000..f8d7c8b --- /dev/null +++ b/rtic/macros/src/syntax/.gitignore @@ -0,0 +1,4 @@ +**/*.rs.bk +.#* +/target/ +Cargo.lock diff --git a/rtic/macros/src/syntax/.travis.yml b/rtic/macros/src/syntax/.travis.yml new file mode 100644 index 0000000..52d1ffd --- /dev/null +++ b/rtic/macros/src/syntax/.travis.yml @@ -0,0 +1,31 @@ +language: rust + +matrix: + include: + # MSRV + - env: TARGET=x86_64-unknown-linux-gnu + rust: 1.36.0 + + - env: TARGET=x86_64-unknown-linux-gnu + rust: stable + +before_install: set -e + +script: + - bash ci/script.sh + +after_script: set +e + +cache: cargo + +before_cache: + - chmod -R a+r $HOME/.cargo; + +branches: + only: + - staging + - trying + +notifications: + email: + on_success: never diff --git a/rtic/macros/src/syntax/accessors.rs b/rtic/macros/src/syntax/accessors.rs new file mode 100644 index 0000000..e75dde6 --- /dev/null +++ b/rtic/macros/src/syntax/accessors.rs @@ -0,0 +1,113 @@ +use syn::Ident; + +use crate::syntax::{ + analyze::Priority, + ast::{Access, App, Local, TaskLocal}, +}; + +impl App { + pub(crate) fn shared_resource_accesses( + &self, + ) -> impl Iterator, &Ident, Access)> { + self.idle + .iter() + .flat_map(|idle| { + idle.args + .shared_resources + .iter() + .map(move |(name, access)| (Some(0), name, *access)) + }) + .chain(self.hardware_tasks.values().flat_map(|task| { + task.args + .shared_resources + .iter() + .map(move |(name, access)| (Some(task.args.priority), name, *access)) + })) + .chain(self.software_tasks.values().flat_map(|task| { + task.args + .shared_resources + .iter() + .map(move |(name, access)| (Some(task.args.priority), name, *access)) + })) + } + + fn is_external(task_local: &TaskLocal) -> bool { + matches!(task_local, TaskLocal::External) + } + + pub(crate) fn local_resource_accesses(&self) -> impl Iterator { + self.init + .args + .local_resources + .iter() + .filter(|(_, task_local)| Self::is_external(task_local)) // Only check the resources declared in `#[local]` + .map(move |(name, _)| name) + .chain(self.idle.iter().flat_map(|idle| { + idle.args + .local_resources + .iter() + .filter(|(_, task_local)| Self::is_external(task_local)) // Only check the resources declared in `#[local]` + .map(move |(name, _)| name) + })) + .chain(self.hardware_tasks.values().flat_map(|task| { + task.args + .local_resources + .iter() + .filter(|(_, task_local)| Self::is_external(task_local)) // Only check the resources declared in `#[local]` + .map(move |(name, _)| name) + })) + .chain(self.software_tasks.values().flat_map(|task| { + task.args + .local_resources + .iter() + .filter(|(_, task_local)| Self::is_external(task_local)) // Only check the resources declared in `#[local]` + .map(move |(name, _)| name) + })) + } + + fn get_declared_local(tl: &TaskLocal) -> Option<&Local> { + match tl { + TaskLocal::External => None, + TaskLocal::Declared(l) => Some(l), + } + } + + /// Get all declared local resources, i.e. `local = [NAME: TYPE = EXPR]`. + /// + /// Returns a vector of (task name, resource name, `Local` struct) + pub fn declared_local_resources(&self) -> Vec<(&Ident, &Ident, &Local)> { + self.init + .args + .local_resources + .iter() + .filter_map(move |(name, tl)| { + Self::get_declared_local(tl).map(|l| (&self.init.name, name, l)) + }) + .chain(self.idle.iter().flat_map(|idle| { + idle.args + .local_resources + .iter() + .filter_map(move |(name, tl)| { + Self::get_declared_local(tl) + .map(|l| (&self.idle.as_ref().unwrap().name, name, l)) + }) + })) + .chain(self.hardware_tasks.iter().flat_map(|(task_name, task)| { + task.args + .local_resources + .iter() + .filter_map(move |(name, tl)| { + Self::get_declared_local(tl).map(|l| (task_name, name, l)) + }) + })) + .chain(self.software_tasks.iter().flat_map(|(task_name, task)| { + task.args + .local_resources + .iter() + .filter_map(move |(name, tl)| { + Self::get_declared_local(tl).map(|l| (task_name, name, l)) + }) + })) + .collect() + } +} diff --git a/rtic/macros/src/syntax/analyze.rs b/rtic/macros/src/syntax/analyze.rs new file mode 100644 index 0000000..3ed1487 --- /dev/null +++ b/rtic/macros/src/syntax/analyze.rs @@ -0,0 +1,417 @@ +//! RTIC application analysis + +use core::cmp; +use std::collections::{BTreeMap, BTreeSet, HashMap}; + +use indexmap::{IndexMap, IndexSet}; +use syn::{Ident, Type}; + +use crate::syntax::{ + ast::{App, LocalResources, TaskLocal}, + Set, +}; + +pub(crate) fn app(app: &App) -> Result { + // Collect all tasks into a vector + type TaskName = Ident; + type Priority = u8; + + // The task list is a Tuple (Name, Shared Resources, Local Resources, Priority) + let task_resources_list: Vec<(TaskName, Vec<&Ident>, &LocalResources, Priority)> = + Some(&app.init) + .iter() + .map(|ht| (ht.name.clone(), Vec::new(), &ht.args.local_resources, 0)) + .chain(app.idle.iter().map(|ht| { + ( + ht.name.clone(), + ht.args + .shared_resources + .iter() + .map(|(v, _)| v) + .collect::>(), + &ht.args.local_resources, + 0, + ) + })) + .chain(app.software_tasks.iter().map(|(name, ht)| { + ( + name.clone(), + ht.args + .shared_resources + .iter() + .map(|(v, _)| v) + .collect::>(), + &ht.args.local_resources, + ht.args.priority, + ) + })) + .chain(app.hardware_tasks.iter().map(|(name, ht)| { + ( + name.clone(), + ht.args + .shared_resources + .iter() + .map(|(v, _)| v) + .collect::>(), + &ht.args.local_resources, + ht.args.priority, + ) + })) + .collect(); + + let mut error = vec![]; + let mut lf_res_with_error = vec![]; + let mut lf_hash = HashMap::new(); + + // Collect lock free resources + let lock_free: Vec<&Ident> = app + .shared_resources + .iter() + .filter(|(_, r)| r.properties.lock_free) + .map(|(i, _)| i) + .collect(); + + // Check that lock_free resources are correct + for lf_res in lock_free.iter() { + for (task, tr, _, priority) in task_resources_list.iter() { + for r in tr { + // Get all uses of resources annotated lock_free + if lf_res == r { + // Check so async tasks do not use lock free resources + if app.software_tasks.get(task).is_some() { + error.push(syn::Error::new( + r.span(), + format!( + "Lock free shared resource {:?} is used by an async tasks, which is forbidden", + r.to_string(), + ), + )); + } + + // HashMap returns the previous existing object if old.key == new.key + if let Some(lf_res) = lf_hash.insert(r.to_string(), (task, r, priority)) { + // Check if priority differ, if it does, append to + // list of resources which will be annotated with errors + if priority != lf_res.2 { + lf_res_with_error.push(lf_res.1); + lf_res_with_error.push(r); + } + + // If the resource already violates lock free properties + if lf_res_with_error.contains(&r) { + lf_res_with_error.push(lf_res.1); + lf_res_with_error.push(r); + } + } + } + } + } + } + + // Add error message in the resource struct + for r in lock_free { + if lf_res_with_error.contains(&&r) { + error.push(syn::Error::new( + r.span(), + format!( + "Lock free shared resource {:?} is used by tasks at different priorities", + r.to_string(), + ), + )); + } + } + + // Add error message for each use of the shared resource + for resource in lf_res_with_error.clone() { + error.push(syn::Error::new( + resource.span(), + format!( + "Shared resource {:?} is declared lock free but used by tasks at different priorities", + resource.to_string(), + ), + )); + } + + // Collect local resources + let local: Vec<&Ident> = app.local_resources.iter().map(|(i, _)| i).collect(); + + let mut lr_with_error = vec![]; + let mut lr_hash = HashMap::new(); + + // Check that local resources are not shared + for lr in local { + for (task, _, local_resources, _) in task_resources_list.iter() { + for (name, res) in local_resources.iter() { + // Get all uses of resources annotated lock_free + if lr == name { + match res { + TaskLocal::External => { + // HashMap returns the previous existing object if old.key == new.key + if let Some(lr) = lr_hash.insert(name.to_string(), (task, name)) { + lr_with_error.push(lr.1); + lr_with_error.push(name); + } + } + // If a declared local has the same name as the `#[local]` struct, it's an + // direct error + TaskLocal::Declared(_) => { + lr_with_error.push(lr); + lr_with_error.push(name); + } + } + } + } + } + } + + // Add error message for each use of the local resource + for resource in lr_with_error.clone() { + error.push(syn::Error::new( + resource.span(), + format!( + "Local resource {:?} is used by multiple tasks or collides with multiple definitions", + resource.to_string(), + ), + )); + } + + // Check 0-priority async software tasks and idle dependency + for (name, task) in &app.software_tasks { + if task.args.priority == 0 { + // If there is a 0-priority task, there must be no idle + if app.idle.is_some() { + error.push(syn::Error::new( + name.span(), + format!( + "Async task {:?} has priority 0, but `#[idle]` is defined. 0-priority async tasks are only allowed if there is no `#[idle]`.", + name.to_string(), + ) + )); + } + } + } + + // Collect errors if any and return/halt + if !error.is_empty() { + let mut err = error.get(0).unwrap().clone(); + error.iter().for_each(|e| err.combine(e.clone())); + return Err(err); + } + + // e. Location of resources + let mut used_shared_resource = IndexSet::new(); + let mut ownerships = Ownerships::new(); + let mut sync_types = SyncTypes::new(); + for (prio, name, access) in app.shared_resource_accesses() { + let res = app.shared_resources.get(name).expect("UNREACHABLE"); + + // (e) + // This shared resource is used + used_shared_resource.insert(name.clone()); + + // (c) + if let Some(priority) = prio { + if let Some(ownership) = ownerships.get_mut(name) { + match *ownership { + Ownership::Owned { priority: ceiling } + | Ownership::CoOwned { priority: ceiling } + | Ownership::Contended { ceiling } + if priority != ceiling => + { + *ownership = Ownership::Contended { + ceiling: cmp::max(ceiling, priority), + }; + + if access.is_shared() { + sync_types.insert(res.ty.clone()); + } + } + + Ownership::Owned { priority: ceil } if ceil == priority => { + *ownership = Ownership::CoOwned { priority }; + } + + _ => {} + } + } else { + ownerships.insert(name.clone(), Ownership::Owned { priority }); + } + } + } + + // Create the list of used local resource Idents + let mut used_local_resource = IndexSet::new(); + + for (_, _, locals, _) in task_resources_list { + for (local, _) in locals { + used_local_resource.insert(local.clone()); + } + } + + // Most shared resources need to be `Send`, only 0 prio does not need it + let mut send_types = SendTypes::new(); + + for (name, res) in app.shared_resources.iter() { + if ownerships + .get(name) + .map(|ownership| match *ownership { + Ownership::Owned { priority: ceiling } + | Ownership::CoOwned { priority: ceiling } + | Ownership::Contended { ceiling } => ceiling != 0, + }) + .unwrap_or(false) + { + send_types.insert(res.ty.clone()); + } + } + + // Most local resources need to be `Send` as well, only 0 prio does not need it + for (name, res) in app.local_resources.iter() { + if ownerships + .get(name) + .map(|ownership| match *ownership { + Ownership::Owned { priority: ceiling } + | Ownership::CoOwned { priority: ceiling } + | Ownership::Contended { ceiling } => ceiling != 0, + }) + .unwrap_or(false) + { + send_types.insert(res.ty.clone()); + } + } + + let mut channels = Channels::new(); + + for (name, spawnee) in &app.software_tasks { + let spawnee_prio = spawnee.args.priority; + + let channel = channels.entry(spawnee_prio).or_default(); + channel.tasks.insert(name.clone()); + + // All inputs are send as we do not know from where they may be spawned. + spawnee.inputs.iter().for_each(|input| { + send_types.insert(input.ty.clone()); + }); + } + + // No channel should ever be empty + debug_assert!(channels.values().all(|channel| !channel.tasks.is_empty())); + + Ok(Analysis { + channels, + shared_resources: used_shared_resource, + local_resources: used_local_resource, + ownerships, + send_types, + sync_types, + }) +} + +// /// Priority ceiling +// pub type Ceiling = Option; + +/// Task priority +pub type Priority = u8; + +/// Resource name +pub type Resource = Ident; + +/// Task name +pub type Task = Ident; + +/// The result of analyzing an RTIC application +pub struct Analysis { + /// SPSC message channels + pub channels: Channels, + + /// Shared resources + /// + /// If a resource is not listed here it means that's a "dead" (never + /// accessed) resource and the backend should not generate code for it + pub shared_resources: UsedSharedResource, + + /// Local resources + /// + /// If a resource is not listed here it means that's a "dead" (never + /// accessed) resource and the backend should not generate code for it + pub local_resources: UsedLocalResource, + + /// Resource ownership + pub ownerships: Ownerships, + + /// These types must implement the `Send` trait + pub send_types: SendTypes, + + /// These types must implement the `Sync` trait + pub sync_types: SyncTypes, +} + +/// All channels, keyed by dispatch priority +pub type Channels = BTreeMap; + +/// Location of all *used* shared resources +pub type UsedSharedResource = IndexSet; + +/// Location of all *used* local resources +pub type UsedLocalResource = IndexSet; + +/// Resource ownership +pub type Ownerships = IndexMap; + +/// These types must implement the `Send` trait +pub type SendTypes = Set>; + +/// These types must implement the `Sync` trait +pub type SyncTypes = Set>; + +/// A channel used to send messages +#[derive(Debug, Default)] +pub struct Channel { + /// The channel capacity + pub capacity: u8, + + /// Tasks that can be spawned on this channel + pub tasks: BTreeSet, +} + +/// Resource ownership +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum Ownership { + /// Owned by a single task + Owned { + /// Priority of the task that owns this resource + priority: u8, + }, + + /// "Co-owned" by more than one task; all of them have the same priority + CoOwned { + /// Priority of the tasks that co-own this resource + priority: u8, + }, + + /// Contended by more than one task; the tasks have different priorities + Contended { + /// Priority ceiling + ceiling: u8, + }, +} + +// impl Ownership { +// /// Whether this resource needs to a lock at this priority level +// pub fn needs_lock(&self, priority: u8) -> bool { +// match self { +// Ownership::Owned { .. } | Ownership::CoOwned { .. } => false, +// +// Ownership::Contended { ceiling } => { +// debug_assert!(*ceiling >= priority); +// +// priority < *ceiling +// } +// } +// } +// +// /// Whether this resource is exclusively owned +// pub fn is_owned(&self) -> bool { +// matches!(self, Ownership::Owned { .. }) +// } +// } diff --git a/rtic/macros/src/syntax/ast.rs b/rtic/macros/src/syntax/ast.rs new file mode 100644 index 0000000..27e6773 --- /dev/null +++ b/rtic/macros/src/syntax/ast.rs @@ -0,0 +1,335 @@ +//! Abstract Syntax Tree + +use syn::{Attribute, Expr, Ident, Item, ItemUse, Pat, PatType, Path, Stmt, Type}; + +use crate::syntax::Map; + +/// The `#[app]` attribute +#[derive(Debug)] +#[non_exhaustive] +pub struct App { + /// The arguments to the `#[app]` attribute + pub args: AppArgs, + + /// The name of the `const` item on which the `#[app]` attribute has been placed + pub name: Ident, + + /// The `#[init]` function + pub init: Init, + + /// The `#[idle]` function + pub idle: Option, + + /// Resources shared between tasks defined in `#[shared]` + pub shared_resources: Map, + + /// Task local resources defined in `#[local]` + pub local_resources: Map, + + /// User imports + pub user_imports: Vec, + + /// User code + pub user_code: Vec, + + /// Hardware tasks: `#[task(binds = ..)]`s + pub hardware_tasks: Map, + + /// Async software tasks: `#[task]` + pub software_tasks: Map, +} + +/// Interrupts used to dispatch software tasks +pub type Dispatchers = Map; + +/// Interrupt that could be used to dispatch software tasks +#[derive(Debug, Clone)] +#[non_exhaustive] +pub struct Dispatcher { + /// Attributes that will apply to this interrupt handler + pub attrs: Vec, +} + +/// The arguments of the `#[app]` attribute +#[derive(Debug)] +pub struct AppArgs { + /// Device + pub device: Path, + + /// Peripherals + pub peripherals: bool, + + /// Interrupts used to dispatch software tasks + pub dispatchers: Dispatchers, +} + +/// The `init`-ialization function +#[derive(Debug)] +#[non_exhaustive] +pub struct Init { + /// `init` context metadata + pub args: InitArgs, + + /// Attributes that will apply to this `init` function + pub attrs: Vec, + + /// The name of the `#[init]` function + pub name: Ident, + + /// The context argument + pub context: Box, + + /// The statements that make up this `init` function + pub stmts: Vec, + + /// The name of the user provided shared resources struct + pub user_shared_struct: Ident, + + /// The name of the user provided local resources struct + pub user_local_struct: Ident, +} + +/// `init` context metadata +#[derive(Debug)] +#[non_exhaustive] +pub struct InitArgs { + /// Local resources that can be accessed from this context + pub local_resources: LocalResources, +} + +impl Default for InitArgs { + fn default() -> Self { + Self { + local_resources: LocalResources::new(), + } + } +} + +/// The `idle` context +#[derive(Debug)] +#[non_exhaustive] +pub struct Idle { + /// `idle` context metadata + pub args: IdleArgs, + + /// Attributes that will apply to this `idle` function + pub attrs: Vec, + + /// The name of the `#[idle]` function + pub name: Ident, + + /// The context argument + pub context: Box, + + /// The statements that make up this `idle` function + pub stmts: Vec, +} + +/// `idle` context metadata +#[derive(Debug)] +#[non_exhaustive] +pub struct IdleArgs { + /// Local resources that can be accessed from this context + pub local_resources: LocalResources, + + /// Shared resources that can be accessed from this context + pub shared_resources: SharedResources, +} + +impl Default for IdleArgs { + fn default() -> Self { + Self { + local_resources: LocalResources::new(), + shared_resources: SharedResources::new(), + } + } +} + +/// Shared resource properties +#[derive(Debug)] +pub struct SharedResourceProperties { + /// A lock free (exclusive resource) + pub lock_free: bool, +} + +/// A shared resource, defined in `#[shared]` +#[derive(Debug)] +#[non_exhaustive] +pub struct SharedResource { + /// `#[cfg]` attributes like `#[cfg(debug_assertions)]` + pub cfgs: Vec, + + /// `#[doc]` attributes like `/// this is a docstring` + pub docs: Vec, + + /// Attributes that will apply to this resource + pub attrs: Vec, + + /// The type of this resource + pub ty: Box, + + /// Shared resource properties + pub properties: SharedResourceProperties, +} + +/// A local resource, defined in `#[local]` +#[derive(Debug)] +#[non_exhaustive] +pub struct LocalResource { + /// `#[cfg]` attributes like `#[cfg(debug_assertions)]` + pub cfgs: Vec, + + /// `#[doc]` attributes like `/// this is a docstring` + pub docs: Vec, + + /// Attributes that will apply to this resource + pub attrs: Vec, + + /// The type of this resource + pub ty: Box, +} + +/// An async software task +#[derive(Debug)] +#[non_exhaustive] +pub struct SoftwareTask { + /// Software task metadata + pub args: SoftwareTaskArgs, + + /// `#[cfg]` attributes like `#[cfg(debug_assertions)]` + pub cfgs: Vec, + + /// Attributes that will apply to this interrupt handler + pub attrs: Vec, + + /// The context argument + pub context: Box, + + /// The inputs of this software task + pub inputs: Vec, + + /// The statements that make up the task handler + pub stmts: Vec, + + /// The task is declared externally + pub is_extern: bool, +} + +/// Software task metadata +#[derive(Debug)] +#[non_exhaustive] +pub struct SoftwareTaskArgs { + /// The priority of this task + pub priority: u8, + + /// Local resources that can be accessed from this context + pub local_resources: LocalResources, + + /// Shared resources that can be accessed from this context + pub shared_resources: SharedResources, +} + +impl Default for SoftwareTaskArgs { + fn default() -> Self { + Self { + priority: 1, + local_resources: LocalResources::new(), + shared_resources: SharedResources::new(), + } + } +} + +/// A hardware task +#[derive(Debug)] +#[non_exhaustive] +pub struct HardwareTask { + /// Hardware task metadata + pub args: HardwareTaskArgs, + + /// `#[cfg]` attributes like `#[cfg(debug_assertions)]` + pub cfgs: Vec, + + /// Attributes that will apply to this interrupt handler + pub attrs: Vec, + + /// The context argument + pub context: Box, + + /// The statements that make up the task handler + pub stmts: Vec, + + /// The task is declared externally + pub is_extern: bool, +} + +/// Hardware task metadata +#[derive(Debug)] +#[non_exhaustive] +pub struct HardwareTaskArgs { + /// The interrupt or exception that this task is bound to + pub binds: Ident, + + /// The priority of this task + pub priority: u8, + + /// Local resources that can be accessed from this context + pub local_resources: LocalResources, + + /// Shared resources that can be accessed from this context + pub shared_resources: SharedResources, +} + +/// A `static mut` variable local to and owned by a context +#[derive(Debug)] +#[non_exhaustive] +pub struct Local { + /// Attributes like `#[link_section]` + pub attrs: Vec, + + /// `#[cfg]` attributes like `#[cfg(debug_assertions)]` + pub cfgs: Vec, + + /// Type + pub ty: Box, + + /// Initial value + pub expr: Box, +} + +/// A wrapper of the 2 kinds of locals that tasks can have +#[derive(Debug)] +#[non_exhaustive] +pub enum TaskLocal { + /// The local is declared externally (i.e. `#[local]` struct) + External, + /// The local is declared in the task + Declared(Local), +} + +/// Resource access +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum Access { + /// `[x]`, a mutable resource + Exclusive, + + /// `[&x]`, a static non-mutable resource + Shared, +} + +impl Access { + /// Is this enum in the `Exclusive` variant? + pub fn is_exclusive(&self) -> bool { + *self == Access::Exclusive + } + + /// Is this enum in the `Shared` variant? + pub fn is_shared(&self) -> bool { + *self == Access::Shared + } +} + +/// Shared resource access list in task attribute +pub type SharedResources = Map; + +/// Local resource access/declaration list in task attribute +pub type LocalResources = Map; diff --git a/rtic/macros/src/syntax/check.rs b/rtic/macros/src/syntax/check.rs new file mode 100644 index 0000000..989d418 --- /dev/null +++ b/rtic/macros/src/syntax/check.rs @@ -0,0 +1,66 @@ +use std::collections::HashSet; + +use syn::parse; + +use crate::syntax::ast::App; + +pub fn app(app: &App) -> parse::Result<()> { + // Check that all referenced resources have been declared + // Check that resources are NOT `Exclusive`-ly shared + let mut owners = HashSet::new(); + for (_, name, access) in app.shared_resource_accesses() { + if app.shared_resources.get(name).is_none() { + return Err(parse::Error::new( + name.span(), + "this shared resource has NOT been declared", + )); + } + + if access.is_exclusive() { + owners.insert(name); + } + } + + for name in app.local_resource_accesses() { + if app.local_resources.get(name).is_none() { + return Err(parse::Error::new( + name.span(), + "this local resource has NOT been declared", + )); + } + } + + // Check that no resource has both types of access (`Exclusive` & `Shared`) + let exclusive_accesses = app + .shared_resource_accesses() + .filter_map(|(priority, name, access)| { + if priority.is_some() && access.is_exclusive() { + Some(name) + } else { + None + } + }) + .collect::>(); + for (_, name, access) in app.shared_resource_accesses() { + if access.is_shared() && exclusive_accesses.contains(name) { + return Err(parse::Error::new( + name.span(), + "this implementation doesn't support shared (`&-`) - exclusive (`&mut-`) locks; use `x` instead of `&x`", + )); + } + } + + // check that dispatchers are not used as hardware tasks + for task in app.hardware_tasks.values() { + let binds = &task.args.binds; + + if app.args.dispatchers.contains_key(binds) { + return Err(parse::Error::new( + binds.span(), + "dispatcher interrupts can't be used as hardware tasks", + )); + } + } + + Ok(()) +} diff --git a/rtic/macros/src/syntax/optimize.rs b/rtic/macros/src/syntax/optimize.rs new file mode 100644 index 0000000..e83ba31 --- /dev/null +++ b/rtic/macros/src/syntax/optimize.rs @@ -0,0 +1,36 @@ +use std::collections::{BTreeSet, HashMap}; + +use crate::syntax::ast::App; + +pub fn app(app: &mut App, settings: &Settings) { + // "compress" priorities + // If the user specified, for example, task priorities of "1, 3, 6", + // compress them into "1, 2, 3" as to leave no gaps + if settings.optimize_priorities { + // all task priorities ordered in ascending order + let priorities = app + .hardware_tasks + .values() + .map(|task| Some(task.args.priority)) + .chain( + app.software_tasks + .values() + .map(|task| Some(task.args.priority)), + ) + .collect::>(); + + let map = priorities + .iter() + .cloned() + .zip(1..) + .collect::>(); + + for task in app.hardware_tasks.values_mut() { + task.args.priority = map[&Some(task.args.priority)]; + } + + for task in app.software_tasks.values_mut() { + task.args.priority = map[&Some(task.args.priority)]; + } + } +} diff --git a/rtic/macros/src/syntax/parse.rs b/rtic/macros/src/syntax/parse.rs new file mode 100644 index 0000000..c78453a --- /dev/null +++ b/rtic/macros/src/syntax/parse.rs @@ -0,0 +1,363 @@ +mod app; +mod hardware_task; +mod idle; +mod init; +mod resource; +mod software_task; +mod util; + +use proc_macro2::TokenStream as TokenStream2; +use syn::{ + braced, parenthesized, + parse::{self, Parse, ParseStream, Parser}, + token::Brace, + Ident, Item, LitInt, Token, +}; + +use crate::syntax::{ + ast::{App, AppArgs, HardwareTaskArgs, IdleArgs, InitArgs, SoftwareTaskArgs, TaskLocal}, + Either, +}; + +// Parse the app, both app arguments and body (input) +pub fn app(args: TokenStream2, input: TokenStream2) -> parse::Result { + let args = AppArgs::parse(args)?; + let input: Input = syn::parse2(input)?; + + App::parse(args, input) +} + +pub(crate) struct Input { + _mod_token: Token![mod], + pub ident: Ident, + _brace_token: Brace, + pub items: Vec, +} + +impl Parse for Input { + fn parse(input: ParseStream<'_>) -> parse::Result { + fn parse_items(input: ParseStream<'_>) -> parse::Result> { + let mut items = vec![]; + + while !input.is_empty() { + items.push(input.parse()?); + } + + Ok(items) + } + + let content; + + let _mod_token = input.parse()?; + let ident = input.parse()?; + let _brace_token = braced!(content in input); + let items = content.call(parse_items)?; + + Ok(Input { + _mod_token, + ident, + _brace_token, + items, + }) + } +} + +fn init_args(tokens: TokenStream2) -> parse::Result { + (|input: ParseStream<'_>| -> parse::Result { + if input.is_empty() { + return Ok(InitArgs::default()); + } + + let mut local_resources = None; + + let content; + parenthesized!(content in input); + + if !content.is_empty() { + loop { + // Parse identifier name + let ident: Ident = content.parse()?; + // Handle equal sign + let _: Token![=] = content.parse()?; + + match &*ident.to_string() { + "local" => { + if local_resources.is_some() { + return Err(parse::Error::new( + ident.span(), + "argument appears more than once", + )); + } + + local_resources = Some(util::parse_local_resources(&content)?); + } + _ => { + return Err(parse::Error::new(ident.span(), "unexpected argument")); + } + } + + if content.is_empty() { + break; + } + // Handle comma: , + let _: Token![,] = content.parse()?; + } + } + + if let Some(locals) = &local_resources { + for (ident, task_local) in locals { + if let TaskLocal::External = task_local { + return Err(parse::Error::new( + ident.span(), + "only declared local resources are allowed in init", + )); + } + } + } + + Ok(InitArgs { + local_resources: local_resources.unwrap_or_default(), + }) + }) + .parse2(tokens) +} + +fn idle_args(tokens: TokenStream2) -> parse::Result { + (|input: ParseStream<'_>| -> parse::Result { + if input.is_empty() { + return Ok(IdleArgs::default()); + } + + let mut shared_resources = None; + let mut local_resources = None; + + let content; + parenthesized!(content in input); + if !content.is_empty() { + loop { + // Parse identifier name + let ident: Ident = content.parse()?; + // Handle equal sign + let _: Token![=] = content.parse()?; + + match &*ident.to_string() { + "shared" => { + if shared_resources.is_some() { + return Err(parse::Error::new( + ident.span(), + "argument appears more than once", + )); + } + + shared_resources = Some(util::parse_shared_resources(&content)?); + } + + "local" => { + if local_resources.is_some() { + return Err(parse::Error::new( + ident.span(), + "argument appears more than once", + )); + } + + local_resources = Some(util::parse_local_resources(&content)?); + } + + _ => { + return Err(parse::Error::new(ident.span(), "unexpected argument")); + } + } + if content.is_empty() { + break; + } + + // Handle comma: , + let _: Token![,] = content.parse()?; + } + } + + Ok(IdleArgs { + shared_resources: shared_resources.unwrap_or_default(), + local_resources: local_resources.unwrap_or_default(), + }) + }) + .parse2(tokens) +} + +fn task_args(tokens: TokenStream2) -> parse::Result> { + (|input: ParseStream<'_>| -> parse::Result> { + if input.is_empty() { + return Ok(Either::Right(SoftwareTaskArgs::default())); + } + + let mut binds = None; + let mut capacity = None; + let mut priority = None; + let mut shared_resources = None; + let mut local_resources = None; + let mut prio_span = None; + + let content; + parenthesized!(content in input); + loop { + if content.is_empty() { + break; + } + + // Parse identifier name + let ident: Ident = content.parse()?; + let ident_s = ident.to_string(); + + // Handle equal sign + let _: Token![=] = content.parse()?; + + match &*ident_s { + "binds" => { + if binds.is_some() { + return Err(parse::Error::new( + ident.span(), + "argument appears more than once", + )); + } + + if capacity.is_some() { + return Err(parse::Error::new( + ident.span(), + "hardware tasks can't use the `capacity` argument", + )); + } + + // Parse identifier name + let ident = content.parse()?; + + binds = Some(ident); + } + + "capacity" => { + if capacity.is_some() { + return Err(parse::Error::new( + ident.span(), + "argument appears more than once", + )); + } + + if binds.is_some() { + return Err(parse::Error::new( + ident.span(), + "hardware tasks can't use the `capacity` argument", + )); + } + + // #lit + let lit: LitInt = content.parse()?; + + if !lit.suffix().is_empty() { + return Err(parse::Error::new( + lit.span(), + "this literal must be unsuffixed", + )); + } + + let value = lit.base10_parse::().ok(); + if value.is_none() || value == Some(0) { + return Err(parse::Error::new( + lit.span(), + "this literal must be in the range 1...255", + )); + } + + capacity = Some(value.unwrap()); + } + + "priority" => { + if priority.is_some() { + return Err(parse::Error::new( + ident.span(), + "argument appears more than once", + )); + } + + // #lit + let lit: LitInt = content.parse()?; + + if !lit.suffix().is_empty() { + return Err(parse::Error::new( + lit.span(), + "this literal must be unsuffixed", + )); + } + + let value = lit.base10_parse::().ok(); + if value.is_none() { + return Err(parse::Error::new( + lit.span(), + "this literal must be in the range 0...255", + )); + } + + prio_span = Some(lit.span()); + priority = Some(value.unwrap()); + } + + "shared" => { + if shared_resources.is_some() { + return Err(parse::Error::new( + ident.span(), + "argument appears more than once", + )); + } + + shared_resources = Some(util::parse_shared_resources(&content)?); + } + + "local" => { + if local_resources.is_some() { + return Err(parse::Error::new( + ident.span(), + "argument appears more than once", + )); + } + + local_resources = Some(util::parse_local_resources(&content)?); + } + + _ => { + return Err(parse::Error::new(ident.span(), "unexpected argument")); + } + } + + if content.is_empty() { + break; + } + + // Handle comma: , + let _: Token![,] = content.parse()?; + } + let priority = priority.unwrap_or(1); + let shared_resources = shared_resources.unwrap_or_default(); + let local_resources = local_resources.unwrap_or_default(); + + Ok(if let Some(binds) = binds { + if priority == 0 { + return Err(parse::Error::new( + prio_span.unwrap(), + "hardware tasks are not allowed to be at priority 0", + )); + } + + Either::Left(HardwareTaskArgs { + binds, + priority, + shared_resources, + local_resources, + }) + } else { + Either::Right(SoftwareTaskArgs { + priority, + shared_resources, + local_resources, + }) + }) + }) + .parse2(tokens) +} diff --git a/rtic/macros/src/syntax/parse/app.rs b/rtic/macros/src/syntax/parse/app.rs new file mode 100644 index 0000000..e797f75 --- /dev/null +++ b/rtic/macros/src/syntax/parse/app.rs @@ -0,0 +1,480 @@ +use std::collections::HashSet; + +// use indexmap::map::Entry; +use proc_macro2::TokenStream as TokenStream2; +use syn::{ + parse::{self, ParseStream, Parser}, + spanned::Spanned, + Expr, ExprArray, Fields, ForeignItem, Ident, Item, LitBool, Path, Token, Visibility, +}; + +use super::Input; +use crate::syntax::{ + ast::{ + App, AppArgs, Dispatcher, Dispatchers, HardwareTask, Idle, IdleArgs, Init, InitArgs, + LocalResource, SharedResource, SoftwareTask, + }, + parse::{self as syntax_parse, util}, + Either, Map, Set, +}; + +impl AppArgs { + pub(crate) fn parse(tokens: TokenStream2) -> parse::Result { + (|input: ParseStream<'_>| -> parse::Result { + let mut custom = Set::new(); + let mut device = None; + let mut peripherals = true; + let mut dispatchers = Dispatchers::new(); + + loop { + if input.is_empty() { + break; + } + + // #ident = .. + let ident: Ident = input.parse()?; + let _eq_token: Token![=] = input.parse()?; + + if custom.contains(&ident) { + return Err(parse::Error::new( + ident.span(), + "argument appears more than once", + )); + } + + custom.insert(ident.clone()); + + let ks = ident.to_string(); + + match &*ks { + "device" => { + if let Ok(p) = input.parse::() { + device = Some(p); + } else { + return Err(parse::Error::new( + ident.span(), + "unexpected argument value; this should be a path", + )); + } + } + + "peripherals" => { + if let Ok(p) = input.parse::() { + peripherals = p.value; + } else { + return Err(parse::Error::new( + ident.span(), + "unexpected argument value; this should be a boolean", + )); + } + } + + "dispatchers" => { + if let Ok(p) = input.parse::() { + for e in p.elems { + match e { + Expr::Path(ep) => { + let path = ep.path; + let ident = if path.leading_colon.is_some() + || path.segments.len() != 1 + { + return Err(parse::Error::new( + path.span(), + "interrupt must be an identifier, not a path", + )); + } else { + path.segments[0].ident.clone() + }; + let span = ident.span(); + if dispatchers.contains_key(&ident) { + return Err(parse::Error::new( + span, + "this extern interrupt is listed more than once", + )); + } else { + dispatchers + .insert(ident, Dispatcher { attrs: ep.attrs }); + } + } + _ => { + return Err(parse::Error::new( + e.span(), + "interrupt must be an identifier", + )); + } + } + } + } else { + return Err(parse::Error::new( + ident.span(), + // increasing the length of the error message will break rustfmt + "unexpected argument value; expected an array", + )); + } + } + _ => { + return Err(parse::Error::new(ident.span(), "unexpected argument")); + } + } + + if input.is_empty() { + break; + } + + // , + let _: Token![,] = input.parse()?; + } + + let device = if let Some(device) = device { + device + } else { + return Err(parse::Error::new(input.span(), "missing `device = ...`")); + }; + + Ok(AppArgs { + device, + peripherals, + dispatchers, + }) + }) + .parse2(tokens) + } +} + +impl App { + pub(crate) fn parse(args: AppArgs, input: Input) -> parse::Result { + let mut init = None; + let mut idle = None; + + let mut shared_resources_ident = None; + let mut shared_resources = Map::new(); + let mut local_resources_ident = None; + let mut local_resources = Map::new(); + let mut hardware_tasks = Map::new(); + let mut software_tasks = Map::new(); + let mut user_imports = vec![]; + let mut user_code = vec![]; + + let mut seen_idents = HashSet::::new(); + let mut bindings = HashSet::::new(); + + let mut check_binding = |ident: &Ident| { + if bindings.contains(ident) { + return Err(parse::Error::new( + ident.span(), + "this interrupt is already bound", + )); + } else { + bindings.insert(ident.clone()); + } + + Ok(()) + }; + + let mut check_ident = |ident: &Ident| { + if seen_idents.contains(ident) { + return Err(parse::Error::new( + ident.span(), + "this identifier has already been used", + )); + } else { + seen_idents.insert(ident.clone()); + } + + Ok(()) + }; + + for mut item in input.items { + match item { + Item::Fn(mut item) => { + let span = item.sig.ident.span(); + if let Some(pos) = item + .attrs + .iter() + .position(|attr| util::attr_eq(attr, "init")) + { + let args = InitArgs::parse(item.attrs.remove(pos).tokens)?; + + // If an init function already exists, error + if init.is_some() { + return Err(parse::Error::new( + span, + "`#[init]` function must appear at most once", + )); + } + + check_ident(&item.sig.ident)?; + + init = Some(Init::parse(args, item)?); + } else if let Some(pos) = item + .attrs + .iter() + .position(|attr| util::attr_eq(attr, "idle")) + { + let args = IdleArgs::parse(item.attrs.remove(pos).tokens)?; + + // If an idle function already exists, error + if idle.is_some() { + return Err(parse::Error::new( + span, + "`#[idle]` function must appear at most once", + )); + } + + check_ident(&item.sig.ident)?; + + idle = Some(Idle::parse(args, item)?); + } else if let Some(pos) = item + .attrs + .iter() + .position(|attr| util::attr_eq(attr, "task")) + { + if hardware_tasks.contains_key(&item.sig.ident) + || software_tasks.contains_key(&item.sig.ident) + { + return Err(parse::Error::new( + span, + "this task is defined multiple times", + )); + } + + match syntax_parse::task_args(item.attrs.remove(pos).tokens)? { + Either::Left(args) => { + check_binding(&args.binds)?; + check_ident(&item.sig.ident)?; + + hardware_tasks.insert( + item.sig.ident.clone(), + HardwareTask::parse(args, item)?, + ); + } + + Either::Right(args) => { + check_ident(&item.sig.ident)?; + + software_tasks.insert( + item.sig.ident.clone(), + SoftwareTask::parse(args, item)?, + ); + } + } + } else { + // Forward normal functions + user_code.push(Item::Fn(item.clone())); + } + } + + Item::Struct(ref mut struct_item) => { + // Match structures with the attribute #[shared], name of structure is not + // important + if let Some(_pos) = struct_item + .attrs + .iter() + .position(|attr| util::attr_eq(attr, "shared")) + { + let span = struct_item.ident.span(); + + shared_resources_ident = Some(struct_item.ident.clone()); + + if !shared_resources.is_empty() { + return Err(parse::Error::new( + span, + "`#[shared]` struct must appear at most once", + )); + } + + if struct_item.vis != Visibility::Inherited { + return Err(parse::Error::new( + struct_item.span(), + "this item must have inherited / private visibility", + )); + } + + if let Fields::Named(fields) = &mut struct_item.fields { + for field in &mut fields.named { + let ident = field.ident.as_ref().expect("UNREACHABLE"); + + if shared_resources.contains_key(ident) { + return Err(parse::Error::new( + ident.span(), + "this resource is listed more than once", + )); + } + + shared_resources.insert( + ident.clone(), + SharedResource::parse(field, ident.span())?, + ); + } + } else { + return Err(parse::Error::new( + struct_item.span(), + "this `struct` must have named fields", + )); + } + } else if let Some(_pos) = struct_item + .attrs + .iter() + .position(|attr| util::attr_eq(attr, "local")) + { + let span = struct_item.ident.span(); + + local_resources_ident = Some(struct_item.ident.clone()); + + if !local_resources.is_empty() { + return Err(parse::Error::new( + span, + "`#[local]` struct must appear at most once", + )); + } + + if struct_item.vis != Visibility::Inherited { + return Err(parse::Error::new( + struct_item.span(), + "this item must have inherited / private visibility", + )); + } + + if let Fields::Named(fields) = &mut struct_item.fields { + for field in &mut fields.named { + let ident = field.ident.as_ref().expect("UNREACHABLE"); + + if local_resources.contains_key(ident) { + return Err(parse::Error::new( + ident.span(), + "this resource is listed more than once", + )); + } + + local_resources.insert( + ident.clone(), + LocalResource::parse(field, ident.span())?, + ); + } + } else { + return Err(parse::Error::new( + struct_item.span(), + "this `struct` must have named fields", + )); + } + } else { + // Structure without the #[resources] attribute should just be passed along + user_code.push(item.clone()); + } + } + + Item::ForeignMod(mod_) => { + if !util::abi_is_rust(&mod_.abi) { + return Err(parse::Error::new( + mod_.abi.extern_token.span(), + "this `extern` block must use the \"Rust\" ABI", + )); + } + + for item in mod_.items { + if let ForeignItem::Fn(mut item) = item { + let span = item.sig.ident.span(); + if let Some(pos) = item + .attrs + .iter() + .position(|attr| util::attr_eq(attr, "task")) + { + if hardware_tasks.contains_key(&item.sig.ident) + || software_tasks.contains_key(&item.sig.ident) + { + return Err(parse::Error::new( + span, + "this task is defined multiple times", + )); + } + + if item.attrs.len() != 1 { + return Err(parse::Error::new( + span, + "`extern` task required `#[task(..)]` attribute", + )); + } + + match syntax_parse::task_args(item.attrs.remove(pos).tokens)? { + Either::Left(args) => { + check_binding(&args.binds)?; + check_ident(&item.sig.ident)?; + + hardware_tasks.insert( + item.sig.ident.clone(), + HardwareTask::parse_foreign(args, item)?, + ); + } + + Either::Right(args) => { + check_ident(&item.sig.ident)?; + + software_tasks.insert( + item.sig.ident.clone(), + SoftwareTask::parse_foreign(args, item)?, + ); + } + } + } else { + return Err(parse::Error::new( + span, + "`extern` task required `#[task(..)]` attribute", + )); + } + } else { + return Err(parse::Error::new( + item.span(), + "this item must live outside the `#[app]` module", + )); + } + } + } + Item::Use(itemuse_) => { + // Store the user provided use-statements + user_imports.push(itemuse_.clone()); + } + _ => { + // Anything else within the module should not make any difference + user_code.push(item.clone()); + } + } + } + + let shared_resources_ident = + shared_resources_ident.expect("No `#[shared]` resource struct defined"); + let local_resources_ident = + local_resources_ident.expect("No `#[local]` resource struct defined"); + let init = init.expect("No `#[init]` function defined"); + + if shared_resources_ident != init.user_shared_struct { + return Err(parse::Error::new( + init.user_shared_struct.span(), + format!( + "This name and the one defined on `#[shared]` are not the same. Should this be `{shared_resources_ident}`?" + ), + )); + } + + if local_resources_ident != init.user_local_struct { + return Err(parse::Error::new( + init.user_local_struct.span(), + format!( + "This name and the one defined on `#[local]` are not the same. Should this be `{local_resources_ident}`?" + ), + )); + } + + Ok(App { + args, + name: input.ident, + init, + idle, + shared_resources, + local_resources, + user_imports, + user_code, + hardware_tasks, + software_tasks, + }) + } +} diff --git a/rtic/macros/src/syntax/parse/hardware_task.rs b/rtic/macros/src/syntax/parse/hardware_task.rs new file mode 100644 index 0000000..7f6dfbe --- /dev/null +++ b/rtic/macros/src/syntax/parse/hardware_task.rs @@ -0,0 +1,76 @@ +use syn::{parse, ForeignItemFn, ItemFn, Stmt}; + +use crate::syntax::parse::util::FilterAttrs; +use crate::syntax::{ + ast::{HardwareTask, HardwareTaskArgs}, + parse::util, +}; + +impl HardwareTask { + pub(crate) fn parse(args: HardwareTaskArgs, item: ItemFn) -> parse::Result { + let span = item.sig.ident.span(); + let valid_signature = util::check_fn_signature(&item, false) + && item.sig.inputs.len() == 1 + && util::type_is_unit(&item.sig.output); + + let name = item.sig.ident.to_string(); + + if valid_signature { + if let Some((context, Ok(rest))) = util::parse_inputs(item.sig.inputs, &name) { + if rest.is_empty() { + let FilterAttrs { cfgs, attrs, .. } = util::filter_attributes(item.attrs); + + return Ok(HardwareTask { + args, + cfgs, + attrs, + context, + stmts: item.block.stmts, + is_extern: false, + }); + } + } + } + + Err(parse::Error::new( + span, + format!("this task handler must have type signature `fn({name}::Context)`"), + )) + } +} + +impl HardwareTask { + pub(crate) fn parse_foreign( + args: HardwareTaskArgs, + item: ForeignItemFn, + ) -> parse::Result { + let span = item.sig.ident.span(); + let valid_signature = util::check_foreign_fn_signature(&item, false) + && item.sig.inputs.len() == 1 + && util::type_is_unit(&item.sig.output); + + let name = item.sig.ident.to_string(); + + if valid_signature { + if let Some((context, Ok(rest))) = util::parse_inputs(item.sig.inputs, &name) { + if rest.is_empty() { + let FilterAttrs { cfgs, attrs, .. } = util::filter_attributes(item.attrs); + + return Ok(HardwareTask { + args, + cfgs, + attrs, + context, + stmts: Vec::::new(), + is_extern: true, + }); + } + } + } + + Err(parse::Error::new( + span, + format!("this task handler must have type signature `fn({name}::Context)`"), + )) + } +} diff --git a/rtic/macros/src/syntax/parse/idle.rs b/rtic/macros/src/syntax/parse/idle.rs new file mode 100644 index 0000000..124c136 --- /dev/null +++ b/rtic/macros/src/syntax/parse/idle.rs @@ -0,0 +1,42 @@ +use proc_macro2::TokenStream as TokenStream2; +use syn::{parse, ItemFn}; + +use crate::syntax::{ + ast::{Idle, IdleArgs}, + parse::util, +}; + +impl IdleArgs { + pub(crate) fn parse(tokens: TokenStream2) -> parse::Result { + crate::syntax::parse::idle_args(tokens) + } +} + +impl Idle { + pub(crate) fn parse(args: IdleArgs, item: ItemFn) -> parse::Result { + let valid_signature = util::check_fn_signature(&item, false) + && item.sig.inputs.len() == 1 + && util::type_is_bottom(&item.sig.output); + + let name = item.sig.ident.to_string(); + + if valid_signature { + if let Some((context, Ok(rest))) = util::parse_inputs(item.sig.inputs, &name) { + if rest.is_empty() { + return Ok(Idle { + args, + attrs: item.attrs, + context, + name: item.sig.ident, + stmts: item.block.stmts, + }); + } + } + } + + Err(parse::Error::new( + item.sig.ident.span(), + format!("this `#[idle]` function must have signature `fn({name}::Context) -> !`"), + )) + } +} diff --git a/rtic/macros/src/syntax/parse/init.rs b/rtic/macros/src/syntax/parse/init.rs new file mode 100644 index 0000000..0aea20b --- /dev/null +++ b/rtic/macros/src/syntax/parse/init.rs @@ -0,0 +1,51 @@ +use proc_macro2::TokenStream as TokenStream2; + +use syn::{parse, ItemFn}; + +use crate::syntax::{ + ast::{Init, InitArgs}, + parse::{self as syntax_parse, util}, +}; + +impl InitArgs { + pub(crate) fn parse(tokens: TokenStream2) -> parse::Result { + syntax_parse::init_args(tokens) + } +} + +impl Init { + pub(crate) fn parse(args: InitArgs, item: ItemFn) -> parse::Result { + let valid_signature = util::check_fn_signature(&item, false) && item.sig.inputs.len() == 1; + + let span = item.sig.ident.span(); + + let name = item.sig.ident.to_string(); + + if valid_signature { + if let Ok((user_shared_struct, user_local_struct)) = + util::type_is_init_return(&item.sig.output) + { + if let Some((context, Ok(rest))) = util::parse_inputs(item.sig.inputs, &name) { + if rest.is_empty() { + return Ok(Init { + args, + attrs: item.attrs, + context, + name: item.sig.ident, + stmts: item.block.stmts, + user_shared_struct, + user_local_struct, + }); + } + } + } + } + + Err(parse::Error::new( + span, + format!( + "the `#[init]` function must have signature `fn({name}::Context) -> (Shared resources struct, Local resources struct)`" + ), + )) + } +} diff --git a/rtic/macros/src/syntax/parse/resource.rs b/rtic/macros/src/syntax/parse/resource.rs new file mode 100644 index 0000000..ff10057 --- /dev/null +++ b/rtic/macros/src/syntax/parse/resource.rs @@ -0,0 +1,55 @@ +use proc_macro2::Span; +use syn::{parse, Field, Visibility}; + +use crate::syntax::parse::util::FilterAttrs; +use crate::syntax::{ + ast::{LocalResource, SharedResource, SharedResourceProperties}, + parse::util, +}; + +impl SharedResource { + pub(crate) fn parse(item: &Field, span: Span) -> parse::Result { + if item.vis != Visibility::Inherited { + return Err(parse::Error::new( + span, + "this field must have inherited / private visibility", + )); + } + + let FilterAttrs { + cfgs, + mut attrs, + docs, + } = util::filter_attributes(item.attrs.clone()); + + let lock_free = util::extract_lock_free(&mut attrs)?; + + Ok(SharedResource { + cfgs, + attrs, + docs, + ty: Box::new(item.ty.clone()), + properties: SharedResourceProperties { lock_free }, + }) + } +} + +impl LocalResource { + pub(crate) fn parse(item: &Field, span: Span) -> parse::Result { + if item.vis != Visibility::Inherited { + return Err(parse::Error::new( + span, + "this field must have inherited / private visibility", + )); + } + + let FilterAttrs { cfgs, attrs, docs } = util::filter_attributes(item.attrs.clone()); + + Ok(LocalResource { + cfgs, + attrs, + docs, + ty: Box::new(item.ty.clone()), + }) + } +} diff --git a/rtic/macros/src/syntax/parse/software_task.rs b/rtic/macros/src/syntax/parse/software_task.rs new file mode 100644 index 0000000..769aa65 --- /dev/null +++ b/rtic/macros/src/syntax/parse/software_task.rs @@ -0,0 +1,76 @@ +use syn::{parse, ForeignItemFn, ItemFn, Stmt}; + +use crate::syntax::parse::util::FilterAttrs; +use crate::syntax::{ + ast::{SoftwareTask, SoftwareTaskArgs}, + parse::util, +}; + +impl SoftwareTask { + pub(crate) fn parse(args: SoftwareTaskArgs, item: ItemFn) -> parse::Result { + let valid_signature = util::check_fn_signature(&item, true) + && util::type_is_unit(&item.sig.output) + && item.sig.asyncness.is_some(); + + let span = item.sig.ident.span(); + + let name = item.sig.ident.to_string(); + + if valid_signature { + if let Some((context, Ok(inputs))) = util::parse_inputs(item.sig.inputs, &name) { + let FilterAttrs { cfgs, attrs, .. } = util::filter_attributes(item.attrs); + + return Ok(SoftwareTask { + args, + attrs, + cfgs, + context, + inputs, + stmts: item.block.stmts, + is_extern: false, + }); + } + } + + Err(parse::Error::new( + span, + format!("this task handler must have type signature `async fn({name}::Context, ..)`"), + )) + } +} + +impl SoftwareTask { + pub(crate) fn parse_foreign( + args: SoftwareTaskArgs, + item: ForeignItemFn, + ) -> parse::Result { + let valid_signature = util::check_foreign_fn_signature(&item, true) + && util::type_is_unit(&item.sig.output) + && item.sig.asyncness.is_some(); + + let span = item.sig.ident.span(); + + let name = item.sig.ident.to_string(); + + if valid_signature { + if let Some((context, Ok(inputs))) = util::parse_inputs(item.sig.inputs, &name) { + let FilterAttrs { cfgs, attrs, .. } = util::filter_attributes(item.attrs); + + return Ok(SoftwareTask { + args, + attrs, + cfgs, + context, + inputs, + stmts: Vec::::new(), + is_extern: true, + }); + } + } + + Err(parse::Error::new( + span, + format!("this task handler must have type signature `async fn({name}::Context, ..)`"), + )) + } +} diff --git a/rtic/macros/src/syntax/parse/util.rs b/rtic/macros/src/syntax/parse/util.rs new file mode 100644 index 0000000..5a5e0c0 --- /dev/null +++ b/rtic/macros/src/syntax/parse/util.rs @@ -0,0 +1,338 @@ +use syn::{ + bracketed, + parse::{self, ParseStream}, + punctuated::Punctuated, + spanned::Spanned, + Abi, AttrStyle, Attribute, Expr, FnArg, ForeignItemFn, Ident, ItemFn, Pat, PatType, Path, + PathArguments, ReturnType, Token, Type, Visibility, +}; + +use crate::syntax::{ + ast::{Access, Local, LocalResources, SharedResources, TaskLocal}, + Map, +}; + +pub fn abi_is_rust(abi: &Abi) -> bool { + match &abi.name { + None => true, + Some(s) => s.value() == "Rust", + } +} + +pub fn attr_eq(attr: &Attribute, name: &str) -> bool { + attr.style == AttrStyle::Outer && attr.path.segments.len() == 1 && { + let segment = attr.path.segments.first().unwrap(); + segment.arguments == PathArguments::None && *segment.ident.to_string() == *name + } +} + +/// checks that a function signature +/// +/// - has no bounds (like where clauses) +/// - is not `async` +/// - is not `const` +/// - is not `unsafe` +/// - is not generic (has no type parameters) +/// - is not variadic +/// - uses the Rust ABI (and not e.g. "C") +pub fn check_fn_signature(item: &ItemFn, allow_async: bool) -> bool { + item.vis == Visibility::Inherited + && item.sig.constness.is_none() + && (item.sig.asyncness.is_none() || allow_async) + && item.sig.abi.is_none() + && item.sig.unsafety.is_none() + && item.sig.generics.params.is_empty() + && item.sig.generics.where_clause.is_none() + && item.sig.variadic.is_none() +} + +#[allow(dead_code)] +pub fn check_foreign_fn_signature(item: &ForeignItemFn, allow_async: bool) -> bool { + item.vis == Visibility::Inherited + && item.sig.constness.is_none() + && (item.sig.asyncness.is_none() || allow_async) + && item.sig.abi.is_none() + && item.sig.unsafety.is_none() + && item.sig.generics.params.is_empty() + && item.sig.generics.where_clause.is_none() + && item.sig.variadic.is_none() +} + +pub struct FilterAttrs { + pub cfgs: Vec, + pub docs: Vec, + pub attrs: Vec, +} + +pub fn filter_attributes(input_attrs: Vec) -> FilterAttrs { + let mut cfgs = vec![]; + let mut docs = vec![]; + let mut attrs = vec![]; + + for attr in input_attrs { + if attr_eq(&attr, "cfg") { + cfgs.push(attr); + } else if attr_eq(&attr, "doc") { + docs.push(attr); + } else { + attrs.push(attr); + } + } + + FilterAttrs { cfgs, docs, attrs } +} + +pub fn extract_lock_free(attrs: &mut Vec) -> parse::Result { + if let Some(pos) = attrs.iter().position(|attr| attr_eq(attr, "lock_free")) { + attrs.remove(pos); + Ok(true) + } else { + Ok(false) + } +} + +pub fn parse_shared_resources(content: ParseStream<'_>) -> parse::Result { + let inner; + bracketed!(inner in content); + + let mut resources = Map::new(); + for e in inner.call(Punctuated::::parse_terminated)? { + let err = Err(parse::Error::new( + e.span(), + "identifier appears more than once in list", + )); + let (access, path) = match e { + Expr::Path(e) => (Access::Exclusive, e.path), + + Expr::Reference(ref r) if r.mutability.is_none() => match &*r.expr { + Expr::Path(e) => (Access::Shared, e.path.clone()), + + _ => return err, + }, + + _ => return err, + }; + + let ident = extract_resource_name_ident(path)?; + + if resources.contains_key(&ident) { + return Err(parse::Error::new( + ident.span(), + "resource appears more than once in list", + )); + } + + resources.insert(ident, access); + } + + Ok(resources) +} + +fn extract_resource_name_ident(path: Path) -> parse::Result { + if path.leading_colon.is_some() + || path.segments.len() != 1 + || path.segments[0].arguments != PathArguments::None + { + Err(parse::Error::new( + path.span(), + "resource must be an identifier, not a path", + )) + } else { + Ok(path.segments[0].ident.clone()) + } +} + +pub fn parse_local_resources(content: ParseStream<'_>) -> parse::Result { + let inner; + bracketed!(inner in content); + + let mut resources = Map::new(); + + for e in inner.call(Punctuated::::parse_terminated)? { + let err = Err(parse::Error::new( + e.span(), + "identifier appears more than once in list", + )); + + let (name, local) = match e { + // local = [IDENT], + Expr::Path(path) => { + if !path.attrs.is_empty() { + return Err(parse::Error::new( + path.span(), + "attributes are not supported here", + )); + } + + let ident = extract_resource_name_ident(path.path)?; + // let (cfgs, attrs) = extract_cfgs(path.attrs); + + (ident, TaskLocal::External) + } + + // local = [IDENT: TYPE = EXPR] + Expr::Assign(e) => { + let (name, ty, cfgs, attrs) = match *e.left { + Expr::Type(t) => { + // Extract name and attributes + let (name, cfgs, attrs) = match *t.expr { + Expr::Path(path) => { + let name = extract_resource_name_ident(path.path)?; + let FilterAttrs { cfgs, attrs, .. } = filter_attributes(path.attrs); + + (name, cfgs, attrs) + } + _ => return err, + }; + + let ty = t.ty; + + // Error check + match &*ty { + Type::Array(_) => {} + Type::Path(_) => {} + Type::Ptr(_) => {} + Type::Tuple(_) => {} + _ => return Err(parse::Error::new( + ty.span(), + "unsupported type, must be an array, tuple, pointer or type path", + )), + }; + + (name, ty, cfgs, attrs) + } + e => return Err(parse::Error::new(e.span(), "malformed, expected a type")), + }; + + let expr = e.right; // Expr + + ( + name, + TaskLocal::Declared(Local { + attrs, + cfgs, + ty, + expr, + }), + ) + } + + expr => { + return Err(parse::Error::new( + expr.span(), + "malformed, expected 'IDENT: TYPE = EXPR'", + )) + } + }; + + resources.insert(name, local); + } + + Ok(resources) +} + +type ParseInputResult = Option<(Box, Result, FnArg>)>; + +pub fn parse_inputs(inputs: Punctuated, name: &str) -> ParseInputResult { + let mut inputs = inputs.into_iter(); + + match inputs.next() { + Some(FnArg::Typed(first)) => { + if type_is_path(&first.ty, &[name, "Context"]) { + let rest = inputs + .map(|arg| match arg { + FnArg::Typed(arg) => Ok(arg), + _ => Err(arg), + }) + .collect::, _>>(); + + Some((first.pat, rest)) + } else { + None + } + } + + _ => None, + } +} + +pub fn type_is_bottom(ty: &ReturnType) -> bool { + if let ReturnType::Type(_, ty) = ty { + matches!(**ty, Type::Never(_)) + } else { + false + } +} + +fn extract_init_resource_name_ident(ty: Type) -> Result { + match ty { + Type::Path(path) => { + let path = path.path; + + if path.leading_colon.is_some() + || path.segments.len() != 1 + || path.segments[0].arguments != PathArguments::None + { + Err(()) + } else { + Ok(path.segments[0].ident.clone()) + } + } + _ => Err(()), + } +} + +/// Checks Init's return type, return the user provided types for analysis +pub fn type_is_init_return(ty: &ReturnType) -> Result<(Ident, Ident), ()> { + match ty { + ReturnType::Default => Err(()), + + ReturnType::Type(_, ty) => match &**ty { + Type::Tuple(t) => { + // return should be: + // fn -> (User's #[shared] struct, User's #[local] struct) + // + // We check the length and the last one here, analysis checks that the user + // provided structs are correct. + if t.elems.len() == 2 { + return Ok(( + extract_init_resource_name_ident(t.elems[0].clone())?, + extract_init_resource_name_ident(t.elems[1].clone())?, + )); + } + + Err(()) + } + + _ => Err(()), + }, + } +} + +pub fn type_is_path(ty: &Type, segments: &[&str]) -> bool { + match ty { + Type::Path(tpath) if tpath.qself.is_none() => { + tpath.path.segments.len() == segments.len() + && tpath + .path + .segments + .iter() + .zip(segments) + .all(|(lhs, rhs)| lhs.ident == **rhs) + } + + _ => false, + } +} + +pub fn type_is_unit(ty: &ReturnType) -> bool { + if let ReturnType::Type(_, ty) = ty { + if let Type::Tuple(ref tuple) = **ty { + tuple.elems.is_empty() + } else { + false + } + } else { + true + } +} diff --git a/rtic/macros/tests/ui.rs b/rtic/macros/tests/ui.rs new file mode 100644 index 0000000..9fb88a1 --- /dev/null +++ b/rtic/macros/tests/ui.rs @@ -0,0 +1,7 @@ +use trybuild::TestCases; + +#[test] +fn ui() { + let t = TestCases::new(); + t.compile_fail("ui/*.rs"); +} diff --git a/rtic/macros/ui/extern-interrupt-used.rs b/rtic/macros/ui/extern-interrupt-used.rs new file mode 100644 index 0000000..6346a7d --- /dev/null +++ b/rtic/macros/ui/extern-interrupt-used.rs @@ -0,0 +1,16 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock, dispatchers = [EXTI0])] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) {} + + #[task(binds = EXTI0)] + fn foo(_: foo::Context) {} +} diff --git a/rtic/macros/ui/extern-interrupt-used.stderr b/rtic/macros/ui/extern-interrupt-used.stderr new file mode 100644 index 0000000..970d39b --- /dev/null +++ b/rtic/macros/ui/extern-interrupt-used.stderr @@ -0,0 +1,5 @@ +error: dispatcher interrupts can't be used as hardware tasks + --> ui/extern-interrupt-used.rs:14:20 + | +14 | #[task(binds = EXTI0)] + | ^^^^^ diff --git a/rtic/macros/ui/idle-double-local.rs b/rtic/macros/ui/idle-double-local.rs new file mode 100644 index 0000000..54e67d3 --- /dev/null +++ b/rtic/macros/ui/idle-double-local.rs @@ -0,0 +1,9 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[idle(local = [A], local = [B])] + fn idle(_: idle::Context) -> ! { + loop {} + } +} diff --git a/rtic/macros/ui/idle-double-local.stderr b/rtic/macros/ui/idle-double-local.stderr new file mode 100644 index 0000000..b558136 --- /dev/null +++ b/rtic/macros/ui/idle-double-local.stderr @@ -0,0 +1,5 @@ +error: argument appears more than once + --> ui/idle-double-local.rs:5:25 + | +5 | #[idle(local = [A], local = [B])] + | ^^^^^ diff --git a/rtic/macros/ui/idle-double-shared.rs b/rtic/macros/ui/idle-double-shared.rs new file mode 100644 index 0000000..f66cb93 --- /dev/null +++ b/rtic/macros/ui/idle-double-shared.rs @@ -0,0 +1,9 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[idle(shared = [A], shared = [B])] + fn idle(_: idle::Context) -> ! { + loop {} + } +} diff --git a/rtic/macros/ui/idle-double-shared.stderr b/rtic/macros/ui/idle-double-shared.stderr new file mode 100644 index 0000000..6f62ad2 --- /dev/null +++ b/rtic/macros/ui/idle-double-shared.stderr @@ -0,0 +1,5 @@ +error: argument appears more than once + --> ui/idle-double-shared.rs:5:26 + | +5 | #[idle(shared = [A], shared = [B])] + | ^^^^^^ diff --git a/rtic/macros/ui/idle-input.rs b/rtic/macros/ui/idle-input.rs new file mode 100644 index 0000000..c896b1c --- /dev/null +++ b/rtic/macros/ui/idle-input.rs @@ -0,0 +1,9 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[idle] + fn idle(_: idle::Context, _undef: u32) -> ! { + loop {} + } +} diff --git a/rtic/macros/ui/idle-input.stderr b/rtic/macros/ui/idle-input.stderr new file mode 100644 index 0000000..34c38fc --- /dev/null +++ b/rtic/macros/ui/idle-input.stderr @@ -0,0 +1,5 @@ +error: this `#[idle]` function must have signature `fn(idle::Context) -> !` + --> ui/idle-input.rs:6:8 + | +6 | fn idle(_: idle::Context, _undef: u32) -> ! { + | ^^^^ diff --git a/rtic/macros/ui/idle-no-context.rs b/rtic/macros/ui/idle-no-context.rs new file mode 100644 index 0000000..bab4680 --- /dev/null +++ b/rtic/macros/ui/idle-no-context.rs @@ -0,0 +1,9 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[idle] + fn idle() -> ! { + loop {} + } +} diff --git a/rtic/macros/ui/idle-no-context.stderr b/rtic/macros/ui/idle-no-context.stderr new file mode 100644 index 0000000..c9f4b3d --- /dev/null +++ b/rtic/macros/ui/idle-no-context.stderr @@ -0,0 +1,5 @@ +error: this `#[idle]` function must have signature `fn(idle::Context) -> !` + --> ui/idle-no-context.rs:6:8 + | +6 | fn idle() -> ! { + | ^^^^ diff --git a/rtic/macros/ui/idle-not-divergent.rs b/rtic/macros/ui/idle-not-divergent.rs new file mode 100644 index 0000000..d1ae8b1 --- /dev/null +++ b/rtic/macros/ui/idle-not-divergent.rs @@ -0,0 +1,7 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[idle] + fn idle(_: idle::Context) {} +} diff --git a/rtic/macros/ui/idle-not-divergent.stderr b/rtic/macros/ui/idle-not-divergent.stderr new file mode 100644 index 0000000..e318f58 --- /dev/null +++ b/rtic/macros/ui/idle-not-divergent.stderr @@ -0,0 +1,5 @@ +error: this `#[idle]` function must have signature `fn(idle::Context) -> !` + --> ui/idle-not-divergent.rs:6:8 + | +6 | fn idle(_: idle::Context) {} + | ^^^^ diff --git a/rtic/macros/ui/idle-output.rs b/rtic/macros/ui/idle-output.rs new file mode 100644 index 0000000..1662157 --- /dev/null +++ b/rtic/macros/ui/idle-output.rs @@ -0,0 +1,9 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[idle] + fn idle(_: idle::Context) -> u32 { + 0 + } +} diff --git a/rtic/macros/ui/idle-output.stderr b/rtic/macros/ui/idle-output.stderr new file mode 100644 index 0000000..7070e25 --- /dev/null +++ b/rtic/macros/ui/idle-output.stderr @@ -0,0 +1,5 @@ +error: this `#[idle]` function must have signature `fn(idle::Context) -> !` + --> ui/idle-output.rs:6:8 + | +6 | fn idle(_: idle::Context) -> u32 { + | ^^^^ diff --git a/rtic/macros/ui/idle-pub.rs b/rtic/macros/ui/idle-pub.rs new file mode 100644 index 0000000..0d8dd01 --- /dev/null +++ b/rtic/macros/ui/idle-pub.rs @@ -0,0 +1,9 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[idle] + pub fn idle(_: idle::Context) -> ! { + loop {} + } +} diff --git a/rtic/macros/ui/idle-pub.stderr b/rtic/macros/ui/idle-pub.stderr new file mode 100644 index 0000000..aa46ac3 --- /dev/null +++ b/rtic/macros/ui/idle-pub.stderr @@ -0,0 +1,5 @@ +error: this `#[idle]` function must have signature `fn(idle::Context) -> !` + --> ui/idle-pub.rs:6:12 + | +6 | pub fn idle(_: idle::Context) -> ! { + | ^^^^ diff --git a/rtic/macros/ui/idle-unsafe.rs b/rtic/macros/ui/idle-unsafe.rs new file mode 100644 index 0000000..3422ef2 --- /dev/null +++ b/rtic/macros/ui/idle-unsafe.rs @@ -0,0 +1,9 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[idle] + unsafe fn idle(_: idle::Context) -> ! { + loop {} + } +} diff --git a/rtic/macros/ui/idle-unsafe.stderr b/rtic/macros/ui/idle-unsafe.stderr new file mode 100644 index 0000000..a416800 --- /dev/null +++ b/rtic/macros/ui/idle-unsafe.stderr @@ -0,0 +1,5 @@ +error: this `#[idle]` function must have signature `fn(idle::Context) -> !` + --> ui/idle-unsafe.rs:6:15 + | +6 | unsafe fn idle(_: idle::Context) -> ! { + | ^^^^ diff --git a/rtic/macros/ui/init-divergent.rs b/rtic/macros/ui/init-divergent.rs new file mode 100644 index 0000000..5e4e96a --- /dev/null +++ b/rtic/macros/ui/init-divergent.rs @@ -0,0 +1,13 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> ! {} +} diff --git a/rtic/macros/ui/init-divergent.stderr b/rtic/macros/ui/init-divergent.stderr new file mode 100644 index 0000000..9f6acf6 --- /dev/null +++ b/rtic/macros/ui/init-divergent.stderr @@ -0,0 +1,5 @@ +error: the `#[init]` function must have signature `fn(init::Context) -> (Shared resources struct, Local resources struct)` + --> ui/init-divergent.rs:12:8 + | +12 | fn init(_: init::Context) -> ! {} + | ^^^^ diff --git a/rtic/macros/ui/init-double-local.rs b/rtic/macros/ui/init-double-local.rs new file mode 100644 index 0000000..5f6d7ac --- /dev/null +++ b/rtic/macros/ui/init-double-local.rs @@ -0,0 +1,7 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[init(local = [A], local = [B])] + fn init(_: init::Context) {} +} diff --git a/rtic/macros/ui/init-double-local.stderr b/rtic/macros/ui/init-double-local.stderr new file mode 100644 index 0000000..07c3b50 --- /dev/null +++ b/rtic/macros/ui/init-double-local.stderr @@ -0,0 +1,5 @@ +error: argument appears more than once + --> ui/init-double-local.rs:5:25 + | +5 | #[init(local = [A], local = [B])] + | ^^^^^ diff --git a/rtic/macros/ui/init-double-shared.rs b/rtic/macros/ui/init-double-shared.rs new file mode 100644 index 0000000..4503c87 --- /dev/null +++ b/rtic/macros/ui/init-double-shared.rs @@ -0,0 +1,7 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[init(shared = [A], shared = [B])] + fn init(_: init::Context) {} +} diff --git a/rtic/macros/ui/init-double-shared.stderr b/rtic/macros/ui/init-double-shared.stderr new file mode 100644 index 0000000..af2a97b --- /dev/null +++ b/rtic/macros/ui/init-double-shared.stderr @@ -0,0 +1,5 @@ +error: unexpected argument + --> ui/init-double-shared.rs:5:12 + | +5 | #[init(shared = [A], shared = [B])] + | ^^^^^^ diff --git a/rtic/macros/ui/init-input.rs b/rtic/macros/ui/init-input.rs new file mode 100644 index 0000000..d41a503 --- /dev/null +++ b/rtic/macros/ui/init-input.rs @@ -0,0 +1,13 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context, _undef: u32) -> (Shared, Local) {} +} diff --git a/rtic/macros/ui/init-input.stderr b/rtic/macros/ui/init-input.stderr new file mode 100644 index 0000000..e236043 --- /dev/null +++ b/rtic/macros/ui/init-input.stderr @@ -0,0 +1,5 @@ +error: the `#[init]` function must have signature `fn(init::Context) -> (Shared resources struct, Local resources struct)` + --> ui/init-input.rs:12:8 + | +12 | fn init(_: init::Context, _undef: u32) -> (Shared, Local) {} + | ^^^^ diff --git a/rtic/macros/ui/init-no-context.rs b/rtic/macros/ui/init-no-context.rs new file mode 100644 index 0000000..cdce4c5 --- /dev/null +++ b/rtic/macros/ui/init-no-context.rs @@ -0,0 +1,13 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init() -> (Shared, Local) {} +} diff --git a/rtic/macros/ui/init-no-context.stderr b/rtic/macros/ui/init-no-context.stderr new file mode 100644 index 0000000..28e1fd4 --- /dev/null +++ b/rtic/macros/ui/init-no-context.stderr @@ -0,0 +1,5 @@ +error: the `#[init]` function must have signature `fn(init::Context) -> (Shared resources struct, Local resources struct)` + --> ui/init-no-context.rs:12:8 + | +12 | fn init() -> (Shared, Local) {} + | ^^^^ diff --git a/rtic/macros/ui/init-output.rs b/rtic/macros/ui/init-output.rs new file mode 100644 index 0000000..7057c95 --- /dev/null +++ b/rtic/macros/ui/init-output.rs @@ -0,0 +1,9 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[init] + fn init(_: init::Context) -> u32 { + 0 + } +} diff --git a/rtic/macros/ui/init-output.stderr b/rtic/macros/ui/init-output.stderr new file mode 100644 index 0000000..8bc3c83 --- /dev/null +++ b/rtic/macros/ui/init-output.stderr @@ -0,0 +1,5 @@ +error: the `#[init]` function must have signature `fn(init::Context) -> (Shared resources struct, Local resources struct)` + --> ui/init-output.rs:6:8 + | +6 | fn init(_: init::Context) -> u32 { + | ^^^^ diff --git a/rtic/macros/ui/init-pub.rs b/rtic/macros/ui/init-pub.rs new file mode 100644 index 0000000..dd59aa1 --- /dev/null +++ b/rtic/macros/ui/init-pub.rs @@ -0,0 +1,13 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + pub fn init(_: init::Context) -> (Shared, Local) {} +} diff --git a/rtic/macros/ui/init-pub.stderr b/rtic/macros/ui/init-pub.stderr new file mode 100644 index 0000000..b1610ed --- /dev/null +++ b/rtic/macros/ui/init-pub.stderr @@ -0,0 +1,5 @@ +error: the `#[init]` function must have signature `fn(init::Context) -> (Shared resources struct, Local resources struct)` + --> ui/init-pub.rs:12:12 + | +12 | pub fn init(_: init::Context) -> (Shared, Local) {} + | ^^^^ diff --git a/rtic/macros/ui/init-unsafe.rs b/rtic/macros/ui/init-unsafe.rs new file mode 100644 index 0000000..4f89baf --- /dev/null +++ b/rtic/macros/ui/init-unsafe.rs @@ -0,0 +1,7 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[init] + unsafe fn init(_: init::Context) -> (Shared, Local) {} +} diff --git a/rtic/macros/ui/init-unsafe.stderr b/rtic/macros/ui/init-unsafe.stderr new file mode 100644 index 0000000..fd0b8f3 --- /dev/null +++ b/rtic/macros/ui/init-unsafe.stderr @@ -0,0 +1,5 @@ +error: the `#[init]` function must have signature `fn(init::Context) -> (Shared resources struct, Local resources struct)` + --> ui/init-unsafe.rs:6:15 + | +6 | unsafe fn init(_: init::Context) -> (Shared, Local) {} + | ^^^^ diff --git a/rtic/macros/ui/interrupt-double.rs b/rtic/macros/ui/interrupt-double.rs new file mode 100644 index 0000000..e2addc7 --- /dev/null +++ b/rtic/macros/ui/interrupt-double.rs @@ -0,0 +1,10 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[task(binds = UART0)] + fn foo(_: foo::Context) {} + + #[task(binds = UART0)] + fn bar(_: bar::Context) {} +} diff --git a/rtic/macros/ui/interrupt-double.stderr b/rtic/macros/ui/interrupt-double.stderr new file mode 100644 index 0000000..8db34e2 --- /dev/null +++ b/rtic/macros/ui/interrupt-double.stderr @@ -0,0 +1,5 @@ +error: this interrupt is already bound + --> ui/interrupt-double.rs:8:20 + | +8 | #[task(binds = UART0)] + | ^^^^^ diff --git a/rtic/macros/ui/local-collision-2.rs b/rtic/macros/ui/local-collision-2.rs new file mode 100644 index 0000000..08bc8e5 --- /dev/null +++ b/rtic/macros/ui/local-collision-2.rs @@ -0,0 +1,18 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local { + a: u32, + } + + #[task(local = [a: u8 = 3])] + async fn bar(_: bar::Context) {} + + #[init(local = [a: u16 = 2])] + fn init(_: init::Context) -> (Shared, Local) {} +} diff --git a/rtic/macros/ui/local-collision-2.stderr b/rtic/macros/ui/local-collision-2.stderr new file mode 100644 index 0000000..47dbbe3 --- /dev/null +++ b/rtic/macros/ui/local-collision-2.stderr @@ -0,0 +1,17 @@ +error: Local resource "a" is used by multiple tasks or collides with multiple definitions + --> ui/local-collision-2.rs:10:9 + | +10 | a: u32, + | ^ + +error: Local resource "a" is used by multiple tasks or collides with multiple definitions + --> ui/local-collision-2.rs:16:21 + | +16 | #[init(local = [a: u16 = 2])] + | ^ + +error: Local resource "a" is used by multiple tasks or collides with multiple definitions + --> ui/local-collision-2.rs:13:21 + | +13 | #[task(local = [a: u8 = 3])] + | ^ diff --git a/rtic/macros/ui/local-collision.rs b/rtic/macros/ui/local-collision.rs new file mode 100644 index 0000000..0e4eef7 --- /dev/null +++ b/rtic/macros/ui/local-collision.rs @@ -0,0 +1,21 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local { + a: u32, + } + + #[task(local = [a])] + async fn foo(_: foo::Context) {} + + #[task(local = [a: u8 = 3])] + async fn bar(_: bar::Context) {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) {} +} diff --git a/rtic/macros/ui/local-collision.stderr b/rtic/macros/ui/local-collision.stderr new file mode 100644 index 0000000..47fbb6e --- /dev/null +++ b/rtic/macros/ui/local-collision.stderr @@ -0,0 +1,11 @@ +error: Local resource "a" is used by multiple tasks or collides with multiple definitions + --> ui/local-collision.rs:10:9 + | +10 | a: u32, + | ^ + +error: Local resource "a" is used by multiple tasks or collides with multiple definitions + --> ui/local-collision.rs:16:21 + | +16 | #[task(local = [a: u8 = 3])] + | ^ diff --git a/rtic/macros/ui/local-malformed-1.rs b/rtic/macros/ui/local-malformed-1.rs new file mode 100644 index 0000000..219eef5 --- /dev/null +++ b/rtic/macros/ui/local-malformed-1.rs @@ -0,0 +1,16 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[task(local = [a:])] + async fn foo(_: foo::Context) {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) {} +} diff --git a/rtic/macros/ui/local-malformed-1.stderr b/rtic/macros/ui/local-malformed-1.stderr new file mode 100644 index 0000000..d15c324 --- /dev/null +++ b/rtic/macros/ui/local-malformed-1.stderr @@ -0,0 +1,5 @@ +error: unexpected end of input, expected one of: `for`, parentheses, `fn`, `unsafe`, `extern`, identifier, `::`, `<`, square brackets, `*`, `&`, `!`, `impl`, `_`, lifetime + --> ui/local-malformed-1.rs:11:23 + | +11 | #[task(local = [a:])] + | ^ diff --git a/rtic/macros/ui/local-malformed-2.rs b/rtic/macros/ui/local-malformed-2.rs new file mode 100644 index 0000000..d691453 --- /dev/null +++ b/rtic/macros/ui/local-malformed-2.rs @@ -0,0 +1,16 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[task(local = [a: u32])] + async fn foo(_: foo::Context) {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) {} +} diff --git a/rtic/macros/ui/local-malformed-2.stderr b/rtic/macros/ui/local-malformed-2.stderr new file mode 100644 index 0000000..0b448f0 --- /dev/null +++ b/rtic/macros/ui/local-malformed-2.stderr @@ -0,0 +1,5 @@ +error: malformed, expected 'IDENT: TYPE = EXPR' + --> ui/local-malformed-2.rs:11:21 + | +11 | #[task(local = [a: u32])] + | ^^^^^^ diff --git a/rtic/macros/ui/local-malformed-3.rs b/rtic/macros/ui/local-malformed-3.rs new file mode 100644 index 0000000..7eddfa4 --- /dev/null +++ b/rtic/macros/ui/local-malformed-3.rs @@ -0,0 +1,16 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[task(local = [a: u32 =])] + async fn foo(_: foo::Context) {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) {} +} diff --git a/rtic/macros/ui/local-malformed-3.stderr b/rtic/macros/ui/local-malformed-3.stderr new file mode 100644 index 0000000..61af4f3 --- /dev/null +++ b/rtic/macros/ui/local-malformed-3.stderr @@ -0,0 +1,5 @@ +error: unexpected end of input, expected expression + --> ui/local-malformed-3.rs:11:29 + | +11 | #[task(local = [a: u32 =])] + | ^ diff --git a/rtic/macros/ui/local-malformed-4.rs b/rtic/macros/ui/local-malformed-4.rs new file mode 100644 index 0000000..b913947 --- /dev/null +++ b/rtic/macros/ui/local-malformed-4.rs @@ -0,0 +1,16 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[task(local = [a = u32])] + async fn foo(_: foo::Context) {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) {} +} diff --git a/rtic/macros/ui/local-malformed-4.stderr b/rtic/macros/ui/local-malformed-4.stderr new file mode 100644 index 0000000..0f7d9e7 --- /dev/null +++ b/rtic/macros/ui/local-malformed-4.stderr @@ -0,0 +1,5 @@ +error: malformed, expected a type + --> ui/local-malformed-4.rs:11:21 + | +11 | #[task(local = [a = u32])] + | ^ diff --git a/rtic/macros/ui/local-not-declared.rs b/rtic/macros/ui/local-not-declared.rs new file mode 100644 index 0000000..7c087e4 --- /dev/null +++ b/rtic/macros/ui/local-not-declared.rs @@ -0,0 +1,16 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[task(local = [A])] + async fn foo(_: foo::Context) {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) {} +} diff --git a/rtic/macros/ui/local-not-declared.stderr b/rtic/macros/ui/local-not-declared.stderr new file mode 100644 index 0000000..10d4b04 --- /dev/null +++ b/rtic/macros/ui/local-not-declared.stderr @@ -0,0 +1,5 @@ +error: this local resource has NOT been declared + --> ui/local-not-declared.rs:11:21 + | +11 | #[task(local = [A])] + | ^ diff --git a/rtic/macros/ui/local-pub.rs b/rtic/macros/ui/local-pub.rs new file mode 100644 index 0000000..42da4f4 --- /dev/null +++ b/rtic/macros/ui/local-pub.rs @@ -0,0 +1,15 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local { + pub x: u32, + } + + #[init] + fn init(_: init::Context) -> (Shared, Local) {} +} diff --git a/rtic/macros/ui/local-pub.stderr b/rtic/macros/ui/local-pub.stderr new file mode 100644 index 0000000..e4814ca --- /dev/null +++ b/rtic/macros/ui/local-pub.stderr @@ -0,0 +1,5 @@ +error: this field must have inherited / private visibility + --> ui/local-pub.rs:10:13 + | +10 | pub x: u32, + | ^ diff --git a/rtic/macros/ui/local-shared-attribute.rs b/rtic/macros/ui/local-shared-attribute.rs new file mode 100644 index 0000000..c594b5f --- /dev/null +++ b/rtic/macros/ui/local-shared-attribute.rs @@ -0,0 +1,21 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) {} + + #[task(local = [ + #[test] + a: u32 = 0, // Ok + #[test] + b, // Error + ])] + fn foo(_: foo::Context) {} +} diff --git a/rtic/macros/ui/local-shared-attribute.stderr b/rtic/macros/ui/local-shared-attribute.stderr new file mode 100644 index 0000000..a8130e8 --- /dev/null +++ b/rtic/macros/ui/local-shared-attribute.stderr @@ -0,0 +1,6 @@ +error: attributes are not supported here + --> ui/local-shared-attribute.rs:17:9 + | +17 | / #[test] +18 | | b, // Error + | |_________^ diff --git a/rtic/macros/ui/local-shared.rs b/rtic/macros/ui/local-shared.rs new file mode 100644 index 0000000..4e8f9f4 --- /dev/null +++ b/rtic/macros/ui/local-shared.rs @@ -0,0 +1,28 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local { + l1: u32, + l2: u32, + } + + #[init] + fn init(_: init::Context) -> (Shared, Local) {} + + // l2 ok + #[idle(local = [l2])] + fn idle(cx: idle::Context) -> ! {} + + // l1 rejected (not local) + #[task(priority = 1, local = [l1])] + async fn uart0(cx: uart0::Context) {} + + // l1 rejected (not lock_free) + #[task(priority = 2, local = [l1])] + async fn uart1(cx: uart1::Context) {} +} diff --git a/rtic/macros/ui/local-shared.stderr b/rtic/macros/ui/local-shared.stderr new file mode 100644 index 0000000..fceb763 --- /dev/null +++ b/rtic/macros/ui/local-shared.stderr @@ -0,0 +1,11 @@ +error: Local resource "l1" is used by multiple tasks or collides with multiple definitions + --> ui/local-shared.rs:22:35 + | +22 | #[task(priority = 1, local = [l1])] + | ^^ + +error: Local resource "l1" is used by multiple tasks or collides with multiple definitions + --> ui/local-shared.rs:26:35 + | +26 | #[task(priority = 2, local = [l1])] + | ^^ diff --git a/rtic/macros/ui/shared-lock-free.rs b/rtic/macros/ui/shared-lock-free.rs new file mode 100644 index 0000000..b3a4b9c --- /dev/null +++ b/rtic/macros/ui/shared-lock-free.rs @@ -0,0 +1,38 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared { + // An exclusive, early resource + #[lock_free] + e1: u32, + + // An exclusive, late resource + #[lock_free] + e2: u32, + } + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) {} + + // e2 ok + #[idle(shared = [e2])] + fn idle(cx: idle::Context) -> ! { + debug::exit(debug::EXIT_SUCCESS); + loop {} + } + + // e1 rejected (not lock_free) + #[task(binds = UART0, priority = 1, shared = [e1])] + fn uart0(cx: uart0::Context) { + *cx.resources.e1 += 10; + } + + // e1 rejected (not lock_free) + #[task(binds = UART1, priority = 2, shared = [e1])] + fn uart1(cx: uart1::Context) {} +} diff --git a/rtic/macros/ui/shared-lock-free.stderr b/rtic/macros/ui/shared-lock-free.stderr new file mode 100644 index 0000000..51e99a0 --- /dev/null +++ b/rtic/macros/ui/shared-lock-free.stderr @@ -0,0 +1,17 @@ +error: Lock free shared resource "e1" is used by tasks at different priorities + --> ui/shared-lock-free.rs:9:9 + | +9 | e1: u32, + | ^^ + +error: Shared resource "e1" is declared lock free but used by tasks at different priorities + --> ui/shared-lock-free.rs:30:51 + | +30 | #[task(binds = UART0, priority = 1, shared = [e1])] + | ^^ + +error: Shared resource "e1" is declared lock free but used by tasks at different priorities + --> ui/shared-lock-free.rs:36:51 + | +36 | #[task(binds = UART1, priority = 2, shared = [e1])] + | ^^ diff --git a/rtic/macros/ui/shared-not-declared.rs b/rtic/macros/ui/shared-not-declared.rs new file mode 100644 index 0000000..5fef534 --- /dev/null +++ b/rtic/macros/ui/shared-not-declared.rs @@ -0,0 +1,16 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[task(shared = [A])] + async fn foo(_: foo::Context) {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) {} +} diff --git a/rtic/macros/ui/shared-not-declared.stderr b/rtic/macros/ui/shared-not-declared.stderr new file mode 100644 index 0000000..7c5fb32 --- /dev/null +++ b/rtic/macros/ui/shared-not-declared.stderr @@ -0,0 +1,5 @@ +error: this shared resource has NOT been declared + --> ui/shared-not-declared.rs:11:22 + | +11 | #[task(shared = [A])] + | ^ diff --git a/rtic/macros/ui/shared-pub.rs b/rtic/macros/ui/shared-pub.rs new file mode 100644 index 0000000..10351fd --- /dev/null +++ b/rtic/macros/ui/shared-pub.rs @@ -0,0 +1,9 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared { + pub x: u32, + } +} diff --git a/rtic/macros/ui/shared-pub.stderr b/rtic/macros/ui/shared-pub.stderr new file mode 100644 index 0000000..7148893 --- /dev/null +++ b/rtic/macros/ui/shared-pub.stderr @@ -0,0 +1,5 @@ +error: this field must have inherited / private visibility + --> ui/shared-pub.rs:7:13 + | +7 | pub x: u32, + | ^ diff --git a/rtic/macros/ui/task-divergent.rs b/rtic/macros/ui/task-divergent.rs new file mode 100644 index 0000000..ffe2dc0 --- /dev/null +++ b/rtic/macros/ui/task-divergent.rs @@ -0,0 +1,9 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[task] + async fn foo(_: foo::Context) -> ! { + loop {} + } +} diff --git a/rtic/macros/ui/task-divergent.stderr b/rtic/macros/ui/task-divergent.stderr new file mode 100644 index 0000000..dd00208 --- /dev/null +++ b/rtic/macros/ui/task-divergent.stderr @@ -0,0 +1,5 @@ +error: this task handler must have type signature `async fn(foo::Context, ..)` + --> ui/task-divergent.rs:6:14 + | +6 | async fn foo(_: foo::Context) -> ! { + | ^^^ diff --git a/rtic/macros/ui/task-double-local.rs b/rtic/macros/ui/task-double-local.rs new file mode 100644 index 0000000..c5277e2 --- /dev/null +++ b/rtic/macros/ui/task-double-local.rs @@ -0,0 +1,7 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[task(local = [A], local = [B])] + async fn foo(_: foo::Context) {} +} diff --git a/rtic/macros/ui/task-double-local.stderr b/rtic/macros/ui/task-double-local.stderr new file mode 100644 index 0000000..91ed844 --- /dev/null +++ b/rtic/macros/ui/task-double-local.stderr @@ -0,0 +1,5 @@ +error: argument appears more than once + --> ui/task-double-local.rs:5:25 + | +5 | #[task(local = [A], local = [B])] + | ^^^^^ diff --git a/rtic/macros/ui/task-double-priority.rs b/rtic/macros/ui/task-double-priority.rs new file mode 100644 index 0000000..5c8bd5b --- /dev/null +++ b/rtic/macros/ui/task-double-priority.rs @@ -0,0 +1,7 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[task(priority = 1, priority = 2)] + async fn foo(_: foo::Context) {} +} diff --git a/rtic/macros/ui/task-double-priority.stderr b/rtic/macros/ui/task-double-priority.stderr new file mode 100644 index 0000000..b3c814a --- /dev/null +++ b/rtic/macros/ui/task-double-priority.stderr @@ -0,0 +1,5 @@ +error: argument appears more than once + --> ui/task-double-priority.rs:5:26 + | +5 | #[task(priority = 1, priority = 2)] + | ^^^^^^^^ diff --git a/rtic/macros/ui/task-double-shared.rs b/rtic/macros/ui/task-double-shared.rs new file mode 100644 index 0000000..f9812d3 --- /dev/null +++ b/rtic/macros/ui/task-double-shared.rs @@ -0,0 +1,7 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[task(shared = [A], shared = [B])] + async fn foo(_: foo::Context) {} +} diff --git a/rtic/macros/ui/task-double-shared.stderr b/rtic/macros/ui/task-double-shared.stderr new file mode 100644 index 0000000..bb90212 --- /dev/null +++ b/rtic/macros/ui/task-double-shared.stderr @@ -0,0 +1,5 @@ +error: argument appears more than once + --> ui/task-double-shared.rs:5:26 + | +5 | #[task(shared = [A], shared = [B])] + | ^^^^^^ diff --git a/rtic/macros/ui/task-idle.rs b/rtic/macros/ui/task-idle.rs new file mode 100644 index 0000000..353c782 --- /dev/null +++ b/rtic/macros/ui/task-idle.rs @@ -0,0 +1,13 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[idle] + fn foo(_: foo::Context) -> ! { + loop {} + } + + // name collides with `#[idle]` function + #[task] + async fn foo(_: foo::Context) {} +} diff --git a/rtic/macros/ui/task-idle.stderr b/rtic/macros/ui/task-idle.stderr new file mode 100644 index 0000000..4ccc113 --- /dev/null +++ b/rtic/macros/ui/task-idle.stderr @@ -0,0 +1,5 @@ +error: this identifier has already been used + --> ui/task-idle.rs:12:14 + | +12 | async fn foo(_: foo::Context) {} + | ^^^ diff --git a/rtic/macros/ui/task-init.rs b/rtic/macros/ui/task-init.rs new file mode 100644 index 0000000..e58fdce --- /dev/null +++ b/rtic/macros/ui/task-init.rs @@ -0,0 +1,17 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn foo(_: foo::Context) -> (Shared, Local) {} + + // name collides with `#[idle]` function + #[task] + async fn foo(_: foo::Context) {} +} diff --git a/rtic/macros/ui/task-init.stderr b/rtic/macros/ui/task-init.stderr new file mode 100644 index 0000000..161e194 --- /dev/null +++ b/rtic/macros/ui/task-init.stderr @@ -0,0 +1,5 @@ +error: this identifier has already been used + --> ui/task-init.rs:16:14 + | +16 | async fn foo(_: foo::Context) {} + | ^^^ diff --git a/rtic/macros/ui/task-interrupt.rs b/rtic/macros/ui/task-interrupt.rs new file mode 100644 index 0000000..3d50bd8 --- /dev/null +++ b/rtic/macros/ui/task-interrupt.rs @@ -0,0 +1,10 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[task(binds = SysTick)] + fn foo(_: foo::Context) {} + + #[task] + async fn foo(_: foo::Context) {} +} diff --git a/rtic/macros/ui/task-interrupt.stderr b/rtic/macros/ui/task-interrupt.stderr new file mode 100644 index 0000000..087b6c6 --- /dev/null +++ b/rtic/macros/ui/task-interrupt.stderr @@ -0,0 +1,5 @@ +error: this task is defined multiple times + --> ui/task-interrupt.rs:9:14 + | +9 | async fn foo(_: foo::Context) {} + | ^^^ diff --git a/rtic/macros/ui/task-no-context.rs b/rtic/macros/ui/task-no-context.rs new file mode 100644 index 0000000..55e8c3b --- /dev/null +++ b/rtic/macros/ui/task-no-context.rs @@ -0,0 +1,7 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[task] + async fn foo() {} +} diff --git a/rtic/macros/ui/task-no-context.stderr b/rtic/macros/ui/task-no-context.stderr new file mode 100644 index 0000000..62147aa --- /dev/null +++ b/rtic/macros/ui/task-no-context.stderr @@ -0,0 +1,5 @@ +error: this task handler must have type signature `async fn(foo::Context, ..)` + --> ui/task-no-context.rs:6:14 + | +6 | async fn foo() {} + | ^^^ diff --git a/rtic/macros/ui/task-priority-too-high.rs b/rtic/macros/ui/task-priority-too-high.rs new file mode 100644 index 0000000..f33ba56 --- /dev/null +++ b/rtic/macros/ui/task-priority-too-high.rs @@ -0,0 +1,7 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[task(priority = 256)] + async fn foo(_: foo::Context) {} +} diff --git a/rtic/macros/ui/task-priority-too-high.stderr b/rtic/macros/ui/task-priority-too-high.stderr new file mode 100644 index 0000000..5790c88 --- /dev/null +++ b/rtic/macros/ui/task-priority-too-high.stderr @@ -0,0 +1,5 @@ +error: this literal must be in the range 0...255 + --> ui/task-priority-too-high.rs:5:23 + | +5 | #[task(priority = 256)] + | ^^^ diff --git a/rtic/macros/ui/task-priority-too-low.rs b/rtic/macros/ui/task-priority-too-low.rs new file mode 100644 index 0000000..16e0557 --- /dev/null +++ b/rtic/macros/ui/task-priority-too-low.rs @@ -0,0 +1,7 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[task(binds = UART0, priority = 0)] + fn foo(_: foo::Context) {} +} diff --git a/rtic/macros/ui/task-priority-too-low.stderr b/rtic/macros/ui/task-priority-too-low.stderr new file mode 100644 index 0000000..85c8660 --- /dev/null +++ b/rtic/macros/ui/task-priority-too-low.stderr @@ -0,0 +1,5 @@ +error: hardware tasks are not allowed to be at priority 0 + --> ui/task-priority-too-low.rs:5:38 + | +5 | #[task(binds = UART0, priority = 0)] + | ^ diff --git a/rtic/macros/ui/task-pub.rs b/rtic/macros/ui/task-pub.rs new file mode 100644 index 0000000..1ae533f --- /dev/null +++ b/rtic/macros/ui/task-pub.rs @@ -0,0 +1,7 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[task] + pub async fn foo(_: foo::Context) {} +} diff --git a/rtic/macros/ui/task-pub.stderr b/rtic/macros/ui/task-pub.stderr new file mode 100644 index 0000000..7b9813d --- /dev/null +++ b/rtic/macros/ui/task-pub.stderr @@ -0,0 +1,5 @@ +error: this task handler must have type signature `async fn(foo::Context, ..)` + --> ui/task-pub.rs:6:18 + | +6 | pub async fn foo(_: foo::Context) {} + | ^^^ diff --git a/rtic/macros/ui/task-unsafe.rs b/rtic/macros/ui/task-unsafe.rs new file mode 100644 index 0000000..a8383ef --- /dev/null +++ b/rtic/macros/ui/task-unsafe.rs @@ -0,0 +1,7 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[task] + async unsafe fn foo(_: foo::Context) {} +} diff --git a/rtic/macros/ui/task-unsafe.stderr b/rtic/macros/ui/task-unsafe.stderr new file mode 100644 index 0000000..90ac76f --- /dev/null +++ b/rtic/macros/ui/task-unsafe.stderr @@ -0,0 +1,5 @@ +error: this task handler must have type signature `async fn(foo::Context, ..)` + --> ui/task-unsafe.rs:6:21 + | +6 | async unsafe fn foo(_: foo::Context) {} + | ^^^ diff --git a/rtic/macros/ui/task-zero-prio.rs b/rtic/macros/ui/task-zero-prio.rs new file mode 100644 index 0000000..de3c86f --- /dev/null +++ b/rtic/macros/ui/task-zero-prio.rs @@ -0,0 +1,19 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) {} + + #[task(priority = 0)] + fn foo(_: foo::Context) {} + + #[idle] + fn idle(_: idle::Context) -> ! {} +} diff --git a/rtic/macros/ui/task-zero-prio.stderr b/rtic/macros/ui/task-zero-prio.stderr new file mode 100644 index 0000000..1ab9aab --- /dev/null +++ b/rtic/macros/ui/task-zero-prio.stderr @@ -0,0 +1,5 @@ +error: this task handler must have type signature `async fn(foo::Context, ..)` + --> ui/task-zero-prio.rs:15:8 + | +15 | fn foo(_: foo::Context) {} + | ^^^ diff --git a/rtic/rust-toolchain.toml b/rtic/rust-toolchain.toml new file mode 100644 index 0000000..e28b55d --- /dev/null +++ b/rtic/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "nightly" +components = [ "rust-src", "rustfmt", "llvm-tools-preview" ] +targets = [ "thumbv6m-none-eabi", "thumbv7m-none-eabi" ] diff --git a/rtic/src/export.rs b/rtic/src/export.rs new file mode 100644 index 0000000..cdca972 --- /dev/null +++ b/rtic/src/export.rs @@ -0,0 +1,324 @@ +pub use bare_metal::CriticalSection; +pub use cortex_m::{ + asm::nop, + asm::wfi, + interrupt, + peripheral::{scb::SystemHandler, DWT, NVIC, SCB, SYST}, + Peripherals, +}; +//pub use portable_atomic as atomic; +pub use atomic_polyfill as atomic; + +pub mod executor; + +/// Mask is used to store interrupt masks on systems without a BASEPRI register (M0, M0+, M23). +/// It needs to be large enough to cover all the relevant interrupts in use. +/// For M0/M0+ there are only 32 interrupts so we only need one u32 value. +/// For M23 there can be as many as 480 interrupts. +/// Rather than providing space for all possible interrupts, we just detect the highest interrupt in +/// use at compile time and allocate enough u32 chunks to cover them. +#[derive(Copy, Clone)] +pub struct Mask([u32; M]); + +impl core::ops::BitOrAssign for Mask { + fn bitor_assign(&mut self, rhs: Self) { + for i in 0..M { + self.0[i] |= rhs.0[i]; + } + } +} + +#[cfg(not(have_basepri))] +impl Mask { + /// Set a bit inside a Mask. + const fn set_bit(mut self, bit: u32) -> Self { + let block = bit / 32; + + if block as usize >= M { + panic!("Generating masks for thumbv6/thumbv8m.base failed! Are you compiling for thumbv6 on an thumbv7 MCU or using an unsupported thumbv8m.base MCU?"); + } + + let offset = bit - (block * 32); + self.0[block as usize] |= 1 << offset; + self + } +} + +#[cfg(have_basepri)] +use cortex_m::register::basepri; + +#[cfg(have_basepri)] +#[inline(always)] +pub fn run(priority: u8, f: F) +where + F: FnOnce(), +{ + if priority == 1 { + // If the priority of this interrupt is `1` then BASEPRI can only be `0` + f(); + unsafe { basepri::write(0) } + } else { + let initial = basepri::read(); + f(); + unsafe { basepri::write(initial) } + } +} + +#[cfg(not(have_basepri))] +#[inline(always)] +pub fn run(_priority: u8, f: F) +where + F: FnOnce(), +{ + f(); +} + +/// Const helper to check architecture +pub const fn have_basepri() -> bool { + #[cfg(have_basepri)] + { + true + } + + #[cfg(not(have_basepri))] + { + false + } +} + +#[inline(always)] +pub fn assert_send() +where + T: Send, +{ +} + +#[inline(always)] +pub fn assert_sync() +where + T: Sync, +{ +} + +/// Lock implementation using BASEPRI and global Critical Section (CS) +/// +/// # Safety +/// +/// The system ceiling is raised from current to ceiling +/// by either +/// - raising the BASEPRI to the ceiling value, or +/// - disable all interrupts in case we want to +/// mask interrupts with maximum priority +/// +/// Dereferencing a raw pointer inside CS +/// +/// The priority.set/priority.get can safely be outside the CS +/// as being a context local cell (not affected by preemptions). +/// It is merely used in order to omit masking in case current +/// priority is current priority >= ceiling. +/// +/// Lock Efficiency: +/// Experiments validate (sub)-zero cost for CS implementation +/// (Sub)-zero as: +/// - Either zero OH (lock optimized out), or +/// - Amounting to an optimal assembly implementation +/// - The BASEPRI value is folded to a constant at compile time +/// - CS entry, single assembly instruction to write BASEPRI +/// - CS exit, single assembly instruction to write BASEPRI +/// - priority.set/get optimized out (their effect not) +/// - On par or better than any handwritten implementation of SRP +/// +/// Limitations: +/// The current implementation reads/writes BASEPRI once +/// even in some edge cases where this may be omitted. +/// Total OH of per task is max 2 clock cycles, negligible in practice +/// but can in theory be fixed. +/// +#[cfg(have_basepri)] +#[inline(always)] +pub unsafe fn lock( + ptr: *mut T, + ceiling: u8, + nvic_prio_bits: u8, + _mask: &[Mask; 3], + f: impl FnOnce(&mut T) -> R, +) -> R { + if ceiling == (1 << nvic_prio_bits) { + let r = interrupt::free(|_| f(&mut *ptr)); + r + } else { + let current = basepri::read(); + basepri::write(logical2hw(ceiling, nvic_prio_bits)); + let r = f(&mut *ptr); + basepri::write(current); + r + } +} + +/// Lock implementation using interrupt masking +/// +/// # Safety +/// +/// The system ceiling is raised from current to ceiling +/// by computing a 32 bit `mask` (1 bit per interrupt) +/// 1: ceiling >= priority > current +/// 0: else +/// +/// On CS entry, `clear_enable_mask(mask)` disables interrupts +/// On CS exit, `set_enable_mask(mask)` re-enables interrupts +/// +/// The priority.set/priority.get can safely be outside the CS +/// as being a context local cell (not affected by preemptions). +/// It is merely used in order to omit masking in case +/// current priority >= ceiling. +/// +/// Dereferencing a raw pointer is done safely inside the CS +/// +/// Lock Efficiency: +/// Early experiments validate (sub)-zero cost for CS implementation +/// (Sub)-zero as: +/// - Either zero OH (lock optimized out), or +/// - Amounting to an optimal assembly implementation +/// - if ceiling == (1 << nvic_prio_bits) +/// - we execute the closure in a global critical section (interrupt free) +/// - CS entry cost, single write to core register +/// - CS exit cost, single write to core register +/// else +/// - The `mask` value is folded to a constant at compile time +/// - CS entry, single write of the 32 bit `mask` to the `icer` register +/// - CS exit, single write of the 32 bit `mask` to the `iser` register +/// - priority.set/get optimized out (their effect not) +/// - On par or better than any hand written implementation of SRP +/// +/// Limitations: +/// Current implementation does not allow for tasks with shared resources +/// to be bound to exception handlers, as these cannot be masked in HW. +/// +/// Possible solutions: +/// - Mask exceptions by global critical sections (interrupt::free) +/// - Temporary lower exception priority +/// +/// These possible solutions are set goals for future work +#[cfg(not(have_basepri))] +#[inline(always)] +pub unsafe fn lock( + ptr: *mut T, + ceiling: u8, + _nvic_prio_bits: u8, + masks: &[Mask; 3], + f: impl FnOnce(&mut T) -> R, +) -> R { + if ceiling >= 4 { + // safe to manipulate outside critical section + // execute closure under protection of raised system ceiling + + // safe to manipulate outside critical section + interrupt::free(|_| f(&mut *ptr)) + } else { + // safe to manipulate outside critical section + let mask = compute_mask(0, ceiling, masks); + clear_enable_mask(mask); + + // execute closure under protection of raised system ceiling + let r = f(&mut *ptr); + + set_enable_mask(mask); + + // safe to manipulate outside critical section + r + } +} + +#[cfg(not(have_basepri))] +#[inline(always)] +fn compute_mask(from_prio: u8, to_prio: u8, masks: &[Mask; 3]) -> Mask { + let mut res = Mask([0; M]); + masks[from_prio as usize..to_prio as usize] + .iter() + .for_each(|m| res |= *m); + res +} + +// enables interrupts +#[cfg(not(have_basepri))] +#[inline(always)] +unsafe fn set_enable_mask(mask: Mask) { + for i in 0..M { + // This check should involve compile time constants and be optimized out. + if mask.0[i] != 0 { + (*NVIC::PTR).iser[i].write(mask.0[i]); + } + } +} + +// disables interrupts +#[cfg(not(have_basepri))] +#[inline(always)] +unsafe fn clear_enable_mask(mask: Mask) { + for i in 0..M { + // This check should involve compile time constants and be optimized out. + if mask.0[i] != 0 { + (*NVIC::PTR).icer[i].write(mask.0[i]); + } + } +} + +#[inline] +#[must_use] +pub fn logical2hw(logical: u8, nvic_prio_bits: u8) -> u8 { + ((1 << nvic_prio_bits) - logical) << (8 - nvic_prio_bits) +} + +#[cfg(have_basepri)] +pub const fn create_mask(_: [u32; N]) -> Mask { + Mask([0; M]) +} + +#[cfg(not(have_basepri))] +pub const fn create_mask(list_of_shifts: [u32; N]) -> Mask { + let mut mask = Mask([0; M]); + let mut i = 0; + + while i < N { + let shift = list_of_shifts[i]; + i += 1; + mask = mask.set_bit(shift); + } + + mask +} + +#[cfg(have_basepri)] +pub const fn compute_mask_chunks(_: [u32; L]) -> usize { + 0 +} + +/// Compute the number of u32 chunks needed to store the Mask value. +/// On M0, M0+ this should always end up being 1. +/// On M23 we will pick a number that allows us to store the highest index used by the code. +/// This means the amount of overhead will vary based on the actually interrupts used by the code. +#[cfg(not(have_basepri))] +pub const fn compute_mask_chunks(ids: [u32; L]) -> usize { + let mut max: usize = 0; + let mut i = 0; + + while i < L { + let id = ids[i] as usize; + i += 1; + + if id > max { + max = id; + } + } + (max + 32) / 32 +} + +#[cfg(have_basepri)] +pub const fn no_basepri_panic() { + // For non-v6 all is fine +} + +#[cfg(not(have_basepri))] +pub const fn no_basepri_panic() { + panic!("Exceptions with shared resources are not allowed when compiling for thumbv6 or thumbv8m.base. Use local resources or `#[lock_free]` shared resources"); +} diff --git a/rtic/src/export/executor.rs b/rtic/src/export/executor.rs new file mode 100644 index 0000000..e64cc43 --- /dev/null +++ b/rtic/src/export/executor.rs @@ -0,0 +1,110 @@ +use super::atomic::{AtomicBool, Ordering}; +use core::{ + cell::UnsafeCell, + future::Future, + mem::{self, MaybeUninit}, + pin::Pin, + task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, +}; + +static WAKER_VTABLE: RawWakerVTable = + RawWakerVTable::new(waker_clone, waker_wake, waker_wake, waker_drop); + +unsafe fn waker_clone(p: *const ()) -> RawWaker { + RawWaker::new(p, &WAKER_VTABLE) +} + +unsafe fn waker_wake(p: *const ()) { + // The only thing we need from a waker is the function to call to pend the async + // dispatcher. + let f: fn() = mem::transmute(p); + f(); +} + +unsafe fn waker_drop(_: *const ()) { + // nop +} + +//============ +// AsyncTaskExecutor + +/// Executor for an async task. +pub struct AsyncTaskExecutor { + // `task` is protected by the `running` flag. + task: UnsafeCell>, + running: AtomicBool, + pending: AtomicBool, +} + +unsafe impl Sync for AsyncTaskExecutor {} + +impl AsyncTaskExecutor { + /// Create a new executor. + #[inline(always)] + pub const fn new() -> Self { + Self { + task: UnsafeCell::new(MaybeUninit::uninit()), + running: AtomicBool::new(false), + pending: AtomicBool::new(false), + } + } + + /// Check if there is an active task in the executor. + #[inline(always)] + pub fn is_running(&self) -> bool { + self.running.load(Ordering::Relaxed) + } + + /// Checks if a waker has pended the executor and simultaneously clears the flag. + #[inline(always)] + fn check_and_clear_pending(&self) -> bool { + // Ordering::Acquire to enforce that update of task is visible to poll + self.pending + .compare_exchange(true, false, Ordering::Acquire, Ordering::Relaxed) + .is_ok() + } + + // Used by wakers to indicate that the executor needs to run. + #[inline(always)] + pub fn set_pending(&self) { + self.pending.store(true, Ordering::Release); + } + + /// Spawn a future + #[inline(always)] + pub fn spawn(&self, future: impl Fn() -> F) -> bool { + // Try to reserve the executor for a future. + if self + .running + .compare_exchange(false, true, Ordering::AcqRel, Ordering::Relaxed) + .is_ok() + { + // This unsafe is protected by `running` being false and the atomic setting it to true. + unsafe { + self.task.get().write(MaybeUninit::new(future())); + } + self.set_pending(); + + true + } else { + false + } + } + + /// Poll the future in the executor. + #[inline(always)] + pub fn poll(&self, wake: fn()) { + if self.is_running() && self.check_and_clear_pending() { + let waker = unsafe { Waker::from_raw(RawWaker::new(wake as *const (), &WAKER_VTABLE)) }; + let mut cx = Context::from_waker(&waker); + let future = unsafe { Pin::new_unchecked(&mut *(self.task.get() as *mut F)) }; + + match future.poll(&mut cx) { + Poll::Ready(_) => { + self.running.store(false, Ordering::Release); + } + Poll::Pending => {} + } + } + } +} diff --git a/rtic/src/lib.rs b/rtic/src/lib.rs new file mode 100644 index 0000000..e8b8140 --- /dev/null +++ b/rtic/src/lib.rs @@ -0,0 +1,121 @@ +//! Real-Time Interrupt-driven Concurrency (RTIC) framework for ARM Cortex-M microcontrollers. +//! +//! **IMPORTANT**: This crate is published as [`cortex-m-rtic`] on crates.io but the name of the +//! library is `rtic`. +//! +//! [`cortex-m-rtic`]: https://crates.io/crates/cortex-m-rtic +//! +//! The user level documentation can be found [here]. +//! +//! [here]: https://rtic.rs +//! +//! Don't forget to check the documentation of the `#[app]` attribute (listed under the reexports +//! section), which is the main component of the framework. +//! +//! # Minimum Supported Rust Version (MSRV) +//! +//! This crate is compiled and tested with the latest toolchain (rolling) as of the release date. +//! If you run into compilation errors, try the latest stable release of the rust toolchain. +//! +//! # Semantic Versioning +//! +//! Like the Rust project, this crate adheres to [SemVer]: breaking changes in the API and semantics +//! require a *semver bump* (since 1.0.0 a new major version release), with the exception of breaking changes +//! that fix soundness issues -- those are considered bug fixes and can be landed in a new patch +//! release. +//! +//! [SemVer]: https://semver.org/spec/v2.0.0.html + +#![deny(missing_docs)] +#![deny(rust_2021_compatibility)] +#![deny(rust_2018_compatibility)] +#![deny(rust_2018_idioms)] +#![no_std] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/rtic-rs/cortex-m-rtic/master/book/en/src/RTIC.svg", + html_favicon_url = "https://raw.githubusercontent.com/rtic-rs/cortex-m-rtic/master/book/en/src/RTIC.svg" +)] +//deny_warnings_placeholder_for_ci +#![allow(clippy::inline_always)] + +use cortex_m::{interrupt::InterruptNumber, peripheral::NVIC}; +pub use rtic_core::{prelude as mutex_prelude, Exclusive, Mutex}; +pub use rtic_macros::app; +pub use rtic_monotonic::{self, Monotonic}; + +/// module `mutex::prelude` provides `Mutex` and multi-lock variants. Recommended over `mutex_prelude` +pub mod mutex { + pub use rtic_core::prelude; + pub use rtic_core::Mutex; +} + +#[doc(hidden)] +pub mod export; + +/// Sets the given `interrupt` as pending +/// +/// This is a convenience function around +/// [`NVIC::pend`](../cortex_m/peripheral/struct.NVIC.html#method.pend) +pub fn pend(interrupt: I) +where + I: InterruptNumber, +{ + NVIC::pend(interrupt); +} + +use core::cell::UnsafeCell; + +/// Internal replacement for `static mut T` +/// +/// Used to represent RTIC Resources +/// +/// Soundness: +/// 1) Unsafe API for internal use only +/// 2) ``get_mut(&self) -> *mut T`` +/// returns a raw mutable pointer to the inner T +/// casting to &mut T is under control of RTIC +/// RTIC ensures &mut T to be unique under Rust aliasing rules. +/// +/// Implementation uses the underlying ``UnsafeCell`` +/// self.0.get() -> *mut T +/// +/// 3) get(&self) -> *const T +/// returns a raw immutable (const) pointer to the inner T +/// casting to &T is under control of RTIC +/// RTIC ensures &T to be shared under Rust aliasing rules. +/// +/// Implementation uses the underlying ``UnsafeCell`` +/// self.0.get() -> *mut T, demoted to *const T +/// +#[repr(transparent)] +pub struct RacyCell(UnsafeCell); + +impl RacyCell { + /// Create a ``RacyCell`` + #[inline(always)] + pub const fn new(value: T) -> Self { + RacyCell(UnsafeCell::new(value)) + } + + /// Get `*mut T` + /// + /// # Safety + /// + /// See documentation notes for [`RacyCell`] + #[inline(always)] + pub unsafe fn get_mut(&self) -> *mut T { + self.0.get() + } + + /// Get `*const T` + /// + /// # Safety + /// + /// See documentation notes for [`RacyCell`] + #[inline(always)] + pub unsafe fn get(&self) -> *const T { + self.0.get() + } +} + +unsafe impl Sync for RacyCell {} diff --git a/rtic/tests/tests.rs b/rtic/tests/tests.rs new file mode 100644 index 0000000..9fb88a1 --- /dev/null +++ b/rtic/tests/tests.rs @@ -0,0 +1,7 @@ +use trybuild::TestCases; + +#[test] +fn ui() { + let t = TestCases::new(); + t.compile_fail("ui/*.rs"); +} diff --git a/rtic/ui/exception-invalid.rs b/rtic/ui/exception-invalid.rs new file mode 100644 index 0000000..4f8e943 --- /dev/null +++ b/rtic/ui/exception-invalid.rs @@ -0,0 +1,18 @@ +#![no_main] + +#[rtic::app(device = lm3s6965)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(cx: init::Context) -> (Shared, Local) { + (Shared {}, Local {}) + } + + #[task(binds = NonMaskableInt)] + fn nmi(_: nmi::Context) {} +} diff --git a/rtic/ui/exception-invalid.stderr b/rtic/ui/exception-invalid.stderr new file mode 100644 index 0000000..3212368 --- /dev/null +++ b/rtic/ui/exception-invalid.stderr @@ -0,0 +1,5 @@ +error: only exceptions with configurable priority can be used as hardware tasks + --> $DIR/exception-invalid.rs:17:8 + | +17 | fn nmi(_: nmi::Context) {} + | ^^^ diff --git a/rtic/ui/extern-interrupt-not-enough.rs b/rtic/ui/extern-interrupt-not-enough.rs new file mode 100644 index 0000000..94c8ee1 --- /dev/null +++ b/rtic/ui/extern-interrupt-not-enough.rs @@ -0,0 +1,18 @@ +#![no_main] + +#[rtic::app(device = lm3s6965)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(cx: init::Context) -> (Shared, Local) { + (Shared {}, Local {}) + } + + #[task] + async fn a(_: a::Context) {} +} diff --git a/rtic/ui/extern-interrupt-not-enough.stderr b/rtic/ui/extern-interrupt-not-enough.stderr new file mode 100644 index 0000000..e6c01b9 --- /dev/null +++ b/rtic/ui/extern-interrupt-not-enough.stderr @@ -0,0 +1,5 @@ +error: not enough interrupts to dispatch all software tasks (need: 1; given: 0) + --> ui/extern-interrupt-not-enough.rs:17:14 + | +17 | async fn a(_: a::Context) {} + | ^ diff --git a/rtic/ui/extern-interrupt-used.rs b/rtic/ui/extern-interrupt-used.rs new file mode 100644 index 0000000..42de4c0 --- /dev/null +++ b/rtic/ui/extern-interrupt-used.rs @@ -0,0 +1,18 @@ +#![no_main] + +#[rtic::app(device = lm3s6965, dispatchers = [UART0])] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(cx: init::Context) -> (Shared, Local) { + (Shared {}, Local {}) + } + + #[task(binds = UART0)] + fn a(_: a::Context) {} +} diff --git a/rtic/ui/extern-interrupt-used.stderr b/rtic/ui/extern-interrupt-used.stderr new file mode 100644 index 0000000..7565739 --- /dev/null +++ b/rtic/ui/extern-interrupt-used.stderr @@ -0,0 +1,5 @@ +error: dispatcher interrupts can't be used as hardware tasks + --> $DIR/extern-interrupt-used.rs:16:20 + | +16 | #[task(binds = UART0)] + | ^^^^^ diff --git a/rtic/ui/task-priority-too-high.rs b/rtic/ui/task-priority-too-high.rs new file mode 100644 index 0000000..44e4a25 --- /dev/null +++ b/rtic/ui/task-priority-too-high.rs @@ -0,0 +1,44 @@ +#![no_main] + +#[rtic::app(device = lm3s6965)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(cx: init::Context) -> (Shared, Local) { + (Shared {}, Local {}) + } + + #[task(binds = GPIOA, priority = 1)] + fn gpioa(_: gpioa::Context) {} + + #[task(binds = GPIOB, priority = 2)] + fn gpiob(_: gpiob::Context) {} + + #[task(binds = GPIOC, priority = 3)] + fn gpioc(_: gpioc::Context) {} + + #[task(binds = GPIOD, priority = 4)] + fn gpiod(_: gpiod::Context) {} + + #[task(binds = GPIOE, priority = 5)] + fn gpioe(_: gpioe::Context) {} + + #[task(binds = UART0, priority = 6)] + fn uart0(_: uart0::Context) {} + + #[task(binds = UART1, priority = 7)] + fn uart1(_: uart1::Context) {} + + // OK, this is the maximum priority supported by the device + #[task(binds = SSI0, priority = 8)] + fn ssi0(_: ssi0::Context) {} + + // this value is too high! + #[task(binds = I2C0, priority = 9)] + fn i2c0(_: i2c0::Context) {} +} diff --git a/rtic/ui/task-priority-too-high.stderr b/rtic/ui/task-priority-too-high.stderr new file mode 100644 index 0000000..1256377 --- /dev/null +++ b/rtic/ui/task-priority-too-high.stderr @@ -0,0 +1,15 @@ +warning: unused variable: `cx` + --> ui/task-priority-too-high.rs:12:13 + | +12 | fn init(cx: init::Context) -> (Shared, Local) { + | ^^ help: if this is intentional, prefix it with an underscore: `_cx` + | + = note: `#[warn(unused_variables)]` on by default + +error[E0080]: evaluation of constant value failed + --> ui/task-priority-too-high.rs:3:1 + | +3 | #[rtic::app(device = lm3s6965)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Maximum priority used by interrupt vector 'I2C0' is more than supported by hardware', $DIR/ui/task-priority-too-high.rs:3:1 + | + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::core::panic` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/rtic/ui/unknown-interrupt.rs b/rtic/ui/unknown-interrupt.rs new file mode 100644 index 0000000..3c6c69f --- /dev/null +++ b/rtic/ui/unknown-interrupt.rs @@ -0,0 +1,15 @@ +#![no_main] + +#[rtic::app(device = lm3s6965, dispatchers = [UnknownInterrupt])] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(cx: init::Context) -> (Shared, Local) { + (Shared {}, Local {}) + } +} diff --git a/rtic/ui/unknown-interrupt.stderr b/rtic/ui/unknown-interrupt.stderr new file mode 100644 index 0000000..c7d3269 --- /dev/null +++ b/rtic/ui/unknown-interrupt.stderr @@ -0,0 +1,5 @@ +error[E0599]: no variant or associated item named `UnknownInterrupt` found for enum `Interrupt` in the current scope + --> $DIR/unknown-interrupt.rs:3:47 + | +3 | #[rtic::app(device = lm3s6965, dispatchers = [UnknownInterrupt])] + | ^^^^^^^^^^^^^^^^ variant or associated item not found in `Interrupt` diff --git a/rtic/ui/v6m-interrupt-not-enough.rs_no b/rtic/ui/v6m-interrupt-not-enough.rs_no new file mode 100644 index 0000000..3fbf3cf --- /dev/null +++ b/rtic/ui/v6m-interrupt-not-enough.rs_no @@ -0,0 +1,54 @@ +//! v6m-interrupt-not-enough.rs_no (not run atm) +//! +//! Expected behavior: +//! should pass +//! > cargo build --example m0_perf_err --target thumbv7m-none-eabi --release +//! +//! should fail +//! > cargo build --example m0_perf_err --target thumbv6m-none-eabi --release +//! Compiling cortex-m-rtic v1.0.0 (/home/pln/rust/rtic/cortex-m-rtic) +//! error[E0308]: mismatched types +//! --> examples/m0_perf_err.rs:25:1 +//! | +//! 25 | #[rtic::app(device = lm3s6965)] +//! | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected an array with a fixed size of 4 elements, found one with 5 elements +//! | +//! = note: this error originates in the attribute macro `rtic::app` (in Nightly builds, run with -Z macro-backtrace for more info) + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965)] +mod app { + + use cortex_m_semihosting::debug; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { + (Shared {}, Local {}, init::Monotonics()) + } + + #[inline(never)] + #[idle] + fn idle(_cx: idle::Context) -> ! { + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + + loop { + cortex_m::asm::nop(); + } + } + + // priority to high for v6m + #[task(binds = GPIOA, priority = 5)] + fn t0(_cx: t0::Context) {} +} diff --git a/rtic/xtask/Cargo.toml b/rtic/xtask/Cargo.toml new file mode 100644 index 0000000..f1c468e --- /dev/null +++ b/rtic/xtask/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "xtask" +version = "0.1.0" +edition = "2018" + +[dependencies] +anyhow = "1.0.43" +os_pipe = "1.1.2" +structopt = "0.3.22" diff --git a/rtic/xtask/src/build.rs b/rtic/xtask/src/build.rs new file mode 100644 index 0000000..148a9fd --- /dev/null +++ b/rtic/xtask/src/build.rs @@ -0,0 +1,13 @@ +use std::{fs, path::Path}; + +const HEX_BUILD_ROOT: &str = "ci/builds"; + +/// make sure we're starting with a clean,but existing slate +pub fn init_build_dir() -> anyhow::Result<()> { + if Path::new(HEX_BUILD_ROOT).exists() { + fs::remove_dir_all(HEX_BUILD_ROOT) + .map_err(|_| anyhow::anyhow!("Could not clear out directory: {}", HEX_BUILD_ROOT))?; + } + fs::create_dir_all(HEX_BUILD_ROOT) + .map_err(|_| anyhow::anyhow!("Could not create directory: {}", HEX_BUILD_ROOT)) +} diff --git a/rtic/xtask/src/command.rs b/rtic/xtask/src/command.rs new file mode 100644 index 0000000..4e90369 --- /dev/null +++ b/rtic/xtask/src/command.rs @@ -0,0 +1,172 @@ +use crate::{RunResult, TestRunError}; +use core::fmt; +use os_pipe::pipe; +use std::{fs::File, io::Read, process::Command}; + +#[allow(dead_code)] +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum BuildMode { + Release, + Debug, +} + +#[derive(Debug)] +pub enum CargoCommand<'a> { + Run { + example: &'a str, + target: &'a str, + features: Option<&'a str>, + mode: BuildMode, + }, + BuildAll { + target: &'a str, + features: Option<&'a str>, + mode: BuildMode, + }, + // Size { + // example_paths: Vec<&'a Path>, + // }, + // Clean, +} + +impl<'a> CargoCommand<'a> { + fn name(&self) -> &str { + match self { + CargoCommand::Run { .. } => "run", + // CargoCommand::Size { example_paths: _ } => "rust-size", + CargoCommand::BuildAll { .. } => "build", + } + } + + pub fn args(&self) -> Vec<&str> { + match self { + CargoCommand::Run { + example, + target, + features, + mode, + } => { + let mut args = vec![ + "+nightly", + self.name(), + "--example", + example, + "--target", + target, + "--features", + "test-critical-section", + ]; + + if let Some(feature_name) = features { + args.extend_from_slice(&["--features", feature_name]); + } + if let Some(flag) = mode.to_flag() { + args.push(flag); + } + args + } + CargoCommand::BuildAll { + target, + features, + mode, + } => { + let mut args = vec![ + "+nightly", + self.name(), + "--examples", + "--target", + target, + "--features", + "test-critical-section", + ]; + + if let Some(feature_name) = features { + args.extend_from_slice(&["--features", feature_name]); + } + if let Some(flag) = mode.to_flag() { + args.push(flag); + } + args + } // CargoCommand::Size { example_paths } => { + // example_paths.iter().map(|p| p.to_str().unwrap()).collect() + // } + } + } + + pub fn command(&self) -> &str { + match self { + // we need to cheat a little here: + // `cargo size` can't be ran on multiple files, so we're using `rust-size` instead – + // which isn't a command that starts wizh `cargo`. So we're sneakily swapping them out :) + // CargoCommand::Size { .. } => "rust-size", + _ => "cargo", + } + } +} + +impl BuildMode { + pub fn to_flag(&self) -> Option<&str> { + match self { + BuildMode::Release => Some("--release"), + BuildMode::Debug => None, + } + } +} + +impl fmt::Display for BuildMode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let cmd = match self { + BuildMode::Release => "release", + BuildMode::Debug => "debug", + }; + + write!(f, "{}", cmd) + } +} + +pub fn run_command(command: &CargoCommand) -> anyhow::Result { + let (mut reader, writer) = pipe()?; + println!("👟 {} {}", command.command(), command.args().join(" ")); + + let mut handle = Command::new(command.command()) + .args(command.args()) + .stdout(writer) + .spawn()?; + + // retrieve output and clean up + let mut output = String::new(); + reader.read_to_string(&mut output)?; + let exit_status = handle.wait()?; + + Ok(RunResult { + exit_status, + output, + }) +} + +/// Check if `run` was successful. +/// returns Ok in case the run went as expected, +/// Err otherwise +pub fn run_successful(run: &RunResult, expected_output_file: String) -> Result<(), TestRunError> { + let mut file_handle = + File::open(expected_output_file.clone()).map_err(|_| TestRunError::FileError { + file: expected_output_file.clone(), + })?; + let mut expected_output = String::new(); + file_handle + .read_to_string(&mut expected_output) + .map_err(|_| TestRunError::FileError { + file: expected_output_file.clone(), + })?; + + if expected_output != run.output { + Err(TestRunError::FileCmpError { + expected: expected_output.clone(), + got: run.output.clone(), + }) + } else if !run.exit_status.success() { + Err(TestRunError::CommandError(run.clone())) + } else { + Ok(()) + } +} diff --git a/rtic/xtask/src/main.rs b/rtic/xtask/src/main.rs new file mode 100644 index 0000000..7eada91 --- /dev/null +++ b/rtic/xtask/src/main.rs @@ -0,0 +1,176 @@ +mod build; +mod command; + +use anyhow::bail; +use core::fmt; +use std::{ + error::Error, + ffi::OsString, + path::{Path, PathBuf}, + process, + process::ExitStatus, + str, +}; +use structopt::StructOpt; + +use crate::{ + build::init_build_dir, + command::{run_command, run_successful, BuildMode, CargoCommand}, +}; + +const ARMV6M: &str = "thumbv6m-none-eabi"; +const ARMV7M: &str = "thumbv7m-none-eabi"; + +#[derive(Debug, StructOpt)] +struct Options { + #[structopt(short, long)] + target: String, +} + +#[derive(Debug, Clone)] +pub struct RunResult { + exit_status: ExitStatus, + output: String, +} + +#[derive(Debug)] +pub enum TestRunError { + FileCmpError { expected: String, got: String }, + FileError { file: String }, + PathConversionError(OsString), + CommandError(RunResult), + IncompatibleCommand, +} + +impl fmt::Display for TestRunError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + TestRunError::FileCmpError { expected, got } => { + writeln!(f, "Differing output in files.")?; + writeln!(f, "")?; + writeln!(f, "Expected:")?; + writeln!(f, "{}", expected)?; + writeln!(f, "")?; + writeln!(f, "Got:")?; + write!(f, "{}", got) + } + TestRunError::FileError { file } => { + write!(f, "File error on: {}", file) + } + TestRunError::CommandError(e) => { + write!( + f, + "Command failed with exit status {}: {}", + e.exit_status, e.output + ) + } + TestRunError::PathConversionError(p) => { + write!(f, "Can't convert path from `OsString` to `String`: {:?}", p) + } + TestRunError::IncompatibleCommand => { + write!(f, "Can't run that command in this context") + } + } + } +} + +impl Error for TestRunError {} + +fn main() -> anyhow::Result<()> { + // if there's an `xtask` folder, we're *probably* at the root of this repo (we can't just + // check the name of `env::current_dir()` because people might clone it into a different name) + let probably_running_from_repo_root = Path::new("./xtask").exists(); + if probably_running_from_repo_root == false { + bail!("xtasks can only be executed from the root of the `cortex-m-rtic` repository"); + } + + let targets = [ARMV7M, ARMV6M]; + + let examples: Vec<_> = std::fs::read_dir("./examples")? + .filter_map(|p| p.ok()) + .map(|p| p.path()) + .filter(|p| p.display().to_string().ends_with(".rs")) + .map(|path| path.file_stem().unwrap().to_str().unwrap().to_string()) + .collect(); + + println!("examples: {examples:?}"); + + let opts = Options::from_args(); + let target = &opts.target; + + init_build_dir()?; + + if target == "all" { + for t in targets { + run_test(t, &examples)?; + } + } else if targets.contains(&target.as_str()) { + run_test(&target, &examples)?; + } else { + eprintln!( + "The target you specified is not available. Available targets are:\ + \n{:?}\n\ + as well as `all` (testing on all of the above)", + targets + ); + process::exit(1); + } + + Ok(()) +} + +fn run_test(target: &str, examples: &[String]) -> anyhow::Result<()> { + arm_example(&CargoCommand::BuildAll { + target, + features: None, + mode: BuildMode::Release, + })?; + + for example in examples { + let cmd = CargoCommand::Run { + example, + target, + features: None, + mode: BuildMode::Release, + }; + + arm_example(&cmd)?; + } + + Ok(()) +} + +// run example binary `example` +fn arm_example(command: &CargoCommand) -> anyhow::Result<()> { + match *command { + CargoCommand::Run { example, .. } => { + let run_file = format!("{}.run", example); + let expected_output_file = ["ci", "expected", &run_file] + .iter() + .collect::() + .into_os_string() + .into_string() + .map_err(|e| TestRunError::PathConversionError(e))?; + + // command is either build or run + let cargo_run_result = run_command(&command)?; + println!("{}", cargo_run_result.output); + + match &command { + CargoCommand::Run { .. } => { + run_successful(&cargo_run_result, expected_output_file)?; + } + _ => (), + } + + Ok(()) + } + CargoCommand::BuildAll { .. } => { + // command is either build or run + let cargo_run_result = run_command(&command)?; + println!("{}", cargo_run_result.output); + + Ok(()) + } // _ => Err(anyhow::Error::new(TestRunError::IncompatibleCommand)), + } +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml deleted file mode 100644 index e28b55d..0000000 --- a/rust-toolchain.toml +++ /dev/null @@ -1,4 +0,0 @@ -[toolchain] -channel = "nightly" -components = [ "rust-src", "rustfmt", "llvm-tools-preview" ] -targets = [ "thumbv6m-none-eabi", "thumbv7m-none-eabi" ] diff --git a/src/export.rs b/src/export.rs deleted file mode 100644 index cdca972..0000000 --- a/src/export.rs +++ /dev/null @@ -1,324 +0,0 @@ -pub use bare_metal::CriticalSection; -pub use cortex_m::{ - asm::nop, - asm::wfi, - interrupt, - peripheral::{scb::SystemHandler, DWT, NVIC, SCB, SYST}, - Peripherals, -}; -//pub use portable_atomic as atomic; -pub use atomic_polyfill as atomic; - -pub mod executor; - -/// Mask is used to store interrupt masks on systems without a BASEPRI register (M0, M0+, M23). -/// It needs to be large enough to cover all the relevant interrupts in use. -/// For M0/M0+ there are only 32 interrupts so we only need one u32 value. -/// For M23 there can be as many as 480 interrupts. -/// Rather than providing space for all possible interrupts, we just detect the highest interrupt in -/// use at compile time and allocate enough u32 chunks to cover them. -#[derive(Copy, Clone)] -pub struct Mask([u32; M]); - -impl core::ops::BitOrAssign for Mask { - fn bitor_assign(&mut self, rhs: Self) { - for i in 0..M { - self.0[i] |= rhs.0[i]; - } - } -} - -#[cfg(not(have_basepri))] -impl Mask { - /// Set a bit inside a Mask. - const fn set_bit(mut self, bit: u32) -> Self { - let block = bit / 32; - - if block as usize >= M { - panic!("Generating masks for thumbv6/thumbv8m.base failed! Are you compiling for thumbv6 on an thumbv7 MCU or using an unsupported thumbv8m.base MCU?"); - } - - let offset = bit - (block * 32); - self.0[block as usize] |= 1 << offset; - self - } -} - -#[cfg(have_basepri)] -use cortex_m::register::basepri; - -#[cfg(have_basepri)] -#[inline(always)] -pub fn run(priority: u8, f: F) -where - F: FnOnce(), -{ - if priority == 1 { - // If the priority of this interrupt is `1` then BASEPRI can only be `0` - f(); - unsafe { basepri::write(0) } - } else { - let initial = basepri::read(); - f(); - unsafe { basepri::write(initial) } - } -} - -#[cfg(not(have_basepri))] -#[inline(always)] -pub fn run(_priority: u8, f: F) -where - F: FnOnce(), -{ - f(); -} - -/// Const helper to check architecture -pub const fn have_basepri() -> bool { - #[cfg(have_basepri)] - { - true - } - - #[cfg(not(have_basepri))] - { - false - } -} - -#[inline(always)] -pub fn assert_send() -where - T: Send, -{ -} - -#[inline(always)] -pub fn assert_sync() -where - T: Sync, -{ -} - -/// Lock implementation using BASEPRI and global Critical Section (CS) -/// -/// # Safety -/// -/// The system ceiling is raised from current to ceiling -/// by either -/// - raising the BASEPRI to the ceiling value, or -/// - disable all interrupts in case we want to -/// mask interrupts with maximum priority -/// -/// Dereferencing a raw pointer inside CS -/// -/// The priority.set/priority.get can safely be outside the CS -/// as being a context local cell (not affected by preemptions). -/// It is merely used in order to omit masking in case current -/// priority is current priority >= ceiling. -/// -/// Lock Efficiency: -/// Experiments validate (sub)-zero cost for CS implementation -/// (Sub)-zero as: -/// - Either zero OH (lock optimized out), or -/// - Amounting to an optimal assembly implementation -/// - The BASEPRI value is folded to a constant at compile time -/// - CS entry, single assembly instruction to write BASEPRI -/// - CS exit, single assembly instruction to write BASEPRI -/// - priority.set/get optimized out (their effect not) -/// - On par or better than any handwritten implementation of SRP -/// -/// Limitations: -/// The current implementation reads/writes BASEPRI once -/// even in some edge cases where this may be omitted. -/// Total OH of per task is max 2 clock cycles, negligible in practice -/// but can in theory be fixed. -/// -#[cfg(have_basepri)] -#[inline(always)] -pub unsafe fn lock( - ptr: *mut T, - ceiling: u8, - nvic_prio_bits: u8, - _mask: &[Mask; 3], - f: impl FnOnce(&mut T) -> R, -) -> R { - if ceiling == (1 << nvic_prio_bits) { - let r = interrupt::free(|_| f(&mut *ptr)); - r - } else { - let current = basepri::read(); - basepri::write(logical2hw(ceiling, nvic_prio_bits)); - let r = f(&mut *ptr); - basepri::write(current); - r - } -} - -/// Lock implementation using interrupt masking -/// -/// # Safety -/// -/// The system ceiling is raised from current to ceiling -/// by computing a 32 bit `mask` (1 bit per interrupt) -/// 1: ceiling >= priority > current -/// 0: else -/// -/// On CS entry, `clear_enable_mask(mask)` disables interrupts -/// On CS exit, `set_enable_mask(mask)` re-enables interrupts -/// -/// The priority.set/priority.get can safely be outside the CS -/// as being a context local cell (not affected by preemptions). -/// It is merely used in order to omit masking in case -/// current priority >= ceiling. -/// -/// Dereferencing a raw pointer is done safely inside the CS -/// -/// Lock Efficiency: -/// Early experiments validate (sub)-zero cost for CS implementation -/// (Sub)-zero as: -/// - Either zero OH (lock optimized out), or -/// - Amounting to an optimal assembly implementation -/// - if ceiling == (1 << nvic_prio_bits) -/// - we execute the closure in a global critical section (interrupt free) -/// - CS entry cost, single write to core register -/// - CS exit cost, single write to core register -/// else -/// - The `mask` value is folded to a constant at compile time -/// - CS entry, single write of the 32 bit `mask` to the `icer` register -/// - CS exit, single write of the 32 bit `mask` to the `iser` register -/// - priority.set/get optimized out (their effect not) -/// - On par or better than any hand written implementation of SRP -/// -/// Limitations: -/// Current implementation does not allow for tasks with shared resources -/// to be bound to exception handlers, as these cannot be masked in HW. -/// -/// Possible solutions: -/// - Mask exceptions by global critical sections (interrupt::free) -/// - Temporary lower exception priority -/// -/// These possible solutions are set goals for future work -#[cfg(not(have_basepri))] -#[inline(always)] -pub unsafe fn lock( - ptr: *mut T, - ceiling: u8, - _nvic_prio_bits: u8, - masks: &[Mask; 3], - f: impl FnOnce(&mut T) -> R, -) -> R { - if ceiling >= 4 { - // safe to manipulate outside critical section - // execute closure under protection of raised system ceiling - - // safe to manipulate outside critical section - interrupt::free(|_| f(&mut *ptr)) - } else { - // safe to manipulate outside critical section - let mask = compute_mask(0, ceiling, masks); - clear_enable_mask(mask); - - // execute closure under protection of raised system ceiling - let r = f(&mut *ptr); - - set_enable_mask(mask); - - // safe to manipulate outside critical section - r - } -} - -#[cfg(not(have_basepri))] -#[inline(always)] -fn compute_mask(from_prio: u8, to_prio: u8, masks: &[Mask; 3]) -> Mask { - let mut res = Mask([0; M]); - masks[from_prio as usize..to_prio as usize] - .iter() - .for_each(|m| res |= *m); - res -} - -// enables interrupts -#[cfg(not(have_basepri))] -#[inline(always)] -unsafe fn set_enable_mask(mask: Mask) { - for i in 0..M { - // This check should involve compile time constants and be optimized out. - if mask.0[i] != 0 { - (*NVIC::PTR).iser[i].write(mask.0[i]); - } - } -} - -// disables interrupts -#[cfg(not(have_basepri))] -#[inline(always)] -unsafe fn clear_enable_mask(mask: Mask) { - for i in 0..M { - // This check should involve compile time constants and be optimized out. - if mask.0[i] != 0 { - (*NVIC::PTR).icer[i].write(mask.0[i]); - } - } -} - -#[inline] -#[must_use] -pub fn logical2hw(logical: u8, nvic_prio_bits: u8) -> u8 { - ((1 << nvic_prio_bits) - logical) << (8 - nvic_prio_bits) -} - -#[cfg(have_basepri)] -pub const fn create_mask(_: [u32; N]) -> Mask { - Mask([0; M]) -} - -#[cfg(not(have_basepri))] -pub const fn create_mask(list_of_shifts: [u32; N]) -> Mask { - let mut mask = Mask([0; M]); - let mut i = 0; - - while i < N { - let shift = list_of_shifts[i]; - i += 1; - mask = mask.set_bit(shift); - } - - mask -} - -#[cfg(have_basepri)] -pub const fn compute_mask_chunks(_: [u32; L]) -> usize { - 0 -} - -/// Compute the number of u32 chunks needed to store the Mask value. -/// On M0, M0+ this should always end up being 1. -/// On M23 we will pick a number that allows us to store the highest index used by the code. -/// This means the amount of overhead will vary based on the actually interrupts used by the code. -#[cfg(not(have_basepri))] -pub const fn compute_mask_chunks(ids: [u32; L]) -> usize { - let mut max: usize = 0; - let mut i = 0; - - while i < L { - let id = ids[i] as usize; - i += 1; - - if id > max { - max = id; - } - } - (max + 32) / 32 -} - -#[cfg(have_basepri)] -pub const fn no_basepri_panic() { - // For non-v6 all is fine -} - -#[cfg(not(have_basepri))] -pub const fn no_basepri_panic() { - panic!("Exceptions with shared resources are not allowed when compiling for thumbv6 or thumbv8m.base. Use local resources or `#[lock_free]` shared resources"); -} diff --git a/src/export/executor.rs b/src/export/executor.rs deleted file mode 100644 index e64cc43..0000000 --- a/src/export/executor.rs +++ /dev/null @@ -1,110 +0,0 @@ -use super::atomic::{AtomicBool, Ordering}; -use core::{ - cell::UnsafeCell, - future::Future, - mem::{self, MaybeUninit}, - pin::Pin, - task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, -}; - -static WAKER_VTABLE: RawWakerVTable = - RawWakerVTable::new(waker_clone, waker_wake, waker_wake, waker_drop); - -unsafe fn waker_clone(p: *const ()) -> RawWaker { - RawWaker::new(p, &WAKER_VTABLE) -} - -unsafe fn waker_wake(p: *const ()) { - // The only thing we need from a waker is the function to call to pend the async - // dispatcher. - let f: fn() = mem::transmute(p); - f(); -} - -unsafe fn waker_drop(_: *const ()) { - // nop -} - -//============ -// AsyncTaskExecutor - -/// Executor for an async task. -pub struct AsyncTaskExecutor { - // `task` is protected by the `running` flag. - task: UnsafeCell>, - running: AtomicBool, - pending: AtomicBool, -} - -unsafe impl Sync for AsyncTaskExecutor {} - -impl AsyncTaskExecutor { - /// Create a new executor. - #[inline(always)] - pub const fn new() -> Self { - Self { - task: UnsafeCell::new(MaybeUninit::uninit()), - running: AtomicBool::new(false), - pending: AtomicBool::new(false), - } - } - - /// Check if there is an active task in the executor. - #[inline(always)] - pub fn is_running(&self) -> bool { - self.running.load(Ordering::Relaxed) - } - - /// Checks if a waker has pended the executor and simultaneously clears the flag. - #[inline(always)] - fn check_and_clear_pending(&self) -> bool { - // Ordering::Acquire to enforce that update of task is visible to poll - self.pending - .compare_exchange(true, false, Ordering::Acquire, Ordering::Relaxed) - .is_ok() - } - - // Used by wakers to indicate that the executor needs to run. - #[inline(always)] - pub fn set_pending(&self) { - self.pending.store(true, Ordering::Release); - } - - /// Spawn a future - #[inline(always)] - pub fn spawn(&self, future: impl Fn() -> F) -> bool { - // Try to reserve the executor for a future. - if self - .running - .compare_exchange(false, true, Ordering::AcqRel, Ordering::Relaxed) - .is_ok() - { - // This unsafe is protected by `running` being false and the atomic setting it to true. - unsafe { - self.task.get().write(MaybeUninit::new(future())); - } - self.set_pending(); - - true - } else { - false - } - } - - /// Poll the future in the executor. - #[inline(always)] - pub fn poll(&self, wake: fn()) { - if self.is_running() && self.check_and_clear_pending() { - let waker = unsafe { Waker::from_raw(RawWaker::new(wake as *const (), &WAKER_VTABLE)) }; - let mut cx = Context::from_waker(&waker); - let future = unsafe { Pin::new_unchecked(&mut *(self.task.get() as *mut F)) }; - - match future.poll(&mut cx) { - Poll::Ready(_) => { - self.running.store(false, Ordering::Release); - } - Poll::Pending => {} - } - } - } -} diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index e8b8140..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,121 +0,0 @@ -//! Real-Time Interrupt-driven Concurrency (RTIC) framework for ARM Cortex-M microcontrollers. -//! -//! **IMPORTANT**: This crate is published as [`cortex-m-rtic`] on crates.io but the name of the -//! library is `rtic`. -//! -//! [`cortex-m-rtic`]: https://crates.io/crates/cortex-m-rtic -//! -//! The user level documentation can be found [here]. -//! -//! [here]: https://rtic.rs -//! -//! Don't forget to check the documentation of the `#[app]` attribute (listed under the reexports -//! section), which is the main component of the framework. -//! -//! # Minimum Supported Rust Version (MSRV) -//! -//! This crate is compiled and tested with the latest toolchain (rolling) as of the release date. -//! If you run into compilation errors, try the latest stable release of the rust toolchain. -//! -//! # Semantic Versioning -//! -//! Like the Rust project, this crate adheres to [SemVer]: breaking changes in the API and semantics -//! require a *semver bump* (since 1.0.0 a new major version release), with the exception of breaking changes -//! that fix soundness issues -- those are considered bug fixes and can be landed in a new patch -//! release. -//! -//! [SemVer]: https://semver.org/spec/v2.0.0.html - -#![deny(missing_docs)] -#![deny(rust_2021_compatibility)] -#![deny(rust_2018_compatibility)] -#![deny(rust_2018_idioms)] -#![no_std] -#![doc( - html_logo_url = "https://raw.githubusercontent.com/rtic-rs/cortex-m-rtic/master/book/en/src/RTIC.svg", - html_favicon_url = "https://raw.githubusercontent.com/rtic-rs/cortex-m-rtic/master/book/en/src/RTIC.svg" -)] -//deny_warnings_placeholder_for_ci -#![allow(clippy::inline_always)] - -use cortex_m::{interrupt::InterruptNumber, peripheral::NVIC}; -pub use rtic_core::{prelude as mutex_prelude, Exclusive, Mutex}; -pub use rtic_macros::app; -pub use rtic_monotonic::{self, Monotonic}; - -/// module `mutex::prelude` provides `Mutex` and multi-lock variants. Recommended over `mutex_prelude` -pub mod mutex { - pub use rtic_core::prelude; - pub use rtic_core::Mutex; -} - -#[doc(hidden)] -pub mod export; - -/// Sets the given `interrupt` as pending -/// -/// This is a convenience function around -/// [`NVIC::pend`](../cortex_m/peripheral/struct.NVIC.html#method.pend) -pub fn pend(interrupt: I) -where - I: InterruptNumber, -{ - NVIC::pend(interrupt); -} - -use core::cell::UnsafeCell; - -/// Internal replacement for `static mut T` -/// -/// Used to represent RTIC Resources -/// -/// Soundness: -/// 1) Unsafe API for internal use only -/// 2) ``get_mut(&self) -> *mut T`` -/// returns a raw mutable pointer to the inner T -/// casting to &mut T is under control of RTIC -/// RTIC ensures &mut T to be unique under Rust aliasing rules. -/// -/// Implementation uses the underlying ``UnsafeCell`` -/// self.0.get() -> *mut T -/// -/// 3) get(&self) -> *const T -/// returns a raw immutable (const) pointer to the inner T -/// casting to &T is under control of RTIC -/// RTIC ensures &T to be shared under Rust aliasing rules. -/// -/// Implementation uses the underlying ``UnsafeCell`` -/// self.0.get() -> *mut T, demoted to *const T -/// -#[repr(transparent)] -pub struct RacyCell(UnsafeCell); - -impl RacyCell { - /// Create a ``RacyCell`` - #[inline(always)] - pub const fn new(value: T) -> Self { - RacyCell(UnsafeCell::new(value)) - } - - /// Get `*mut T` - /// - /// # Safety - /// - /// See documentation notes for [`RacyCell`] - #[inline(always)] - pub unsafe fn get_mut(&self) -> *mut T { - self.0.get() - } - - /// Get `*const T` - /// - /// # Safety - /// - /// See documentation notes for [`RacyCell`] - #[inline(always)] - pub unsafe fn get(&self) -> *const T { - self.0.get() - } -} - -unsafe impl Sync for RacyCell {} diff --git a/tests/tests.rs b/tests/tests.rs deleted file mode 100644 index 9fb88a1..0000000 --- a/tests/tests.rs +++ /dev/null @@ -1,7 +0,0 @@ -use trybuild::TestCases; - -#[test] -fn ui() { - let t = TestCases::new(); - t.compile_fail("ui/*.rs"); -} diff --git a/ui/exception-invalid.rs b/ui/exception-invalid.rs deleted file mode 100644 index 4f8e943..0000000 --- a/ui/exception-invalid.rs +++ /dev/null @@ -1,18 +0,0 @@ -#![no_main] - -#[rtic::app(device = lm3s6965)] -mod app { - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(cx: init::Context) -> (Shared, Local) { - (Shared {}, Local {}) - } - - #[task(binds = NonMaskableInt)] - fn nmi(_: nmi::Context) {} -} diff --git a/ui/exception-invalid.stderr b/ui/exception-invalid.stderr deleted file mode 100644 index 3212368..0000000 --- a/ui/exception-invalid.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: only exceptions with configurable priority can be used as hardware tasks - --> $DIR/exception-invalid.rs:17:8 - | -17 | fn nmi(_: nmi::Context) {} - | ^^^ diff --git a/ui/extern-interrupt-not-enough.rs b/ui/extern-interrupt-not-enough.rs deleted file mode 100644 index 94c8ee1..0000000 --- a/ui/extern-interrupt-not-enough.rs +++ /dev/null @@ -1,18 +0,0 @@ -#![no_main] - -#[rtic::app(device = lm3s6965)] -mod app { - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(cx: init::Context) -> (Shared, Local) { - (Shared {}, Local {}) - } - - #[task] - async fn a(_: a::Context) {} -} diff --git a/ui/extern-interrupt-not-enough.stderr b/ui/extern-interrupt-not-enough.stderr deleted file mode 100644 index e6c01b9..0000000 --- a/ui/extern-interrupt-not-enough.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: not enough interrupts to dispatch all software tasks (need: 1; given: 0) - --> ui/extern-interrupt-not-enough.rs:17:14 - | -17 | async fn a(_: a::Context) {} - | ^ diff --git a/ui/extern-interrupt-used.rs b/ui/extern-interrupt-used.rs deleted file mode 100644 index 42de4c0..0000000 --- a/ui/extern-interrupt-used.rs +++ /dev/null @@ -1,18 +0,0 @@ -#![no_main] - -#[rtic::app(device = lm3s6965, dispatchers = [UART0])] -mod app { - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(cx: init::Context) -> (Shared, Local) { - (Shared {}, Local {}) - } - - #[task(binds = UART0)] - fn a(_: a::Context) {} -} diff --git a/ui/extern-interrupt-used.stderr b/ui/extern-interrupt-used.stderr deleted file mode 100644 index 7565739..0000000 --- a/ui/extern-interrupt-used.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: dispatcher interrupts can't be used as hardware tasks - --> $DIR/extern-interrupt-used.rs:16:20 - | -16 | #[task(binds = UART0)] - | ^^^^^ diff --git a/ui/task-priority-too-high.rs b/ui/task-priority-too-high.rs deleted file mode 100644 index 44e4a25..0000000 --- a/ui/task-priority-too-high.rs +++ /dev/null @@ -1,44 +0,0 @@ -#![no_main] - -#[rtic::app(device = lm3s6965)] -mod app { - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(cx: init::Context) -> (Shared, Local) { - (Shared {}, Local {}) - } - - #[task(binds = GPIOA, priority = 1)] - fn gpioa(_: gpioa::Context) {} - - #[task(binds = GPIOB, priority = 2)] - fn gpiob(_: gpiob::Context) {} - - #[task(binds = GPIOC, priority = 3)] - fn gpioc(_: gpioc::Context) {} - - #[task(binds = GPIOD, priority = 4)] - fn gpiod(_: gpiod::Context) {} - - #[task(binds = GPIOE, priority = 5)] - fn gpioe(_: gpioe::Context) {} - - #[task(binds = UART0, priority = 6)] - fn uart0(_: uart0::Context) {} - - #[task(binds = UART1, priority = 7)] - fn uart1(_: uart1::Context) {} - - // OK, this is the maximum priority supported by the device - #[task(binds = SSI0, priority = 8)] - fn ssi0(_: ssi0::Context) {} - - // this value is too high! - #[task(binds = I2C0, priority = 9)] - fn i2c0(_: i2c0::Context) {} -} diff --git a/ui/task-priority-too-high.stderr b/ui/task-priority-too-high.stderr deleted file mode 100644 index 1256377..0000000 --- a/ui/task-priority-too-high.stderr +++ /dev/null @@ -1,15 +0,0 @@ -warning: unused variable: `cx` - --> ui/task-priority-too-high.rs:12:13 - | -12 | fn init(cx: init::Context) -> (Shared, Local) { - | ^^ help: if this is intentional, prefix it with an underscore: `_cx` - | - = note: `#[warn(unused_variables)]` on by default - -error[E0080]: evaluation of constant value failed - --> ui/task-priority-too-high.rs:3:1 - | -3 | #[rtic::app(device = lm3s6965)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Maximum priority used by interrupt vector 'I2C0' is more than supported by hardware', $DIR/ui/task-priority-too-high.rs:3:1 - | - = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::core::panic` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/ui/unknown-interrupt.rs b/ui/unknown-interrupt.rs deleted file mode 100644 index 3c6c69f..0000000 --- a/ui/unknown-interrupt.rs +++ /dev/null @@ -1,15 +0,0 @@ -#![no_main] - -#[rtic::app(device = lm3s6965, dispatchers = [UnknownInterrupt])] -mod app { - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(cx: init::Context) -> (Shared, Local) { - (Shared {}, Local {}) - } -} diff --git a/ui/unknown-interrupt.stderr b/ui/unknown-interrupt.stderr deleted file mode 100644 index c7d3269..0000000 --- a/ui/unknown-interrupt.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error[E0599]: no variant or associated item named `UnknownInterrupt` found for enum `Interrupt` in the current scope - --> $DIR/unknown-interrupt.rs:3:47 - | -3 | #[rtic::app(device = lm3s6965, dispatchers = [UnknownInterrupt])] - | ^^^^^^^^^^^^^^^^ variant or associated item not found in `Interrupt` diff --git a/ui/v6m-interrupt-not-enough.rs_no b/ui/v6m-interrupt-not-enough.rs_no deleted file mode 100644 index 3fbf3cf..0000000 --- a/ui/v6m-interrupt-not-enough.rs_no +++ /dev/null @@ -1,54 +0,0 @@ -//! v6m-interrupt-not-enough.rs_no (not run atm) -//! -//! Expected behavior: -//! should pass -//! > cargo build --example m0_perf_err --target thumbv7m-none-eabi --release -//! -//! should fail -//! > cargo build --example m0_perf_err --target thumbv6m-none-eabi --release -//! Compiling cortex-m-rtic v1.0.0 (/home/pln/rust/rtic/cortex-m-rtic) -//! error[E0308]: mismatched types -//! --> examples/m0_perf_err.rs:25:1 -//! | -//! 25 | #[rtic::app(device = lm3s6965)] -//! | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected an array with a fixed size of 4 elements, found one with 5 elements -//! | -//! = note: this error originates in the attribute macro `rtic::app` (in Nightly builds, run with -Z macro-backtrace for more info) - -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965)] -mod app { - - use cortex_m_semihosting::debug; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { - (Shared {}, Local {}, init::Monotonics()) - } - - #[inline(never)] - #[idle] - fn idle(_cx: idle::Context) -> ! { - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - - loop { - cortex_m::asm::nop(); - } - } - - // priority to high for v6m - #[task(binds = GPIOA, priority = 5)] - fn t0(_cx: t0::Context) {} -} diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml deleted file mode 100644 index f1c468e..0000000 --- a/xtask/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "xtask" -version = "0.1.0" -edition = "2018" - -[dependencies] -anyhow = "1.0.43" -os_pipe = "1.1.2" -structopt = "0.3.22" diff --git a/xtask/src/build.rs b/xtask/src/build.rs deleted file mode 100644 index 148a9fd..0000000 --- a/xtask/src/build.rs +++ /dev/null @@ -1,13 +0,0 @@ -use std::{fs, path::Path}; - -const HEX_BUILD_ROOT: &str = "ci/builds"; - -/// make sure we're starting with a clean,but existing slate -pub fn init_build_dir() -> anyhow::Result<()> { - if Path::new(HEX_BUILD_ROOT).exists() { - fs::remove_dir_all(HEX_BUILD_ROOT) - .map_err(|_| anyhow::anyhow!("Could not clear out directory: {}", HEX_BUILD_ROOT))?; - } - fs::create_dir_all(HEX_BUILD_ROOT) - .map_err(|_| anyhow::anyhow!("Could not create directory: {}", HEX_BUILD_ROOT)) -} diff --git a/xtask/src/command.rs b/xtask/src/command.rs deleted file mode 100644 index 4e90369..0000000 --- a/xtask/src/command.rs +++ /dev/null @@ -1,172 +0,0 @@ -use crate::{RunResult, TestRunError}; -use core::fmt; -use os_pipe::pipe; -use std::{fs::File, io::Read, process::Command}; - -#[allow(dead_code)] -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum BuildMode { - Release, - Debug, -} - -#[derive(Debug)] -pub enum CargoCommand<'a> { - Run { - example: &'a str, - target: &'a str, - features: Option<&'a str>, - mode: BuildMode, - }, - BuildAll { - target: &'a str, - features: Option<&'a str>, - mode: BuildMode, - }, - // Size { - // example_paths: Vec<&'a Path>, - // }, - // Clean, -} - -impl<'a> CargoCommand<'a> { - fn name(&self) -> &str { - match self { - CargoCommand::Run { .. } => "run", - // CargoCommand::Size { example_paths: _ } => "rust-size", - CargoCommand::BuildAll { .. } => "build", - } - } - - pub fn args(&self) -> Vec<&str> { - match self { - CargoCommand::Run { - example, - target, - features, - mode, - } => { - let mut args = vec![ - "+nightly", - self.name(), - "--example", - example, - "--target", - target, - "--features", - "test-critical-section", - ]; - - if let Some(feature_name) = features { - args.extend_from_slice(&["--features", feature_name]); - } - if let Some(flag) = mode.to_flag() { - args.push(flag); - } - args - } - CargoCommand::BuildAll { - target, - features, - mode, - } => { - let mut args = vec![ - "+nightly", - self.name(), - "--examples", - "--target", - target, - "--features", - "test-critical-section", - ]; - - if let Some(feature_name) = features { - args.extend_from_slice(&["--features", feature_name]); - } - if let Some(flag) = mode.to_flag() { - args.push(flag); - } - args - } // CargoCommand::Size { example_paths } => { - // example_paths.iter().map(|p| p.to_str().unwrap()).collect() - // } - } - } - - pub fn command(&self) -> &str { - match self { - // we need to cheat a little here: - // `cargo size` can't be ran on multiple files, so we're using `rust-size` instead – - // which isn't a command that starts wizh `cargo`. So we're sneakily swapping them out :) - // CargoCommand::Size { .. } => "rust-size", - _ => "cargo", - } - } -} - -impl BuildMode { - pub fn to_flag(&self) -> Option<&str> { - match self { - BuildMode::Release => Some("--release"), - BuildMode::Debug => None, - } - } -} - -impl fmt::Display for BuildMode { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let cmd = match self { - BuildMode::Release => "release", - BuildMode::Debug => "debug", - }; - - write!(f, "{}", cmd) - } -} - -pub fn run_command(command: &CargoCommand) -> anyhow::Result { - let (mut reader, writer) = pipe()?; - println!("👟 {} {}", command.command(), command.args().join(" ")); - - let mut handle = Command::new(command.command()) - .args(command.args()) - .stdout(writer) - .spawn()?; - - // retrieve output and clean up - let mut output = String::new(); - reader.read_to_string(&mut output)?; - let exit_status = handle.wait()?; - - Ok(RunResult { - exit_status, - output, - }) -} - -/// Check if `run` was successful. -/// returns Ok in case the run went as expected, -/// Err otherwise -pub fn run_successful(run: &RunResult, expected_output_file: String) -> Result<(), TestRunError> { - let mut file_handle = - File::open(expected_output_file.clone()).map_err(|_| TestRunError::FileError { - file: expected_output_file.clone(), - })?; - let mut expected_output = String::new(); - file_handle - .read_to_string(&mut expected_output) - .map_err(|_| TestRunError::FileError { - file: expected_output_file.clone(), - })?; - - if expected_output != run.output { - Err(TestRunError::FileCmpError { - expected: expected_output.clone(), - got: run.output.clone(), - }) - } else if !run.exit_status.success() { - Err(TestRunError::CommandError(run.clone())) - } else { - Ok(()) - } -} diff --git a/xtask/src/main.rs b/xtask/src/main.rs deleted file mode 100644 index 7eada91..0000000 --- a/xtask/src/main.rs +++ /dev/null @@ -1,176 +0,0 @@ -mod build; -mod command; - -use anyhow::bail; -use core::fmt; -use std::{ - error::Error, - ffi::OsString, - path::{Path, PathBuf}, - process, - process::ExitStatus, - str, -}; -use structopt::StructOpt; - -use crate::{ - build::init_build_dir, - command::{run_command, run_successful, BuildMode, CargoCommand}, -}; - -const ARMV6M: &str = "thumbv6m-none-eabi"; -const ARMV7M: &str = "thumbv7m-none-eabi"; - -#[derive(Debug, StructOpt)] -struct Options { - #[structopt(short, long)] - target: String, -} - -#[derive(Debug, Clone)] -pub struct RunResult { - exit_status: ExitStatus, - output: String, -} - -#[derive(Debug)] -pub enum TestRunError { - FileCmpError { expected: String, got: String }, - FileError { file: String }, - PathConversionError(OsString), - CommandError(RunResult), - IncompatibleCommand, -} - -impl fmt::Display for TestRunError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - TestRunError::FileCmpError { expected, got } => { - writeln!(f, "Differing output in files.")?; - writeln!(f, "")?; - writeln!(f, "Expected:")?; - writeln!(f, "{}", expected)?; - writeln!(f, "")?; - writeln!(f, "Got:")?; - write!(f, "{}", got) - } - TestRunError::FileError { file } => { - write!(f, "File error on: {}", file) - } - TestRunError::CommandError(e) => { - write!( - f, - "Command failed with exit status {}: {}", - e.exit_status, e.output - ) - } - TestRunError::PathConversionError(p) => { - write!(f, "Can't convert path from `OsString` to `String`: {:?}", p) - } - TestRunError::IncompatibleCommand => { - write!(f, "Can't run that command in this context") - } - } - } -} - -impl Error for TestRunError {} - -fn main() -> anyhow::Result<()> { - // if there's an `xtask` folder, we're *probably* at the root of this repo (we can't just - // check the name of `env::current_dir()` because people might clone it into a different name) - let probably_running_from_repo_root = Path::new("./xtask").exists(); - if probably_running_from_repo_root == false { - bail!("xtasks can only be executed from the root of the `cortex-m-rtic` repository"); - } - - let targets = [ARMV7M, ARMV6M]; - - let examples: Vec<_> = std::fs::read_dir("./examples")? - .filter_map(|p| p.ok()) - .map(|p| p.path()) - .filter(|p| p.display().to_string().ends_with(".rs")) - .map(|path| path.file_stem().unwrap().to_str().unwrap().to_string()) - .collect(); - - println!("examples: {examples:?}"); - - let opts = Options::from_args(); - let target = &opts.target; - - init_build_dir()?; - - if target == "all" { - for t in targets { - run_test(t, &examples)?; - } - } else if targets.contains(&target.as_str()) { - run_test(&target, &examples)?; - } else { - eprintln!( - "The target you specified is not available. Available targets are:\ - \n{:?}\n\ - as well as `all` (testing on all of the above)", - targets - ); - process::exit(1); - } - - Ok(()) -} - -fn run_test(target: &str, examples: &[String]) -> anyhow::Result<()> { - arm_example(&CargoCommand::BuildAll { - target, - features: None, - mode: BuildMode::Release, - })?; - - for example in examples { - let cmd = CargoCommand::Run { - example, - target, - features: None, - mode: BuildMode::Release, - }; - - arm_example(&cmd)?; - } - - Ok(()) -} - -// run example binary `example` -fn arm_example(command: &CargoCommand) -> anyhow::Result<()> { - match *command { - CargoCommand::Run { example, .. } => { - let run_file = format!("{}.run", example); - let expected_output_file = ["ci", "expected", &run_file] - .iter() - .collect::() - .into_os_string() - .into_string() - .map_err(|e| TestRunError::PathConversionError(e))?; - - // command is either build or run - let cargo_run_result = run_command(&command)?; - println!("{}", cargo_run_result.output); - - match &command { - CargoCommand::Run { .. } => { - run_successful(&cargo_run_result, expected_output_file)?; - } - _ => (), - } - - Ok(()) - } - CargoCommand::BuildAll { .. } => { - // command is either build or run - let cargo_run_result = run_command(&command)?; - println!("{}", cargo_run_result.output); - - Ok(()) - } // _ => Err(anyhow::Error::new(TestRunError::IncompatibleCommand)), - } -} -- cgit v1.2.3 From a3f48a524b94107a6e250f41f87f29c1c0d65821 Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Mon, 23 Jan 2023 20:14:50 +0100 Subject: Does CI work again? --- .github/workflows/build.yml | 19 ++++++++++++++++++- .github/workflows/changelog.yml | 22 ++++++++++++++++++++-- rtic-monotonics/CHANGELOG.md | 0 rtic-timer/CHANGELOG.md | 0 rtic/Cargo.toml | 2 +- 5 files changed, 39 insertions(+), 4 deletions(-) create mode 100644 rtic-monotonics/CHANGELOG.md create mode 100644 rtic-timer/CHANGELOG.md diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 35c0bff..1493c3f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,10 +22,11 @@ jobs: uses: actions/checkout@v3 - name: Fail on warnings + working-directory: ./rtic run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs macros/src/lib.rs - - name: cargo fmt --check + working-directory: ./rtic run: cargo fmt --all -- --check # Compilation check @@ -45,20 +46,24 @@ jobs: uses: actions/checkout@v3 - name: Install Rust ${{ matrix.toolchain }} + working-directory: ./rtic run: | rustup set profile minimal rustup override set ${{ matrix.toolchain }} - name: Configure Rust target (${{ matrix.target }}) + working-directory: ./rtic run: rustup target add ${{ matrix.target }} - name: Fail on warnings + working-directory: ./rtic run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs macros/src/lib.rs - name: Cache Dependencies uses: Swatinem/rust-cache@v2 - name: cargo check + working-directory: ./rtic run: cargo check --target=${{ matrix.target }} # Clippy @@ -70,15 +75,18 @@ jobs: uses: actions/checkout@v3 - name: Fail on warnings + working-directory: ./rtic run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs macros/src/lib.rs - name: Add Rust component clippy + working-directory: ./rtic run: rustup component add clippy - name: Cache Dependencies uses: Swatinem/rust-cache@v2 - name: cargo clippy + working-directory: ./rtic run: cargo clippy # Verify all examples, checks @@ -113,6 +121,7 @@ jobs: uses: Swatinem/rust-cache@v2 - name: Check the examples + working-directory: ./rtic run: cargo check --examples --target=${{ matrix.target }} # Verify the example output with run-pass tests @@ -154,9 +163,11 @@ jobs: sudo apt install -y qemu-system-arm - name: Fail on warnings + working-directory: ./rtic run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs macros/src/lib.rs - name: Run-pass tests + working-directory: ./rtic run: cargo xtask --target ${{ matrix.target }} # Check the correctness of macros/ crate @@ -185,9 +196,11 @@ jobs: uses: Swatinem/rust-cache@v2 - name: Fail on warnings + working-directory: ./rtic run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs macros/src/lib.rs - name: cargo check + working-directory: ./rtic run: cargo check --manifest-path macros/Cargo.toml --target=${{ matrix.target }} # Run the macros test-suite @@ -202,9 +215,11 @@ jobs: uses: Swatinem/rust-cache@v2 - name: Fail on warnings + working-directory: ./rtic run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs macros/src/lib.rs - name: cargo check + working-directory: ./rtic run: cargo test --manifest-path macros/Cargo.toml # Run test suite @@ -219,9 +234,11 @@ jobs: uses: Swatinem/rust-cache@v2 - name: Fail on warnings + working-directory: ./rtic run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs macros/src/lib.rs - name: Run cargo test + working-directory: ./rtic run: cargo test --test tests # # Build documentation, check links diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml index 74b821d..6e23a7a 100644 --- a/.github/workflows/changelog.yml +++ b/.github/workflows/changelog.yml @@ -18,10 +18,28 @@ jobs: - name: Checkout sources uses: actions/checkout@v3 - - name: Check that changelog updated + - name: Check that changelog updated (rtic) uses: dangoslen/changelog-enforcer@v3 with: - changeLogPath: CHANGELOG.md + changeLogPath: ./rtic/CHANGELOG.md + skipLabels: 'needs-changelog, skip-changelog' + missingUpdateErrorMessage: 'Please add a changelog entry in the CHANGELOG.md file.' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Check that changelog updated (rtic-timer) + uses: dangoslen/changelog-enforcer@v3 + with: + changeLogPath: ./rtic-timer/CHANGELOG.md + skipLabels: 'needs-changelog, skip-changelog' + missingUpdateErrorMessage: 'Please add a changelog entry in the CHANGELOG.md file.' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Check that changelog updated (rtic-monotonics) + uses: dangoslen/changelog-enforcer@v3 + with: + changeLogPath: ./rtic-monotonics/CHANGELOG.md skipLabels: 'needs-changelog, skip-changelog' missingUpdateErrorMessage: 'Please add a changelog entry in the CHANGELOG.md file.' env: diff --git a/rtic-monotonics/CHANGELOG.md b/rtic-monotonics/CHANGELOG.md new file mode 100644 index 0000000..e69de29 diff --git a/rtic-timer/CHANGELOG.md b/rtic-timer/CHANGELOG.md new file mode 100644 index 0000000..e69de29 diff --git a/rtic/Cargo.toml b/rtic/Cargo.toml index c22d023..6eb691d 100644 --- a/rtic/Cargo.toml +++ b/rtic/Cargo.toml @@ -51,7 +51,7 @@ codegen-units = 1 lto = true [workspace] -members = ["macros", "xtask", "rtic-timer"] +members = ["macros", "xtask"] # do not optimize proc-macro deps or build scripts [profile.dev.build-override] -- cgit v1.2.3 From 0f6ae7c1dd0ce10a83682a922bf68aca9121df1c Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Mon, 23 Jan 2023 20:28:28 +0100 Subject: Add gitignore for book --- book/.gitignore | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 book/.gitignore diff --git a/book/.gitignore b/book/.gitignore new file mode 100644 index 0000000..ee70b5e --- /dev/null +++ b/book/.gitignore @@ -0,0 +1,7 @@ +**/*.rs.bk +.#* +.gdb_history +/*/book +/target +Cargo.lock +*.hex -- cgit v1.2.3 From 71b5f9438e1beb5fe12b90415d9d6307e79c0cdf Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Mon, 23 Jan 2023 20:57:56 +0100 Subject: Fixed systick monotonic --- rtic-monotonics/Cargo.toml | 4 +- rtic-monotonics/src/lib.rs | 2 +- rtic-monotonics/src/systick_monotonic.rs | 133 ++++++++++++ rtic-time/.gitignore | 6 + rtic-time/CHANGELOG.md | 0 rtic-time/Cargo.toml | 10 + rtic-time/rust-toolchain.toml | 4 + rtic-time/src/lib.rs | 336 +++++++++++++++++++++++++++++++ rtic-time/src/linked_list.rs | 173 ++++++++++++++++ rtic-time/src/monotonic.rs | 60 ++++++ rtic-timer/.gitignore | 6 - rtic-timer/CHANGELOG.md | 0 rtic-timer/Cargo.toml | 10 - rtic-timer/rust-toolchain.toml | 4 - rtic-timer/src/lib.rs | 336 ------------------------------- rtic-timer/src/linked_list.rs | 173 ---------------- rtic-timer/src/monotonic.rs | 60 ------ 17 files changed, 725 insertions(+), 592 deletions(-) create mode 100644 rtic-time/.gitignore create mode 100644 rtic-time/CHANGELOG.md create mode 100644 rtic-time/Cargo.toml create mode 100644 rtic-time/rust-toolchain.toml create mode 100644 rtic-time/src/lib.rs create mode 100644 rtic-time/src/linked_list.rs create mode 100644 rtic-time/src/monotonic.rs delete mode 100644 rtic-timer/.gitignore delete mode 100644 rtic-timer/CHANGELOG.md delete mode 100644 rtic-timer/Cargo.toml delete mode 100644 rtic-timer/rust-toolchain.toml delete mode 100644 rtic-timer/src/lib.rs delete mode 100644 rtic-timer/src/linked_list.rs delete mode 100644 rtic-timer/src/monotonic.rs diff --git a/rtic-monotonics/Cargo.toml b/rtic-monotonics/Cargo.toml index 24448fb..68daba4 100644 --- a/rtic-monotonics/Cargo.toml +++ b/rtic-monotonics/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "rtic-timer" +name = "rtic-monotonics" version = "0.1.0" edition = "2021" @@ -9,4 +9,4 @@ edition = "2021" cortex-m = { version = "0.7.6" } embedded-hal-async = "0.2.0-alpha.0" fugit = { version = "0.3.6", features = ["defmt"] } -rtic-timer = { version = "1.0.0", path = "../rtic-timer" } +rtic-time = { version = "1.0.0", path = "../rtic-time" } diff --git a/rtic-monotonics/src/lib.rs b/rtic-monotonics/src/lib.rs index 88398ca..ce30c36 100644 --- a/rtic-monotonics/src/lib.rs +++ b/rtic-monotonics/src/lib.rs @@ -6,6 +6,6 @@ #![allow(incomplete_features)] #![feature(async_fn_in_trait)] -pub use rtic_timer::{Monotonic, TimeoutError, TimerQueue}; +pub use rtic_time::{Monotonic, TimeoutError, TimerQueue}; pub mod systick_monotonic; diff --git a/rtic-monotonics/src/systick_monotonic.rs b/rtic-monotonics/src/systick_monotonic.rs index 491cf81..7c713b6 100644 --- a/rtic-monotonics/src/systick_monotonic.rs +++ b/rtic-monotonics/src/systick_monotonic.rs @@ -1 +1,134 @@ //! ... + +use super::Monotonic; +pub use super::{TimeoutError, TimerQueue}; +use core::{ + ops::Deref, + sync::atomic::{AtomicU32, Ordering}, +}; +use cortex_m::peripheral::SYST; +use embedded_hal_async::delay::DelayUs; +use fugit::ExtU32; + +/// Systick implementing `rtic_monotonic::Monotonic` which runs at a +/// settable rate using the `TIMER_HZ` parameter. +pub struct Systick; + +impl Systick { + /// Start a `Monotonic` based on SysTick. + /// + /// The `sysclk` parameter is the speed at which SysTick runs at. This value should come from + /// the clock generation function of the used HAL. + /// + /// Notice that the actual rate of the timer is a best approximation based on the given + /// `sysclk` and `TIMER_HZ`. + /// + /// Note: Give the return value to `TimerQueue::initialize()` to initialize the timer queue. + #[must_use] + pub fn start(mut systick: cortex_m::peripheral::SYST, sysclk: u32) -> Self { + // + TIMER_HZ / 2 provides round to nearest instead of round to 0. + // - 1 as the counter range is inclusive [0, reload] + let reload = (sysclk + TIMER_HZ / 2) / TIMER_HZ - 1; + + assert!(reload <= 0x00ff_ffff); + assert!(reload > 0); + + systick.disable_counter(); + systick.set_clock_source(cortex_m::peripheral::syst::SystClkSource::Core); + systick.set_reload(reload); + systick.enable_interrupt(); + systick.enable_counter(); + + Systick {} + } + + fn systick() -> SYST { + unsafe { core::mem::transmute::<(), SYST>(()) } + } +} + +static SYSTICK_CNT: AtomicU32 = AtomicU32::new(0); + +impl Monotonic for Systick { + type Instant = fugit::TimerInstantU32; + type Duration = fugit::TimerDurationU32; + + const ZERO: Self::Instant = Self::Instant::from_ticks(0); + + fn now() -> Self::Instant { + if Self::systick().has_wrapped() { + SYSTICK_CNT.fetch_add(1, Ordering::AcqRel); + } + + Self::Instant::from_ticks(SYSTICK_CNT.load(Ordering::Relaxed)) + } + + fn set_compare(_: Self::Instant) { + // No need to do something here, we get interrupts anyway. + } + + fn clear_compare_flag() { + // NOOP with SysTick interrupt + } + + fn pend_interrupt() { + cortex_m::peripheral::SCB::set_pendst(); + } + + fn on_interrupt() { + if Self::systick().has_wrapped() { + SYSTICK_CNT.fetch_add(1, Ordering::AcqRel); + } + } + + fn enable_timer() {} + + fn disable_timer() {} +} + +/// Timer queue wrapper to implement traits on +pub struct SystickTimerQueue(TimerQueue>); + +impl SystickTimerQueue { + /// Create a new timer queue. + pub const fn new() -> Self { + Self(TimerQueue::new()) + } +} + +impl Deref for SystickTimerQueue { + type Target = TimerQueue>; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DelayUs for SystickTimerQueue { + type Error = core::convert::Infallible; + + async fn delay_us(&mut self, us: u32) -> Result<(), Self::Error> { + self.delay(us.micros()).await; + Ok(()) + } + + async fn delay_ms(&mut self, ms: u32) -> Result<(), Self::Error> { + self.delay(ms.millis()).await; + Ok(()) + } +} + +/// Register the Systick interrupt and crate a timer queue with a specific name and speed. +#[macro_export] +macro_rules! make_systick_timer_queue { + ($timer_queue_name:ident, $systick_speed:expr) => { + static $timer_queue_name: SystickTimerQueue<$systick_speed> = SystickTimerQueue::new(); + + #[no_mangle] + #[allow(non_snake_case)] + unsafe extern "C" fn SysTick() { + $timer_queue_name.on_monotonic_interrupt(); + } + }; +} diff --git a/rtic-time/.gitignore b/rtic-time/.gitignore new file mode 100644 index 0000000..c400256 --- /dev/null +++ b/rtic-time/.gitignore @@ -0,0 +1,6 @@ +**/*.rs.bk +.#* +.gdb_history +/target +Cargo.lock +*.hex diff --git a/rtic-time/CHANGELOG.md b/rtic-time/CHANGELOG.md new file mode 100644 index 0000000..e69de29 diff --git a/rtic-time/Cargo.toml b/rtic-time/Cargo.toml new file mode 100644 index 0000000..ea05939 --- /dev/null +++ b/rtic-time/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "rtic-time" +version = "1.0.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +critical-section = "1" +futures-util = { version = "0.3.25", default-features = false } diff --git a/rtic-time/rust-toolchain.toml b/rtic-time/rust-toolchain.toml new file mode 100644 index 0000000..e28b55d --- /dev/null +++ b/rtic-time/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "nightly" +components = [ "rust-src", "rustfmt", "llvm-tools-preview" ] +targets = [ "thumbv6m-none-eabi", "thumbv7m-none-eabi" ] diff --git a/rtic-time/src/lib.rs b/rtic-time/src/lib.rs new file mode 100644 index 0000000..d7faa07 --- /dev/null +++ b/rtic-time/src/lib.rs @@ -0,0 +1,336 @@ +//! Crate + +#![no_std] +#![no_main] +#![deny(missing_docs)] +#![allow(incomplete_features)] +#![feature(async_fn_in_trait)] + +pub mod monotonic; + +use core::future::{poll_fn, Future}; +use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; +use core::task::{Poll, Waker}; +use futures_util::{ + future::{select, Either}, + pin_mut, +}; +pub use monotonic::Monotonic; + +mod linked_list; + +use linked_list::{Link, LinkedList}; + +/// Holds a waker and at which time instant this waker shall be awoken. +struct WaitingWaker { + waker: Waker, + release_at: Mono::Instant, +} + +impl Clone for WaitingWaker { + fn clone(&self) -> Self { + Self { + waker: self.waker.clone(), + release_at: self.release_at, + } + } +} + +impl PartialEq for WaitingWaker { + fn eq(&self, other: &Self) -> bool { + self.release_at == other.release_at + } +} + +impl PartialOrd for WaitingWaker { + fn partial_cmp(&self, other: &Self) -> Option { + self.release_at.partial_cmp(&other.release_at) + } +} + +/// A generic timer queue for async executors. +/// +/// # Blocking +/// +/// The internal priority queue uses global critical sections to manage access. This means that +/// `await`ing a delay will cause a lock of the entire system for O(n) time. In practice the lock +/// duration is ~10 clock cycles per element in the queue. +/// +/// # Safety +/// +/// This timer queue is based on an intrusive linked list, and by extension the links are strored +/// on the async stacks of callers. The links are deallocated on `drop` or when the wait is +/// complete. +/// +/// Do not call `mem::forget` on an awaited future, or there will be dragons! +pub struct TimerQueue { + queue: LinkedList>, + initialized: AtomicBool, +} + +/// This indicates that there was a timeout. +pub struct TimeoutError; + +impl TimerQueue { + /// Make a new queue. + pub const fn new() -> Self { + Self { + queue: LinkedList::new(), + initialized: AtomicBool::new(false), + } + } + + /// Forwards the `Monotonic::now()` method. + #[inline(always)] + pub fn now(&self) -> Mono::Instant { + Mono::now() + } + + /// Takes the initialized monotonic to initialize the TimerQueue. + pub fn initialize(&self, monotonic: Mono) { + self.initialized.store(true, Ordering::SeqCst); + + // Don't run drop on `Mono` + core::mem::forget(monotonic); + } + + /// Call this in the interrupt handler of the hardware timer supporting the `Monotonic` + /// + /// # Safety + /// + /// It's always safe to call, but it must only be called from the interrupt of the + /// monotonic timer for correct operation. + pub unsafe fn on_monotonic_interrupt(&self) { + Mono::clear_compare_flag(); + Mono::on_interrupt(); + + loop { + let mut release_at = None; + let head = self.queue.pop_if(|head| { + release_at = Some(head.release_at); + + Mono::now() >= head.release_at + }); + + match (head, release_at) { + (Some(link), _) => { + link.waker.wake(); + } + (None, Some(instant)) => { + Mono::enable_timer(); + Mono::set_compare(instant); + + if Mono::now() >= instant { + // The time for the next instant passed while handling it, + // continue dequeueing + continue; + } + + break; + } + (None, None) => { + // Queue is empty + Mono::disable_timer(); + + break; + } + } + } + } + + /// Timeout at a specific time. + pub async fn timeout_at( + &self, + instant: Mono::Instant, + future: F, + ) -> Result { + let delay = self.delay_until(instant); + + pin_mut!(future); + pin_mut!(delay); + + match select(future, delay).await { + Either::Left((r, _)) => Ok(r), + Either::Right(_) => Err(TimeoutError), + } + } + + /// Timeout after a specific duration. + #[inline] + pub async fn timeout_after( + &self, + duration: Mono::Duration, + future: F, + ) -> Result { + self.timeout_at(Mono::now() + duration, future).await + } + + /// Delay for some duration of time. + #[inline] + pub async fn delay(&self, duration: Mono::Duration) { + let now = Mono::now(); + + self.delay_until(now + duration).await; + } + + /// Delay to some specific time instant. + pub async fn delay_until(&self, instant: Mono::Instant) { + if !self.initialized.load(Ordering::Relaxed) { + panic!( + "The timer queue is not initialized with a monotonic, you need to run `initialize`" + ); + } + + let mut first_run = true; + let queue = &self.queue; + let mut link = Link::new(WaitingWaker { + waker: poll_fn(|cx| Poll::Ready(cx.waker().clone())).await, + release_at: instant, + }); + + let marker = &AtomicUsize::new(0); + + let dropper = OnDrop::new(|| { + queue.delete(marker.load(Ordering::Relaxed)); + }); + + poll_fn(|_| { + if Mono::now() >= instant { + return Poll::Ready(()); + } + + if first_run { + first_run = false; + let (was_empty, addr) = queue.insert(&mut link); + marker.store(addr, Ordering::Relaxed); + + if was_empty { + // Pend the monotonic handler if the queue was empty to setup the timer. + Mono::pend_interrupt(); + } + } + + Poll::Pending + }) + .await; + + // Make sure that our link is deleted from the list before we drop this stack + drop(dropper); + } +} + +struct OnDrop { + f: core::mem::MaybeUninit, +} + +impl OnDrop { + pub fn new(f: F) -> Self { + Self { + f: core::mem::MaybeUninit::new(f), + } + } + + #[allow(unused)] + pub fn defuse(self) { + core::mem::forget(self) + } +} + +impl Drop for OnDrop { + fn drop(&mut self) { + unsafe { self.f.as_ptr().read()() } + } +} + +// -------- Test program --------- +// +// +// use systick_monotonic::{Systick, TimerQueue}; +// +// // same panicking *behavior* as `panic-probe` but doesn't print a panic message +// // this prevents the panic message being printed *twice* when `defmt::panic` is invoked +// #[defmt::panic_handler] +// fn panic() -> ! { +// cortex_m::asm::udf() +// } +// +// /// Terminates the application and makes `probe-run` exit with exit-code = 0 +// pub fn exit() -> ! { +// loop { +// cortex_m::asm::bkpt(); +// } +// } +// +// defmt::timestamp!("{=u64:us}", { +// let time_us: fugit::MicrosDurationU32 = MONO.now().duration_since_epoch().convert(); +// +// time_us.ticks() as u64 +// }); +// +// make_systick_timer_queue!(MONO, Systick<1_000>); +// +// #[rtic::app( +// device = nrf52832_hal::pac, +// dispatchers = [SWI0_EGU0, SWI1_EGU1, SWI2_EGU2, SWI3_EGU3, SWI4_EGU4, SWI5_EGU5], +// )] +// mod app { +// use super::{Systick, MONO}; +// use fugit::ExtU32; +// +// #[shared] +// struct Shared {} +// +// #[local] +// struct Local {} +// +// #[init] +// fn init(cx: init::Context) -> (Shared, Local) { +// defmt::println!("init"); +// +// let systick = Systick::start(cx.core.SYST, 64_000_000); +// +// defmt::println!("initializing monotonic"); +// +// MONO.initialize(systick); +// +// async_task::spawn().ok(); +// async_task2::spawn().ok(); +// async_task3::spawn().ok(); +// +// (Shared {}, Local {}) +// } +// +// #[idle] +// fn idle(_: idle::Context) -> ! { +// defmt::println!("idle"); +// +// loop { +// core::hint::spin_loop(); +// } +// } +// +// #[task] +// async fn async_task(_: async_task::Context) { +// loop { +// defmt::println!("async task waiting for 1 second"); +// MONO.delay(1.secs()).await; +// } +// } +// +// #[task] +// async fn async_task2(_: async_task2::Context) { +// loop { +// defmt::println!(" async task 2 waiting for 0.5 second"); +// MONO.delay(500.millis()).await; +// } +// } +// +// #[task] +// async fn async_task3(_: async_task3::Context) { +// loop { +// defmt::println!(" async task 3 waiting for 0.2 second"); +// MONO.delay(200.millis()).await; +// } +// } +// } +// diff --git a/rtic-time/src/linked_list.rs b/rtic-time/src/linked_list.rs new file mode 100644 index 0000000..42ff8cb --- /dev/null +++ b/rtic-time/src/linked_list.rs @@ -0,0 +1,173 @@ +//! ... + +use core::marker::PhantomPinned; +use core::sync::atomic::{AtomicPtr, Ordering}; +use critical_section as cs; + +/// A sorted linked list for the timer queue. +pub struct LinkedList { + head: AtomicPtr>, +} + +impl LinkedList { + /// Create a new linked list. + pub const fn new() -> Self { + Self { + head: AtomicPtr::new(core::ptr::null_mut()), + } + } +} + +impl LinkedList { + /// Pop the first element in the queue if the closure returns true. + pub fn pop_if bool>(&self, f: F) -> Option { + cs::with(|_| { + // Make sure all previous writes are visible + core::sync::atomic::fence(Ordering::SeqCst); + + let head = self.head.load(Ordering::Relaxed); + + // SAFETY: `as_ref` is safe as `insert` requires a valid reference to a link + if let Some(head) = unsafe { head.as_ref() } { + if f(&head.val) { + // Move head to the next element + self.head + .store(head.next.load(Ordering::Relaxed), Ordering::Relaxed); + + // We read the value at head + let head_val = head.val.clone(); + + return Some(head_val); + } + } + None + }) + } + + /// Delete a link at an address. + pub fn delete(&self, addr: usize) { + cs::with(|_| { + // Make sure all previous writes are visible + core::sync::atomic::fence(Ordering::SeqCst); + + let head = self.head.load(Ordering::Relaxed); + + // SAFETY: `as_ref` is safe as `insert` requires a valid reference to a link + let head_ref = if let Some(head_ref) = unsafe { head.as_ref() } { + head_ref + } else { + // 1. List is empty, do nothing + return; + }; + + if head as *const _ as usize == addr { + // 2. Replace head with head.next + self.head + .store(head_ref.next.load(Ordering::Relaxed), Ordering::Relaxed); + + return; + } + + // 3. search list for correct node + let mut curr = head_ref; + let mut next = head_ref.next.load(Ordering::Relaxed); + + // SAFETY: `as_ref` is safe as `insert` requires a valid reference to a link + while let Some(next_link) = unsafe { next.as_ref() } { + // Next is not null + + if next as *const _ as usize == addr { + curr.next + .store(next_link.next.load(Ordering::Relaxed), Ordering::Relaxed); + + return; + } + + // Continue searching + curr = next_link; + next = next_link.next.load(Ordering::Relaxed); + } + }) + } + + /// Insert a new link into the linked list. + /// The return is (was_empty, address), where the address of the link is for use with `delete`. + pub fn insert(&self, val: &mut Link) -> (bool, usize) { + cs::with(|_| { + let addr = val as *const _ as usize; + + // Make sure all previous writes are visible + core::sync::atomic::fence(Ordering::SeqCst); + + let head = self.head.load(Ordering::Relaxed); + + // 3 cases to handle + + // 1. List is empty, write to head + // SAFETY: `as_ref` is safe as `insert` requires a valid reference to a link + let head_ref = if let Some(head_ref) = unsafe { head.as_ref() } { + head_ref + } else { + self.head.store(val, Ordering::Relaxed); + return (true, addr); + }; + + // 2. val needs to go in first + if val.val < head_ref.val { + // Set current head as next of `val` + val.next.store(head, Ordering::Relaxed); + + // `val` is now first in the queue + self.head.store(val, Ordering::Relaxed); + + return (false, addr); + } + + // 3. search list for correct place + let mut curr = head_ref; + let mut next = head_ref.next.load(Ordering::Relaxed); + + // SAFETY: `as_ref` is safe as `insert` requires a valid reference to a link + while let Some(next_link) = unsafe { next.as_ref() } { + // Next is not null + + if val.val < next_link.val { + // Replace next with `val` + val.next.store(next, Ordering::Relaxed); + + // Insert `val` + curr.next.store(val, Ordering::Relaxed); + + return (false, addr); + } + + // Continue searching + curr = next_link; + next = next_link.next.load(Ordering::Relaxed); + } + + // No next, write link to last position in list + curr.next.store(val, Ordering::Relaxed); + + (false, addr) + }) + } +} + +/// A link in the linked list. +pub struct Link { + val: T, + next: AtomicPtr>, + _up: PhantomPinned, +} + +impl Link { + /// Create a new link. + pub const fn new(val: T) -> Self { + Self { + val, + next: AtomicPtr::new(core::ptr::null_mut()), + _up: PhantomPinned, + } + } +} diff --git a/rtic-time/src/monotonic.rs b/rtic-time/src/monotonic.rs new file mode 100644 index 0000000..9b3742f --- /dev/null +++ b/rtic-time/src/monotonic.rs @@ -0,0 +1,60 @@ +//! ... + +/// # A monotonic clock / counter definition. +/// +/// ## Correctness +/// +/// The trait enforces that proper time-math is implemented between `Instant` and `Duration`. This +/// is a requirement on the time library that the user chooses to use. +pub trait Monotonic { + /// The time at time zero. + const ZERO: Self::Instant; + + /// The type for instant, defining an instant in time. + /// + /// **Note:** In all APIs in RTIC that use instants from this monotonic, this type will be used. + type Instant: Ord + + Copy + + core::ops::Add + + core::ops::Sub + + core::ops::Sub; + + /// The type for duration, defining an duration of time. + /// + /// **Note:** In all APIs in RTIC that use duration from this monotonic, this type will be used. + type Duration; + + /// Get the current time. + fn now() -> Self::Instant; + + /// Set the compare value of the timer interrupt. + /// + /// **Note:** This method does not need to handle race conditions of the monotonic, the timer + /// queue in RTIC checks this. + fn set_compare(instant: Self::Instant); + + /// Clear the compare interrupt flag. + fn clear_compare_flag(); + + /// Pend the timer's interrupt. + fn pend_interrupt(); + + /// Optional. Runs on interrupt before any timer queue handling. + fn on_interrupt() {} + + /// Optional. This is used to save power, this is called when the timer queue is not empty. + /// + /// Enabling and disabling the monotonic needs to propagate to `now` so that an instant + /// based of `now()` is still valid. + /// + /// NOTE: This may be called more than once. + fn enable_timer() {} + + /// Optional. This is used to save power, this is called when the timer queue is empty. + /// + /// Enabling and disabling the monotonic needs to propagate to `now` so that an instant + /// based of `now()` is still valid. + /// + /// NOTE: This may be called more than once. + fn disable_timer() {} +} diff --git a/rtic-timer/.gitignore b/rtic-timer/.gitignore deleted file mode 100644 index c400256..0000000 --- a/rtic-timer/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -**/*.rs.bk -.#* -.gdb_history -/target -Cargo.lock -*.hex diff --git a/rtic-timer/CHANGELOG.md b/rtic-timer/CHANGELOG.md deleted file mode 100644 index e69de29..0000000 diff --git a/rtic-timer/Cargo.toml b/rtic-timer/Cargo.toml deleted file mode 100644 index b7b3a5f..0000000 --- a/rtic-timer/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "rtic-timer" -version = "1.0.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -critical-section = "1" -futures-util = { version = "0.3.25", default-features = false } diff --git a/rtic-timer/rust-toolchain.toml b/rtic-timer/rust-toolchain.toml deleted file mode 100644 index e28b55d..0000000 --- a/rtic-timer/rust-toolchain.toml +++ /dev/null @@ -1,4 +0,0 @@ -[toolchain] -channel = "nightly" -components = [ "rust-src", "rustfmt", "llvm-tools-preview" ] -targets = [ "thumbv6m-none-eabi", "thumbv7m-none-eabi" ] diff --git a/rtic-timer/src/lib.rs b/rtic-timer/src/lib.rs deleted file mode 100644 index d7faa07..0000000 --- a/rtic-timer/src/lib.rs +++ /dev/null @@ -1,336 +0,0 @@ -//! Crate - -#![no_std] -#![no_main] -#![deny(missing_docs)] -#![allow(incomplete_features)] -#![feature(async_fn_in_trait)] - -pub mod monotonic; - -use core::future::{poll_fn, Future}; -use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; -use core::task::{Poll, Waker}; -use futures_util::{ - future::{select, Either}, - pin_mut, -}; -pub use monotonic::Monotonic; - -mod linked_list; - -use linked_list::{Link, LinkedList}; - -/// Holds a waker and at which time instant this waker shall be awoken. -struct WaitingWaker { - waker: Waker, - release_at: Mono::Instant, -} - -impl Clone for WaitingWaker { - fn clone(&self) -> Self { - Self { - waker: self.waker.clone(), - release_at: self.release_at, - } - } -} - -impl PartialEq for WaitingWaker { - fn eq(&self, other: &Self) -> bool { - self.release_at == other.release_at - } -} - -impl PartialOrd for WaitingWaker { - fn partial_cmp(&self, other: &Self) -> Option { - self.release_at.partial_cmp(&other.release_at) - } -} - -/// A generic timer queue for async executors. -/// -/// # Blocking -/// -/// The internal priority queue uses global critical sections to manage access. This means that -/// `await`ing a delay will cause a lock of the entire system for O(n) time. In practice the lock -/// duration is ~10 clock cycles per element in the queue. -/// -/// # Safety -/// -/// This timer queue is based on an intrusive linked list, and by extension the links are strored -/// on the async stacks of callers. The links are deallocated on `drop` or when the wait is -/// complete. -/// -/// Do not call `mem::forget` on an awaited future, or there will be dragons! -pub struct TimerQueue { - queue: LinkedList>, - initialized: AtomicBool, -} - -/// This indicates that there was a timeout. -pub struct TimeoutError; - -impl TimerQueue { - /// Make a new queue. - pub const fn new() -> Self { - Self { - queue: LinkedList::new(), - initialized: AtomicBool::new(false), - } - } - - /// Forwards the `Monotonic::now()` method. - #[inline(always)] - pub fn now(&self) -> Mono::Instant { - Mono::now() - } - - /// Takes the initialized monotonic to initialize the TimerQueue. - pub fn initialize(&self, monotonic: Mono) { - self.initialized.store(true, Ordering::SeqCst); - - // Don't run drop on `Mono` - core::mem::forget(monotonic); - } - - /// Call this in the interrupt handler of the hardware timer supporting the `Monotonic` - /// - /// # Safety - /// - /// It's always safe to call, but it must only be called from the interrupt of the - /// monotonic timer for correct operation. - pub unsafe fn on_monotonic_interrupt(&self) { - Mono::clear_compare_flag(); - Mono::on_interrupt(); - - loop { - let mut release_at = None; - let head = self.queue.pop_if(|head| { - release_at = Some(head.release_at); - - Mono::now() >= head.release_at - }); - - match (head, release_at) { - (Some(link), _) => { - link.waker.wake(); - } - (None, Some(instant)) => { - Mono::enable_timer(); - Mono::set_compare(instant); - - if Mono::now() >= instant { - // The time for the next instant passed while handling it, - // continue dequeueing - continue; - } - - break; - } - (None, None) => { - // Queue is empty - Mono::disable_timer(); - - break; - } - } - } - } - - /// Timeout at a specific time. - pub async fn timeout_at( - &self, - instant: Mono::Instant, - future: F, - ) -> Result { - let delay = self.delay_until(instant); - - pin_mut!(future); - pin_mut!(delay); - - match select(future, delay).await { - Either::Left((r, _)) => Ok(r), - Either::Right(_) => Err(TimeoutError), - } - } - - /// Timeout after a specific duration. - #[inline] - pub async fn timeout_after( - &self, - duration: Mono::Duration, - future: F, - ) -> Result { - self.timeout_at(Mono::now() + duration, future).await - } - - /// Delay for some duration of time. - #[inline] - pub async fn delay(&self, duration: Mono::Duration) { - let now = Mono::now(); - - self.delay_until(now + duration).await; - } - - /// Delay to some specific time instant. - pub async fn delay_until(&self, instant: Mono::Instant) { - if !self.initialized.load(Ordering::Relaxed) { - panic!( - "The timer queue is not initialized with a monotonic, you need to run `initialize`" - ); - } - - let mut first_run = true; - let queue = &self.queue; - let mut link = Link::new(WaitingWaker { - waker: poll_fn(|cx| Poll::Ready(cx.waker().clone())).await, - release_at: instant, - }); - - let marker = &AtomicUsize::new(0); - - let dropper = OnDrop::new(|| { - queue.delete(marker.load(Ordering::Relaxed)); - }); - - poll_fn(|_| { - if Mono::now() >= instant { - return Poll::Ready(()); - } - - if first_run { - first_run = false; - let (was_empty, addr) = queue.insert(&mut link); - marker.store(addr, Ordering::Relaxed); - - if was_empty { - // Pend the monotonic handler if the queue was empty to setup the timer. - Mono::pend_interrupt(); - } - } - - Poll::Pending - }) - .await; - - // Make sure that our link is deleted from the list before we drop this stack - drop(dropper); - } -} - -struct OnDrop { - f: core::mem::MaybeUninit, -} - -impl OnDrop { - pub fn new(f: F) -> Self { - Self { - f: core::mem::MaybeUninit::new(f), - } - } - - #[allow(unused)] - pub fn defuse(self) { - core::mem::forget(self) - } -} - -impl Drop for OnDrop { - fn drop(&mut self) { - unsafe { self.f.as_ptr().read()() } - } -} - -// -------- Test program --------- -// -// -// use systick_monotonic::{Systick, TimerQueue}; -// -// // same panicking *behavior* as `panic-probe` but doesn't print a panic message -// // this prevents the panic message being printed *twice* when `defmt::panic` is invoked -// #[defmt::panic_handler] -// fn panic() -> ! { -// cortex_m::asm::udf() -// } -// -// /// Terminates the application and makes `probe-run` exit with exit-code = 0 -// pub fn exit() -> ! { -// loop { -// cortex_m::asm::bkpt(); -// } -// } -// -// defmt::timestamp!("{=u64:us}", { -// let time_us: fugit::MicrosDurationU32 = MONO.now().duration_since_epoch().convert(); -// -// time_us.ticks() as u64 -// }); -// -// make_systick_timer_queue!(MONO, Systick<1_000>); -// -// #[rtic::app( -// device = nrf52832_hal::pac, -// dispatchers = [SWI0_EGU0, SWI1_EGU1, SWI2_EGU2, SWI3_EGU3, SWI4_EGU4, SWI5_EGU5], -// )] -// mod app { -// use super::{Systick, MONO}; -// use fugit::ExtU32; -// -// #[shared] -// struct Shared {} -// -// #[local] -// struct Local {} -// -// #[init] -// fn init(cx: init::Context) -> (Shared, Local) { -// defmt::println!("init"); -// -// let systick = Systick::start(cx.core.SYST, 64_000_000); -// -// defmt::println!("initializing monotonic"); -// -// MONO.initialize(systick); -// -// async_task::spawn().ok(); -// async_task2::spawn().ok(); -// async_task3::spawn().ok(); -// -// (Shared {}, Local {}) -// } -// -// #[idle] -// fn idle(_: idle::Context) -> ! { -// defmt::println!("idle"); -// -// loop { -// core::hint::spin_loop(); -// } -// } -// -// #[task] -// async fn async_task(_: async_task::Context) { -// loop { -// defmt::println!("async task waiting for 1 second"); -// MONO.delay(1.secs()).await; -// } -// } -// -// #[task] -// async fn async_task2(_: async_task2::Context) { -// loop { -// defmt::println!(" async task 2 waiting for 0.5 second"); -// MONO.delay(500.millis()).await; -// } -// } -// -// #[task] -// async fn async_task3(_: async_task3::Context) { -// loop { -// defmt::println!(" async task 3 waiting for 0.2 second"); -// MONO.delay(200.millis()).await; -// } -// } -// } -// diff --git a/rtic-timer/src/linked_list.rs b/rtic-timer/src/linked_list.rs deleted file mode 100644 index 42ff8cb..0000000 --- a/rtic-timer/src/linked_list.rs +++ /dev/null @@ -1,173 +0,0 @@ -//! ... - -use core::marker::PhantomPinned; -use core::sync::atomic::{AtomicPtr, Ordering}; -use critical_section as cs; - -/// A sorted linked list for the timer queue. -pub struct LinkedList { - head: AtomicPtr>, -} - -impl LinkedList { - /// Create a new linked list. - pub const fn new() -> Self { - Self { - head: AtomicPtr::new(core::ptr::null_mut()), - } - } -} - -impl LinkedList { - /// Pop the first element in the queue if the closure returns true. - pub fn pop_if bool>(&self, f: F) -> Option { - cs::with(|_| { - // Make sure all previous writes are visible - core::sync::atomic::fence(Ordering::SeqCst); - - let head = self.head.load(Ordering::Relaxed); - - // SAFETY: `as_ref` is safe as `insert` requires a valid reference to a link - if let Some(head) = unsafe { head.as_ref() } { - if f(&head.val) { - // Move head to the next element - self.head - .store(head.next.load(Ordering::Relaxed), Ordering::Relaxed); - - // We read the value at head - let head_val = head.val.clone(); - - return Some(head_val); - } - } - None - }) - } - - /// Delete a link at an address. - pub fn delete(&self, addr: usize) { - cs::with(|_| { - // Make sure all previous writes are visible - core::sync::atomic::fence(Ordering::SeqCst); - - let head = self.head.load(Ordering::Relaxed); - - // SAFETY: `as_ref` is safe as `insert` requires a valid reference to a link - let head_ref = if let Some(head_ref) = unsafe { head.as_ref() } { - head_ref - } else { - // 1. List is empty, do nothing - return; - }; - - if head as *const _ as usize == addr { - // 2. Replace head with head.next - self.head - .store(head_ref.next.load(Ordering::Relaxed), Ordering::Relaxed); - - return; - } - - // 3. search list for correct node - let mut curr = head_ref; - let mut next = head_ref.next.load(Ordering::Relaxed); - - // SAFETY: `as_ref` is safe as `insert` requires a valid reference to a link - while let Some(next_link) = unsafe { next.as_ref() } { - // Next is not null - - if next as *const _ as usize == addr { - curr.next - .store(next_link.next.load(Ordering::Relaxed), Ordering::Relaxed); - - return; - } - - // Continue searching - curr = next_link; - next = next_link.next.load(Ordering::Relaxed); - } - }) - } - - /// Insert a new link into the linked list. - /// The return is (was_empty, address), where the address of the link is for use with `delete`. - pub fn insert(&self, val: &mut Link) -> (bool, usize) { - cs::with(|_| { - let addr = val as *const _ as usize; - - // Make sure all previous writes are visible - core::sync::atomic::fence(Ordering::SeqCst); - - let head = self.head.load(Ordering::Relaxed); - - // 3 cases to handle - - // 1. List is empty, write to head - // SAFETY: `as_ref` is safe as `insert` requires a valid reference to a link - let head_ref = if let Some(head_ref) = unsafe { head.as_ref() } { - head_ref - } else { - self.head.store(val, Ordering::Relaxed); - return (true, addr); - }; - - // 2. val needs to go in first - if val.val < head_ref.val { - // Set current head as next of `val` - val.next.store(head, Ordering::Relaxed); - - // `val` is now first in the queue - self.head.store(val, Ordering::Relaxed); - - return (false, addr); - } - - // 3. search list for correct place - let mut curr = head_ref; - let mut next = head_ref.next.load(Ordering::Relaxed); - - // SAFETY: `as_ref` is safe as `insert` requires a valid reference to a link - while let Some(next_link) = unsafe { next.as_ref() } { - // Next is not null - - if val.val < next_link.val { - // Replace next with `val` - val.next.store(next, Ordering::Relaxed); - - // Insert `val` - curr.next.store(val, Ordering::Relaxed); - - return (false, addr); - } - - // Continue searching - curr = next_link; - next = next_link.next.load(Ordering::Relaxed); - } - - // No next, write link to last position in list - curr.next.store(val, Ordering::Relaxed); - - (false, addr) - }) - } -} - -/// A link in the linked list. -pub struct Link { - val: T, - next: AtomicPtr>, - _up: PhantomPinned, -} - -impl Link { - /// Create a new link. - pub const fn new(val: T) -> Self { - Self { - val, - next: AtomicPtr::new(core::ptr::null_mut()), - _up: PhantomPinned, - } - } -} diff --git a/rtic-timer/src/monotonic.rs b/rtic-timer/src/monotonic.rs deleted file mode 100644 index 9b3742f..0000000 --- a/rtic-timer/src/monotonic.rs +++ /dev/null @@ -1,60 +0,0 @@ -//! ... - -/// # A monotonic clock / counter definition. -/// -/// ## Correctness -/// -/// The trait enforces that proper time-math is implemented between `Instant` and `Duration`. This -/// is a requirement on the time library that the user chooses to use. -pub trait Monotonic { - /// The time at time zero. - const ZERO: Self::Instant; - - /// The type for instant, defining an instant in time. - /// - /// **Note:** In all APIs in RTIC that use instants from this monotonic, this type will be used. - type Instant: Ord - + Copy - + core::ops::Add - + core::ops::Sub - + core::ops::Sub; - - /// The type for duration, defining an duration of time. - /// - /// **Note:** In all APIs in RTIC that use duration from this monotonic, this type will be used. - type Duration; - - /// Get the current time. - fn now() -> Self::Instant; - - /// Set the compare value of the timer interrupt. - /// - /// **Note:** This method does not need to handle race conditions of the monotonic, the timer - /// queue in RTIC checks this. - fn set_compare(instant: Self::Instant); - - /// Clear the compare interrupt flag. - fn clear_compare_flag(); - - /// Pend the timer's interrupt. - fn pend_interrupt(); - - /// Optional. Runs on interrupt before any timer queue handling. - fn on_interrupt() {} - - /// Optional. This is used to save power, this is called when the timer queue is not empty. - /// - /// Enabling and disabling the monotonic needs to propagate to `now` so that an instant - /// based of `now()` is still valid. - /// - /// NOTE: This may be called more than once. - fn enable_timer() {} - - /// Optional. This is used to save power, this is called when the timer queue is empty. - /// - /// Enabling and disabling the monotonic needs to propagate to `now` so that an instant - /// based of `now()` is still valid. - /// - /// NOTE: This may be called more than once. - fn disable_timer() {} -} -- cgit v1.2.3 From 143cd136eeeb2856d06a1b83e3ef5682f720c251 Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Tue, 24 Jan 2023 11:55:48 +0100 Subject: Optimize linked list popping so delete is not run everytime --- rtic-time/src/lib.rs | 131 +++++++++---------------------------------- rtic-time/src/linked_list.rs | 4 +- 2 files changed, 28 insertions(+), 107 deletions(-) diff --git a/rtic-time/src/lib.rs b/rtic-time/src/lib.rs index d7faa07..850885b 100644 --- a/rtic-time/src/lib.rs +++ b/rtic-time/src/lib.rs @@ -6,9 +6,8 @@ #![allow(incomplete_features)] #![feature(async_fn_in_trait)] -pub mod monotonic; - use core::future::{poll_fn, Future}; +use core::mem::MaybeUninit; use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use core::task::{Poll, Waker}; use futures_util::{ @@ -18,20 +17,25 @@ use futures_util::{ pub use monotonic::Monotonic; mod linked_list; +mod monotonic; use linked_list::{Link, LinkedList}; /// Holds a waker and at which time instant this waker shall be awoken. struct WaitingWaker { - waker: Waker, + // This is alway initialized when used, we create this struct on the async stack and then + // initialize the waker field in the `poll_fn` closure (we then know the waker) + waker: MaybeUninit, release_at: Mono::Instant, + was_poped: AtomicBool, } impl Clone for WaitingWaker { fn clone(&self) -> Self { Self { - waker: self.waker.clone(), + waker: MaybeUninit::new(unsafe { self.waker.assume_init_ref() }.clone()), release_at: self.release_at, + was_poped: AtomicBool::new(self.was_poped.load(Ordering::Relaxed)), } } } @@ -109,12 +113,15 @@ impl TimerQueue { let head = self.queue.pop_if(|head| { release_at = Some(head.release_at); - Mono::now() >= head.release_at + let should_pop = Mono::now() >= head.release_at; + head.was_poped.store(should_pop, Ordering::Relaxed); + + should_pop }); match (head, release_at) { (Some(link), _) => { - link.waker.wake(); + link.waker.assume_init().wake(); } (None, Some(instant)) => { Mono::enable_timer(); @@ -181,26 +188,28 @@ impl TimerQueue { ); } - let mut first_run = true; - let queue = &self.queue; let mut link = Link::new(WaitingWaker { - waker: poll_fn(|cx| Poll::Ready(cx.waker().clone())).await, + waker: MaybeUninit::uninit(), release_at: instant, + was_poped: AtomicBool::new(false), }); + let mut first_run = true; + let queue = &self.queue; let marker = &AtomicUsize::new(0); let dropper = OnDrop::new(|| { queue.delete(marker.load(Ordering::Relaxed)); }); - poll_fn(|_| { + poll_fn(|cx| { if Mono::now() >= instant { return Poll::Ready(()); } if first_run { first_run = false; + link.val.waker.write(cx.waker().clone()); let (was_empty, addr) = queue.insert(&mut link); marker.store(addr, Ordering::Relaxed); @@ -214,8 +223,13 @@ impl TimerQueue { }) .await; - // Make sure that our link is deleted from the list before we drop this stack - drop(dropper); + if link.val.was_poped.load(Ordering::Relaxed) { + // If it was poped from the queue there is no need to run delete + dropper.defuse(); + } else { + // Make sure that our link is deleted from the list before we drop this stack + drop(dropper); + } } } @@ -241,96 +255,3 @@ impl Drop for OnDrop { unsafe { self.f.as_ptr().read()() } } } - -// -------- Test program --------- -// -// -// use systick_monotonic::{Systick, TimerQueue}; -// -// // same panicking *behavior* as `panic-probe` but doesn't print a panic message -// // this prevents the panic message being printed *twice* when `defmt::panic` is invoked -// #[defmt::panic_handler] -// fn panic() -> ! { -// cortex_m::asm::udf() -// } -// -// /// Terminates the application and makes `probe-run` exit with exit-code = 0 -// pub fn exit() -> ! { -// loop { -// cortex_m::asm::bkpt(); -// } -// } -// -// defmt::timestamp!("{=u64:us}", { -// let time_us: fugit::MicrosDurationU32 = MONO.now().duration_since_epoch().convert(); -// -// time_us.ticks() as u64 -// }); -// -// make_systick_timer_queue!(MONO, Systick<1_000>); -// -// #[rtic::app( -// device = nrf52832_hal::pac, -// dispatchers = [SWI0_EGU0, SWI1_EGU1, SWI2_EGU2, SWI3_EGU3, SWI4_EGU4, SWI5_EGU5], -// )] -// mod app { -// use super::{Systick, MONO}; -// use fugit::ExtU32; -// -// #[shared] -// struct Shared {} -// -// #[local] -// struct Local {} -// -// #[init] -// fn init(cx: init::Context) -> (Shared, Local) { -// defmt::println!("init"); -// -// let systick = Systick::start(cx.core.SYST, 64_000_000); -// -// defmt::println!("initializing monotonic"); -// -// MONO.initialize(systick); -// -// async_task::spawn().ok(); -// async_task2::spawn().ok(); -// async_task3::spawn().ok(); -// -// (Shared {}, Local {}) -// } -// -// #[idle] -// fn idle(_: idle::Context) -> ! { -// defmt::println!("idle"); -// -// loop { -// core::hint::spin_loop(); -// } -// } -// -// #[task] -// async fn async_task(_: async_task::Context) { -// loop { -// defmt::println!("async task waiting for 1 second"); -// MONO.delay(1.secs()).await; -// } -// } -// -// #[task] -// async fn async_task2(_: async_task2::Context) { -// loop { -// defmt::println!(" async task 2 waiting for 0.5 second"); -// MONO.delay(500.millis()).await; -// } -// } -// -// #[task] -// async fn async_task3(_: async_task3::Context) { -// loop { -// defmt::println!(" async task 3 waiting for 0.2 second"); -// MONO.delay(200.millis()).await; -// } -// } -// } -// diff --git a/rtic-time/src/linked_list.rs b/rtic-time/src/linked_list.rs index 42ff8cb..52a955b 100644 --- a/rtic-time/src/linked_list.rs +++ b/rtic-time/src/linked_list.rs @@ -5,7 +5,7 @@ use core::sync::atomic::{AtomicPtr, Ordering}; use critical_section as cs; /// A sorted linked list for the timer queue. -pub struct LinkedList { +pub(crate) struct LinkedList { head: AtomicPtr>, } @@ -156,7 +156,7 @@ impl LinkedList { /// A link in the linked list. pub struct Link { - val: T, + pub(crate) val: T, next: AtomicPtr>, _up: PhantomPinned, } -- cgit v1.2.3 From bdf577c30800aedb0ab6b27768e228c057e18fb5 Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Tue, 24 Jan 2023 12:34:11 +0100 Subject: Systick runs at 1 kHz --- rtic-monotonics/src/systick_monotonic.rs | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/rtic-monotonics/src/systick_monotonic.rs b/rtic-monotonics/src/systick_monotonic.rs index 7c713b6..af17f93 100644 --- a/rtic-monotonics/src/systick_monotonic.rs +++ b/rtic-monotonics/src/systick_monotonic.rs @@ -10,11 +10,12 @@ use cortex_m::peripheral::SYST; use embedded_hal_async::delay::DelayUs; use fugit::ExtU32; -/// Systick implementing `rtic_monotonic::Monotonic` which runs at a -/// settable rate using the `TIMER_HZ` parameter. -pub struct Systick; +const TIMER_HZ: u32 = 1_000; -impl Systick { +/// Systick implementing `rtic_monotonic::Monotonic` which runs at 1 kHz. +pub struct Systick; + +impl Systick { /// Start a `Monotonic` based on SysTick. /// /// The `sysclk` parameter is the speed at which SysTick runs at. This value should come from @@ -49,7 +50,7 @@ impl Systick { static SYSTICK_CNT: AtomicU32 = AtomicU32::new(0); -impl Monotonic for Systick { +impl Monotonic for Systick { type Instant = fugit::TimerInstantU32; type Duration = fugit::TimerDurationU32; @@ -87,17 +88,17 @@ impl Monotonic for Systick { } /// Timer queue wrapper to implement traits on -pub struct SystickTimerQueue(TimerQueue>); +pub struct SystickTimerQueue(TimerQueue); -impl SystickTimerQueue { +impl SystickTimerQueue { /// Create a new timer queue. pub const fn new() -> Self { Self(TimerQueue::new()) } } -impl Deref for SystickTimerQueue { - type Target = TimerQueue>; +impl Deref for SystickTimerQueue { + type Target = TimerQueue; #[inline(always)] fn deref(&self) -> &Self::Target { @@ -105,7 +106,7 @@ impl Deref for SystickTimerQueue { } } -impl DelayUs for SystickTimerQueue { +impl DelayUs for SystickTimerQueue { type Error = core::convert::Infallible; async fn delay_us(&mut self, us: u32) -> Result<(), Self::Error> { @@ -122,8 +123,8 @@ impl DelayUs for SystickTimerQueue { /// Register the Systick interrupt and crate a timer queue with a specific name and speed. #[macro_export] macro_rules! make_systick_timer_queue { - ($timer_queue_name:ident, $systick_speed:expr) => { - static $timer_queue_name: SystickTimerQueue<$systick_speed> = SystickTimerQueue::new(); + ($timer_queue_name:ident) => { + static $timer_queue_name: SystickTimerQueue = SystickTimerQueue::new(); #[no_mangle] #[allow(non_snake_case)] -- cgit v1.2.3 From 2e96229c912076829d4c2be83a8b5ce27d81ebaa Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Wed, 25 Jan 2023 15:24:32 +0100 Subject: Remove unnecessary MaybeUninit --- rtic-time/src/lib.rs | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/rtic-time/src/lib.rs b/rtic-time/src/lib.rs index 850885b..34f9362 100644 --- a/rtic-time/src/lib.rs +++ b/rtic-time/src/lib.rs @@ -7,7 +7,6 @@ #![feature(async_fn_in_trait)] use core::future::{poll_fn, Future}; -use core::mem::MaybeUninit; use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use core::task::{Poll, Waker}; use futures_util::{ @@ -23,9 +22,7 @@ use linked_list::{Link, LinkedList}; /// Holds a waker and at which time instant this waker shall be awoken. struct WaitingWaker { - // This is alway initialized when used, we create this struct on the async stack and then - // initialize the waker field in the `poll_fn` closure (we then know the waker) - waker: MaybeUninit, + waker: Waker, release_at: Mono::Instant, was_poped: AtomicBool, } @@ -33,7 +30,7 @@ struct WaitingWaker { impl Clone for WaitingWaker { fn clone(&self) -> Self { Self { - waker: MaybeUninit::new(unsafe { self.waker.assume_init_ref() }.clone()), + waker: self.waker.clone(), release_at: self.release_at, was_poped: AtomicBool::new(self.was_poped.load(Ordering::Relaxed)), } @@ -121,7 +118,7 @@ impl TimerQueue { match (head, release_at) { (Some(link), _) => { - link.waker.assume_init().wake(); + link.waker.wake(); } (None, Some(instant)) => { Mono::enable_timer(); @@ -188,13 +185,8 @@ impl TimerQueue { ); } - let mut link = Link::new(WaitingWaker { - waker: MaybeUninit::uninit(), - release_at: instant, - was_poped: AtomicBool::new(false), - }); + let mut link = None; - let mut first_run = true; let queue = &self.queue; let marker = &AtomicUsize::new(0); @@ -207,10 +199,14 @@ impl TimerQueue { return Poll::Ready(()); } - if first_run { - first_run = false; - link.val.waker.write(cx.waker().clone()); - let (was_empty, addr) = queue.insert(&mut link); + if link.is_none() { + let mut link_ref = link.insert(Link::new(WaitingWaker { + waker: cx.waker().clone(), + release_at: instant, + was_poped: AtomicBool::new(false), + })); + + let (was_empty, addr) = queue.insert(&mut link_ref); marker.store(addr, Ordering::Relaxed); if was_empty { @@ -223,9 +219,11 @@ impl TimerQueue { }) .await; - if link.val.was_poped.load(Ordering::Relaxed) { - // If it was poped from the queue there is no need to run delete - dropper.defuse(); + if let Some(link) = link { + if link.val.was_poped.load(Ordering::Relaxed) { + // If it was poped from the queue there is no need to run delete + dropper.defuse(); + } } else { // Make sure that our link is deleted from the list before we drop this stack drop(dropper); -- cgit v1.2.3 From 51d4eccc726d4dc7950aac6f8d74a34ddf669f67 Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Thu, 26 Jan 2023 21:29:52 +0100 Subject: Fixes in MPSC linked list and dropper handling --- rtic-channel/Cargo.toml | 15 ++ rtic-channel/src/lib.rs | 380 +++++++++++++++++++++++++++++++++++++++++ rtic-channel/src/wait_queue.rs | 278 ++++++++++++++++++++++++++++++ rtic-time/src/lib.rs | 1 - 4 files changed, 673 insertions(+), 1 deletion(-) create mode 100644 rtic-channel/Cargo.toml create mode 100644 rtic-channel/src/lib.rs create mode 100644 rtic-channel/src/wait_queue.rs diff --git a/rtic-channel/Cargo.toml b/rtic-channel/Cargo.toml new file mode 100644 index 0000000..8962352 --- /dev/null +++ b/rtic-channel/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "rtic-channel" +version = "1.0.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +heapless = "0.7" +critical-section = "1" + + +[features] +default = [] +testing = ["critical-section/std"] diff --git a/rtic-channel/src/lib.rs b/rtic-channel/src/lib.rs new file mode 100644 index 0000000..a7098ee --- /dev/null +++ b/rtic-channel/src/lib.rs @@ -0,0 +1,380 @@ +//! Crate + +#![no_std] +#![deny(missing_docs)] + +use core::{ + cell::UnsafeCell, + future::poll_fn, + mem::MaybeUninit, + ptr, + task::{Poll, Waker}, +}; +use heapless::Deque; +use wait_queue::WaitQueue; +use waker_registration::CriticalSectionWakerRegistration as WakerRegistration; + +mod wait_queue; +mod waker_registration; + +/// An MPSC channel for use in no-alloc systems. `N` sets the size of the queue. +/// +/// This channel uses critical sections, however there are extremely small and all `memcpy` +/// operations of `T` are done without critical sections. +pub struct Channel { + // Here are all indexes that are not used in `slots` and ready to be allocated. + freeq: UnsafeCell>, + // Here are wakers and indexes to slots that are ready to be dequeued by the receiver. + readyq: UnsafeCell>, + // Waker for the receiver. + receiver_waker: WakerRegistration, + // Storage for N `T`s, so we don't memcpy around a lot of `T`s. + slots: [UnsafeCell>; N], + // If there is no room in the queue a `Sender`s can wait for there to be place in the queue. + wait_queue: WaitQueue, + // Keep track of the receiver. + receiver_dropped: UnsafeCell, + // Keep track of the number of senders. + num_senders: UnsafeCell, +} + +struct UnsafeAccess<'a, const N: usize> { + freeq: &'a mut Deque, + readyq: &'a mut Deque, + receiver_dropped: &'a mut bool, + num_senders: &'a mut usize, +} + +impl Channel { + const _CHECK: () = assert!(N < 256, "This queue support a maximum of 255 entries"); + + const INIT_SLOTS: UnsafeCell> = UnsafeCell::new(MaybeUninit::uninit()); + + /// Create a new channel. + pub const fn new() -> Self { + Self { + freeq: UnsafeCell::new(Deque::new()), + readyq: UnsafeCell::new(Deque::new()), + receiver_waker: WakerRegistration::new(), + slots: [Self::INIT_SLOTS; N], + wait_queue: WaitQueue::new(), + receiver_dropped: UnsafeCell::new(false), + num_senders: UnsafeCell::new(0), + } + } + + /// Split the queue into a `Sender`/`Receiver` pair. + pub fn split<'a>(&'a mut self) -> (Sender<'a, T, N>, Receiver<'a, T, N>) { + // Fill free queue + for idx in 0..(N - 1) as u8 { + debug_assert!(!self.freeq.get_mut().is_full()); + + // SAFETY: This safe as the loop goes from 0 to the capacity of the underlying queue. + unsafe { + self.freeq.get_mut().push_back_unchecked(idx); + } + } + + debug_assert!(self.freeq.get_mut().is_full()); + + // There is now 1 sender + *self.num_senders.get_mut() = 1; + + (Sender(self), Receiver(self)) + } + + fn access<'a>(&'a self, _cs: critical_section::CriticalSection) -> UnsafeAccess<'a, N> { + // SAFETY: This is safe as are in a critical section. + unsafe { + UnsafeAccess { + freeq: &mut *self.freeq.get(), + readyq: &mut *self.readyq.get(), + receiver_dropped: &mut *self.receiver_dropped.get(), + num_senders: &mut *self.num_senders.get(), + } + } + } +} + +/// Creates a split channel with `'static` lifetime. +#[macro_export] +macro_rules! make_channel { + ($type:path, $size:expr) => {{ + static mut CHANNEL: Channel<$type, $size> = Channel::new(); + + // SAFETY: This is safe as we hide the static mut from others to access it. + // Only this point is where the mutable access happens. + unsafe { CHANNEL.split() } + }}; +} + +// -------- Sender + +/// Error state for when the receiver has been dropped. +pub struct NoReceiver(pub T); + +/// A `Sender` can send to the channel and can be cloned. +pub struct Sender<'a, T, const N: usize>(&'a Channel); + +unsafe impl<'a, T, const N: usize> Send for Sender<'a, T, N> {} + +impl<'a, T, const N: usize> Sender<'a, T, N> { + #[inline(always)] + fn send_footer(&mut self, idx: u8, val: T) { + // Write the value to the slots, note; this memcpy is not under a critical section. + unsafe { + ptr::write( + self.0.slots.get_unchecked(idx as usize).get() as *mut T, + val, + ) + } + + // Write the value into the ready queue. + critical_section::with(|cs| unsafe { self.0.access(cs).readyq.push_back_unchecked(idx) }); + + // If there is a receiver waker, wake it. + self.0.receiver_waker.wake(); + } + + /// Try to send a value, non-blocking. If the channel is full this will return an error. + /// Note; this does not check if the channel is closed. + pub fn try_send(&mut self, val: T) -> Result<(), T> { + // If the wait queue is not empty, we can't try to push into the queue. + if !self.0.wait_queue.is_empty() { + return Err(val); + } + + let idx = + if let Some(idx) = critical_section::with(|cs| self.0.access(cs).freeq.pop_front()) { + idx + } else { + return Err(val); + }; + + self.send_footer(idx, val); + + Ok(()) + } + + /// Send a value. If there is no place left in the queue this will wait until there is. + /// If the receiver does not exist this will return an error. + pub async fn send(&mut self, val: T) -> Result<(), NoReceiver> { + if self.is_closed() {} + + let mut __hidden_link: Option> = None; + + // Make this future `Drop`-safe + let link_ptr = &mut __hidden_link as *mut Option>; + let dropper = OnDrop::new(|| { + // SAFETY: We only run this closure and dereference the pointer if we have + // exited the `poll_fn` below in the `drop(dropper)` call. The other dereference + // of this pointer is in the `poll_fn`. + if let Some(link) = unsafe { &mut *link_ptr } { + link.remove_from_list(&self.0.wait_queue); + } + }); + + let idx = poll_fn(|cx| { + if self.is_closed() { + return Poll::Ready(Err(())); + } + + // Do all this in one critical section, else there can be race conditions + let queue_idx = critical_section::with(|cs| { + if !self.0.wait_queue.is_empty() || self.0.access(cs).freeq.is_empty() { + // SAFETY: This pointer is only dereferenced here and on drop of the future. + let link = unsafe { &mut *link_ptr }; + if link.is_none() { + // Place the link in the wait queue on first run. + let link_ref = link.insert(wait_queue::Link::new(cx.waker().clone())); + self.0.wait_queue.push(link_ref); + } + + return None; + } + + // Get index as the queue is guaranteed not empty and the wait queue is empty + let idx = unsafe { self.0.access(cs).freeq.pop_front_unchecked() }; + + Some(idx) + }); + + if let Some(idx) = queue_idx { + // Return the index + Poll::Ready(Ok(idx)) + } else { + return Poll::Pending; + } + }) + .await; + + // Make sure the link is removed from the queue. + drop(dropper); + + if let Ok(idx) = idx { + self.send_footer(idx, val); + + Ok(()) + } else { + Err(NoReceiver(val)) + } + } + + /// Returns true if there is no `Receiver`s. + pub fn is_closed(&self) -> bool { + critical_section::with(|cs| *self.0.access(cs).receiver_dropped) + } + + /// Is the queue full. + pub fn is_full(&self) -> bool { + critical_section::with(|cs| self.0.access(cs).freeq.is_empty()) + } + + /// Is the queue empty. + pub fn is_empty(&self) -> bool { + critical_section::with(|cs| self.0.access(cs).freeq.is_full()) + } +} + +impl<'a, T, const N: usize> Drop for Sender<'a, T, N> { + fn drop(&mut self) { + // Count down the reference counter + let num_senders = critical_section::with(|cs| { + *self.0.access(cs).num_senders -= 1; + + *self.0.access(cs).num_senders + }); + + // If there are no senders, wake the receiver to do error handling. + if num_senders == 0 { + self.0.receiver_waker.wake(); + } + } +} + +impl<'a, T, const N: usize> Clone for Sender<'a, T, N> { + fn clone(&self) -> Self { + // Count up the reference counter + critical_section::with(|cs| *self.0.access(cs).num_senders += 1); + + Self(self.0) + } +} + +// -------- Receiver + +/// A receiver of the channel. There can only be one receiver at any time. +pub struct Receiver<'a, T, const N: usize>(&'a Channel); + +/// Error state for when all senders has been dropped. +pub struct NoSender; + +impl<'a, T, const N: usize> Receiver<'a, T, N> { + /// Receives a value if there is one in the channel, non-blocking. + /// Note; this does not check if the channel is closed. + pub fn try_recv(&mut self) -> Option { + // Try to get a ready slot. + let ready_slot = + critical_section::with(|cs| self.0.access(cs).readyq.pop_front().map(|rs| rs)); + + if let Some(rs) = ready_slot { + // Read the value from the slots, note; this memcpy is not under a critical section. + let r = unsafe { ptr::read(self.0.slots.get_unchecked(rs as usize).get() as *const T) }; + + // Return the index to the free queue after we've read the value. + critical_section::with(|cs| unsafe { self.0.access(cs).freeq.push_back_unchecked(rs) }); + + // If someone is waiting in the WaiterQueue, wake the first one up. + if let Some(wait_head) = self.0.wait_queue.pop() { + wait_head.wake(); + } + + Some(r) + } else { + None + } + } + + /// Receives a value, waiting if the queue is empty. + /// If all senders are dropped this will error with `NoSender`. + pub async fn recv(&mut self) -> Result { + // There was nothing in the queue, setup the waiting. + poll_fn(|cx| { + // Register waker. + // TODO: Should it happen here or after the if? This might cause a spurious wake. + self.0.receiver_waker.register(cx.waker()); + + // Try to dequeue. + if let Some(val) = self.try_recv() { + return Poll::Ready(Ok(val)); + } + + // If the queue is empty and there is no sender, return the error. + if self.is_closed() { + return Poll::Ready(Err(NoSender)); + } + + Poll::Pending + }) + .await + } + + /// Returns true if there are no `Sender`s. + pub fn is_closed(&self) -> bool { + critical_section::with(|cs| *self.0.access(cs).num_senders == 0) + } + + /// Is the queue full. + pub fn is_full(&self) -> bool { + critical_section::with(|cs| self.0.access(cs).readyq.is_empty()) + } + + /// Is the queue empty. + pub fn is_empty(&self) -> bool { + critical_section::with(|cs| self.0.access(cs).readyq.is_empty()) + } +} + +impl<'a, T, const N: usize> Drop for Receiver<'a, T, N> { + fn drop(&mut self) { + // Mark the receiver as dropped and wake all waiters + critical_section::with(|cs| *self.0.access(cs).receiver_dropped = true); + + while let Some(waker) = self.0.wait_queue.pop() { + waker.wake(); + } + } +} + +struct OnDrop { + f: core::mem::MaybeUninit, +} + +impl OnDrop { + pub fn new(f: F) -> Self { + Self { + f: core::mem::MaybeUninit::new(f), + } + } + + #[allow(unused)] + pub fn defuse(self) { + core::mem::forget(self) + } +} + +impl Drop for OnDrop { + fn drop(&mut self) { + unsafe { self.f.as_ptr().read()() } + } +} + +#[cfg(test)] +#[macro_use] +extern crate std; + +#[cfg(test)] +mod tests { + #[test] + fn channel() {} +} diff --git a/rtic-channel/src/wait_queue.rs b/rtic-channel/src/wait_queue.rs new file mode 100644 index 0000000..90d762b --- /dev/null +++ b/rtic-channel/src/wait_queue.rs @@ -0,0 +1,278 @@ +//! ... + +use core::cell::UnsafeCell; +use core::marker::PhantomPinned; +use core::ptr::null_mut; +use core::sync::atomic::{AtomicPtr, Ordering}; +use core::task::Waker; +use critical_section as cs; + +pub type WaitQueue = LinkedList; + +struct MyLinkPtr(UnsafeCell<*mut Link>); + +impl MyLinkPtr { + #[inline(always)] + fn new(val: *mut Link) -> Self { + Self(UnsafeCell::new(val)) + } + + /// SAFETY: Only use this in a critical section, and don't forget them barriers. + #[inline(always)] + unsafe fn load_relaxed(&self) -> *mut Link { + unsafe { *self.0.get() } + } + + /// SAFETY: Only use this in a critical section, and don't forget them barriers. + #[inline(always)] + unsafe fn store_relaxed(&self, val: *mut Link) { + unsafe { self.0.get().write(val) } + } +} + +/// A FIFO linked list for a wait queue. +pub struct LinkedList { + head: AtomicPtr>, // UnsafeCell<*mut Link> + tail: AtomicPtr>, +} + +impl LinkedList { + /// Create a new linked list. + pub const fn new() -> Self { + Self { + head: AtomicPtr::new(null_mut()), + tail: AtomicPtr::new(null_mut()), + } + } +} + +impl LinkedList { + const R: Ordering = Ordering::Relaxed; + + /// Pop the first element in the queue. + pub fn pop(&self) -> Option { + cs::with(|_| { + // Make sure all previous writes are visible + core::sync::atomic::fence(Ordering::SeqCst); + + let head = self.head.load(Self::R); + + // SAFETY: `as_ref` is safe as `insert` requires a valid reference to a link + if let Some(head_ref) = unsafe { head.as_ref() } { + // Move head to the next element + self.head.store(head_ref.next.load(Self::R), Self::R); + + // We read the value at head + let head_val = head_ref.val.clone(); + + let tail = self.tail.load(Self::R); + if head == tail { + // The queue is empty + self.tail.store(null_mut(), Self::R); + } + + if let Some(next_ref) = unsafe { head_ref.next.load(Self::R).as_ref() } { + next_ref.prev.store(null_mut(), Self::R); + } + + // Clear the pointers in the node. + head_ref.next.store(null_mut(), Self::R); + head_ref.prev.store(null_mut(), Self::R); + + return Some(head_val); + } + + None + }) + } + + /// Put an element at the back of the queue. + pub fn push(&self, link: &mut Link) { + cs::with(|_| { + // Make sure all previous writes are visible + core::sync::atomic::fence(Ordering::SeqCst); + + let tail = self.tail.load(Self::R); + + if let Some(tail_ref) = unsafe { tail.as_ref() } { + // Queue is not empty + link.prev.store(tail, Self::R); + self.tail.store(link, Self::R); + tail_ref.next.store(link, Self::R); + } else { + // Queue is empty + self.tail.store(link, Self::R); + self.head.store(link, Self::R); + } + }); + } + + /// Check if the queue is empty. + pub fn is_empty(&self) -> bool { + self.head.load(Self::R).is_null() + } +} + +/// A link in the linked list. +pub struct Link { + pub(crate) val: T, + next: AtomicPtr>, + prev: AtomicPtr>, + _up: PhantomPinned, +} + +impl Link { + const R: Ordering = Ordering::Relaxed; + + /// Create a new link. + pub const fn new(val: T) -> Self { + Self { + val, + next: AtomicPtr::new(null_mut()), + prev: AtomicPtr::new(null_mut()), + _up: PhantomPinned, + } + } + + pub fn remove_from_list(&mut self, list: &LinkedList) { + cs::with(|_| { + // Make sure all previous writes are visible + core::sync::atomic::fence(Ordering::SeqCst); + + let prev = self.prev.load(Self::R); + let next = self.next.load(Self::R); + + match unsafe { (prev.as_ref(), next.as_ref()) } { + (None, None) => { + // Not in the list or alone in the list, check if list head == node address + let sp = self as *const _; + + if sp == list.head.load(Ordering::Relaxed) { + list.head.store(null_mut(), Self::R); + list.tail.store(null_mut(), Self::R); + } + } + (None, Some(next_ref)) => { + // First in the list + next_ref.prev.store(null_mut(), Self::R); + list.head.store(next, Self::R); + } + (Some(prev_ref), None) => { + // Last in the list + prev_ref.next.store(null_mut(), Self::R); + list.tail.store(prev, Self::R); + } + (Some(prev_ref), Some(next_ref)) => { + // Somewhere in the list + + // Connect the `prev.next` and `next.prev` with each other to remove the node + prev_ref.next.store(next, Self::R); + next_ref.prev.store(prev, Self::R); + } + } + }) + } +} + +#[cfg(test)] +impl LinkedList { + fn print(&self) { + cs::with(|_| { + // Make sure all previous writes are visible + core::sync::atomic::fence(Ordering::SeqCst); + + let mut head = self.head.load(Self::R); + let tail = self.tail.load(Self::R); + + println!( + "List - h = 0x{:x}, t = 0x{:x}", + head as usize, tail as usize + ); + + let mut i = 0; + + // SAFETY: `as_ref` is safe as `insert` requires a valid reference to a link + while let Some(head_ref) = unsafe { head.as_ref() } { + println!( + " {}: {:?}, s = 0x{:x}, n = 0x{:x}, p = 0x{:x}", + i, + head_ref.val, + head as usize, + head_ref.next.load(Ordering::Relaxed) as usize, + head_ref.prev.load(Ordering::Relaxed) as usize + ); + + head = head_ref.next.load(Self::R); + + i += 1; + } + }); + } +} + +#[cfg(test)] +impl Link { + fn print(&self) { + cs::with(|_| { + // Make sure all previous writes are visible + core::sync::atomic::fence(Ordering::SeqCst); + + println!("Link:"); + + println!( + " val = {:?}, n = 0x{:x}, p = 0x{:x}", + self.val, + self.next.load(Ordering::Relaxed) as usize, + self.prev.load(Ordering::Relaxed) as usize + ); + }); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn linked_list() { + let mut wq = LinkedList::::new(); + + let mut i1 = Link::new(10); + let mut i2 = Link::new(11); + let mut i3 = Link::new(12); + let mut i4 = Link::new(13); + let mut i5 = Link::new(14); + + wq.push(&mut i1); + wq.push(&mut i2); + wq.push(&mut i3); + wq.push(&mut i4); + wq.push(&mut i5); + + wq.print(); + + wq.pop(); + i1.print(); + + wq.print(); + + i4.remove_from_list(&wq); + + wq.print(); + + // i1.remove_from_list(&wq); + // wq.print(); + + println!("i2"); + i2.remove_from_list(&wq); + wq.print(); + + println!("i3"); + i3.remove_from_list(&wq); + wq.print(); + + println!("i5"); + i5.remove_from_list(&wq); + wq.print(); + } +} diff --git a/rtic-time/src/lib.rs b/rtic-time/src/lib.rs index 34f9362..78ece1d 100644 --- a/rtic-time/src/lib.rs +++ b/rtic-time/src/lib.rs @@ -1,7 +1,6 @@ //! Crate #![no_std] -#![no_main] #![deny(missing_docs)] #![allow(incomplete_features)] #![feature(async_fn_in_trait)] -- cgit v1.2.3 From 1974f1f00ac950c68a99b6c83fe448093aca5011 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Tj=C3=A4der?= Date: Fri, 27 Jan 2023 11:24:43 +0100 Subject: Make clippy and fmt happy --- rtic/macros/src/codegen/util.rs | 2 +- rtic/macros/src/lib.rs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/rtic/macros/src/codegen/util.rs b/rtic/macros/src/codegen/util.rs index e121487..d0c8cc0 100644 --- a/rtic/macros/src/codegen/util.rs +++ b/rtic/macros/src/codegen/util.rs @@ -123,7 +123,7 @@ pub fn regroup_inputs( let mut tys = vec![]; for (i, input) in inputs.iter().enumerate() { - let i = Ident::new(&format!("_{}", i), Span::call_site()); + let i = Ident::new(&format!("_{i}"), Span::call_site()); let ty = &input.ty; args.push(quote!(#i: #ty)); diff --git a/rtic/macros/src/lib.rs b/rtic/macros/src/lib.rs index a8422d0..92d7cdd 100644 --- a/rtic/macros/src/lib.rs +++ b/rtic/macros/src/lib.rs @@ -2,7 +2,6 @@ html_logo_url = "https://raw.githubusercontent.com/rtic-rs/rtic/master/book/en/src/RTIC.svg", html_favicon_url = "https://raw.githubusercontent.com/rtic-rs/rtic/master/book/en/src/RTIC.svg" )] - //deny_warnings_placeholder_for_ci use proc_macro::TokenStream; -- cgit v1.2.3 From 2af2cbf637fdc9f9b8d533ad0cc00b928a209659 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Tj=C3=A4der?= Date: Fri, 27 Jan 2023 12:09:39 +0100 Subject: Experiment with changelog enforcer per path --- .github/workflows/changelog.yml | 70 ++++++++++++++++++++++++++--------------- 1 file changed, 45 insertions(+), 25 deletions(-) diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml index 6e23a7a..5616180 100644 --- a/.github/workflows/changelog.yml +++ b/.github/workflows/changelog.yml @@ -18,29 +18,49 @@ jobs: - name: Checkout sources uses: actions/checkout@v3 - - name: Check that changelog updated (rtic) - uses: dangoslen/changelog-enforcer@v3 + - name: Check which component is modified + uses: dorny/paths-filter@v2 + id: changes with: - changeLogPath: ./rtic/CHANGELOG.md - skipLabels: 'needs-changelog, skip-changelog' - missingUpdateErrorMessage: 'Please add a changelog entry in the CHANGELOG.md file.' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Check that changelog updated (rtic-timer) - uses: dangoslen/changelog-enforcer@v3 - with: - changeLogPath: ./rtic-timer/CHANGELOG.md - skipLabels: 'needs-changelog, skip-changelog' - missingUpdateErrorMessage: 'Please add a changelog entry in the CHANGELOG.md file.' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Check that changelog updated (rtic-monotonics) - uses: dangoslen/changelog-enforcer@v3 - with: - changeLogPath: ./rtic-monotonics/CHANGELOG.md - skipLabels: 'needs-changelog, skip-changelog' - missingUpdateErrorMessage: 'Please add a changelog entry in the CHANGELOG.md file.' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + filters: | + rtic: + - 'rtic/**' + rtic-timer: + - 'rtic-timer/**' + rtic-monotonics: + - 'rtic-monotonics/**' + + # run only if some file in matching folder was changed + - if: steps.changes.outputs.rtic == 'true' + steps: + + - name: Check that changelog updated (rtic) + uses: dangoslen/changelog-enforcer@v3 + with: + changeLogPath: ./rtic/CHANGELOG.md + skipLabels: 'needs-changelog, skip-changelog' + missingUpdateErrorMessage: 'Please add a changelog entry in the rtic/CHANGELOG.md file.' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - if: steps.changes.outputs.rtic-timer == 'true' + steps: + - name: Check that changelog updated (rtic-timer) + uses: dangoslen/changelog-enforcer@v3 + with: + changeLogPath: ./rtic-timer/CHANGELOG.md + skipLabels: 'needs-changelog, skip-changelog' + missingUpdateErrorMessage: 'Please add a changelog entry in the rtic-timer/CHANGELOG.md file.' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - if: steps.changes.outputs.rtic-monotonics == 'true' + steps: + - name: Check that changelog updated (rtic-monotonics) + uses: dangoslen/changelog-enforcer@v3 + with: + changeLogPath: ./rtic-monotonics/CHANGELOG.md + skipLabels: 'needs-changelog, skip-changelog' + missingUpdateErrorMessage: 'Please add a changelog entry in the rtic-monotonics/CHANGELOG.md file.' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file -- cgit v1.2.3 From 67b16594bfc35ad6fd5ed170c61384b7bdcee406 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Tj=C3=A4der?= Date: Fri, 27 Jan 2023 12:28:32 +0100 Subject: CI: Changelog fix syntax --- .github/workflows/changelog.yml | 63 +++++++++++++++++++---------------------- 1 file changed, 29 insertions(+), 34 deletions(-) diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml index 5616180..eb71572 100644 --- a/.github/workflows/changelog.yml +++ b/.github/workflows/changelog.yml @@ -30,37 +30,32 @@ jobs: rtic-monotonics: - 'rtic-monotonics/**' - # run only if some file in matching folder was changed - - if: steps.changes.outputs.rtic == 'true' - steps: - - - name: Check that changelog updated (rtic) - uses: dangoslen/changelog-enforcer@v3 - with: - changeLogPath: ./rtic/CHANGELOG.md - skipLabels: 'needs-changelog, skip-changelog' - missingUpdateErrorMessage: 'Please add a changelog entry in the rtic/CHANGELOG.md file.' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - if: steps.changes.outputs.rtic-timer == 'true' - steps: - - name: Check that changelog updated (rtic-timer) - uses: dangoslen/changelog-enforcer@v3 - with: - changeLogPath: ./rtic-timer/CHANGELOG.md - skipLabels: 'needs-changelog, skip-changelog' - missingUpdateErrorMessage: 'Please add a changelog entry in the rtic-timer/CHANGELOG.md file.' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - if: steps.changes.outputs.rtic-monotonics == 'true' - steps: - - name: Check that changelog updated (rtic-monotonics) - uses: dangoslen/changelog-enforcer@v3 - with: - changeLogPath: ./rtic-monotonics/CHANGELOG.md - skipLabels: 'needs-changelog, skip-changelog' - missingUpdateErrorMessage: 'Please add a changelog entry in the rtic-monotonics/CHANGELOG.md file.' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + - name: Check that changelog updated (rtic) + if: steps.changes.outputs.rtic == 'true' + uses: dangoslen/changelog-enforcer@v3 + with: + changeLogPath: ./rtic/CHANGELOG.md + skipLabels: 'needs-changelog, skip-changelog' + missingUpdateErrorMessage: 'Please add a changelog entry in the rtic/CHANGELOG.md file.' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Check that changelog updated (rtic-timer) + if: steps.changes.outputs.rtic-timer == 'true' + uses: dangoslen/changelog-enforcer@v3 + with: + changeLogPath: ./rtic-timer/CHANGELOG.md + skipLabels: 'needs-changelog, skip-changelog' + missingUpdateErrorMessage: 'Please add a changelog entry in the rtic-timer/CHANGELOG.md file.' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Check that changelog updated (rtic-monotonics) + if: steps.changes.outputs.rtic-monotonics == 'true' + uses: dangoslen/changelog-enforcer@v3 + with: + changeLogPath: ./rtic-monotonics/CHANGELOG.md + skipLabels: 'needs-changelog, skip-changelog' + missingUpdateErrorMessage: 'Please add a changelog entry in the rtic-monotonics/CHANGELOG.md file.' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file -- cgit v1.2.3 From 9d3c3a89aa22ba4287e1f989222a8a31b83f97fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Tj=C3=A4der?= Date: Fri, 27 Jan 2023 12:53:08 +0100 Subject: CI: Changelog: s/timer/time/ --- .github/workflows/changelog.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml index eb71572..5a3df0a 100644 --- a/.github/workflows/changelog.yml +++ b/.github/workflows/changelog.yml @@ -25,8 +25,8 @@ jobs: filters: | rtic: - 'rtic/**' - rtic-timer: - - 'rtic-timer/**' + rtic-time: + - 'rtic-time/**' rtic-monotonics: - 'rtic-monotonics/**' @@ -40,13 +40,13 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Check that changelog updated (rtic-timer) - if: steps.changes.outputs.rtic-timer == 'true' + - name: Check that changelog updated (rtic-time) + if: steps.changes.outputs.rtic-time == 'true' uses: dangoslen/changelog-enforcer@v3 with: - changeLogPath: ./rtic-timer/CHANGELOG.md + changeLogPath: ./rtic-time/CHANGELOG.md skipLabels: 'needs-changelog, skip-changelog' - missingUpdateErrorMessage: 'Please add a changelog entry in the rtic-timer/CHANGELOG.md file.' + missingUpdateErrorMessage: 'Please add a changelog entry in the rtic-time/CHANGELOG.md file.' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -- cgit v1.2.3 From f62d0d17b2a1fb05635fc7468a80f9151b514d6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Tj=C3=A4der?= Date: Fri, 27 Jan 2023 12:55:26 +0100 Subject: CI: Clippy for time, monotonics, channel --- .github/workflows/build.yml | 69 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1493c3f..6c51d89 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -89,6 +89,72 @@ jobs: working-directory: ./rtic run: cargo clippy + clippytime: + name: Cargo clippy rtic-time + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Fail on warnings + working-directory: ./rtic-time + run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs + + - name: Add Rust component clippy + working-directory: ./rtic-time + run: rustup component add clippy + + - name: Cache Dependencies + uses: Swatinem/rust-cache@v2 + + - name: cargo clippy + working-directory: ./rtic-time + run: cargo clippy + + clippymonotonics: + name: Cargo clippy rtic-monotonics + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Fail on warnings + working-directory: ./rtic-monotonics + run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs + + - name: Add Rust component clippy + working-directory: ./rtic-monotonics + run: rustup component add clippy + + - name: Cache Dependencies + uses: Swatinem/rust-cache@v2 + + - name: cargo clippy + working-directory: ./rtic-monotonics + run: cargo clippy + + clippychannel: + name: Cargo clippy rtic-channel + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Fail on warnings + working-directory: ./rtic-channel + run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs + + - name: Add Rust component clippy + working-directory: ./rtic-channel + run: rustup component add clippy + + - name: Cache Dependencies + uses: Swatinem/rust-cache@v2 + + - name: cargo clippy + working-directory: ./rtic-channel + run: cargo clippy + # Verify all examples, checks checkexamples: name: checkexamples @@ -523,6 +589,9 @@ jobs: - style - check - clippy + - clippytime + - clippymonotonics + - clippychannel - checkexamples - testexamples - checkmacros -- cgit v1.2.3 From 3f60522d63ae18dbda5c6d5daf38d5e7ed96f858 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Tj=C3=A4der?= Date: Fri, 27 Jan 2023 13:00:16 +0100 Subject: waker registration somehow lost, back again --- rtic-channel/src/waker_registration.rs | 64 ++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 rtic-channel/src/waker_registration.rs diff --git a/rtic-channel/src/waker_registration.rs b/rtic-channel/src/waker_registration.rs new file mode 100644 index 0000000..c30df7f --- /dev/null +++ b/rtic-channel/src/waker_registration.rs @@ -0,0 +1,64 @@ +use core::cell::UnsafeCell; +use core::task::Waker; + +/// A critical section based waker handler. +pub struct CriticalSectionWakerRegistration { + waker: UnsafeCell>, +} + +unsafe impl Send for CriticalSectionWakerRegistration {} +unsafe impl Sync for CriticalSectionWakerRegistration {} + +impl CriticalSectionWakerRegistration { + /// Create a new waker registration. + pub const fn new() -> Self { + Self { + waker: UnsafeCell::new(None), + } + } + + /// Register a waker. + /// This will overwrite the previous waker if there was one. + pub fn register(&self, new_waker: &Waker) { + critical_section::with(|_| { + // SAFETY: This access is protected by the critical section. + let self_waker = unsafe { &mut *self.waker.get() }; + + // From embassy + // https://github.com/embassy-rs/embassy/blob/b99533607ceed225dd12ae73aaa9a0d969a7365e/embassy-sync/src/waitqueue/waker.rs#L59-L61 + match self_waker { + // Optimization: If both the old and new Wakers wake the same task, we can simply + // keep the old waker, skipping the clone. (In most executor implementations, + // cloning a waker is somewhat expensive, comparable to cloning an Arc). + Some(ref w2) if (w2.will_wake(new_waker)) => {} + _ => { + // clone the new waker and store it + if let Some(old_waker) = core::mem::replace(self_waker, Some(new_waker.clone())) + { + // We had a waker registered for another task. Wake it, so the other task can + // reregister itself if it's still interested. + // + // If two tasks are waiting on the same thing concurrently, this will cause them + // to wake each other in a loop fighting over this WakerRegistration. This wastes + // CPU but things will still work. + // + // If the user wants to have two tasks waiting on the same thing they should use + // a more appropriate primitive that can store multiple wakers. + old_waker.wake() + } + } + } + }); + } + + /// Wake the waker. + pub fn wake(&self) { + critical_section::with(|_| { + // SAFETY: This access is protected by the critical section. + let self_waker = unsafe { &mut *self.waker.get() }; + if let Some(waker) = self_waker.take() { + waker.wake() + } + }); + } +} -- cgit v1.2.3 From 1baa4a4228cae4576e194174618bf35f5c206959 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Tj=C3=A4der?= Date: Fri, 27 Jan 2023 13:18:29 +0100 Subject: CI: Don't let warnings get away --- rtic-channel/src/lib.rs | 1 + rtic-monotonics/src/lib.rs | 1 + rtic-time/src/lib.rs | 1 + 3 files changed, 3 insertions(+) diff --git a/rtic-channel/src/lib.rs b/rtic-channel/src/lib.rs index a7098ee..166015f 100644 --- a/rtic-channel/src/lib.rs +++ b/rtic-channel/src/lib.rs @@ -2,6 +2,7 @@ #![no_std] #![deny(missing_docs)] +//deny_warnings_placeholder_for_ci use core::{ cell::UnsafeCell, diff --git a/rtic-monotonics/src/lib.rs b/rtic-monotonics/src/lib.rs index ce30c36..1e23088 100644 --- a/rtic-monotonics/src/lib.rs +++ b/rtic-monotonics/src/lib.rs @@ -3,6 +3,7 @@ #![no_std] #![no_main] #![deny(missing_docs)] +//deny_warnings_placeholder_for_ci #![allow(incomplete_features)] #![feature(async_fn_in_trait)] diff --git a/rtic-time/src/lib.rs b/rtic-time/src/lib.rs index 78ece1d..eeecd86 100644 --- a/rtic-time/src/lib.rs +++ b/rtic-time/src/lib.rs @@ -2,6 +2,7 @@ #![no_std] #![deny(missing_docs)] +//deny_warnings_placeholder_for_ci #![allow(incomplete_features)] #![feature(async_fn_in_trait)] -- cgit v1.2.3 From d23de6282365bbe83099e3d10877bdd435559016 Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Fri, 27 Jan 2023 19:33:25 +0100 Subject: Remove parsing on `capacity` --- rtic/macros/src/syntax/analyze.rs | 3 --- rtic/macros/src/syntax/parse.rs | 44 --------------------------------------- 2 files changed, 47 deletions(-) diff --git a/rtic/macros/src/syntax/analyze.rs b/rtic/macros/src/syntax/analyze.rs index 3ed1487..57f9f2c 100644 --- a/rtic/macros/src/syntax/analyze.rs +++ b/rtic/macros/src/syntax/analyze.rs @@ -367,9 +367,6 @@ pub type SyncTypes = Set>; /// A channel used to send messages #[derive(Debug, Default)] pub struct Channel { - /// The channel capacity - pub capacity: u8, - /// Tasks that can be spawned on this channel pub tasks: BTreeSet, } diff --git a/rtic/macros/src/syntax/parse.rs b/rtic/macros/src/syntax/parse.rs index c78453a..72eeeaf 100644 --- a/rtic/macros/src/syntax/parse.rs +++ b/rtic/macros/src/syntax/parse.rs @@ -191,7 +191,6 @@ fn task_args(tokens: TokenStream2) -> parse::Result parse::Result { - if capacity.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - if binds.is_some() { - return Err(parse::Error::new( - ident.span(), - "hardware tasks can't use the `capacity` argument", - )); - } - - // #lit - let lit: LitInt = content.parse()?; - - if !lit.suffix().is_empty() { - return Err(parse::Error::new( - lit.span(), - "this literal must be unsuffixed", - )); - } - - let value = lit.base10_parse::().ok(); - if value.is_none() || value == Some(0) { - return Err(parse::Error::new( - lit.span(), - "this literal must be in the range 1...255", - )); - } - - capacity = Some(value.unwrap()); - } - "priority" => { if priority.is_some() { return Err(parse::Error::new( -- cgit v1.2.3 From 922f1ad0eb56d362e527ff28e456b6fa133e212b Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Fri, 27 Jan 2023 20:20:14 +0100 Subject: Added examples for async crates + fixed codegen for non-Copy arguments --- rtic-channel/.gitignore | 2 + rtic-channel/src/lib.rs | 36 ++++++++++++++++-- rtic-channel/src/wait_queue.rs | 22 ----------- rtic-monotonics/Cargo.toml | 1 + rtic-monotonics/src/systick_monotonic.rs | 8 ++-- rtic/Cargo.toml | 3 ++ rtic/ci/expected/async-channel.run | 6 +++ rtic/ci/expected/message_passing.run | 2 - rtic/examples/async-channel.rs | 61 ++++++++++++++++++++++++++++++ rtic/examples/async-delay.no_rs | 63 ------------------------------- rtic/examples/async-delay.rs | 65 ++++++++++++++++++++++++++++++++ rtic/examples/message_passing.no_rs | 37 ------------------ rtic/examples/message_passing.rs | 34 +++++++++++++++++ rtic/macros/src/codegen/module.rs | 19 ++++++---- rtic/src/export/executor.rs | 24 ++++++------ 15 files changed, 229 insertions(+), 154 deletions(-) create mode 100644 rtic-channel/.gitignore create mode 100644 rtic/ci/expected/async-channel.run create mode 100644 rtic/examples/async-channel.rs delete mode 100644 rtic/examples/async-delay.no_rs create mode 100644 rtic/examples/async-delay.rs delete mode 100644 rtic/examples/message_passing.no_rs create mode 100644 rtic/examples/message_passing.rs diff --git a/rtic-channel/.gitignore b/rtic-channel/.gitignore new file mode 100644 index 0000000..1e7caa9 --- /dev/null +++ b/rtic-channel/.gitignore @@ -0,0 +1,2 @@ +Cargo.lock +target/ diff --git a/rtic-channel/src/lib.rs b/rtic-channel/src/lib.rs index 166015f..5ee2c71 100644 --- a/rtic-channel/src/lib.rs +++ b/rtic-channel/src/lib.rs @@ -67,7 +67,7 @@ impl Channel { /// Split the queue into a `Sender`/`Receiver` pair. pub fn split<'a>(&'a mut self) -> (Sender<'a, T, N>, Receiver<'a, T, N>) { // Fill free queue - for idx in 0..(N - 1) as u8 { + for idx in 0..N as u8 { debug_assert!(!self.freeq.get_mut().is_full()); // SAFETY: This safe as the loop goes from 0 to the capacity of the underlying queue. @@ -114,11 +114,26 @@ macro_rules! make_channel { /// Error state for when the receiver has been dropped. pub struct NoReceiver(pub T); +impl core::fmt::Debug for NoReceiver +where + T: core::fmt::Debug, +{ + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "NoReceiver({:?})", self.0) + } +} + /// A `Sender` can send to the channel and can be cloned. pub struct Sender<'a, T, const N: usize>(&'a Channel); unsafe impl<'a, T, const N: usize> Send for Sender<'a, T, N> {} +impl<'a, T, const N: usize> core::fmt::Debug for Sender<'a, T, N> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "Sender") + } +} + impl<'a, T, const N: usize> Sender<'a, T, N> { #[inline(always)] fn send_footer(&mut self, idx: u8, val: T) { @@ -204,7 +219,7 @@ impl<'a, T, const N: usize> Sender<'a, T, N> { // Return the index Poll::Ready(Ok(idx)) } else { - return Poll::Pending; + Poll::Pending } }) .await; @@ -267,16 +282,29 @@ impl<'a, T, const N: usize> Clone for Sender<'a, T, N> { /// A receiver of the channel. There can only be one receiver at any time. pub struct Receiver<'a, T, const N: usize>(&'a Channel); +unsafe impl<'a, T, const N: usize> Send for Receiver<'a, T, N> {} + +impl<'a, T, const N: usize> core::fmt::Debug for Receiver<'a, T, N> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "Receiver") + } +} + /// Error state for when all senders has been dropped. pub struct NoSender; +impl core::fmt::Debug for NoSender { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "NoSender") + } +} + impl<'a, T, const N: usize> Receiver<'a, T, N> { /// Receives a value if there is one in the channel, non-blocking. /// Note; this does not check if the channel is closed. pub fn try_recv(&mut self) -> Option { // Try to get a ready slot. - let ready_slot = - critical_section::with(|cs| self.0.access(cs).readyq.pop_front().map(|rs| rs)); + let ready_slot = critical_section::with(|cs| self.0.access(cs).readyq.pop_front()); if let Some(rs) = ready_slot { // Read the value from the slots, note; this memcpy is not under a critical section. diff --git a/rtic-channel/src/wait_queue.rs b/rtic-channel/src/wait_queue.rs index 90d762b..5b59983 100644 --- a/rtic-channel/src/wait_queue.rs +++ b/rtic-channel/src/wait_queue.rs @@ -1,6 +1,5 @@ //! ... -use core::cell::UnsafeCell; use core::marker::PhantomPinned; use core::ptr::null_mut; use core::sync::atomic::{AtomicPtr, Ordering}; @@ -9,27 +8,6 @@ use critical_section as cs; pub type WaitQueue = LinkedList; -struct MyLinkPtr(UnsafeCell<*mut Link>); - -impl MyLinkPtr { - #[inline(always)] - fn new(val: *mut Link) -> Self { - Self(UnsafeCell::new(val)) - } - - /// SAFETY: Only use this in a critical section, and don't forget them barriers. - #[inline(always)] - unsafe fn load_relaxed(&self) -> *mut Link { - unsafe { *self.0.get() } - } - - /// SAFETY: Only use this in a critical section, and don't forget them barriers. - #[inline(always)] - unsafe fn store_relaxed(&self, val: *mut Link) { - unsafe { self.0.get().write(val) } - } -} - /// A FIFO linked list for a wait queue. pub struct LinkedList { head: AtomicPtr>, // UnsafeCell<*mut Link> diff --git a/rtic-monotonics/Cargo.toml b/rtic-monotonics/Cargo.toml index 68daba4..9d364c8 100644 --- a/rtic-monotonics/Cargo.toml +++ b/rtic-monotonics/Cargo.toml @@ -10,3 +10,4 @@ cortex-m = { version = "0.7.6" } embedded-hal-async = "0.2.0-alpha.0" fugit = { version = "0.3.6", features = ["defmt"] } rtic-time = { version = "1.0.0", path = "../rtic-time" } +atomic-polyfill = "1" diff --git a/rtic-monotonics/src/systick_monotonic.rs b/rtic-monotonics/src/systick_monotonic.rs index af17f93..fec97f2 100644 --- a/rtic-monotonics/src/systick_monotonic.rs +++ b/rtic-monotonics/src/systick_monotonic.rs @@ -2,13 +2,11 @@ use super::Monotonic; pub use super::{TimeoutError, TimerQueue}; -use core::{ - ops::Deref, - sync::atomic::{AtomicU32, Ordering}, -}; +use atomic_polyfill::{AtomicU32, Ordering}; +use core::ops::Deref; use cortex_m::peripheral::SYST; use embedded_hal_async::delay::DelayUs; -use fugit::ExtU32; +pub use fugit::ExtU32; const TIMER_HZ: u32 = 1_000; diff --git a/rtic/Cargo.toml b/rtic/Cargo.toml index 6eb691d..12a34da 100644 --- a/rtic/Cargo.toml +++ b/rtic/Cargo.toml @@ -38,6 +38,9 @@ version_check = "0.9" lm3s6965 = "0.1.3" cortex-m-semihosting = "0.5.0" systick-monotonic = "1.0.0" +rtic-time = { path = "../rtic-time" } +rtic-channel = { path = "../rtic-channel" } +rtic-monotonics = { path = "../rtic-monotonics" } [dev-dependencies.panic-semihosting] features = ["exit"] diff --git a/rtic/ci/expected/async-channel.run b/rtic/ci/expected/async-channel.run new file mode 100644 index 0000000..3e3c232 --- /dev/null +++ b/rtic/ci/expected/async-channel.run @@ -0,0 +1,6 @@ +Sender 1 sending: 1 +Sender 2 sending: 2 +Sender 3 sending: 3 +Receiver got: 1 +Receiver got: 2 +Receiver got: 5 diff --git a/rtic/ci/expected/message_passing.run b/rtic/ci/expected/message_passing.run index a1448d8..9085e13 100644 --- a/rtic/ci/expected/message_passing.run +++ b/rtic/ci/expected/message_passing.run @@ -1,3 +1 @@ foo 1, 1 -foo 1, 2 -foo 2, 3 diff --git a/rtic/examples/async-channel.rs b/rtic/examples/async-channel.rs new file mode 100644 index 0000000..3a2e491 --- /dev/null +++ b/rtic/examples/async-channel.rs @@ -0,0 +1,61 @@ +//! examples/async-channel.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use rtic_channel::*; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + let (s, r) = make_channel!(u32, 5); + + receiver::spawn(r).unwrap(); + sender1::spawn(s.clone()).unwrap(); + sender2::spawn(s.clone()).unwrap(); + sender3::spawn(s).unwrap(); + + (Shared {}, Local {}) + } + + #[task] + async fn receiver(_c: receiver::Context, mut receiver: Receiver<'static, u32, 5>) { + while let Ok(val) = receiver.recv().await { + hprintln!("Receiver got: {}", val); + if val == 5 { + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } + } + } + + #[task] + async fn sender1(_c: sender1::Context, mut sender: Sender<'static, u32, 5>) { + hprintln!("Sender 1 sending: 1"); + sender.send(1).await.unwrap(); + } + + #[task] + async fn sender2(_c: sender2::Context, mut sender: Sender<'static, u32, 5>) { + hprintln!("Sender 2 sending: 2"); + sender.send(2).await.unwrap(); + } + + #[task] + async fn sender3(_c: sender3::Context, mut sender: Sender<'static, u32, 5>) { + hprintln!("Sender 3 sending: 3"); + sender.send(5).await.unwrap(); + } +} diff --git a/rtic/examples/async-delay.no_rs b/rtic/examples/async-delay.no_rs deleted file mode 100644 index fb478c3..0000000 --- a/rtic/examples/async-delay.no_rs +++ /dev/null @@ -1,63 +0,0 @@ -#![no_main] -#![no_std] -#![feature(type_alias_impl_trait)] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0, UART0], peripherals = true)] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - use systick_monotonic::*; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[monotonic(binds = SysTick, default = true)] - type MyMono = Systick<100>; - - #[init] - fn init(cx: init::Context) -> (Shared, Local) { - hprintln!("init").unwrap(); - - foo::spawn().ok(); - bar::spawn().ok(); - baz::spawn().ok(); - - (Shared {}, Local {}) - } - - #[idle] - fn idle(_: idle::Context) -> ! { - // debug::exit(debug::EXIT_SUCCESS); - loop { - // hprintln!("idle"); - cortex_m::asm::wfi(); // put the MCU in sleep mode until interrupt occurs - } - } - - #[task] - async fn foo(_cx: foo::Context) { - hprintln!("hello from foo").ok(); - monotonics::delay(100.millis()).await; - hprintln!("bye from foo").ok(); - } - - #[task] - async fn bar(_cx: bar::Context) { - hprintln!("hello from bar").ok(); - monotonics::delay(200.millis()).await; - hprintln!("bye from bar").ok(); - } - - #[task] - async fn baz(_cx: baz::Context) { - hprintln!("hello from baz").ok(); - monotonics::delay(300.millis()).await; - hprintln!("bye from baz").ok(); - - debug::exit(debug::EXIT_SUCCESS); - } -} diff --git a/rtic/examples/async-delay.rs b/rtic/examples/async-delay.rs new file mode 100644 index 0000000..0440f77 --- /dev/null +++ b/rtic/examples/async-delay.rs @@ -0,0 +1,65 @@ +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0, UART0], peripherals = true)] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use rtic_monotonics::systick_monotonic::*; + + rtic_monotonics::make_systick_timer_queue!(TIMER); + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(cx: init::Context) -> (Shared, Local) { + hprintln!("init"); + + let systick = Systick::start(cx.core.SYST, 12_000_000); + TIMER.initialize(systick); + + foo::spawn().ok(); + bar::spawn().ok(); + baz::spawn().ok(); + + (Shared {}, Local {}) + } + + #[idle] + fn idle(_: idle::Context) -> ! { + // debug::exit(debug::EXIT_SUCCESS); + loop { + // hprintln!("idle"); + cortex_m::asm::wfi(); // put the MCU in sleep mode until interrupt occurs + } + } + + #[task] + async fn foo(_cx: foo::Context) { + hprintln!("hello from foo"); + TIMER.delay(100.millis()).await; + hprintln!("bye from foo"); + } + + #[task] + async fn bar(_cx: bar::Context) { + hprintln!("hello from bar"); + TIMER.delay(200.millis()).await; + hprintln!("bye from bar"); + } + + #[task] + async fn baz(_cx: baz::Context) { + hprintln!("hello from baz"); + TIMER.delay(300.millis()).await; + hprintln!("bye from baz"); + + debug::exit(debug::EXIT_SUCCESS); + } +} diff --git a/rtic/examples/message_passing.no_rs b/rtic/examples/message_passing.no_rs deleted file mode 100644 index ffa9537..0000000 --- a/rtic/examples/message_passing.no_rs +++ /dev/null @@ -1,37 +0,0 @@ -//! examples/message_passing.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { - foo::spawn(1, 1).unwrap(); - foo::spawn(1, 2).unwrap(); - foo::spawn(2, 3).unwrap(); - assert!(foo::spawn(1, 4).is_err()); // The capacity of `foo` is reached - - (Shared {}, Local {}, init::Monotonics()) - } - - #[task(capacity = 3)] - fn foo(_c: foo::Context, x: i32, y: u32) { - hprintln!("foo {}, {}", x, y).unwrap(); - if x == 2 { - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } - } -} diff --git a/rtic/examples/message_passing.rs b/rtic/examples/message_passing.rs new file mode 100644 index 0000000..01f1a65 --- /dev/null +++ b/rtic/examples/message_passing.rs @@ -0,0 +1,34 @@ +//! examples/message_passing.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + foo::spawn(1, 1).unwrap(); + assert!(foo::spawn(1, 4).is_err()); // The capacity of `foo` is reached + + (Shared {}, Local {}) + } + + #[task] + async fn foo(_c: foo::Context, x: i32, y: u32) { + hprintln!("foo {}, {}", x, y); + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } +} diff --git a/rtic/macros/src/codegen/module.rs b/rtic/macros/src/codegen/module.rs index 4725b9a..8b3fca2 100644 --- a/rtic/macros/src/codegen/module.rs +++ b/rtic/macros/src/codegen/module.rs @@ -157,14 +157,17 @@ pub fn codegen(ctxt: Context, app: &App, analysis: &Analysis) -> TokenStream2 { #[allow(non_snake_case)] #[doc(hidden)] pub fn #internal_spawn_ident(#(#input_args,)*) -> Result<(), #input_ty> { - - if #exec_name.spawn(|| #name(unsafe { #name::Context::new() } #(,#input_untupled)*) ) { - - #pend_interrupt - - Ok(()) - } else { - Err(#input_tupled) + // SAFETY: If `try_allocate` suceeds one must call `spawn`, which we do. + unsafe { + if #exec_name.try_allocate() { + let f = #name(unsafe { #name::Context::new() } #(,#input_untupled)*); + #exec_name.spawn(f); + #pend_interrupt + + Ok(()) + } else { + Err(#input_tupled) + } } } diff --git a/rtic/src/export/executor.rs b/rtic/src/export/executor.rs index e64cc43..36e47d7 100644 --- a/rtic/src/export/executor.rs +++ b/rtic/src/export/executor.rs @@ -70,25 +70,23 @@ impl AsyncTaskExecutor { self.pending.store(true, Ordering::Release); } - /// Spawn a future + /// Allocate the executor. To use with `spawn`. #[inline(always)] - pub fn spawn(&self, future: impl Fn() -> F) -> bool { + pub unsafe fn try_allocate(&self) -> bool { // Try to reserve the executor for a future. - if self - .running + self.running .compare_exchange(false, true, Ordering::AcqRel, Ordering::Relaxed) .is_ok() - { - // This unsafe is protected by `running` being false and the atomic setting it to true. - unsafe { - self.task.get().write(MaybeUninit::new(future())); - } - self.set_pending(); + } - true - } else { - false + /// Spawn a future + #[inline(always)] + pub unsafe fn spawn(&self, future: F) { + // This unsafe is protected by `running` being false and the atomic setting it to true. + unsafe { + self.task.get().write(MaybeUninit::new(future)); } + self.set_pending(); } /// Poll the future in the executor. -- cgit v1.2.3 From 9c6e2c1c99448304fb54354297c284c1a8810cd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Tj=C3=A4der?= Date: Sat, 28 Jan 2023 11:15:59 +0100 Subject: Add changelog templates --- rtic-channel/CHANGELOG.md | 16 ++++++++++++++++ rtic-monotonics/CHANGELOG.md | 16 ++++++++++++++++ rtic-time/CHANGELOG.md | 16 ++++++++++++++++ 3 files changed, 48 insertions(+) create mode 100644 rtic-channel/CHANGELOG.md diff --git a/rtic-channel/CHANGELOG.md b/rtic-channel/CHANGELOG.md new file mode 100644 index 0000000..d3a9d84 --- /dev/null +++ b/rtic-channel/CHANGELOG.md @@ -0,0 +1,16 @@ +# Change Log + +All notable changes to this project will be documented in this file. +This project adheres to [Semantic Versioning](http://semver.org/). + +For each category, *Added*, *Changed*, *Fixed* add new entries at the top! + +## [Unreleased] + +### Added + +### Changed + +### Fixed + +## [v1.0.0] - 2023-xx-xx diff --git a/rtic-monotonics/CHANGELOG.md b/rtic-monotonics/CHANGELOG.md index e69de29..e990223 100644 --- a/rtic-monotonics/CHANGELOG.md +++ b/rtic-monotonics/CHANGELOG.md @@ -0,0 +1,16 @@ +# Change Log + +All notable changes to this project will be documented in this file. +This project adheres to [Semantic Versioning](http://semver.org/). + +For each category, *Added*, *Changed*, *Fixed* add new entries at the top! + +## [Unreleased] + +### Added + +### Changed + +### Fixed + +## [v0.1.0] - 2023-xx-xx diff --git a/rtic-time/CHANGELOG.md b/rtic-time/CHANGELOG.md index e69de29..d3a9d84 100644 --- a/rtic-time/CHANGELOG.md +++ b/rtic-time/CHANGELOG.md @@ -0,0 +1,16 @@ +# Change Log + +All notable changes to this project will be documented in this file. +This project adheres to [Semantic Versioning](http://semver.org/). + +For each category, *Added*, *Changed*, *Fixed* add new entries at the top! + +## [Unreleased] + +### Added + +### Changed + +### Fixed + +## [v1.0.0] - 2023-xx-xx -- cgit v1.2.3 From ff12a02d020965956ae8f9bda75ef243b3d1dd3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Tj=C3=A4der?= Date: Sat, 28 Jan 2023 11:28:59 +0100 Subject: CI: Add rtic-channel to Changelog, remove defunct changelog --- .github/workflows/changelog.yml | 12 ++++++++++ .../src/syntax/.github/workflows/changelog.yml | 28 ---------------------- 2 files changed, 12 insertions(+), 28 deletions(-) delete mode 100644 rtic/macros/src/syntax/.github/workflows/changelog.yml diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml index 5a3df0a..0eb4cf6 100644 --- a/.github/workflows/changelog.yml +++ b/.github/workflows/changelog.yml @@ -25,6 +25,8 @@ jobs: filters: | rtic: - 'rtic/**' + rtic-channel: + - 'rtic-channel/**' rtic-time: - 'rtic-time/**' rtic-monotonics: @@ -40,6 +42,16 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Check that changelog updated (rtic-channel) + if: steps.changes.outputs.rtic-channel == 'true' + uses: dangoslen/changelog-enforcer@v3 + with: + changeLogPath: ./rtic-channel/CHANGELOG.md + skipLabels: 'needs-changelog, skip-changelog' + missingUpdateErrorMessage: 'Please add a changelog entry in the rtic-channel/CHANGELOG.md file.' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Check that changelog updated (rtic-time) if: steps.changes.outputs.rtic-time == 'true' uses: dangoslen/changelog-enforcer@v3 diff --git a/rtic/macros/src/syntax/.github/workflows/changelog.yml b/rtic/macros/src/syntax/.github/workflows/changelog.yml deleted file mode 100644 index ccf6eb9..0000000 --- a/rtic/macros/src/syntax/.github/workflows/changelog.yml +++ /dev/null @@ -1,28 +0,0 @@ -# Check that the changelog is updated for all changes. -# -# This is only run for PRs. - -on: - pull_request: - # opened, reopened, synchronize are the default types for pull_request. - # labeled, unlabeled ensure this check is also run if a label is added or removed. - types: [opened, reopened, labeled, unlabeled, synchronize] - -name: Changelog - -jobs: - changelog: - name: Changelog - runs-on: ubuntu-latest - steps: - - name: Checkout sources - uses: actions/checkout@v2 - - - name: Check that changelog updated - uses: dangoslen/changelog-enforcer@v3 - with: - changeLogPath: CHANGELOG.md - skipLabels: 'needs-changelog, skip-changelog' - missingUpdateErrorMessage: 'Please add a changelog entry in the CHANGELOG.md file.' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file -- cgit v1.2.3 From 07c11b071d048ef9f31e76584484719587e3ed71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Tj=C3=A4der?= Date: Sat, 28 Jan 2023 11:42:24 +0100 Subject: CI: Cargo fmt for channel, mono., time --- .github/workflows/build.yml | 48 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6c51d89..a709fb9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,7 +15,7 @@ env: jobs: # Run cargo fmt --check, includes macros/ style: - name: style + name: cargo fmt runs-on: ubuntu-22.04 steps: - name: Checkout @@ -29,6 +29,52 @@ jobs: working-directory: ./rtic run: cargo fmt --all -- --check + stylechannel: + name: cargo fmt rtic-channel + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Fail on warnings + working-directory: ./rtic-channel + run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs + + - name: cargo fmt --check + working-directory: ./rtic-channel + run: cargo fmt --all -- --check + + stylemonotonics: + name: cargo fmt rtic-monotonics + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Fail on warnings + working-directory: ./rtic-monotonics + run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs + + - name: cargo fmt --check + working-directory: ./rtic-monotonics + run: cargo fmt --all -- --check + + styletime: + name: cargo fmt rtic-time + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Fail on warnings + working-directory: ./rtic-time + run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs + + - name: cargo fmt --check + working-directory: ./rtic-time + run: cargo fmt --all -- --check + + # Compilation check check: name: check -- cgit v1.2.3 From 8cb05049bea710dce441a7221ed3a541e5f4f7b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Tj=C3=A4der?= Date: Sat, 28 Jan 2023 11:42:56 +0100 Subject: CI: Alphabetical sort of clippy jobs --- .github/workflows/build.yml | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a709fb9..9f7d221 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -135,28 +135,29 @@ jobs: working-directory: ./rtic run: cargo clippy - clippytime: - name: Cargo clippy rtic-time + clippychannel: + name: Cargo clippy rtic-channel runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v3 - name: Fail on warnings - working-directory: ./rtic-time + working-directory: ./rtic-channel run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs - name: Add Rust component clippy - working-directory: ./rtic-time + working-directory: ./rtic-channel run: rustup component add clippy - name: Cache Dependencies uses: Swatinem/rust-cache@v2 - name: cargo clippy - working-directory: ./rtic-time + working-directory: ./rtic-channel run: cargo clippy + clippymonotonics: name: Cargo clippy rtic-monotonics runs-on: ubuntu-22.04 @@ -178,27 +179,27 @@ jobs: - name: cargo clippy working-directory: ./rtic-monotonics run: cargo clippy - - clippychannel: - name: Cargo clippy rtic-channel + + clippytime: + name: Cargo clippy rtic-time runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v3 - name: Fail on warnings - working-directory: ./rtic-channel + working-directory: ./rtic-time run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs - name: Add Rust component clippy - working-directory: ./rtic-channel + working-directory: ./rtic-time run: rustup component add clippy - name: Cache Dependencies uses: Swatinem/rust-cache@v2 - name: cargo clippy - working-directory: ./rtic-channel + working-directory: ./rtic-time run: cargo clippy # Verify all examples, checks -- cgit v1.2.3 From 5908d5bdbc3a3daf741d58b925fa280a3a81bf6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Tj=C3=A4der?= Date: Sat, 28 Jan 2023 12:07:05 +0100 Subject: CI: Cleanup old syntax CI --- rtic/macros/src/syntax/.github/bors.toml | 3 - rtic/macros/src/syntax/.github/workflows/build.yml | 213 --------------------- .../workflows/properties/build.properties.json | 6 - rtic/macros/src/syntax/.gitignore | 4 - 4 files changed, 226 deletions(-) delete mode 100644 rtic/macros/src/syntax/.github/bors.toml delete mode 100644 rtic/macros/src/syntax/.github/workflows/build.yml delete mode 100644 rtic/macros/src/syntax/.github/workflows/properties/build.properties.json delete mode 100644 rtic/macros/src/syntax/.gitignore diff --git a/rtic/macros/src/syntax/.github/bors.toml b/rtic/macros/src/syntax/.github/bors.toml deleted file mode 100644 index aee6042..0000000 --- a/rtic/macros/src/syntax/.github/bors.toml +++ /dev/null @@ -1,3 +0,0 @@ -block_labels = ["S-blocked"] -delete_merged_branches = true -status = ["ci"] diff --git a/rtic/macros/src/syntax/.github/workflows/build.yml b/rtic/macros/src/syntax/.github/workflows/build.yml deleted file mode 100644 index 29971b1..0000000 --- a/rtic/macros/src/syntax/.github/workflows/build.yml +++ /dev/null @@ -1,213 +0,0 @@ -name: Build -on: - pull_request: - push: - branches: - - master - - staging - - trying - - bors/staging - - bors/trying - -env: - CARGO_TERM_COLOR: always - -jobs: - # Run cargo fmt --check - style: - name: style - runs-on: ubuntu-20.04 - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - override: true - components: rustfmt - - - name: Fail on warnings - run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs - - - name: cargo fmt --check - uses: actions-rs/cargo@v1 - with: - command: fmt - args: --all -- --check - - # Compilation check - check: - name: check - runs-on: ubuntu-20.04 - strategy: - matrix: - target: - - x86_64-unknown-linux-gnu - toolchain: - - stable - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Install Rust ${{ matrix.toolchain }} with target (${{ matrix.target }}) - uses: actions-rs/toolchain@v1 - with: - toolchain: ${{ matrix.toolchain }} - target: ${{ matrix.target }} - override: true - - - name: Fail on warnings - run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs - - - name: Cache Dependencies - uses: Swatinem/rust-cache@v1 - - - name: cargo check - uses: actions-rs/cargo@v1 - with: - use-cross: false - command: check - args: --target=${{ matrix.target }} - - # Clippy - clippy: - name: Cargo clippy - runs-on: ubuntu-20.04 - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Install Rust stable - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - target: x86_64-unknown-linux-gnu - override: true - - - name: Fail on warnings - run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs - - - name: Cache Dependencies - uses: Swatinem/rust-cache@v1 - - - name: cargo clippy - uses: actions-rs/cargo@v1 - with: - use-cross: false - command: clippy - - # Verify all examples - testexamples: - name: testexamples - runs-on: ubuntu-20.04 - strategy: - matrix: - target: - - x86_64-unknown-linux-gnu - toolchain: - - stable - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Install Rust ${{ matrix.toolchain }} with target (${{ matrix.target }}) - uses: actions-rs/toolchain@v1 - with: - toolchain: ${{ matrix.toolchain }} - target: ${{ matrix.target }} - override: true - - - name: Cache Dependencies - uses: Swatinem/rust-cache@v1 - - - name: Fail on warnings - run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs - - - uses: actions-rs/cargo@v1 - with: - use-cross: false - command: test - args: --examples - - # Run test suite for UI - testui: - name: testui - runs-on: ubuntu-20.04 - strategy: - matrix: - target: - - x86_64-unknown-linux-gnu - toolchain: - - stable - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Install Rust ${{ matrix.toolchain }} with target (${{ matrix.target }}) - uses: actions-rs/toolchain@v1 - with: - toolchain: ${{ matrix.toolchain }} - target: ${{ matrix.target }} - override: true - - - name: Cache Dependencies - uses: Swatinem/rust-cache@v1 - - - name: Fail on warnings - run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs - - - - uses: actions-rs/cargo@v1 - with: - use-cross: false - command: test - args: --test ui - - # Run test suite - test: - name: test - runs-on: ubuntu-20.04 - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - target: thumbv7m-none-eabi - override: true - - - name: Cache Dependencies - uses: Swatinem/rust-cache@v1 - - - name: Fail on warnings - run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs - - - uses: actions-rs/cargo@v1 - with: - use-cross: false - command: test - args: --lib - - # Refs: https://github.com/rust-lang/crater/blob/9ab6f9697c901c4a44025cf0a39b73ad5b37d198/.github/workflows/bors.yml#L125-L149 - # - # ALL THE PREVIOUS JOBS NEEDS TO BE ADDED TO THE `needs` SECTION OF THIS JOB! - - ci-success: - name: ci - if: github.event_name == 'push' && success() - needs: - - style - - check - - clippy - - testexamples - - test - - testui - runs-on: ubuntu-20.04 - steps: - - name: Mark the job as a success - run: exit 0 diff --git a/rtic/macros/src/syntax/.github/workflows/properties/build.properties.json b/rtic/macros/src/syntax/.github/workflows/properties/build.properties.json deleted file mode 100644 index fd3eed3..0000000 --- a/rtic/macros/src/syntax/.github/workflows/properties/build.properties.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "Build", - "description": "RTIC Test Suite", - "iconName": "rust", - "categories": ["Rust"] -} diff --git a/rtic/macros/src/syntax/.gitignore b/rtic/macros/src/syntax/.gitignore deleted file mode 100644 index f8d7c8b..0000000 --- a/rtic/macros/src/syntax/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -**/*.rs.bk -.#* -/target/ -Cargo.lock -- cgit v1.2.3 From 3050fc0591f087a4fbe08840c69633e89d3f58a7 Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Sat, 28 Jan 2023 13:21:44 +0100 Subject: Use `Pin` in the linked lists --- rtic-channel/src/lib.rs | 18 +++++++++++++----- rtic-channel/src/wait_queue.rs | 16 ++++++++++------ rtic-time/src/lib.rs | 18 +++++++++++++++--- rtic-time/src/linked_list.rs | 5 ++++- 4 files changed, 42 insertions(+), 15 deletions(-) diff --git a/rtic-channel/src/lib.rs b/rtic-channel/src/lib.rs index 5ee2c71..20086ac 100644 --- a/rtic-channel/src/lib.rs +++ b/rtic-channel/src/lib.rs @@ -8,6 +8,7 @@ use core::{ cell::UnsafeCell, future::poll_fn, mem::MaybeUninit, + pin::Pin, ptr, task::{Poll, Waker}, }; @@ -177,10 +178,11 @@ impl<'a, T, const N: usize> Sender<'a, T, N> { pub async fn send(&mut self, val: T) -> Result<(), NoReceiver> { if self.is_closed() {} - let mut __hidden_link: Option> = None; + let mut link_ptr: Option> = None; + + // Make this future `Drop`-safe, also shadow the original definition so we can't abuse it. + let link_ptr = &mut link_ptr as *mut Option>; - // Make this future `Drop`-safe - let link_ptr = &mut __hidden_link as *mut Option>; let dropper = OnDrop::new(|| { // SAFETY: We only run this closure and dereference the pointer if we have // exited the `poll_fn` below in the `drop(dropper)` call. The other dereference @@ -198,12 +200,18 @@ impl<'a, T, const N: usize> Sender<'a, T, N> { // Do all this in one critical section, else there can be race conditions let queue_idx = critical_section::with(|cs| { if !self.0.wait_queue.is_empty() || self.0.access(cs).freeq.is_empty() { - // SAFETY: This pointer is only dereferenced here and on drop of the future. + // SAFETY: This pointer is only dereferenced here and on drop of the future + // which happens outside this `poll_fn`'s stack frame. let link = unsafe { &mut *link_ptr }; if link.is_none() { // Place the link in the wait queue on first run. let link_ref = link.insert(wait_queue::Link::new(cx.waker().clone())); - self.0.wait_queue.push(link_ref); + + // SAFETY: The address to the link is stable as it is hidden behind + // `link_ptr`, and `link_ptr` shadows the original making it unmovable. + self.0 + .wait_queue + .push(unsafe { Pin::new_unchecked(link_ref) }); } return None; diff --git a/rtic-channel/src/wait_queue.rs b/rtic-channel/src/wait_queue.rs index 5b59983..ba05e6b 100644 --- a/rtic-channel/src/wait_queue.rs +++ b/rtic-channel/src/wait_queue.rs @@ -1,6 +1,7 @@ //! ... use core::marker::PhantomPinned; +use core::pin::Pin; use core::ptr::null_mut; use core::sync::atomic::{AtomicPtr, Ordering}; use core::task::Waker; @@ -65,13 +66,16 @@ impl LinkedList { } /// Put an element at the back of the queue. - pub fn push(&self, link: &mut Link) { + pub fn push(&self, link: Pin<&mut Link>) { cs::with(|_| { // Make sure all previous writes are visible core::sync::atomic::fence(Ordering::SeqCst); let tail = self.tail.load(Self::R); + // SAFETY: This datastructure does not move the underlying value. + let link = unsafe { link.get_unchecked_mut() }; + if let Some(tail_ref) = unsafe { tail.as_ref() } { // Queue is not empty link.prev.store(tail, Self::R); @@ -221,11 +225,11 @@ mod tests { let mut i4 = Link::new(13); let mut i5 = Link::new(14); - wq.push(&mut i1); - wq.push(&mut i2); - wq.push(&mut i3); - wq.push(&mut i4); - wq.push(&mut i5); + wq.push(unsafe { Pin::new_unchecked(&mut i1) }); + wq.push(unsafe { Pin::new_unchecked(&mut i2) }); + wq.push(unsafe { Pin::new_unchecked(&mut i3) }); + wq.push(unsafe { Pin::new_unchecked(&mut i4) }); + wq.push(unsafe { Pin::new_unchecked(&mut i5) }); wq.print(); diff --git a/rtic-time/src/lib.rs b/rtic-time/src/lib.rs index eeecd86..6b23f76 100644 --- a/rtic-time/src/lib.rs +++ b/rtic-time/src/lib.rs @@ -7,6 +7,7 @@ #![feature(async_fn_in_trait)] use core::future::{poll_fn, Future}; +use core::pin::Pin; use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use core::task::{Poll, Waker}; use futures_util::{ @@ -185,7 +186,10 @@ impl TimerQueue { ); } - let mut link = None; + let mut link_ptr: Option>> = None; + + // Make this future `Drop`-safe, also shadow the original definition so we can't abuse it. + let link_ptr = &mut link_ptr as *mut Option>>; let queue = &self.queue; let marker = &AtomicUsize::new(0); @@ -199,6 +203,9 @@ impl TimerQueue { return Poll::Ready(()); } + // SAFETY: This pointer is only dereferenced here and on drop of the future + // which happens outside this `poll_fn`'s stack frame. + let link = unsafe { &mut *link_ptr }; if link.is_none() { let mut link_ref = link.insert(Link::new(WaitingWaker { waker: cx.waker().clone(), @@ -206,7 +213,9 @@ impl TimerQueue { was_poped: AtomicBool::new(false), })); - let (was_empty, addr) = queue.insert(&mut link_ref); + // SAFETY: The address to the link is stable as it is defined outside this stack + // frame. + let (was_empty, addr) = queue.insert(unsafe { Pin::new_unchecked(&mut link_ref) }); marker.store(addr, Ordering::Relaxed); if was_empty { @@ -219,7 +228,10 @@ impl TimerQueue { }) .await; - if let Some(link) = link { + // SAFETY: We only run this and dereference the pointer if we have + // exited the `poll_fn` below in the `drop(dropper)` call. The other dereference + // of this pointer is in the `poll_fn`. + if let Some(link) = unsafe { &mut *link_ptr } { if link.val.was_poped.load(Ordering::Relaxed) { // If it was poped from the queue there is no need to run delete dropper.defuse(); diff --git a/rtic-time/src/linked_list.rs b/rtic-time/src/linked_list.rs index 52a955b..de5ea2a 100644 --- a/rtic-time/src/linked_list.rs +++ b/rtic-time/src/linked_list.rs @@ -1,6 +1,7 @@ //! ... use core::marker::PhantomPinned; +use core::pin::Pin; use core::sync::atomic::{AtomicPtr, Ordering}; use critical_section as cs; @@ -92,8 +93,10 @@ impl LinkedList { /// Insert a new link into the linked list. /// The return is (was_empty, address), where the address of the link is for use with `delete`. - pub fn insert(&self, val: &mut Link) -> (bool, usize) { + pub fn insert(&self, val: Pin<&mut Link>) -> (bool, usize) { cs::with(|_| { + // SAFETY: This datastructure does not move the underlying value. + let val = unsafe { val.get_unchecked_mut() }; let addr = val as *const _ as usize; // Make sure all previous writes are visible -- cgit v1.2.3 From b2c53824303dddd092ba4d9d7928635d1f89f789 Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Sat, 28 Jan 2023 13:35:37 +0100 Subject: rtic-channel: Fix clippy lint --- rtic-channel/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtic-channel/src/lib.rs b/rtic-channel/src/lib.rs index 20086ac..b6a317f 100644 --- a/rtic-channel/src/lib.rs +++ b/rtic-channel/src/lib.rs @@ -66,7 +66,7 @@ impl Channel { } /// Split the queue into a `Sender`/`Receiver` pair. - pub fn split<'a>(&'a mut self) -> (Sender<'a, T, N>, Receiver<'a, T, N>) { + pub fn split(&mut self) -> (Sender<'_, T, N>, Receiver<'_, T, N>) { // Fill free queue for idx in 0..N as u8 { debug_assert!(!self.freeq.get_mut().is_full()); -- cgit v1.2.3 From 4cf8bbbf192064aad2e55e29f6494c1bf1010437 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Tj=C3=A4der?= Date: Sat, 28 Jan 2023 12:21:48 +0100 Subject: Book: Fix gitignore to exclude mdbook output --- book/.gitignore | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/book/.gitignore b/book/.gitignore index ee70b5e..79339e2 100644 --- a/book/.gitignore +++ b/book/.gitignore @@ -1,7 +1,2 @@ -**/*.rs.bk -.#* -.gdb_history -/*/book -/target -Cargo.lock -*.hex +# Make sure that mdbook output in repo-root/book//book is ignored +*/book -- cgit v1.2.3 From 6021aa2df8cbcf74910ea4b19ad24036f529710f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Tj=C3=A4der?= Date: Sat, 28 Jan 2023 14:02:54 +0100 Subject: CI: Check and tests for all crates --- .github/workflows/build.yml | 176 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 172 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9f7d221..2e3f2b7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,7 +15,7 @@ env: jobs: # Run cargo fmt --check, includes macros/ style: - name: cargo fmt + name: cargo fmt rtic runs-on: ubuntu-22.04 steps: - name: Checkout @@ -77,7 +77,7 @@ jobs: # Compilation check check: - name: check + name: check rtic runs-on: ubuntu-22.04 strategy: matrix: @@ -112,9 +112,120 @@ jobs: working-directory: ./rtic run: cargo check --target=${{ matrix.target }} + # Compilation check + checkchannel: + name: check rtic-channel + runs-on: ubuntu-22.04 + strategy: + matrix: + target: + - thumbv7m-none-eabi + - thumbv6m-none-eabi + - x86_64-unknown-linux-gnu + toolchain: + - nightly + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Install Rust ${{ matrix.toolchain }} + working-directory: ./rtic-channel + run: | + rustup set profile minimal + rustup override set ${{ matrix.toolchain }} + + - name: Configure Rust target (${{ matrix.target }}) + working-directory: ./rtic-channel + run: rustup target add ${{ matrix.target }} + + - name: Fail on warnings + working-directory: ./rtic-channel + run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs + + - name: Cache Dependencies + uses: Swatinem/rust-cache@v2 + + - name: cargo check + working-directory: ./rtic-channel + run: cargo check --target=${{ matrix.target }} + + # Compilation check + checkmonotonics: + name: check rtic-monotonics + runs-on: ubuntu-22.04 + strategy: + matrix: + target: + - thumbv7m-none-eabi + - thumbv6m-none-eabi + - x86_64-unknown-linux-gnu + toolchain: + - nightly + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Install Rust ${{ matrix.toolchain }} + working-directory: ./rtic-monotonics + run: | + rustup set profile minimal + rustup override set ${{ matrix.toolchain }} + + - name: Configure Rust target (${{ matrix.target }}) + working-directory: ./rtic-monotonics + run: rustup target add ${{ matrix.target }} + + - name: Fail on warnings + working-directory: ./rtic-monotonics + run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs + + - name: Cache Dependencies + uses: Swatinem/rust-cache@v2 + + - name: cargo check + working-directory: ./rtic-monotonics + run: cargo check --target=${{ matrix.target }} + + # Compilation check + checktime: + name: check rtic-time + runs-on: ubuntu-22.04 + strategy: + matrix: + target: + - thumbv7m-none-eabi + - thumbv6m-none-eabi + - x86_64-unknown-linux-gnu + toolchain: + - nightly + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Install Rust ${{ matrix.toolchain }} + working-directory: ./rtic-time + run: | + rustup set profile minimal + rustup override set ${{ matrix.toolchain }} + + - name: Configure Rust target (${{ matrix.target }}) + working-directory: ./rtic-time + run: rustup target add ${{ matrix.target }} + + - name: Fail on warnings + working-directory: ./rtic-time + run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs + + - name: Cache Dependencies + uses: Swatinem/rust-cache@v2 + + - name: cargo check + working-directory: ./rtic-time + run: cargo check --target=${{ matrix.target }} + # Clippy clippy: - name: Cargo clippy + name: Cargo clippy rtic runs-on: ubuntu-22.04 steps: - name: Checkout @@ -337,7 +448,7 @@ jobs: # Run test suite tests: - name: tests + name: tests rtic runs-on: ubuntu-22.04 steps: - name: Checkout @@ -354,6 +465,63 @@ jobs: working-directory: ./rtic run: cargo test --test tests + # Run test suite + testschannel: + name: tests rtic-channel + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Cache Dependencies + uses: Swatinem/rust-cache@v2 + + - name: Fail on warnings + working-directory: ./rtic-channel + run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs + + - name: Run cargo test + working-directory: ./rtic-channel + run: cargo test --test tests + + # Run test suite + testsmonotonics: + name: tests rtic-monotonics + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Cache Dependencies + uses: Swatinem/rust-cache@v2 + + - name: Fail on warnings + working-directory: ./rtic-monotonics + run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs + + - name: Run cargo test + working-directory: ./rtic-monotonics + run: cargo test --test tests + + # Run test suite + teststime: + name: tests rtic-time + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Cache Dependencies + uses: Swatinem/rust-cache@v2 + + - name: Fail on warnings + working-directory: ./rtic-time + run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs + + - name: Run cargo test + working-directory: ./rtic-time + run: cargo test --test tests + # # Build documentation, check links # docs: # name: docs -- cgit v1.2.3 From 48ac310036bb0053d36d9cfce191351028808651 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Tj=C3=A4der?= Date: Sat, 28 Jan 2023 14:12:32 +0100 Subject: CI: Check/build the docs Still no publish or further steps --- .github/workflows/build.yml | 184 ++++++++++++++++++++++---------------------- 1 file changed, 94 insertions(+), 90 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2e3f2b7..004a5ec 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -522,96 +522,100 @@ jobs: working-directory: ./rtic-time run: cargo test --test tests -# # Build documentation, check links -# docs: -# name: docs -# runs-on: ubuntu-22.04 -# steps: -# - name: Checkout -# uses: actions/checkout@v3 -# -# - name: Cache pip installed linkchecker -# uses: actions/cache@v3 -# with: -# path: ~/.cache/pip -# key: ${{ runner.os }}-pip -# restore-keys: | -# ${{ runner.os }}-pip- -# -# - name: Set up Python 3.x -# uses: actions/setup-python@v4 -# with: -# # Semantic version range syntax or exact version of a Python version -# python-version: '3.x' -# -# # You can test your matrix by printing the current Python version -# - name: Display Python version -# run: python -c "import sys; print(sys.version)" -# -# - name: Install dependencies -# run: pip install git+https://github.com/linkchecker/linkchecker.git -# -# - name: Remove cargo-config -# run: rm -f .cargo/config -# -# - name: Fail on warnings -# run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs macros/src/lib.rs -# -# - name: Build docs -# run: cargo doc -# -# - name: Check links -# run: | -# td=$(mktemp -d) -# cp -r target/doc $td/api -# linkchecker $td/api/rtic/ -# linkchecker $td/api/cortex_m_rtic_macros/ -# -# # Build the books -# mdbook: -# name: mdbook -# runs-on: ubuntu-22.04 -# steps: -# - name: Checkout -# uses: actions/checkout@v3 -# - name: Set up Python 3.x -# uses: actions/setup-python@v4 -# with: -# # Semantic version range syntax or exact version of a Python version -# python-version: '3.x' -# -# # You can test your matrix by printing the current Python version -# - name: Display Python version -# run: python -c "import sys; print(sys.version)" -# -# - name: Install dependencies -# run: pip install git+https://github.com/linkchecker/linkchecker.git -# -# - name: mdBook Action -# uses: peaceiris/actions-mdbook@v1 -# with: -# mdbook-version: 'latest' -# -# - name: Build book in English -# shell: 'script --return --quiet --command "bash {0}"' -# run: cd book/en && if mdbook build |& tee /dev/tty | grep "\[ERROR\]"; then exit 1; else exit 0; fi -# -# - name: Build book in Russian -# shell: 'script --return --quiet --command "bash {0}"' -# run: cd book/ru && if mdbook build |& tee /dev/tty | grep "\[ERROR\]"; then echo "Russian book needs updating!"; else exit 0; fi -# -# - name: Check links -# run: | -# td=$(mktemp -d) -# mkdir $td/book -# cp -r book/en/book $td/book/en -# cp -r book/ru/book $td/book/ru -# cp LICENSE-* $td/book/en -# cp LICENSE-* $td/book/ru -# -# linkchecker $td/book/en/ -# linkchecker $td/book/ru/ -# + # Build documentation, check links + docs: + name: docs + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Cache pip installed linkchecker + uses: actions/cache@v3 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip + restore-keys: | + ${{ runner.os }}-pip- + + - name: Set up Python 3.x + uses: actions/setup-python@v4 + with: + # Semantic version range syntax or exact version of a Python version + python-version: '3.x' + + # You can test your matrix by printing the current Python version + - name: Display Python version + run: python -c "import sys; print(sys.version)" + + - name: Install dependencies + run: pip install git+https://github.com/linkchecker/linkchecker.git + + - name: Remove cargo-config + working-directory: ./rtic + run: rm -f .cargo/config + + - name: Fail on warnings + working-directory: ./rtic + run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs macros/src/lib.rs + + - name: Build docs + working-directory: ./rtic + run: cargo doc + + - name: Check links + working-directory: ./rtic + run: | + td=$(mktemp -d) + cp -r target/doc $td/api + linkchecker $td/api/rtic/ + linkchecker $td/api/rtic_macros/ + + # Build the books + mdbook: + name: mdbook + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Set up Python 3.x + uses: actions/setup-python@v4 + with: + # Semantic version range syntax or exact version of a Python version + python-version: '3.x' + + # You can test your matrix by printing the current Python version + - name: Display Python version + run: python -c "import sys; print(sys.version)" + + - name: Install dependencies + run: pip install git+https://github.com/linkchecker/linkchecker.git + + - name: mdBook Action + uses: peaceiris/actions-mdbook@v1 + with: + mdbook-version: 'latest' + + - name: Build book in English + shell: 'script --return --quiet --command "bash {0}"' + run: cd book/en && if mdbook build |& tee /dev/tty | grep "\[ERROR\]"; then exit 1; else exit 0; fi + + - name: Build book in Russian + shell: 'script --return --quiet --command "bash {0}"' + run: cd book/ru && if mdbook build |& tee /dev/tty | grep "\[ERROR\]"; then echo "Russian book needs updating!"; else exit 0; fi + + - name: Check links + run: | + td=$(mktemp -d) + mkdir $td/book + cp -r book/en/book $td/book/en + cp -r book/ru/book $td/book/ru + cp LICENSE-* $td/book/en + cp LICENSE-* $td/book/ru + + linkchecker $td/book/en/ + linkchecker $td/book/ru/ + # # Update stable branch # # # # This needs to run before book is built -- cgit v1.2.3 From 94b00df2c7e82efb9003945bb90675e73ed0f5e0 Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Sat, 28 Jan 2023 20:47:21 +0100 Subject: rtic-channel: Add testing, fix bugs --- rtic-channel/Cargo.toml | 3 + rtic-channel/src/lib.rs | 170 +++++++++++++++++++++++++++++++++++++---- rtic-channel/src/wait_queue.rs | 14 +++- 3 files changed, 172 insertions(+), 15 deletions(-) diff --git a/rtic-channel/Cargo.toml b/rtic-channel/Cargo.toml index 8962352..5d4cbd0 100644 --- a/rtic-channel/Cargo.toml +++ b/rtic-channel/Cargo.toml @@ -9,6 +9,9 @@ edition = "2021" heapless = "0.7" critical-section = "1" +[dev-dependencies] +tokio = { version = "1", features = ["rt", "macros", "time"] } + [features] default = [] diff --git a/rtic-channel/src/lib.rs b/rtic-channel/src/lib.rs index b6a317f..1077b5a 100644 --- a/rtic-channel/src/lib.rs +++ b/rtic-channel/src/lib.rs @@ -10,6 +10,7 @@ use core::{ mem::MaybeUninit, pin::Pin, ptr, + sync::atomic::{fence, Ordering}, task::{Poll, Waker}, }; use heapless::Deque; @@ -40,6 +41,9 @@ pub struct Channel { num_senders: UnsafeCell, } +unsafe impl Send for Channel {} +unsafe impl Sync for Channel {} + struct UnsafeAccess<'a, const N: usize> { freeq: &'a mut Deque, readyq: &'a mut Deque, @@ -129,6 +133,21 @@ pub struct Sender<'a, T, const N: usize>(&'a Channel); unsafe impl<'a, T, const N: usize> Send for Sender<'a, T, N> {} +/// This is needed to make the async closure in `send` accept that we "share" +/// the link possible between threads. +#[derive(Clone)] +struct LinkPtr(*mut Option>); + +impl LinkPtr { + /// This will dereference the pointer stored within and give out an `&mut`. + unsafe fn get(&self) -> &mut Option> { + &mut *self.0 + } +} + +unsafe impl Send for LinkPtr {} +unsafe impl Sync for LinkPtr {} + impl<'a, T, const N: usize> core::fmt::Debug for Sender<'a, T, N> { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "Sender") @@ -147,7 +166,12 @@ impl<'a, T, const N: usize> Sender<'a, T, N> { } // Write the value into the ready queue. - critical_section::with(|cs| unsafe { self.0.access(cs).readyq.push_back_unchecked(idx) }); + critical_section::with(|cs| { + debug_assert!(!self.0.access(cs).readyq.is_full()); + unsafe { self.0.access(cs).readyq.push_back_unchecked(idx) } + }); + + fence(Ordering::SeqCst); // If there is a receiver waker, wake it. self.0.receiver_waker.wake(); @@ -176,18 +200,17 @@ impl<'a, T, const N: usize> Sender<'a, T, N> { /// Send a value. If there is no place left in the queue this will wait until there is. /// If the receiver does not exist this will return an error. pub async fn send(&mut self, val: T) -> Result<(), NoReceiver> { - if self.is_closed() {} - let mut link_ptr: Option> = None; // Make this future `Drop`-safe, also shadow the original definition so we can't abuse it. - let link_ptr = &mut link_ptr as *mut Option>; + let link_ptr = LinkPtr(&mut link_ptr as *mut Option>); + let link_ptr2 = link_ptr.clone(); let dropper = OnDrop::new(|| { // SAFETY: We only run this closure and dereference the pointer if we have // exited the `poll_fn` below in the `drop(dropper)` call. The other dereference // of this pointer is in the `poll_fn`. - if let Some(link) = unsafe { &mut *link_ptr } { + if let Some(link) = unsafe { link_ptr2.get() } { link.remove_from_list(&self.0.wait_queue); } }); @@ -199,11 +222,19 @@ impl<'a, T, const N: usize> Sender<'a, T, N> { // Do all this in one critical section, else there can be race conditions let queue_idx = critical_section::with(|cs| { - if !self.0.wait_queue.is_empty() || self.0.access(cs).freeq.is_empty() { + let wq_empty = self.0.wait_queue.is_empty(); + let fq_empty = self.0.access(cs).freeq.is_empty(); + if !wq_empty || fq_empty { // SAFETY: This pointer is only dereferenced here and on drop of the future // which happens outside this `poll_fn`'s stack frame. - let link = unsafe { &mut *link_ptr }; - if link.is_none() { + let link = unsafe { link_ptr.get() }; + if let Some(link) = link { + if !link.is_poped() { + return None; + } else { + // Fall through to dequeue + } + } else { // Place the link in the wait queue on first run. let link_ref = link.insert(wait_queue::Link::new(cx.waker().clone())); @@ -212,11 +243,12 @@ impl<'a, T, const N: usize> Sender<'a, T, N> { self.0 .wait_queue .push(unsafe { Pin::new_unchecked(link_ref) }); - } - return None; + return None; + } } + debug_assert!(!self.0.access(cs).freeq.is_empty()); // Get index as the queue is guaranteed not empty and the wait queue is empty let idx = unsafe { self.0.access(cs).freeq.pop_front_unchecked() }; @@ -319,7 +351,12 @@ impl<'a, T, const N: usize> Receiver<'a, T, N> { let r = unsafe { ptr::read(self.0.slots.get_unchecked(rs as usize).get() as *const T) }; // Return the index to the free queue after we've read the value. - critical_section::with(|cs| unsafe { self.0.access(cs).freeq.push_back_unchecked(rs) }); + critical_section::with(|cs| { + debug_assert!(!self.0.access(cs).freeq.is_full()); + unsafe { self.0.access(cs).freeq.push_back_unchecked(rs) } + }); + + fence(Ordering::SeqCst); // If someone is waiting in the WaiterQueue, wake the first one up. if let Some(wait_head) = self.0.wait_queue.pop() { @@ -363,7 +400,7 @@ impl<'a, T, const N: usize> Receiver<'a, T, N> { /// Is the queue full. pub fn is_full(&self) -> bool { - critical_section::with(|cs| self.0.access(cs).readyq.is_empty()) + critical_section::with(|cs| self.0.access(cs).readyq.is_full()) } /// Is the queue empty. @@ -412,6 +449,113 @@ extern crate std; #[cfg(test)] mod tests { + use super::*; + + #[test] + fn empty() { + let (mut s, mut r) = make_channel!(u32, 10); + + assert!(s.is_empty()); + assert!(r.is_empty()); + + s.try_send(1).unwrap(); + + assert!(!s.is_empty()); + assert!(!r.is_empty()); + + r.try_recv().unwrap(); + + assert!(s.is_empty()); + assert!(r.is_empty()); + } + + #[test] + fn full() { + let (mut s, mut r) = make_channel!(u32, 3); + + for _ in 0..3 { + assert!(!s.is_full()); + assert!(!r.is_full()); + + s.try_send(1).unwrap(); + } + + assert!(s.is_full()); + assert!(r.is_full()); + + for _ in 0..3 { + r.try_recv().unwrap(); + + assert!(!s.is_full()); + assert!(!r.is_full()); + } + } + + #[test] + fn send_recieve() { + let (mut s, mut r) = make_channel!(u32, 10); + + for i in 0..10 { + s.try_send(i).unwrap(); + } + + assert_eq!(s.try_send(11), Err(11)); + + for i in 0..10 { + assert_eq!(r.try_recv().unwrap(), i); + } + + assert_eq!(r.try_recv(), None); + } + + #[test] + fn closed_recv() { + let (s, mut r) = make_channel!(u32, 10); + + drop(s); + + assert!(r.is_closed()); + + assert_eq!(r.try_recv(), None); + } + #[test] - fn channel() {} + fn closed_sender() { + let (mut s, r) = make_channel!(u32, 10); + + drop(r); + + assert!(s.is_closed()); + + assert_eq!(s.try_send(11), Ok(())); + } + + #[tokio::test] + async fn stress_channel() { + const NUM_RUNS: usize = 1_000; + const QUEUE_SIZE: usize = 10; + + let (s, mut r) = make_channel!(u32, QUEUE_SIZE); + let mut v = std::vec::Vec::new(); + + for i in 0..NUM_RUNS { + let mut s = s.clone(); + + v.push(tokio::spawn(async move { + s.send(i as _).await.unwrap(); + })); + } + + let mut map = std::collections::BTreeSet::new(); + + for _ in 0..NUM_RUNS { + map.insert(r.recv().await.unwrap()); + } + + assert_eq!(map.len(), NUM_RUNS); + + for v in v { + v.await.unwrap(); + } + } } diff --git a/rtic-channel/src/wait_queue.rs b/rtic-channel/src/wait_queue.rs index ba05e6b..e6d5a8b 100644 --- a/rtic-channel/src/wait_queue.rs +++ b/rtic-channel/src/wait_queue.rs @@ -3,7 +3,7 @@ use core::marker::PhantomPinned; use core::pin::Pin; use core::ptr::null_mut; -use core::sync::atomic::{AtomicPtr, Ordering}; +use core::sync::atomic::{AtomicBool, AtomicPtr, Ordering}; use core::task::Waker; use critical_section as cs; @@ -57,6 +57,7 @@ impl LinkedList { // Clear the pointers in the node. head_ref.next.store(null_mut(), Self::R); head_ref.prev.store(null_mut(), Self::R); + head_ref.is_poped.store(true, Self::R); return Some(head_val); } @@ -100,9 +101,12 @@ pub struct Link { pub(crate) val: T, next: AtomicPtr>, prev: AtomicPtr>, + is_poped: AtomicBool, _up: PhantomPinned, } +unsafe impl Send for Link {} + impl Link { const R: Ordering = Ordering::Relaxed; @@ -112,10 +116,15 @@ impl Link { val, next: AtomicPtr::new(null_mut()), prev: AtomicPtr::new(null_mut()), + is_poped: AtomicBool::new(false), _up: PhantomPinned, } } + pub fn is_poped(&self) -> bool { + self.is_poped.load(Self::R) + } + pub fn remove_from_list(&mut self, list: &LinkedList) { cs::with(|_| { // Make sure all previous writes are visible @@ -123,6 +132,7 @@ impl Link { let prev = self.prev.load(Self::R); let next = self.next.load(Self::R); + self.is_poped.store(true, Self::R); match unsafe { (prev.as_ref(), next.as_ref()) } { (None, None) => { @@ -217,7 +227,7 @@ mod tests { #[test] fn linked_list() { - let mut wq = LinkedList::::new(); + let wq = LinkedList::::new(); let mut i1 = Link::new(10); let mut i2 = Link::new(11); -- cgit v1.2.3 From bef6c359a0802cb93c7bf0963d0fca7db540f64b Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Sat, 28 Jan 2023 20:54:34 +0100 Subject: Fix CI for rtic-channel --- .github/workflows/build.yml | 2 +- rtic-channel/src/lib.rs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 004a5ec..650fc53 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -482,7 +482,7 @@ jobs: - name: Run cargo test working-directory: ./rtic-channel - run: cargo test --test tests + run: cargo test --features testing # Run test suite testsmonotonics: diff --git a/rtic-channel/src/lib.rs b/rtic-channel/src/lib.rs index 1077b5a..eafa25c 100644 --- a/rtic-channel/src/lib.rs +++ b/rtic-channel/src/lib.rs @@ -140,7 +140,7 @@ struct LinkPtr(*mut Option>); impl LinkPtr { /// This will dereference the pointer stored within and give out an `&mut`. - unsafe fn get(&self) -> &mut Option> { + unsafe fn get(&mut self) -> &mut Option> { &mut *self.0 } } @@ -203,9 +203,9 @@ impl<'a, T, const N: usize> Sender<'a, T, N> { let mut link_ptr: Option> = None; // Make this future `Drop`-safe, also shadow the original definition so we can't abuse it. - let link_ptr = LinkPtr(&mut link_ptr as *mut Option>); + let mut link_ptr = LinkPtr(&mut link_ptr as *mut Option>); - let link_ptr2 = link_ptr.clone(); + let mut link_ptr2 = link_ptr.clone(); let dropper = OnDrop::new(|| { // SAFETY: We only run this closure and dereference the pointer if we have // exited the `poll_fn` below in the `drop(dropper)` call. The other dereference @@ -532,7 +532,7 @@ mod tests { #[tokio::test] async fn stress_channel() { - const NUM_RUNS: usize = 1_000; + const NUM_RUNS: usize = 1_000000; const QUEUE_SIZE: usize = 10; let (s, mut r) = make_channel!(u32, QUEUE_SIZE); -- cgit v1.2.3 From 2bd70baeb9362050196d431f2801551066e27e59 Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Sat, 28 Jan 2023 21:11:18 +0100 Subject: rtic-time: Make Send happy --- rtic-channel/src/lib.rs | 2 +- rtic-channel/src/wait_queue.rs | 2 -- rtic-time/src/lib.rs | 28 +++++++++++++++++++++++++--- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/rtic-channel/src/lib.rs b/rtic-channel/src/lib.rs index eafa25c..acfa801 100644 --- a/rtic-channel/src/lib.rs +++ b/rtic-channel/src/lib.rs @@ -532,7 +532,7 @@ mod tests { #[tokio::test] async fn stress_channel() { - const NUM_RUNS: usize = 1_000000; + const NUM_RUNS: usize = 1_000; const QUEUE_SIZE: usize = 10; let (s, mut r) = make_channel!(u32, QUEUE_SIZE); diff --git a/rtic-channel/src/wait_queue.rs b/rtic-channel/src/wait_queue.rs index e6d5a8b..2de6311 100644 --- a/rtic-channel/src/wait_queue.rs +++ b/rtic-channel/src/wait_queue.rs @@ -105,8 +105,6 @@ pub struct Link { _up: PhantomPinned, } -unsafe impl Send for Link {} - impl Link { const R: Ordering = Ordering::Relaxed; diff --git a/rtic-time/src/lib.rs b/rtic-time/src/lib.rs index 6b23f76..44fdbce 100644 --- a/rtic-time/src/lib.rs +++ b/rtic-time/src/lib.rs @@ -73,6 +73,26 @@ pub struct TimerQueue { /// This indicates that there was a timeout. pub struct TimeoutError; +/// This is needed to make the async closure in `delay_until` accept that we "share" +/// the link possible between threads. +struct LinkPtr(*mut Option>>); + +impl Clone for LinkPtr { + fn clone(&self) -> Self { + LinkPtr(self.0) + } +} + +impl LinkPtr { + /// This will dereference the pointer stored within and give out an `&mut`. + unsafe fn get(&mut self) -> &mut Option>> { + &mut *self.0 + } +} + +unsafe impl Send for LinkPtr {} +unsafe impl Sync for LinkPtr {} + impl TimerQueue { /// Make a new queue. pub const fn new() -> Self { @@ -189,7 +209,9 @@ impl TimerQueue { let mut link_ptr: Option>> = None; // Make this future `Drop`-safe, also shadow the original definition so we can't abuse it. - let link_ptr = &mut link_ptr as *mut Option>>; + let mut link_ptr = + LinkPtr(&mut link_ptr as *mut Option>>); + let mut link_ptr2 = link_ptr.clone(); let queue = &self.queue; let marker = &AtomicUsize::new(0); @@ -205,7 +227,7 @@ impl TimerQueue { // SAFETY: This pointer is only dereferenced here and on drop of the future // which happens outside this `poll_fn`'s stack frame. - let link = unsafe { &mut *link_ptr }; + let link = unsafe { link_ptr2.get() }; if link.is_none() { let mut link_ref = link.insert(Link::new(WaitingWaker { waker: cx.waker().clone(), @@ -231,7 +253,7 @@ impl TimerQueue { // SAFETY: We only run this and dereference the pointer if we have // exited the `poll_fn` below in the `drop(dropper)` call. The other dereference // of this pointer is in the `poll_fn`. - if let Some(link) = unsafe { &mut *link_ptr } { + if let Some(link) = unsafe { link_ptr.get() } { if link.val.was_poped.load(Ordering::Relaxed) { // If it was poped from the queue there is no need to run delete dropper.defuse(); -- cgit v1.2.3 From 58692a35e87ddc8b8faca5bb262070d343ceb869 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Tj=C3=A4der?= Date: Sat, 28 Jan 2023 20:26:50 +0100 Subject: Fix some references to cortex-m-rtic --- rtic/macros/src/lib.rs | 4 ++-- rtic/src/lib.rs | 9 ++------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/rtic/macros/src/lib.rs b/rtic/macros/src/lib.rs index 92d7cdd..3ac2701 100644 --- a/rtic/macros/src/lib.rs +++ b/rtic/macros/src/lib.rs @@ -58,8 +58,8 @@ pub fn app(args: TokenStream, input: TokenStream) -> TokenStream { out_dir = Path::new(&out_str); // Default build path, annotated below: - // $(pwd)/target/thumbv7em-none-eabihf/debug/build/cortex-m-rtic-/out/ - // ///debug/build/cortex-m-rtic-/out/ + // $(pwd)/target/thumbv7em-none-eabihf/debug/build/rtic-/out/ + // ///debug/build/rtic-/out/ // // traverse up to first occurrence of TARGET, approximated with starts_with("thumbv") // and use the parent() of this path diff --git a/rtic/src/lib.rs b/rtic/src/lib.rs index e8b8140..860e743 100644 --- a/rtic/src/lib.rs +++ b/rtic/src/lib.rs @@ -1,10 +1,5 @@ //! Real-Time Interrupt-driven Concurrency (RTIC) framework for ARM Cortex-M microcontrollers. //! -//! **IMPORTANT**: This crate is published as [`cortex-m-rtic`] on crates.io but the name of the -//! library is `rtic`. -//! -//! [`cortex-m-rtic`]: https://crates.io/crates/cortex-m-rtic -//! //! The user level documentation can be found [here]. //! //! [here]: https://rtic.rs @@ -32,8 +27,8 @@ #![deny(rust_2018_idioms)] #![no_std] #![doc( - html_logo_url = "https://raw.githubusercontent.com/rtic-rs/cortex-m-rtic/master/book/en/src/RTIC.svg", - html_favicon_url = "https://raw.githubusercontent.com/rtic-rs/cortex-m-rtic/master/book/en/src/RTIC.svg" + html_logo_url = "https://raw.githubusercontent.com/rtic-rs/rtic/master/book/en/src/RTIC.svg", + html_favicon_url = "https://raw.githubusercontent.com/rtic-rs/rtic/master/book/en/src/RTIC.svg" )] //deny_warnings_placeholder_for_ci #![allow(clippy::inline_always)] -- cgit v1.2.3 From e65e532c2a342f77080ac6fc8e5be11aa7d82575 Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Sun, 29 Jan 2023 20:16:23 +0100 Subject: Move common data structures to `rtic-common` --- rtic-channel/Cargo.toml | 3 +- rtic-channel/src/lib.rs | 17 +-- rtic-channel/src/wait_queue.rs | 268 -------------------------------- rtic-channel/src/waker_registration.rs | 64 -------- rtic-common/.gitignore | 2 + rtic-common/CHANGELOG.md | 16 ++ rtic-common/Cargo.toml | 13 ++ rtic-common/src/lib.rs | 8 + rtic-common/src/wait_queue.rs | 271 +++++++++++++++++++++++++++++++++ rtic-common/src/waker_registration.rs | 66 ++++++++ 10 files changed, 385 insertions(+), 343 deletions(-) delete mode 100644 rtic-channel/src/wait_queue.rs delete mode 100644 rtic-channel/src/waker_registration.rs create mode 100644 rtic-common/.gitignore create mode 100644 rtic-common/CHANGELOG.md create mode 100644 rtic-common/Cargo.toml create mode 100644 rtic-common/src/lib.rs create mode 100644 rtic-common/src/wait_queue.rs create mode 100644 rtic-common/src/waker_registration.rs diff --git a/rtic-channel/Cargo.toml b/rtic-channel/Cargo.toml index 5d4cbd0..a0955bc 100644 --- a/rtic-channel/Cargo.toml +++ b/rtic-channel/Cargo.toml @@ -8,6 +8,7 @@ edition = "2021" [dependencies] heapless = "0.7" critical-section = "1" +rtic-common = { version = "1.0.0", path = "../rtic-common" } [dev-dependencies] tokio = { version = "1", features = ["rt", "macros", "time"] } @@ -15,4 +16,4 @@ tokio = { version = "1", features = ["rt", "macros", "time"] } [features] default = [] -testing = ["critical-section/std"] +testing = ["critical-section/std", "rtic-common/testing"] diff --git a/rtic-channel/src/lib.rs b/rtic-channel/src/lib.rs index acfa801..2b237f6 100644 --- a/rtic-channel/src/lib.rs +++ b/rtic-channel/src/lib.rs @@ -14,11 +14,8 @@ use core::{ task::{Poll, Waker}, }; use heapless::Deque; -use wait_queue::WaitQueue; -use waker_registration::CriticalSectionWakerRegistration as WakerRegistration; - -mod wait_queue; -mod waker_registration; +use rtic_common::wait_queue::{Link, WaitQueue}; +use rtic_common::waker_registration::CriticalSectionWakerRegistration as WakerRegistration; /// An MPSC channel for use in no-alloc systems. `N` sets the size of the queue. /// @@ -136,11 +133,11 @@ unsafe impl<'a, T, const N: usize> Send for Sender<'a, T, N> {} /// This is needed to make the async closure in `send` accept that we "share" /// the link possible between threads. #[derive(Clone)] -struct LinkPtr(*mut Option>); +struct LinkPtr(*mut Option>); impl LinkPtr { /// This will dereference the pointer stored within and give out an `&mut`. - unsafe fn get(&mut self) -> &mut Option> { + unsafe fn get(&mut self) -> &mut Option> { &mut *self.0 } } @@ -200,10 +197,10 @@ impl<'a, T, const N: usize> Sender<'a, T, N> { /// Send a value. If there is no place left in the queue this will wait until there is. /// If the receiver does not exist this will return an error. pub async fn send(&mut self, val: T) -> Result<(), NoReceiver> { - let mut link_ptr: Option> = None; + let mut link_ptr: Option> = None; // Make this future `Drop`-safe, also shadow the original definition so we can't abuse it. - let mut link_ptr = LinkPtr(&mut link_ptr as *mut Option>); + let mut link_ptr = LinkPtr(&mut link_ptr as *mut Option>); let mut link_ptr2 = link_ptr.clone(); let dropper = OnDrop::new(|| { @@ -236,7 +233,7 @@ impl<'a, T, const N: usize> Sender<'a, T, N> { } } else { // Place the link in the wait queue on first run. - let link_ref = link.insert(wait_queue::Link::new(cx.waker().clone())); + let link_ref = link.insert(Link::new(cx.waker().clone())); // SAFETY: The address to the link is stable as it is hidden behind // `link_ptr`, and `link_ptr` shadows the original making it unmovable. diff --git a/rtic-channel/src/wait_queue.rs b/rtic-channel/src/wait_queue.rs deleted file mode 100644 index 2de6311..0000000 --- a/rtic-channel/src/wait_queue.rs +++ /dev/null @@ -1,268 +0,0 @@ -//! ... - -use core::marker::PhantomPinned; -use core::pin::Pin; -use core::ptr::null_mut; -use core::sync::atomic::{AtomicBool, AtomicPtr, Ordering}; -use core::task::Waker; -use critical_section as cs; - -pub type WaitQueue = LinkedList; - -/// A FIFO linked list for a wait queue. -pub struct LinkedList { - head: AtomicPtr>, // UnsafeCell<*mut Link> - tail: AtomicPtr>, -} - -impl LinkedList { - /// Create a new linked list. - pub const fn new() -> Self { - Self { - head: AtomicPtr::new(null_mut()), - tail: AtomicPtr::new(null_mut()), - } - } -} - -impl LinkedList { - const R: Ordering = Ordering::Relaxed; - - /// Pop the first element in the queue. - pub fn pop(&self) -> Option { - cs::with(|_| { - // Make sure all previous writes are visible - core::sync::atomic::fence(Ordering::SeqCst); - - let head = self.head.load(Self::R); - - // SAFETY: `as_ref` is safe as `insert` requires a valid reference to a link - if let Some(head_ref) = unsafe { head.as_ref() } { - // Move head to the next element - self.head.store(head_ref.next.load(Self::R), Self::R); - - // We read the value at head - let head_val = head_ref.val.clone(); - - let tail = self.tail.load(Self::R); - if head == tail { - // The queue is empty - self.tail.store(null_mut(), Self::R); - } - - if let Some(next_ref) = unsafe { head_ref.next.load(Self::R).as_ref() } { - next_ref.prev.store(null_mut(), Self::R); - } - - // Clear the pointers in the node. - head_ref.next.store(null_mut(), Self::R); - head_ref.prev.store(null_mut(), Self::R); - head_ref.is_poped.store(true, Self::R); - - return Some(head_val); - } - - None - }) - } - - /// Put an element at the back of the queue. - pub fn push(&self, link: Pin<&mut Link>) { - cs::with(|_| { - // Make sure all previous writes are visible - core::sync::atomic::fence(Ordering::SeqCst); - - let tail = self.tail.load(Self::R); - - // SAFETY: This datastructure does not move the underlying value. - let link = unsafe { link.get_unchecked_mut() }; - - if let Some(tail_ref) = unsafe { tail.as_ref() } { - // Queue is not empty - link.prev.store(tail, Self::R); - self.tail.store(link, Self::R); - tail_ref.next.store(link, Self::R); - } else { - // Queue is empty - self.tail.store(link, Self::R); - self.head.store(link, Self::R); - } - }); - } - - /// Check if the queue is empty. - pub fn is_empty(&self) -> bool { - self.head.load(Self::R).is_null() - } -} - -/// A link in the linked list. -pub struct Link { - pub(crate) val: T, - next: AtomicPtr>, - prev: AtomicPtr>, - is_poped: AtomicBool, - _up: PhantomPinned, -} - -impl Link { - const R: Ordering = Ordering::Relaxed; - - /// Create a new link. - pub const fn new(val: T) -> Self { - Self { - val, - next: AtomicPtr::new(null_mut()), - prev: AtomicPtr::new(null_mut()), - is_poped: AtomicBool::new(false), - _up: PhantomPinned, - } - } - - pub fn is_poped(&self) -> bool { - self.is_poped.load(Self::R) - } - - pub fn remove_from_list(&mut self, list: &LinkedList) { - cs::with(|_| { - // Make sure all previous writes are visible - core::sync::atomic::fence(Ordering::SeqCst); - - let prev = self.prev.load(Self::R); - let next = self.next.load(Self::R); - self.is_poped.store(true, Self::R); - - match unsafe { (prev.as_ref(), next.as_ref()) } { - (None, None) => { - // Not in the list or alone in the list, check if list head == node address - let sp = self as *const _; - - if sp == list.head.load(Ordering::Relaxed) { - list.head.store(null_mut(), Self::R); - list.tail.store(null_mut(), Self::R); - } - } - (None, Some(next_ref)) => { - // First in the list - next_ref.prev.store(null_mut(), Self::R); - list.head.store(next, Self::R); - } - (Some(prev_ref), None) => { - // Last in the list - prev_ref.next.store(null_mut(), Self::R); - list.tail.store(prev, Self::R); - } - (Some(prev_ref), Some(next_ref)) => { - // Somewhere in the list - - // Connect the `prev.next` and `next.prev` with each other to remove the node - prev_ref.next.store(next, Self::R); - next_ref.prev.store(prev, Self::R); - } - } - }) - } -} - -#[cfg(test)] -impl LinkedList { - fn print(&self) { - cs::with(|_| { - // Make sure all previous writes are visible - core::sync::atomic::fence(Ordering::SeqCst); - - let mut head = self.head.load(Self::R); - let tail = self.tail.load(Self::R); - - println!( - "List - h = 0x{:x}, t = 0x{:x}", - head as usize, tail as usize - ); - - let mut i = 0; - - // SAFETY: `as_ref` is safe as `insert` requires a valid reference to a link - while let Some(head_ref) = unsafe { head.as_ref() } { - println!( - " {}: {:?}, s = 0x{:x}, n = 0x{:x}, p = 0x{:x}", - i, - head_ref.val, - head as usize, - head_ref.next.load(Ordering::Relaxed) as usize, - head_ref.prev.load(Ordering::Relaxed) as usize - ); - - head = head_ref.next.load(Self::R); - - i += 1; - } - }); - } -} - -#[cfg(test)] -impl Link { - fn print(&self) { - cs::with(|_| { - // Make sure all previous writes are visible - core::sync::atomic::fence(Ordering::SeqCst); - - println!("Link:"); - - println!( - " val = {:?}, n = 0x{:x}, p = 0x{:x}", - self.val, - self.next.load(Ordering::Relaxed) as usize, - self.prev.load(Ordering::Relaxed) as usize - ); - }); - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn linked_list() { - let wq = LinkedList::::new(); - - let mut i1 = Link::new(10); - let mut i2 = Link::new(11); - let mut i3 = Link::new(12); - let mut i4 = Link::new(13); - let mut i5 = Link::new(14); - - wq.push(unsafe { Pin::new_unchecked(&mut i1) }); - wq.push(unsafe { Pin::new_unchecked(&mut i2) }); - wq.push(unsafe { Pin::new_unchecked(&mut i3) }); - wq.push(unsafe { Pin::new_unchecked(&mut i4) }); - wq.push(unsafe { Pin::new_unchecked(&mut i5) }); - - wq.print(); - - wq.pop(); - i1.print(); - - wq.print(); - - i4.remove_from_list(&wq); - - wq.print(); - - // i1.remove_from_list(&wq); - // wq.print(); - - println!("i2"); - i2.remove_from_list(&wq); - wq.print(); - - println!("i3"); - i3.remove_from_list(&wq); - wq.print(); - - println!("i5"); - i5.remove_from_list(&wq); - wq.print(); - } -} diff --git a/rtic-channel/src/waker_registration.rs b/rtic-channel/src/waker_registration.rs deleted file mode 100644 index c30df7f..0000000 --- a/rtic-channel/src/waker_registration.rs +++ /dev/null @@ -1,64 +0,0 @@ -use core::cell::UnsafeCell; -use core::task::Waker; - -/// A critical section based waker handler. -pub struct CriticalSectionWakerRegistration { - waker: UnsafeCell>, -} - -unsafe impl Send for CriticalSectionWakerRegistration {} -unsafe impl Sync for CriticalSectionWakerRegistration {} - -impl CriticalSectionWakerRegistration { - /// Create a new waker registration. - pub const fn new() -> Self { - Self { - waker: UnsafeCell::new(None), - } - } - - /// Register a waker. - /// This will overwrite the previous waker if there was one. - pub fn register(&self, new_waker: &Waker) { - critical_section::with(|_| { - // SAFETY: This access is protected by the critical section. - let self_waker = unsafe { &mut *self.waker.get() }; - - // From embassy - // https://github.com/embassy-rs/embassy/blob/b99533607ceed225dd12ae73aaa9a0d969a7365e/embassy-sync/src/waitqueue/waker.rs#L59-L61 - match self_waker { - // Optimization: If both the old and new Wakers wake the same task, we can simply - // keep the old waker, skipping the clone. (In most executor implementations, - // cloning a waker is somewhat expensive, comparable to cloning an Arc). - Some(ref w2) if (w2.will_wake(new_waker)) => {} - _ => { - // clone the new waker and store it - if let Some(old_waker) = core::mem::replace(self_waker, Some(new_waker.clone())) - { - // We had a waker registered for another task. Wake it, so the other task can - // reregister itself if it's still interested. - // - // If two tasks are waiting on the same thing concurrently, this will cause them - // to wake each other in a loop fighting over this WakerRegistration. This wastes - // CPU but things will still work. - // - // If the user wants to have two tasks waiting on the same thing they should use - // a more appropriate primitive that can store multiple wakers. - old_waker.wake() - } - } - } - }); - } - - /// Wake the waker. - pub fn wake(&self) { - critical_section::with(|_| { - // SAFETY: This access is protected by the critical section. - let self_waker = unsafe { &mut *self.waker.get() }; - if let Some(waker) = self_waker.take() { - waker.wake() - } - }); - } -} diff --git a/rtic-common/.gitignore b/rtic-common/.gitignore new file mode 100644 index 0000000..1e7caa9 --- /dev/null +++ b/rtic-common/.gitignore @@ -0,0 +1,2 @@ +Cargo.lock +target/ diff --git a/rtic-common/CHANGELOG.md b/rtic-common/CHANGELOG.md new file mode 100644 index 0000000..d3a9d84 --- /dev/null +++ b/rtic-common/CHANGELOG.md @@ -0,0 +1,16 @@ +# Change Log + +All notable changes to this project will be documented in this file. +This project adheres to [Semantic Versioning](http://semver.org/). + +For each category, *Added*, *Changed*, *Fixed* add new entries at the top! + +## [Unreleased] + +### Added + +### Changed + +### Fixed + +## [v1.0.0] - 2023-xx-xx diff --git a/rtic-common/Cargo.toml b/rtic-common/Cargo.toml new file mode 100644 index 0000000..258caae --- /dev/null +++ b/rtic-common/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "rtic-common" +version = "1.0.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +critical-section = "1" + +[features] +default = [] +testing = ["critical-section/std"] diff --git a/rtic-common/src/lib.rs b/rtic-common/src/lib.rs new file mode 100644 index 0000000..3c75856 --- /dev/null +++ b/rtic-common/src/lib.rs @@ -0,0 +1,8 @@ +//! Crate + +#![no_std] +#![deny(missing_docs)] +//deny_warnings_placeholder_for_ci + +pub mod wait_queue; +pub mod waker_registration; diff --git a/rtic-common/src/wait_queue.rs b/rtic-common/src/wait_queue.rs new file mode 100644 index 0000000..ba8ff54 --- /dev/null +++ b/rtic-common/src/wait_queue.rs @@ -0,0 +1,271 @@ +//! ... + +use core::marker::PhantomPinned; +use core::pin::Pin; +use core::ptr::null_mut; +use core::sync::atomic::{AtomicBool, AtomicPtr, Ordering}; +use core::task::Waker; +use critical_section as cs; + +/// A helper definition of a wait queue. +pub type WaitQueue = LinkedList; + +/// A FIFO linked list for a wait queue. +pub struct LinkedList { + head: AtomicPtr>, // UnsafeCell<*mut Link> + tail: AtomicPtr>, +} + +impl LinkedList { + /// Create a new linked list. + pub const fn new() -> Self { + Self { + head: AtomicPtr::new(null_mut()), + tail: AtomicPtr::new(null_mut()), + } + } +} + +impl LinkedList { + const R: Ordering = Ordering::Relaxed; + + /// Pop the first element in the queue. + pub fn pop(&self) -> Option { + cs::with(|_| { + // Make sure all previous writes are visible + core::sync::atomic::fence(Ordering::SeqCst); + + let head = self.head.load(Self::R); + + // SAFETY: `as_ref` is safe as `insert` requires a valid reference to a link + if let Some(head_ref) = unsafe { head.as_ref() } { + // Move head to the next element + self.head.store(head_ref.next.load(Self::R), Self::R); + + // We read the value at head + let head_val = head_ref.val.clone(); + + let tail = self.tail.load(Self::R); + if head == tail { + // The queue is empty + self.tail.store(null_mut(), Self::R); + } + + if let Some(next_ref) = unsafe { head_ref.next.load(Self::R).as_ref() } { + next_ref.prev.store(null_mut(), Self::R); + } + + // Clear the pointers in the node. + head_ref.next.store(null_mut(), Self::R); + head_ref.prev.store(null_mut(), Self::R); + head_ref.is_poped.store(true, Self::R); + + return Some(head_val); + } + + None + }) + } + + /// Put an element at the back of the queue. + pub fn push(&self, link: Pin<&mut Link>) { + cs::with(|_| { + // Make sure all previous writes are visible + core::sync::atomic::fence(Ordering::SeqCst); + + let tail = self.tail.load(Self::R); + + // SAFETY: This datastructure does not move the underlying value. + let link = unsafe { link.get_unchecked_mut() }; + + if let Some(tail_ref) = unsafe { tail.as_ref() } { + // Queue is not empty + link.prev.store(tail, Self::R); + self.tail.store(link, Self::R); + tail_ref.next.store(link, Self::R); + } else { + // Queue is empty + self.tail.store(link, Self::R); + self.head.store(link, Self::R); + } + }); + } + + /// Check if the queue is empty. + pub fn is_empty(&self) -> bool { + self.head.load(Self::R).is_null() + } +} + +/// A link in the linked list. +pub struct Link { + pub(crate) val: T, + next: AtomicPtr>, + prev: AtomicPtr>, + is_poped: AtomicBool, + _up: PhantomPinned, +} + +impl Link { + const R: Ordering = Ordering::Relaxed; + + /// Create a new link. + pub const fn new(val: T) -> Self { + Self { + val, + next: AtomicPtr::new(null_mut()), + prev: AtomicPtr::new(null_mut()), + is_poped: AtomicBool::new(false), + _up: PhantomPinned, + } + } + + /// Return true if this link has been poped from the list. + pub fn is_poped(&self) -> bool { + self.is_poped.load(Self::R) + } + + /// Remove this link from a linked list. + pub fn remove_from_list(&mut self, list: &LinkedList) { + cs::with(|_| { + // Make sure all previous writes are visible + core::sync::atomic::fence(Ordering::SeqCst); + + let prev = self.prev.load(Self::R); + let next = self.next.load(Self::R); + self.is_poped.store(true, Self::R); + + match unsafe { (prev.as_ref(), next.as_ref()) } { + (None, None) => { + // Not in the list or alone in the list, check if list head == node address + let sp = self as *const _; + + if sp == list.head.load(Ordering::Relaxed) { + list.head.store(null_mut(), Self::R); + list.tail.store(null_mut(), Self::R); + } + } + (None, Some(next_ref)) => { + // First in the list + next_ref.prev.store(null_mut(), Self::R); + list.head.store(next, Self::R); + } + (Some(prev_ref), None) => { + // Last in the list + prev_ref.next.store(null_mut(), Self::R); + list.tail.store(prev, Self::R); + } + (Some(prev_ref), Some(next_ref)) => { + // Somewhere in the list + + // Connect the `prev.next` and `next.prev` with each other to remove the node + prev_ref.next.store(next, Self::R); + next_ref.prev.store(prev, Self::R); + } + } + }) + } +} + +#[cfg(test)] +impl LinkedList { + fn print(&self) { + cs::with(|_| { + // Make sure all previous writes are visible + core::sync::atomic::fence(Ordering::SeqCst); + + let mut head = self.head.load(Self::R); + let tail = self.tail.load(Self::R); + + println!( + "List - h = 0x{:x}, t = 0x{:x}", + head as usize, tail as usize + ); + + let mut i = 0; + + // SAFETY: `as_ref` is safe as `insert` requires a valid reference to a link + while let Some(head_ref) = unsafe { head.as_ref() } { + println!( + " {}: {:?}, s = 0x{:x}, n = 0x{:x}, p = 0x{:x}", + i, + head_ref.val, + head as usize, + head_ref.next.load(Ordering::Relaxed) as usize, + head_ref.prev.load(Ordering::Relaxed) as usize + ); + + head = head_ref.next.load(Self::R); + + i += 1; + } + }); + } +} + +#[cfg(test)] +impl Link { + fn print(&self) { + cs::with(|_| { + // Make sure all previous writes are visible + core::sync::atomic::fence(Ordering::SeqCst); + + println!("Link:"); + + println!( + " val = {:?}, n = 0x{:x}, p = 0x{:x}", + self.val, + self.next.load(Ordering::Relaxed) as usize, + self.prev.load(Ordering::Relaxed) as usize + ); + }); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn linked_list() { + let wq = LinkedList::::new(); + + let mut i1 = Link::new(10); + let mut i2 = Link::new(11); + let mut i3 = Link::new(12); + let mut i4 = Link::new(13); + let mut i5 = Link::new(14); + + wq.push(unsafe { Pin::new_unchecked(&mut i1) }); + wq.push(unsafe { Pin::new_unchecked(&mut i2) }); + wq.push(unsafe { Pin::new_unchecked(&mut i3) }); + wq.push(unsafe { Pin::new_unchecked(&mut i4) }); + wq.push(unsafe { Pin::new_unchecked(&mut i5) }); + + wq.print(); + + wq.pop(); + i1.print(); + + wq.print(); + + i4.remove_from_list(&wq); + + wq.print(); + + // i1.remove_from_list(&wq); + // wq.print(); + + println!("i2"); + i2.remove_from_list(&wq); + wq.print(); + + println!("i3"); + i3.remove_from_list(&wq); + wq.print(); + + println!("i5"); + i5.remove_from_list(&wq); + wq.print(); + } +} diff --git a/rtic-common/src/waker_registration.rs b/rtic-common/src/waker_registration.rs new file mode 100644 index 0000000..174765c --- /dev/null +++ b/rtic-common/src/waker_registration.rs @@ -0,0 +1,66 @@ +//! Waker registration utility. + +use core::cell::UnsafeCell; +use core::task::Waker; + +/// A critical section based waker handler. +pub struct CriticalSectionWakerRegistration { + waker: UnsafeCell>, +} + +unsafe impl Send for CriticalSectionWakerRegistration {} +unsafe impl Sync for CriticalSectionWakerRegistration {} + +impl CriticalSectionWakerRegistration { + /// Create a new waker registration. + pub const fn new() -> Self { + Self { + waker: UnsafeCell::new(None), + } + } + + /// Register a waker. + /// This will overwrite the previous waker if there was one. + pub fn register(&self, new_waker: &Waker) { + critical_section::with(|_| { + // SAFETY: This access is protected by the critical section. + let self_waker = unsafe { &mut *self.waker.get() }; + + // From embassy + // https://github.com/embassy-rs/embassy/blob/b99533607ceed225dd12ae73aaa9a0d969a7365e/embassy-sync/src/waitqueue/waker.rs#L59-L61 + match self_waker { + // Optimization: If both the old and new Wakers wake the same task, we can simply + // keep the old waker, skipping the clone. (In most executor implementations, + // cloning a waker is somewhat expensive, comparable to cloning an Arc). + Some(ref w2) if (w2.will_wake(new_waker)) => {} + _ => { + // clone the new waker and store it + if let Some(old_waker) = core::mem::replace(self_waker, Some(new_waker.clone())) + { + // We had a waker registered for another task. Wake it, so the other task can + // reregister itself if it's still interested. + // + // If two tasks are waiting on the same thing concurrently, this will cause them + // to wake each other in a loop fighting over this WakerRegistration. This wastes + // CPU but things will still work. + // + // If the user wants to have two tasks waiting on the same thing they should use + // a more appropriate primitive that can store multiple wakers. + old_waker.wake() + } + } + } + }); + } + + /// Wake the waker. + pub fn wake(&self) { + critical_section::with(|_| { + // SAFETY: This access is protected by the critical section. + let self_waker = unsafe { &mut *self.waker.get() }; + if let Some(waker) = self_waker.take() { + waker.wake() + } + }); + } +} -- cgit v1.2.3 From 5c1cefbf4e249c38467e3f6eb4e061e5b8073d6c Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Mon, 30 Jan 2023 14:49:24 +0100 Subject: Add `rtic-arbiter` --- rtic-arbiter/.gitignore | 2 + rtic-arbiter/CHANGELOG.md | 16 +++++ rtic-arbiter/Cargo.toml | 18 +++++ rtic-arbiter/src/lib.rs | 175 +++++++++++++++++++++++++++++++++++++++++++++ rtic-channel/src/lib.rs | 28 ++------ rtic-common/src/dropper.rs | 26 +++++++ rtic-common/src/lib.rs | 1 + 7 files changed, 242 insertions(+), 24 deletions(-) create mode 100644 rtic-arbiter/.gitignore create mode 100644 rtic-arbiter/CHANGELOG.md create mode 100644 rtic-arbiter/Cargo.toml create mode 100644 rtic-arbiter/src/lib.rs create mode 100644 rtic-common/src/dropper.rs diff --git a/rtic-arbiter/.gitignore b/rtic-arbiter/.gitignore new file mode 100644 index 0000000..1e7caa9 --- /dev/null +++ b/rtic-arbiter/.gitignore @@ -0,0 +1,2 @@ +Cargo.lock +target/ diff --git a/rtic-arbiter/CHANGELOG.md b/rtic-arbiter/CHANGELOG.md new file mode 100644 index 0000000..d3a9d84 --- /dev/null +++ b/rtic-arbiter/CHANGELOG.md @@ -0,0 +1,16 @@ +# Change Log + +All notable changes to this project will be documented in this file. +This project adheres to [Semantic Versioning](http://semver.org/). + +For each category, *Added*, *Changed*, *Fixed* add new entries at the top! + +## [Unreleased] + +### Added + +### Changed + +### Fixed + +## [v1.0.0] - 2023-xx-xx diff --git a/rtic-arbiter/Cargo.toml b/rtic-arbiter/Cargo.toml new file mode 100644 index 0000000..b1afaf4 --- /dev/null +++ b/rtic-arbiter/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "rtic-arbiter" +version = "1.0.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +critical-section = "1" +rtic-common = { version = "1.0.0", path = "../rtic-common" } + +[dev-dependencies] +tokio = { version = "1", features = ["rt", "macros", "time"] } + + +[features] +default = [] +testing = ["critical-section/std", "rtic-common/testing"] diff --git a/rtic-arbiter/src/lib.rs b/rtic-arbiter/src/lib.rs new file mode 100644 index 0000000..487c64c --- /dev/null +++ b/rtic-arbiter/src/lib.rs @@ -0,0 +1,175 @@ +//! Crate + +#![no_std] +#![deny(missing_docs)] +//deny_warnings_placeholder_for_ci + +use core::cell::UnsafeCell; +use core::future::poll_fn; +use core::ops::{Deref, DerefMut}; +use core::pin::Pin; +use core::sync::atomic::{fence, AtomicBool, Ordering}; +use core::task::{Poll, Waker}; + +use rtic_common::dropper::OnDrop; +use rtic_common::wait_queue::{Link, WaitQueue}; + +/// This is needed to make the async closure in `send` accept that we "share" +/// the link possible between threads. +#[derive(Clone)] +struct LinkPtr(*mut Option>); + +impl LinkPtr { + /// This will dereference the pointer stored within and give out an `&mut`. + unsafe fn get(&mut self) -> &mut Option> { + &mut *self.0 + } +} + +unsafe impl Send for LinkPtr {} +unsafe impl Sync for LinkPtr {} + +/// An FIFO waitqueue for use in shared bus usecases. +pub struct Arbiter { + wait_queue: WaitQueue, + inner: UnsafeCell, + taken: AtomicBool, +} + +impl Arbiter { + /// Create a new arbiter. + pub const fn new(inner: T) -> Self { + Self { + wait_queue: WaitQueue::new(), + inner: UnsafeCell::new(inner), + taken: AtomicBool::new(false), + } + } + + /// Get access to the inner value in the `Arbiter`. This will wait until access is granted, + /// for non-blocking access use `try_access`. + pub async fn access(&self) -> ExclusiveAccess<'_, T> { + let mut link_ptr: Option> = None; + + // Make this future `Drop`-safe, also shadow the original definition so we can't abuse it. + let mut link_ptr = LinkPtr(&mut link_ptr as *mut Option>); + + let mut link_ptr2 = link_ptr.clone(); + let dropper = OnDrop::new(|| { + // SAFETY: We only run this closure and dereference the pointer if we have + // exited the `poll_fn` below in the `drop(dropper)` call. The other dereference + // of this pointer is in the `poll_fn`. + if let Some(link) = unsafe { link_ptr2.get() } { + link.remove_from_list(&self.wait_queue); + } + }); + + poll_fn(|cx| { + critical_section::with(|_| { + fence(Ordering::SeqCst); + + // The queue is empty and noone has taken the value. + if self.wait_queue.is_empty() && !self.taken.load(Ordering::Relaxed) { + self.taken.store(true, Ordering::Relaxed); + + return Poll::Ready(()); + } + + // SAFETY: This pointer is only dereferenced here and on drop of the future + // which happens outside this `poll_fn`'s stack frame. + let link = unsafe { link_ptr.get() }; + if let Some(link) = link { + if link.is_poped() { + return Poll::Ready(()); + } + } else { + // Place the link in the wait queue on first run. + let link_ref = link.insert(Link::new(cx.waker().clone())); + + // SAFETY: The address to the link is stable as it is hidden behind + // `link_ptr`, and `link_ptr` shadows the original making it unmovable. + self.wait_queue + .push(unsafe { Pin::new_unchecked(link_ref) }); + } + + Poll::Pending + }) + }) + .await; + + // Make sure the link is removed from the queue. + drop(dropper); + + // SAFETY: One only gets here if there is exlusive access. + ExclusiveAccess { + arbiter: self, + inner: unsafe { &mut *self.inner.get() }, + } + } + + /// Non-blockingly tries to access the underlying value. + /// If someone is in queue to get it, this will return `None`. + pub fn try_access(&self) -> Option> { + critical_section::with(|_| { + fence(Ordering::SeqCst); + + // The queue is empty and noone has taken the value. + if self.wait_queue.is_empty() && !self.taken.load(Ordering::Relaxed) { + self.taken.store(true, Ordering::Relaxed); + + // SAFETY: One only gets here if there is exlusive access. + Some(ExclusiveAccess { + arbiter: self, + inner: unsafe { &mut *self.inner.get() }, + }) + } else { + None + } + }) + } +} + +/// This token represents exclusive access to the value protected by the `Arbiter`. +pub struct ExclusiveAccess<'a, T> { + arbiter: &'a Arbiter, + inner: &'a mut T, +} + +impl<'a, T> Drop for ExclusiveAccess<'a, T> { + fn drop(&mut self) { + critical_section::with(|_| { + fence(Ordering::SeqCst); + + if self.arbiter.wait_queue.is_empty() { + // If noone is in queue and we release exclusive access, reset `taken`. + self.arbiter.taken.store(false, Ordering::Relaxed); + } else if let Some(next) = self.arbiter.wait_queue.pop() { + // Wake the next one in queue. + next.wake(); + } + }) + } +} + +impl<'a, T> Deref for ExclusiveAccess<'a, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.inner + } +} + +impl<'a, T> DerefMut for ExclusiveAccess<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.inner + } +} + +#[cfg(test)] +#[macro_use] +extern crate std; + +#[cfg(test)] +mod tests { + // use super::*; +} diff --git a/rtic-channel/src/lib.rs b/rtic-channel/src/lib.rs index 2b237f6..6f816b5 100644 --- a/rtic-channel/src/lib.rs +++ b/rtic-channel/src/lib.rs @@ -14,8 +14,11 @@ use core::{ task::{Poll, Waker}, }; use heapless::Deque; -use rtic_common::wait_queue::{Link, WaitQueue}; use rtic_common::waker_registration::CriticalSectionWakerRegistration as WakerRegistration; +use rtic_common::{ + dropper::OnDrop, + wait_queue::{Link, WaitQueue}, +}; /// An MPSC channel for use in no-alloc systems. `N` sets the size of the queue. /// @@ -417,29 +420,6 @@ impl<'a, T, const N: usize> Drop for Receiver<'a, T, N> { } } -struct OnDrop { - f: core::mem::MaybeUninit, -} - -impl OnDrop { - pub fn new(f: F) -> Self { - Self { - f: core::mem::MaybeUninit::new(f), - } - } - - #[allow(unused)] - pub fn defuse(self) { - core::mem::forget(self) - } -} - -impl Drop for OnDrop { - fn drop(&mut self) { - unsafe { self.f.as_ptr().read()() } - } -} - #[cfg(test)] #[macro_use] extern crate std; diff --git a/rtic-common/src/dropper.rs b/rtic-common/src/dropper.rs new file mode 100644 index 0000000..a4b4d15 --- /dev/null +++ b/rtic-common/src/dropper.rs @@ -0,0 +1,26 @@ +//! A drop implementation runner. + +/// Runs a closure on drop. +pub struct OnDrop { + f: core::mem::MaybeUninit, +} + +impl OnDrop { + /// Make a new droppper given a closure. + pub fn new(f: F) -> Self { + Self { + f: core::mem::MaybeUninit::new(f), + } + } + + /// Make it not run drop. + pub fn defuse(self) { + core::mem::forget(self) + } +} + +impl Drop for OnDrop { + fn drop(&mut self) { + unsafe { self.f.as_ptr().read()() } + } +} diff --git a/rtic-common/src/lib.rs b/rtic-common/src/lib.rs index 3c75856..b8b5e0d 100644 --- a/rtic-common/src/lib.rs +++ b/rtic-common/src/lib.rs @@ -4,5 +4,6 @@ #![deny(missing_docs)] //deny_warnings_placeholder_for_ci +pub mod dropper; pub mod wait_queue; pub mod waker_registration; -- cgit v1.2.3 From f2e0cd342ee11ab1a2e480b67a1a91d3b277932b Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Mon, 30 Jan 2023 21:24:12 +0100 Subject: Added testing to rtic-arbiter --- .github/workflows/build.yml | 93 +++++++++++++++++++++++++++++++++++++++++++++ rtic-arbiter/src/lib.rs | 25 +++++++++++- 2 files changed, 117 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 650fc53..3ef7d52 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -29,6 +29,21 @@ jobs: working-directory: ./rtic run: cargo fmt --all -- --check + stylearbiter: + name: cargo fmt rtic-arbiter + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Fail on warnings + working-directory: ./rtic-arbiter + run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs + + - name: cargo fmt --check + working-directory: ./rtic-arbiter + run: cargo fmt --all -- --check + stylechannel: name: cargo fmt rtic-channel runs-on: ubuntu-22.04 @@ -112,6 +127,43 @@ jobs: working-directory: ./rtic run: cargo check --target=${{ matrix.target }} + # Compilation check + checkarbiter: + name: check rtic-arbiter + runs-on: ubuntu-22.04 + strategy: + matrix: + target: + - thumbv7m-none-eabi + - thumbv6m-none-eabi + - x86_64-unknown-linux-gnu + toolchain: + - nightly + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Install Rust ${{ matrix.toolchain }} + working-directory: ./rtic-arbiter + run: | + rustup set profile minimal + rustup override set ${{ matrix.toolchain }} + + - name: Configure Rust target (${{ matrix.target }}) + working-directory: ./rtic-arbiter + run: rustup target add ${{ matrix.target }} + + - name: Fail on warnings + working-directory: ./rtic-arbiter + run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs + + - name: Cache Dependencies + uses: Swatinem/rust-cache@v2 + + - name: cargo check + working-directory: ./rtic-arbiter + run: cargo check --target=${{ matrix.target }} + # Compilation check checkchannel: name: check rtic-channel @@ -246,6 +298,28 @@ jobs: working-directory: ./rtic run: cargo clippy + clippyarbiter: + name: Cargo clippy rtic-arbiter + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Fail on warnings + working-directory: ./rtic-arbiter + run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs + + - name: Add Rust component clippy + working-directory: ./rtic-arbiter + run: rustup component add clippy + + - name: Cache Dependencies + uses: Swatinem/rust-cache@v2 + + - name: cargo clippy + working-directory: ./rtic-arbiter + run: cargo clippy + clippychannel: name: Cargo clippy rtic-channel runs-on: ubuntu-22.04 @@ -465,6 +539,25 @@ jobs: working-directory: ./rtic run: cargo test --test tests + # Run test suite + testsarbiter: + name: tests rtic-arbiter + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Cache Dependencies + uses: Swatinem/rust-cache@v2 + + - name: Fail on warnings + working-directory: ./rtic-arbiter + run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs + + - name: Run cargo test + working-directory: ./rtic-arbiter + run: cargo test --features testing + # Run test suite testschannel: name: tests rtic-channel diff --git a/rtic-arbiter/src/lib.rs b/rtic-arbiter/src/lib.rs index 487c64c..519ce2c 100644 --- a/rtic-arbiter/src/lib.rs +++ b/rtic-arbiter/src/lib.rs @@ -36,6 +36,9 @@ pub struct Arbiter { taken: AtomicBool, } +unsafe impl Send for Arbiter {} +unsafe impl Sync for Arbiter {} + impl Arbiter { /// Create a new arbiter. pub const fn new(inner: T) -> Self { @@ -171,5 +174,25 @@ extern crate std; #[cfg(test)] mod tests { - // use super::*; + use super::*; + + #[tokio::test] + async fn stress_channel() { + const NUM_RUNS: usize = 100_000; + + static ARB: Arbiter = Arbiter::new(0); + let mut v = std::vec::Vec::new(); + + for _ in 0..NUM_RUNS { + v.push(tokio::spawn(async move { + *ARB.access().await += 1; + })); + } + + for v in v { + v.await.unwrap(); + } + + assert_eq!(*ARB.access().await, NUM_RUNS) + } } -- cgit v1.2.3 From 8f38470a44407dd40e64e3f1cd193ff4f6d769c0 Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Tue, 31 Jan 2023 14:54:31 +0100 Subject: rtic-channel: try_* APIs now error if Sender/Receiver does not exist --- rtic-channel/src/lib.rs | 96 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 69 insertions(+), 27 deletions(-) diff --git a/rtic-channel/src/lib.rs b/rtic-channel/src/lib.rs index 6f816b5..3cee78b 100644 --- a/rtic-channel/src/lib.rs +++ b/rtic-channel/src/lib.rs @@ -119,6 +119,14 @@ macro_rules! make_channel { /// Error state for when the receiver has been dropped. pub struct NoReceiver(pub T); +/// Errors that 'try_send` can have. +pub enum TrySendError { + /// Error state for when the receiver has been dropped. + NoReceiver(T), + /// Error state when the queue is full. + Full(T), +} + impl core::fmt::Debug for NoReceiver where T: core::fmt::Debug, @@ -128,6 +136,32 @@ where } } +impl core::fmt::Debug for TrySendError +where + T: core::fmt::Debug, +{ + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + TrySendError::NoReceiver(v) => write!(f, "NoReceiver({:?})", v), + TrySendError::Full(v) => write!(f, "Full({:?})", v), + } + } +} + +impl PartialEq for TrySendError +where + T: PartialEq, +{ + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (TrySendError::NoReceiver(v1), TrySendError::NoReceiver(v2)) => v1.eq(v2), + (TrySendError::NoReceiver(_), TrySendError::Full(_)) => false, + (TrySendError::Full(_), TrySendError::NoReceiver(_)) => false, + (TrySendError::Full(v1), TrySendError::Full(v2)) => v1.eq(v2), + } + } +} + /// A `Sender` can send to the channel and can be cloned. pub struct Sender<'a, T, const N: usize>(&'a Channel); @@ -178,18 +212,22 @@ impl<'a, T, const N: usize> Sender<'a, T, N> { } /// Try to send a value, non-blocking. If the channel is full this will return an error. - /// Note; this does not check if the channel is closed. - pub fn try_send(&mut self, val: T) -> Result<(), T> { + pub fn try_send(&mut self, val: T) -> Result<(), TrySendError> { // If the wait queue is not empty, we can't try to push into the queue. if !self.0.wait_queue.is_empty() { - return Err(val); + return Err(TrySendError::Full(val)); + } + + // No receiver available. + if self.is_closed() { + return Err(TrySendError::NoReceiver(val)); } let idx = if let Some(idx) = critical_section::with(|cs| self.0.access(cs).freeq.pop_front()) { idx } else { - return Err(val); + return Err(TrySendError::Full(val)); }; self.send_footer(idx, val); @@ -330,19 +368,18 @@ impl<'a, T, const N: usize> core::fmt::Debug for Receiver<'a, T, N> { } } -/// Error state for when all senders has been dropped. -pub struct NoSender; - -impl core::fmt::Debug for NoSender { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "NoSender") - } +/// Possible receive errors. +#[derive(Debug, PartialEq, Eq)] +pub enum ReceiveError { + /// Error state for when all senders has been dropped. + NoSender, + /// Error state for when the queue is empty. + Empty, } impl<'a, T, const N: usize> Receiver<'a, T, N> { /// Receives a value if there is one in the channel, non-blocking. - /// Note; this does not check if the channel is closed. - pub fn try_recv(&mut self) -> Option { + pub fn try_recv(&mut self) -> Result { // Try to get a ready slot. let ready_slot = critical_section::with(|cs| self.0.access(cs).readyq.pop_front()); @@ -363,15 +400,19 @@ impl<'a, T, const N: usize> Receiver<'a, T, N> { wait_head.wake(); } - Some(r) + Ok(r) } else { - None + if self.is_closed() { + Err(ReceiveError::NoSender) + } else { + Err(ReceiveError::Empty) + } } } /// Receives a value, waiting if the queue is empty. /// If all senders are dropped this will error with `NoSender`. - pub async fn recv(&mut self) -> Result { + pub async fn recv(&mut self) -> Result { // There was nothing in the queue, setup the waiting. poll_fn(|cx| { // Register waker. @@ -379,13 +420,14 @@ impl<'a, T, const N: usize> Receiver<'a, T, N> { self.0.receiver_waker.register(cx.waker()); // Try to dequeue. - if let Some(val) = self.try_recv() { - return Poll::Ready(Ok(val)); - } - - // If the queue is empty and there is no sender, return the error. - if self.is_closed() { - return Poll::Ready(Err(NoSender)); + match self.try_recv() { + Ok(val) => { + return Poll::Ready(Ok(val)); + } + Err(ReceiveError::NoSender) => { + return Poll::Ready(Err(ReceiveError::NoSender)); + } + _ => {} } Poll::Pending @@ -476,13 +518,13 @@ mod tests { s.try_send(i).unwrap(); } - assert_eq!(s.try_send(11), Err(11)); + assert_eq!(s.try_send(11), Err(TrySendError::Full(11))); for i in 0..10 { assert_eq!(r.try_recv().unwrap(), i); } - assert_eq!(r.try_recv(), None); + assert_eq!(r.try_recv(), Err(ReceiveError::Empty)); } #[test] @@ -493,7 +535,7 @@ mod tests { assert!(r.is_closed()); - assert_eq!(r.try_recv(), None); + assert_eq!(r.try_recv(), Err(ReceiveError::NoSender)); } #[test] @@ -504,7 +546,7 @@ mod tests { assert!(s.is_closed()); - assert_eq!(s.try_send(11), Ok(())); + assert_eq!(s.try_send(11), Err(TrySendError::NoReceiver(11))); } #[tokio::test] -- cgit v1.2.3 From 15d788b7fad0254ad3323962b8dd6753cf95796d Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Tue, 31 Jan 2023 15:55:14 +0100 Subject: Fix spelling error --- rtic-arbiter/src/lib.rs | 2 +- rtic-channel/src/lib.rs | 2 +- rtic-common/src/wait_queue.rs | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/rtic-arbiter/src/lib.rs b/rtic-arbiter/src/lib.rs index 519ce2c..09d1b2e 100644 --- a/rtic-arbiter/src/lib.rs +++ b/rtic-arbiter/src/lib.rs @@ -82,7 +82,7 @@ impl Arbiter { // which happens outside this `poll_fn`'s stack frame. let link = unsafe { link_ptr.get() }; if let Some(link) = link { - if link.is_poped() { + if link.is_popped() { return Poll::Ready(()); } } else { diff --git a/rtic-channel/src/lib.rs b/rtic-channel/src/lib.rs index 3cee78b..fef546b 100644 --- a/rtic-channel/src/lib.rs +++ b/rtic-channel/src/lib.rs @@ -267,7 +267,7 @@ impl<'a, T, const N: usize> Sender<'a, T, N> { // which happens outside this `poll_fn`'s stack frame. let link = unsafe { link_ptr.get() }; if let Some(link) = link { - if !link.is_poped() { + if !link.is_popped() { return None; } else { // Fall through to dequeue diff --git a/rtic-common/src/wait_queue.rs b/rtic-common/src/wait_queue.rs index ba8ff54..4ced6ab 100644 --- a/rtic-common/src/wait_queue.rs +++ b/rtic-common/src/wait_queue.rs @@ -58,7 +58,7 @@ impl LinkedList { // Clear the pointers in the node. head_ref.next.store(null_mut(), Self::R); head_ref.prev.store(null_mut(), Self::R); - head_ref.is_poped.store(true, Self::R); + head_ref.is_popped.store(true, Self::R); return Some(head_val); } @@ -102,7 +102,7 @@ pub struct Link { pub(crate) val: T, next: AtomicPtr>, prev: AtomicPtr>, - is_poped: AtomicBool, + is_popped: AtomicBool, _up: PhantomPinned, } @@ -115,14 +115,14 @@ impl Link { val, next: AtomicPtr::new(null_mut()), prev: AtomicPtr::new(null_mut()), - is_poped: AtomicBool::new(false), + is_popped: AtomicBool::new(false), _up: PhantomPinned, } } /// Return true if this link has been poped from the list. - pub fn is_poped(&self) -> bool { - self.is_poped.load(Self::R) + pub fn is_popped(&self) -> bool { + self.is_popped.load(Self::R) } /// Remove this link from a linked list. @@ -133,7 +133,7 @@ impl Link { let prev = self.prev.load(Self::R); let next = self.next.load(Self::R); - self.is_poped.store(true, Self::R); + self.is_popped.store(true, Self::R); match unsafe { (prev.as_ref(), next.as_ref()) } { (None, None) => { -- cgit v1.2.3 From d0c51269608c18a105fd010f070bd9af6f443c60 Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Tue, 31 Jan 2023 22:05:43 +0100 Subject: Cleanup common code and clippy fixes --- rtic-channel/src/lib.rs | 12 +++++------- rtic-time/Cargo.toml | 1 + rtic-time/src/lib.rs | 27 ++------------------------- 3 files changed, 8 insertions(+), 32 deletions(-) diff --git a/rtic-channel/src/lib.rs b/rtic-channel/src/lib.rs index fef546b..47e4a77 100644 --- a/rtic-channel/src/lib.rs +++ b/rtic-channel/src/lib.rs @@ -142,8 +142,8 @@ where { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { - TrySendError::NoReceiver(v) => write!(f, "NoReceiver({:?})", v), - TrySendError::Full(v) => write!(f, "Full({:?})", v), + TrySendError::NoReceiver(v) => write!(f, "NoReceiver({v:?})"), + TrySendError::Full(v) => write!(f, "Full({v:?})"), } } } @@ -401,12 +401,10 @@ impl<'a, T, const N: usize> Receiver<'a, T, N> { } Ok(r) + } else if self.is_closed() { + Err(ReceiveError::NoSender) } else { - if self.is_closed() { - Err(ReceiveError::NoSender) - } else { - Err(ReceiveError::Empty) - } + Err(ReceiveError::Empty) } } diff --git a/rtic-time/Cargo.toml b/rtic-time/Cargo.toml index ea05939..dbcf454 100644 --- a/rtic-time/Cargo.toml +++ b/rtic-time/Cargo.toml @@ -8,3 +8,4 @@ edition = "2021" [dependencies] critical-section = "1" futures-util = { version = "0.3.25", default-features = false } +rtic-common = { version = "1.0.0", path = "../rtic-common" } diff --git a/rtic-time/src/lib.rs b/rtic-time/src/lib.rs index 44fdbce..5e4457c 100644 --- a/rtic-time/src/lib.rs +++ b/rtic-time/src/lib.rs @@ -14,13 +14,13 @@ use futures_util::{ future::{select, Either}, pin_mut, }; +use linked_list::{Link, LinkedList}; pub use monotonic::Monotonic; +use rtic_common::dropper::OnDrop; mod linked_list; mod monotonic; -use linked_list::{Link, LinkedList}; - /// Holds a waker and at which time instant this waker shall be awoken. struct WaitingWaker { waker: Waker, @@ -264,26 +264,3 @@ impl TimerQueue { } } } - -struct OnDrop { - f: core::mem::MaybeUninit, -} - -impl OnDrop { - pub fn new(f: F) -> Self { - Self { - f: core::mem::MaybeUninit::new(f), - } - } - - #[allow(unused)] - pub fn defuse(self) { - core::mem::forget(self) - } -} - -impl Drop for OnDrop { - fn drop(&mut self) { - unsafe { self.f.as_ptr().read()() } - } -} -- cgit v1.2.3 From 1f51b10297e9cbb4797aa1ed8be6a2b84c9f2e07 Mon Sep 17 00:00:00 2001 From: Per Lindgren Date: Sat, 28 Jan 2023 21:57:43 +0100 Subject: Book: Major rework for RTIC v2 --- book/en/src/SUMMARY.md | 19 +-- book/en/src/by-example.md | 12 +- book/en/src/by-example/app.md | 24 ++-- book/en/src/by-example/app_idle.md | 29 ++--- book/en/src/by-example/app_init.md | 25 ++-- book/en/src/by-example/app_minimal.md | 16 ++- book/en/src/by-example/app_priorities.md | 30 ++--- book/en/src/by-example/app_task.md | 17 ++- book/en/src/by-example/channel.md | 112 +++++++++++++++++ book/en/src/by-example/delay.md | 116 ++++++++++++++++++ book/en/src/by-example/hardware_tasks.md | 30 ++--- book/en/src/by-example/resources.md | 136 ++++++++------------- book/en/src/by-example/software_tasks.md | 106 ++++++++++++----- book/en/src/by-example/starting_a_project.md | 2 + book/en/src/preface.md | 159 ++++++++++++++++++++++--- book/en/src/rtic_vs.md | 31 +++++ rtic/Cargo.toml | 20 +++- rtic/ci/expected/async-channel-done.run | 9 ++ rtic/ci/expected/async-channel-no-receiver.run | 1 + rtic/ci/expected/async-channel-no-sender.run | 1 + rtic/ci/expected/async-channel-try.run | 2 + rtic/ci/expected/async-channel.run | 2 +- rtic/ci/expected/async-timeout.run | 19 ++- rtic/ci/expected/common.run | 3 + rtic/ci/expected/message_passing.run | 1 - rtic/ci/expected/spawn_arguments.run | 1 + rtic/ci/expected/spawn_err.run | 3 + rtic/ci/expected/spawn_loop.run | 7 ++ rtic/examples/async-channel-done.rs | 65 ++++++++++ rtic/examples/async-channel-no-receiver.rs | 40 +++++++ rtic/examples/async-channel-no-sender.rs | 40 +++++++ rtic/examples/async-channel-try.rs | 48 ++++++++ rtic/examples/async-channel.rs | 15 +-- rtic/examples/async-delay.rs | 2 + rtic/examples/async-timeout.rs | 87 ++++++++++++++ rtic/examples/common.rs | 86 +++++++++++++ rtic/examples/lock-free.rs | 50 ++++++++ rtic/examples/message_passing.rs | 34 ------ rtic/examples/spawn_arguments.rs | 34 ++++++ rtic/examples/spawn_err.rs | 40 +++++++ rtic/examples/spawn_loop.rs | 42 +++++++ rtic/examples/zero-prio-task_err.no_rs | 66 ++++++++++ 42 files changed, 1299 insertions(+), 283 deletions(-) create mode 100644 book/en/src/by-example/channel.md create mode 100644 book/en/src/by-example/delay.md create mode 100644 book/en/src/rtic_vs.md create mode 100644 rtic/ci/expected/async-channel-done.run create mode 100644 rtic/ci/expected/async-channel-no-receiver.run create mode 100644 rtic/ci/expected/async-channel-no-sender.run create mode 100644 rtic/ci/expected/async-channel-try.run delete mode 100644 rtic/ci/expected/message_passing.run create mode 100644 rtic/ci/expected/spawn_arguments.run create mode 100644 rtic/ci/expected/spawn_err.run create mode 100644 rtic/ci/expected/spawn_loop.run create mode 100644 rtic/examples/async-channel-done.rs create mode 100644 rtic/examples/async-channel-no-receiver.rs create mode 100644 rtic/examples/async-channel-no-sender.rs create mode 100644 rtic/examples/async-channel-try.rs create mode 100644 rtic/examples/async-timeout.rs create mode 100644 rtic/examples/common.rs create mode 100644 rtic/examples/lock-free.rs delete mode 100644 rtic/examples/message_passing.rs create mode 100644 rtic/examples/spawn_arguments.rs create mode 100644 rtic/examples/spawn_err.rs create mode 100644 rtic/examples/spawn_loop.rs create mode 100644 rtic/examples/zero-prio-task_err.no_rs diff --git a/book/en/src/SUMMARY.md b/book/en/src/SUMMARY.md index 853f3a5..407be6d 100644 --- a/book/en/src/SUMMARY.md +++ b/book/en/src/SUMMARY.md @@ -4,15 +4,13 @@ - [RTIC by example](./by-example.md) - [The `app`](./by-example/app.md) + - [Hardware tasks & `pend`](./by-example/hardware_tasks.md) + - [Software tasks & `spawn`](./by-example/software_tasks.md) - [Resources](./by-example/resources.md) - [The init task](./by-example/app_init.md) - [The idle task](./by-example/app_idle.md) - - [Defining tasks](./by-example/app_task.md) - - [Hardware tasks](./by-example/hardware_tasks.md) - - [Software tasks & `spawn`](./by-example/software_tasks.md) - - [Message passing & `capacity`](./by-example/message_passing.md) - - [Task priorities](./by-example/app_priorities.md) - - [Monotonic & `spawn_{at/after}`](./by-example/monotonic.md) + - [Channel based communication](./by-example/channel.md) + - [Tasks with delay](./by-example/delay.md) - [Starting a new project](./by-example/starting_a_project.md) - [The minimal app](./by-example/app_minimal.md) - [Tips & Tricks](./by-example/tips.md) @@ -23,13 +21,13 @@ - [Inspecting generated code](./by-example/tips_view_code.md) - [Running tasks from RAM](./by-example/tips_from_ram.md) +- [RTIC vs. the world](./rtic_vs.md) - [Awesome RTIC examples](./awesome_rtic.md) - [Migration Guides](./migration.md) - [v0.5.x to v1.0.x](./migration/migration_v5.md) - [v0.4.x to v0.5.x](./migration/migration_v4.md) - [RTFM to RTIC](./migration/migration_rtic.md) - [Under the hood](./internals.md) - - [Cortex-M architectures](./internals/targets.md) @@ -38,3 +36,10 @@ + + + \ No newline at end of file diff --git a/book/en/src/by-example.md b/book/en/src/by-example.md index 419a4ba..a2e5b27 100644 --- a/book/en/src/by-example.md +++ b/book/en/src/by-example.md @@ -1,14 +1,15 @@ # RTIC by example -This part of the book introduces the Real-Time Interrupt-driven Concurrency (RTIC) framework -to new users by walking them through examples of increasing complexity. +This part of the book introduces the RTIC framework to new users by walking them through examples of increasing complexity. All examples in this part of the book are accessible at the [GitHub repository][repoexamples]. The examples are runnable on QEMU (emulating a Cortex M3 target), thus no special hardware required to follow along. -[repoexamples]: https://github.com/rtic-rs/cortex-m-rtic/tree/master/examples +[repoexamples]: https://github.com/rtic-rs/rtic/tree/master/examples + +## Running an example To run the examples with QEMU you will need the `qemu-system-arm` program. Check [the embedded Rust book] for instructions on how to set up an @@ -28,11 +29,12 @@ $ cargo run --target thumbv7m-none-eabi --example locals Yields this output: ``` console -{{#include ../../../ci/expected/locals.run}} +{{#include ../../../rtic/ci/expected/locals.run}} ``` > **NOTE**: You can choose target device by passing a target > triple to cargo (e.g. `cargo run --example init --target thumbv7m-none-eabi`) or > configure a default target in `.cargo/config.toml`. > -> For running the examples, we use a Cortex M3 emulated in QEMU, so the target is `thumbv7m-none-eabi`. \ No newline at end of file +> For running the examples, we (typically) use a Cortex M3 emulated in QEMU, so the target is `thumbv7m-none-eabi`. +> Since the M3 architecture is backwards compatible to the M0/M0+ architecture, you may also use the `thumbv6m-none-eabi`, in case you want to inspect generated assembly code for the M0/M0+ architecture. diff --git a/book/en/src/by-example/app.md b/book/en/src/by-example/app.md index 2c6aca7..cef8288 100644 --- a/book/en/src/by-example/app.md +++ b/book/en/src/by-example/app.md @@ -2,25 +2,31 @@ ## Requirements on the `app` attribute -All RTIC applications use the [`app`] attribute (`#[app(..)]`). This attribute -only applies to a `mod`-item containing the RTIC application. The `app` -attribute has a mandatory `device` argument that takes a *path* as a value. -This must be a full path pointing to a -*peripheral access crate* (PAC) generated using [`svd2rust`] **v0.14.x** or -newer. +All RTIC applications use the [`app`] attribute (`#[app(..)]`). This attribute only applies to a `mod`-item containing the RTIC application. The `app` attribute has a mandatory `device` argument that takes a *path* as a value. This must be a full path pointing to a *peripheral access crate* (PAC) generated using [`svd2rust`] **v0.14.x** or newer. -The `app` attribute will expand into a suitable entry point and thus replaces -the use of the [`cortex_m_rt::entry`] attribute. +The `app` attribute will expand into a suitable entry point and thus replaces the use of the [`cortex_m_rt::entry`] attribute. [`app`]: ../../../api/cortex_m_rtic_macros/attr.app.html [`svd2rust`]: https://crates.io/crates/svd2rust [`cortex_m_rt::entry`]: ../../../api/cortex_m_rt_macros/attr.entry.html +## Structure and zero-cost concurrency + +An RTIC `app` is an executable system model for since-core applications, declaring a set of `local` and `shared` resources operated on by a set of `init`, `idle`, *hardware* and *software* tasks. In short the `init` task runs before any other task returning the set of `local` and `shared` resources. Tasks run preemptively based on their associated static priority, `idle` has the lowest priority (and can be used for background work, and/or to put the system to sleep until woken by some event). Hardware tasks are bound to underlying hardware interrupts, while software tasks are scheduled by asynchronous executors (one for each software task priority). + +At compile time the task/resource model is analyzed under the Stack Resource Policy (SRP) and executable code generated with the following outstanding properties: + +- guaranteed race-free resource access and deadlock-free execution on a single-shared stack + - hardware task scheduling is performed directly by the hardware, and + - software task scheduling is performed by auto generated async executors tailored to the application. + +Overall, the generated code infers no additional overhead in comparison to a hand-written implementation, thus in Rust terms RTIC offers a zero-cost abstraction to concurrency. + ## An RTIC application example To give a flavour of RTIC, the following example contains commonly used features. In the following sections we will go through each feature in detail. ``` rust -{{#include ../../../../examples/common.rs}} +{{#include ../../../../rtic/examples/common.rs}} ``` diff --git a/book/en/src/by-example/app_idle.md b/book/en/src/by-example/app_idle.md index 537902a..4856ee1 100644 --- a/book/en/src/by-example/app_idle.md +++ b/book/en/src/by-example/app_idle.md @@ -1,52 +1,47 @@ # The background task `#[idle]` -A function marked with the `idle` attribute can optionally appear in the -module. This becomes the special *idle task* and must have signature -`fn(idle::Context) -> !`. +A function marked with the `idle` attribute can optionally appear in the module. This becomes the special *idle task* and must have signature `fn(idle::Context) -> !`. -When present, the runtime will execute the `idle` task after `init`. Unlike -`init`, `idle` will run *with interrupts enabled* and must never return, -as the `-> !` function signature indicates. +When present, the runtime will execute the `idle` task after `init`. Unlike `init`, `idle` will run *with interrupts enabled* and must never return, as the `-> !` function signature indicates. [The Rust type `!` means “never”][nevertype]. [nevertype]: https://doc.rust-lang.org/core/primitive.never.html -Like in `init`, locally declared resources will have `'static` lifetimes that -are safe to access. +Like in `init`, locally declared resources will have `'static` lifetimes that are safe to access. The example below shows that `idle` runs after `init`. ``` rust -{{#include ../../../../examples/idle.rs}} +{{#include ../../../../rtic/examples/idle.rs}} ``` ``` console $ cargo run --target thumbv7m-none-eabi --example idle -{{#include ../../../../ci/expected/idle.run}} +{{#include ../../../../rtic/ci/expected/idle.run}} ``` By default, the RTIC `idle` task does not try to optimize for any specific targets. -A common useful optimization is to enable the [SLEEPONEXIT] and allow the MCU -to enter sleep when reaching `idle`. +A common useful optimization is to enable the [SLEEPONEXIT] and allow the MCU to enter sleep when reaching `idle`. ->**Caution** some hardware unless configured disables the debug unit during sleep mode. +>**Caution**: some hardware unless configured disables the debug unit during sleep mode. > >Consult your hardware specific documentation as this is outside the scope of RTIC. The following example shows how to enable sleep by setting the -[`SLEEPONEXIT`][SLEEPONEXIT] and providing a custom `idle` task replacing the -default [`nop()`][NOP] with [`wfi()`][WFI]. +[`SLEEPONEXIT`][SLEEPONEXIT] and providing a custom `idle` task replacing the default [`nop()`][NOP] with [`wfi()`][WFI]. [SLEEPONEXIT]: https://developer.arm.com/docs/100737/0100/power-management/sleep-mode/sleep-on-exit-bit [WFI]: https://developer.arm.com/documentation/dui0662/b/The-Cortex-M0--Instruction-Set/Miscellaneous-instructions/WFI [NOP]: https://developer.arm.com/documentation/dui0662/b/The-Cortex-M0--Instruction-Set/Miscellaneous-instructions/NOP ``` rust -{{#include ../../../../examples/idle-wfi.rs}} +{{#include ../../../../rtic/examples/idle-wfi.rs}} ``` ``` console $ cargo run --target thumbv7m-none-eabi --example idle-wfi -{{#include ../../../../ci/expected/idle-wfi.run}} +{{#include ../../../../rtic/ci/expected/idle-wfi.run}} ``` + +> **Notice**: The `idle` task cannot be used together with *software* tasks running at priority zero. The reason is that `idle` is running as a non-returning Rust function at priority zero. Thus there would be no way for an executor at priority zero to give control to *software* tasks at the same priority. diff --git a/book/en/src/by-example/app_init.md b/book/en/src/by-example/app_init.md index 5bf6200..62fb55b 100644 --- a/book/en/src/by-example/app_init.md +++ b/book/en/src/by-example/app_init.md @@ -1,35 +1,28 @@ # App initialization and the `#[init]` task An RTIC application requires an `init` task setting up the system. The corresponding `init` function must have the -signature `fn(init::Context) -> (Shared, Local, init::Monotonics)`, where `Shared` and `Local` are resource +signature `fn(init::Context) -> (Shared, Local)`, where `Shared` and `Local` are the resource structures defined by the user. -The `init` task executes after system reset, [after an optionally defined `pre-init` code section][pre-init] and an always occurring internal RTIC -initialization. - +The `init` task executes after system reset (after the optionally defined [pre-init] and internal RTIC +initialization). The `init` task runs *with interrupts disabled* and has exclusive access to Cortex-M (the +`bare_metal::CriticalSection` token is available as `cs`) while device specific peripherals are available through +the `core` and `device` fields of `init::Context`. [pre-init]: https://docs.rs/cortex-m-rt/latest/cortex_m_rt/attr.pre_init.html - -The `init` and optional `pre-init` tasks runs *with interrupts disabled* and have exclusive access to Cortex-M (the -`bare_metal::CriticalSection` token is available as `cs`). - -Device specific peripherals are available through the `core` and `device` fields of `init::Context`. - ## Example -The example below shows the types of the `core`, `device` and `cs` fields, and showcases the use of a `local` -variable with `'static` lifetime. -Such variables can be delegated from the `init` task to other tasks of the RTIC application. +The example below shows the types of the `core`, `device` and `cs` fields, and showcases the use of a `local` variable with `'static` lifetime. Such variables can be delegated from the `init` task to other tasks of the RTIC application. -The `device` field is only available when the `peripherals` argument is set to the default value `true`. +The `device` field is available when the `peripherals` argument is set to the default value `true`. In the rare case you want to implement an ultra-slim application you can explicitly set `peripherals` to `false`. ``` rust -{{#include ../../../../examples/init.rs}} +{{#include ../../../../rtic/examples/init.rs}} ``` Running the example will print `init` to the console and then exit the QEMU process. ``` console $ cargo run --target thumbv7m-none-eabi --example init -{{#include ../../../../ci/expected/init.run}} +{{#include ../../../../rtic/ci/expected/init.run}} ``` diff --git a/book/en/src/by-example/app_minimal.md b/book/en/src/by-example/app_minimal.md index d0ff40a..f241089 100644 --- a/book/en/src/by-example/app_minimal.md +++ b/book/en/src/by-example/app_minimal.md @@ -3,5 +3,19 @@ This is the smallest possible RTIC application: ``` rust -{{#include ../../../../examples/smallest.rs}} +{{#include ../../../../rtic/examples/smallest.rs}} ``` + +RTIC is designed with resource efficiency in mind. RTIC itself does not rely on any dynamic memory allocation, thus RAM requirement is dependent only on the application. The flash memory footprint is below 1kB including the interrupt vector table. + +For a minimal example you can expect something like: +``` console +$ cargo size --example smallest --target thumbv7m-none-eabi --release +Finished release [optimized] target(s) in 0.07s + text data bss dec hex filename + 924 0 0 924 39c smallest +``` + + diff --git a/book/en/src/by-example/app_priorities.md b/book/en/src/by-example/app_priorities.md index 8cee749..f03ebf7 100644 --- a/book/en/src/by-example/app_priorities.md +++ b/book/en/src/by-example/app_priorities.md @@ -4,23 +4,18 @@ The `priority` argument declares the static priority of each `task`. -For Cortex-M, tasks can have priorities in the range `1..=(1 << NVIC_PRIO_BITS)` -where `NVIC_PRIO_BITS` is a constant defined in the `device` crate. +For Cortex-M, tasks can have priorities in the range `1..=(1 << NVIC_PRIO_BITS)` where `NVIC_PRIO_BITS` is a constant defined in the `device` crate. -Omitting the `priority` argument the task priority defaults to `1`. -The `idle` task has a non-configurable static priority of `0`, the lowest priority. +Omitting the `priority` argument the task priority defaults to `1`. The `idle` task has a non-configurable static priority of `0`, the lowest priority. > A higher number means a higher priority in RTIC, which is the opposite from what > Cortex-M does in the NVIC peripheral. > Explicitly, this means that number `10` has a **higher** priority than number `9`. -The highest static priority task takes precedence when more than one -task are ready to execute. +The highest static priority task takes precedence when more than one task are ready to execute. The following scenario demonstrates task prioritization: -Spawning a higher priority task A during execution of a lower priority task B suspends -task B. Task A has higher priority thus preempting task B which gets suspended -until task A completes execution. Thus, when task A completes task B resumes execution. +Spawning a higher priority task A during execution of a lower priority task B pends task A. Task A has higher priority thus preempting task B which gets suspended until task A completes execution. Thus, when task A completes task B resumes execution. ```text Task Priority @@ -39,23 +34,17 @@ Task Priority The following example showcases the priority based scheduling of tasks: ``` rust -{{#include ../../../../examples/preempt.rs}} +{{#include ../../../../rtic/examples/preempt.rs}} ``` ``` console $ cargo run --target thumbv7m-none-eabi --example preempt -{{#include ../../../../ci/expected/preempt.run}} +{{#include ../../../../rtic/ci/expected/preempt.run}} ``` -Note that the task `bar` does *not* preempt task `baz` because its priority -is the *same* as `baz`'s. The higher priority task `bar` runs before `foo` -when `baz`returns. When `bar` returns `foo` can resume. +Note that the task `bar` does *not* preempt task `baz` because its priority is the *same* as `baz`'s. The higher priority task `bar` runs before `foo` when `baz`returns. When `bar` returns `foo` can resume. -One more note about priorities: choosing a priority higher than what the device -supports will result in a compilation error. - -The error is cryptic due to limitations in the Rust language -if `priority = 9` for task `uart0_interrupt` in `example/common.rs` this looks like: +One more note about priorities: choosing a priority higher than what the device supports will result in a compilation error. The error is cryptic due to limitations in the Rust language, if `priority = 9` for task `uart0_interrupt` in `example/common.rs` this looks like: ```text error[E0080]: evaluation of constant value failed @@ -68,5 +57,4 @@ if `priority = 9` for task `uart0_interrupt` in `example/common.rs` this looks l ``` -The error message incorrectly points to the starting point of the macro, but at least the -value subtracted (in this case 9) will suggest which task causes the error. +The error message incorrectly points to the starting point of the macro, but at least the value subtracted (in this case 9) will suggest which task causes the error. diff --git a/book/en/src/by-example/app_task.md b/book/en/src/by-example/app_task.md index d83f1ff..e0c67ad 100644 --- a/book/en/src/by-example/app_task.md +++ b/book/en/src/by-example/app_task.md @@ -1,21 +1,18 @@ + + # Defining tasks with `#[task]` Tasks, defined with `#[task]`, are the main mechanism of getting work done in RTIC. Tasks can -* Be spawned (now or in the future, also by themselves) -* Receive messages (passing messages between tasks) -* Be prioritized, allowing preemptive multitasking +* Be spawned (now or in the future) +* Receive messages (message passing) +* Prioritized allowing preemptive multitasking * Optionally bind to a hardware interrupt -RTIC makes a distinction between “software tasks” and “hardware tasks”. - -*Hardware tasks* are tasks that are bound to a specific interrupt vector in the MCU while software tasks are not. - -This means that if a hardware task is bound to, lets say, a UART RX interrupt, the task will be run every -time that interrupt triggers, usually when a character is received. +RTIC makes a distinction between “software tasks” and “hardware tasks”. Hardware tasks are tasks that are bound to a specific interrupt vector in the MCU while software tasks are not. -*Software tasks* are explicitly spawned in a task, either immediately or using the Monotonic timer mechanism. +This means that if a hardware task is bound to an UART RX interrupt the task will run every time this interrupt triggers, usually when a character is received. In the coming pages we will explore both tasks and the different options available. diff --git a/book/en/src/by-example/channel.md b/book/en/src/by-example/channel.md new file mode 100644 index 0000000..99bfedd --- /dev/null +++ b/book/en/src/by-example/channel.md @@ -0,0 +1,112 @@ +# Communication over channels. + +Channels can be used to communicate data between running *software* tasks. The channel is essentially a wait queue, allowing tasks with multiple producers and a single receiver. A channel is constructed in the `init` task and backed by statically allocated memory. Send and receive endpoints are distributed to *software* tasks: + +```rust +... +const CAPACITY: usize = 5; +#[init] + fn init(_: init::Context) -> (Shared, Local) { + let (s, r) = make_channel!(u32, CAPACITY); + receiver::spawn(r).unwrap(); + sender1::spawn(s.clone()).unwrap(); + sender2::spawn(s.clone()).unwrap(); + ... +``` + +In this case the channel holds data of `u32` type with a capacity of 5 elements. + +## Sending data + +The `send` method post a message on the channel as shown below: + +```rust +#[task] +async fn sender1(_c: sender1::Context, mut sender: Sender<'static, u32, CAPACITY>) { + hprintln!("Sender 1 sending: 1"); + sender.send(1).await.unwrap(); +} +``` + +## Receiving data + +The receiver can `await` incoming messages: + +```rust +#[task] +async fn receiver(_c: receiver::Context, mut receiver: Receiver<'static, u32, CAPACITY>) { + while let Ok(val) = receiver.recv().await { + hprintln!("Receiver got: {}", val); + ... + } +} +``` + +For a complete example: + +``` rust +{{#include ../../../../rtic/examples/async-channel.rs}} +``` + +``` console +$ cargo run --target thumbv7m-none-eabi --example async-channel --features test-critical-section +{{#include ../../../../rtic/ci/expected/async-channel.run}} +``` + +Also sender endpoint can be awaited. In case there the channel capacity has not been reached, `await` the sender can progress immediately, while in the case the capacity is reached, the sender is blocked until there is free space in the queue. In this way data is never lost. + +In the below example the `CAPACITY` has been reduced to 1, forcing sender tasks to wait until the data in the channel has been received. + +``` rust +{{#include ../../../../rtic/examples/async-channel-done.rs}} +``` + +Looking at the output, we find that `Sender 2` will wait until the data sent by `Sender 1` as been received. + +> **NOTICE** *Software* tasks at the same priority are executed asynchronously to each other, thus **NO** strict order can be assumed. (The presented order here applies only to the current implementation, and may change between RTIC framework releases.) + +``` console +$ cargo run --target thumbv7m-none-eabi --example async-channel-done --features test-critical-section +{{#include ../../../../rtic/ci/expected/async-channel-done.run}} +``` + +## Error handling + +In case all senders have been dropped `await` on an empty receiver channel results in an error. This allows to gracefully implement different types of shutdown operations. + +``` rust +{{#include ../../../../rtic/examples/async-channel-no-sender.rs}} +``` + +``` console +$ cargo run --target thumbv7m-none-eabi --example async-channel-no-sender --features test-critical-section +{{#include ../../../../rtic/ci/expected/async-channel-no-sender.run}} +``` + +Similarly, `await` on a send channel results in an error in case the receiver has been dropped. This allows to gracefully implement application level error handling. + +The resulting error returns the data back to the sender, allowing the sender to take appropriate action (e.g., storing the data to later retry sending it). + +``` rust +{{#include ../../../../rtic/examples/async-channel-no-receiver.rs}} +``` + +``` console +$ cargo run --target thumbv7m-none-eabi --example async-channel-no-receiver --features test-critical-section +{{#include ../../../../rtic/ci/expected/async-channel-no-receiver.run}} +``` + + + +## Try API + +In cases you wish the sender to proceed even in case the channel is full. To that end, a `try_send` API is provided. + +``` rust +{{#include ../../../../rtic/examples/async-channel-try.rs}} +``` + +``` console +$ cargo run --target thumbv7m-none-eabi --example async-channel-try --features test-critical-section +{{#include ../../../../rtic/ci/expected/async-channel-try.run}} +``` \ No newline at end of file diff --git a/book/en/src/by-example/delay.md b/book/en/src/by-example/delay.md new file mode 100644 index 0000000..d35d9da --- /dev/null +++ b/book/en/src/by-example/delay.md @@ -0,0 +1,116 @@ +# Tasks with delay + +A convenient way to express *miniminal* timing requirements is by means of delaying progression. + +This can be achieved by instantiating a monotonic timer: + +```rust +... +rtic_monotonics::make_systick_timer_queue!(TIMER); + +#[init] +fn init(cx: init::Context) -> (Shared, Local) { + let systick = Systick::start(cx.core.SYST, 12_000_000); + TIMER.initialize(systick); + ... +``` + +A *software* task can `await` the delay to expire: + +```rust +#[task] +async fn foo(_cx: foo::Context) { + ... + TIMER.delay(100.millis()).await; + ... +``` + +Technically, the timer queue is implemented as a list based priority queue, where list-nodes are statically allocated as part of the underlying task `Future`. Thus, the timer queue is infallible at run-time (its size and allocation is determined at compile time). + +For a complete example: + +``` rust +{{#include ../../../../rtic/examples/async-delay.rs}} +``` + +``` console +$ cargo run --target thumbv7m-none-eabi --example async-delay --features test-critical-section +{{#include ../../../../rtic/ci/expected/async-delay.run}} +``` + +## Timeout + +Rust `Futures` (underlying Rust `async`/`await`) are composable. This makes it possible to `select` in between `Futures` that have completed. + +A common use case is transactions with associated timeout. In the examples shown below, we introduce a fake HAL device which performs some transaction. We have modelled the time it takes based on the input parameter (`n`) as `350ms + n * 100ms)`. + +Using the `select_biased` macro from the `futures` crate it may look like this: + +```rust +// Call hal with short relative timeout using `select_biased` +select_biased! { + v = hal_get(&TIMER, 1).fuse() => hprintln!("hal returned {}", v), + _ = TIMER.delay(200.millis()).fuse() => hprintln!("timeout", ), // this will finish first +} +``` + +Assuming the `hal_get` will take 450ms to finish, a short timeout of 200ms will expire. + +```rust +// Call hal with long relative timeout using `select_biased` +select_biased! { + v = hal_get(&TIMER, 1).fuse() => hprintln!("hal returned {}", v), // hal finish first + _ = TIMER.delay(1000.millis()).fuse() => hprintln!("timeout", ), +} +``` + +By extending the timeout to 1000ms, the `hal_get` will finish first. + +Using `select_biased` any number of futures can be combined, so its very powerful. However, as the timeout pattern is frequently used, it is directly supported by the RTIC [rtc-monotonics] and [rtic-time] crates. The second example from above using `timeout_after`: + +```rust +// Call hal with long relative timeout using monotonic `timeout_after` +match TIMER.timeout_after(1000.millis(), hal_get(&TIMER, 1)).await { + Ok(v) => hprintln!("hal returned {}", v), + _ => hprintln!("timeout"), +} +``` + +In cases you want exact control over time without drift. For this purpose we can use exact points in time using `Instance`, and spans of time using `Duration`. Operations on the `Instance` and `Duration` types are given by the [fugit] crate. + +[fugit]: https://crates.io/crates/fugit + +```rust +// get the current time instance +let mut instant = TIMER.now(); + +// do this 3 times +for n in 0..3 { + // exact point in time without drift + instant += 1000.millis(); + TIMER.delay_until(instant).await; + + // exact point it time for timeout + let timeout = instant + 500.millis(); + hprintln!("now is {:?}, timeout at {:?}", TIMER.now(), timeout); + + match TIMER.timeout_at(timeout, hal_get(&TIMER, n)).await { + Ok(v) => hprintln!("hal returned {} at time {:?}", v, TIMER.now()), + _ => hprintln!("timeout"), + } +} +``` + +`instant = TIMER.now()` gives the baseline (i.e., the exact current point in time). We want to call `hal_get` after 1000ms relative to this exact point in time. This can be accomplished by `TIMER.delay_until(instant).await;`. We define the absolute point in time for the `timeout`, and call `TIMER.timeout_at(timeout, hal_get(&TIMER, n)).await`. For the first loop iteration `n == 0`, and the `hal_get` will take 350ms (and finishes before the timeout). For the second iteration `n == 1`, and `hal_get` will take 450ms (and again succeeds to finish before the timeout). For the third iteration `n == 2` (`hal_get` will take 5500ms to finish). In this case we will run into a timeout. + + +The complete example: + +``` rust +{{#include ../../../../rtic/examples/async-timeout.rs}} +``` + +``` console +$ cargo run --target thumbv7m-none-eabi --example async-timeout --features test-critical-section +{{#include ../../../../rtic/ci/expected/async-timeout.run}} +``` diff --git a/book/en/src/by-example/hardware_tasks.md b/book/en/src/by-example/hardware_tasks.md index 2d405d3..e3e51ac 100644 --- a/book/en/src/by-example/hardware_tasks.md +++ b/book/en/src/by-example/hardware_tasks.md @@ -1,39 +1,27 @@ # Hardware tasks -At its core RTIC is using a hardware interrupt controller ([ARM NVIC on cortex-m][NVIC]) -to schedule and start execution of tasks. All tasks except `pre-init`, `#[init]` and `#[idle]` -run as interrupt handlers. +At its core RTIC is using the hardware interrupt controller ([ARM NVIC on cortex-m][NVIC]) to perform scheduling and executing tasks, and all (*hardware*) tasks except `#[init]` and `#[idle]` run as interrupt handlers. This also means that you can manually bind tasks to interrupt handlers. -Hardware tasks are explicitly bound to interrupt handlers. +To bind an interrupt use the `#[task]` attribute argument `binds = InterruptName`. This task becomes the interrupt handler for this hardware interrupt vector. -To bind a task to an interrupt, use the `#[task]` attribute argument `binds = InterruptName`. -This task then becomes the interrupt handler for this hardware interrupt vector. +All tasks bound to an explicit interrupt are *hardware tasks* since they start execution in reaction to a hardware event (interrupt). -All tasks bound to an explicit interrupt are called *hardware tasks* since they -start execution in reaction to a hardware event. +Specifying a non-existing interrupt name will cause a compilation error. The interrupt names are commonly defined by [PAC or HAL][pacorhal] crates. -Specifying a non-existing interrupt name will cause a compilation error. The interrupt names -are commonly defined by [PAC or HAL][pacorhal] crates. +Any available interrupt vector should work, but different hardware might have added special properties to select interrupt priority levels, such as the [nRF “softdevice”](https://github.com/rtic-rs/cortex-m-rtic/issues/434). -Any available interrupt vector should work. Specific devices may bind -specific interrupt priorities to specific interrupt vectors outside -user code control. See for example the -[nRF “softdevice”](https://github.com/rtic-rs/cortex-m-rtic/issues/434). - -Beware of using interrupt vectors that are used internally by hardware features; -RTIC is unaware of such hardware specific details. +Beware of re-purposing interrupt vectors used internally by hardware features, RTIC is unaware of such hardware specific details. [pacorhal]: https://docs.rust-embedded.org/book/start/registers.html [NVIC]: https://developer.arm.com/documentation/100166/0001/Nested-Vectored-Interrupt-Controller/NVIC-functional-description/NVIC-interrupts -The example below demonstrates the use of the `#[task(binds = InterruptName)]` attribute to declare a -hardware task bound to an interrupt handler. +The example below demonstrates the use of the `#[task(binds = InterruptName)]` attribute to declare a hardware task bound to an interrupt handler. In the example the interrupt triggering task execution is manually pended (`rtic::pend(Interrupt::UART0)`). However, in the typical case, interrupts are pended by the hardware peripheral. RTIC does not interfere with mechanisms for clearing peripheral interrupts, so any hardware specific implementation is completely up to the implementer. ``` rust -{{#include ../../../../examples/hardware.rs}} +{{#include ../../../../rtic/examples/hardware.rs}} ``` ``` console $ cargo run --target thumbv7m-none-eabi --example hardware -{{#include ../../../../ci/expected/hardware.run}} +{{#include ../../../../rtic/ci/expected/hardware.run}} ``` diff --git a/book/en/src/by-example/resources.md b/book/en/src/by-example/resources.md index 30089d3..ea67b26 100644 --- a/book/en/src/by-example/resources.md +++ b/book/en/src/by-example/resources.md @@ -1,176 +1,138 @@ # Resource usage -The RTIC framework manages shared and task local resources allowing persistent data -storage and safe accesses without the use of `unsafe` code. +The RTIC framework manages shared and task local resources allowing persistent data storage and safe accesses without the use of `unsafe` code. -RTIC resources are visible only to functions declared within the `#[app]` module and the framework -gives the user complete control (on a per-task basis) over resource accessibility. +RTIC resources are visible only to functions declared within the `#[app]` module and the framework gives the user complete control (on a per-task basis) over resource accessibility. -Declaration of system-wide resources is done by annotating **two** `struct`s within the `#[app]` module -with the attribute `#[local]` and `#[shared]`. -Each field in these structures corresponds to a different resource (identified by field name). -The difference between these two sets of resources will be covered below. +Declaration of system-wide resources is done by annotating **two** `struct`s within the `#[app]` module with the attribute `#[local]` and `#[shared]`. Each field in these structures corresponds to a different resource (identified by field name). The difference between these two sets of resources will be covered below. -Each task must declare the resources it intends to access in its corresponding metadata attribute -using the `local` and `shared` arguments. Each argument takes a list of resource identifiers. -The listed resources are made available to the context under the `local` and `shared` fields of the -`Context` structure. +Each task must declare the resources it intends to access in its corresponding metadata attribute using the `local` and `shared` arguments. Each argument takes a list of resource identifiers. The listed resources are made available to the context under the `local` and `shared` fields of the `Context` structure. -The `init` task returns the initial values for the system-wide (`#[shared]` and `#[local]`) -resources, and the set of initialized timers used by the application. The monotonic timers will be -further discussed in [Monotonic & `spawn_{at/after}`](./monotonic.md). +The `init` task returns the initial values for the system-wide (`#[shared]` and `#[local]`) resources. + + ## `#[local]` resources -`#[local]` resources are locally accessible to a specific task, meaning that only that task can -access the resource and does so without locks or critical sections. This allows for the resources, -commonly drivers or large objects, to be initialized in `#[init]` and then be passed to a specific -task. +`#[local]` resources accessible only to a single task. This task is given unique access to the resource without the use of locks or critical sections. -Thus, a task `#[local]` resource can only be accessed by one singular task. -Attempting to assign the same `#[local]` resource to more than one task is a compile-time error. +This allows for the resources, commonly drivers or large objects, to be initialized in `#[init]` and then be passed to a specific task. (Thus, a task `#[local]` resource can only be accessed by one single task.) Attempting to assign the same `#[local]` resource to more than one task is a compile-time error. -Types of `#[local]` resources must implement a [`Send`] trait as they are being sent from `init` -to a target task, crossing a thread boundary. +Types of `#[local]` resources must implement [`Send`] trait as they are being sent from `init` to the target task and thus crossing the *thread* boundary. [`Send`]: https://doc.rust-lang.org/stable/core/marker/trait.Send.html -The example application shown below contains two tasks where each task has access to its own -`#[local]` resource; the `idle` task has its own `#[local]` as well. +The example application shown below contains three tasks `foo`, `bar` and `idle`, each having access to its own `#[local]` resource. ``` rust -{{#include ../../../../examples/locals.rs}} +{{#include ../../../../rtic/examples/locals.rs}} ``` Running the example: ``` console $ cargo run --target thumbv7m-none-eabi --example locals -{{#include ../../../../ci/expected/locals.run}} +{{#include ../../../../rtic/ci/expected/locals.run}} ``` -Local resources in `#[init]` and `#[idle]` have `'static` -lifetimes. This is safe since both tasks are not re-entrant. - ### Task local initialized resources -Local resources can also be specified directly in the resource claim like so: -`#[task(local = [my_var: TYPE = INITIAL_VALUE, ...])]`; this allows for creating locals which do no need to be -initialized in `#[init]`. +A special use-case of local resources are the ones specified directly in the task declaration, `#[task(local = [my_var: TYPE = INITIAL_VALUE, ...])]`. This allows for creating locals which do no need to be initialized in `#[init]`. Moreover, local resources in `#[init]` and `#[idle]` have `'static` lifetimes, this is safe since both are not re-entrant. -Types of `#[task(local = [..])]` resources have to be neither [`Send`] nor [`Sync`] as they -are not crossing any thread boundary. +Types of `#[task(local = [..])]` resources have to be neither [`Send`] nor [`Sync`] as they are not crossing any thread boundary. [`Sync`]: https://doc.rust-lang.org/stable/core/marker/trait.Sync.html In the example below the different uses and lifetimes are shown: ``` rust -{{#include ../../../../examples/declared_locals.rs}} +{{#include ../../../../rtic/examples/declared_locals.rs}} ``` - +You can run the application, but as the example is designed merely to showcase the lifetime properties there is no output (it suffices to build the application). + +``` console +$ cargo build --target thumbv7m-none-eabi --example declared_locals +``` + ## `#[shared]` resources and `lock` -Critical sections are required to access `#[shared]` resources in a data race-free manner and to -achieve this the `shared` field of the passed `Context` implements the [`Mutex`] trait for each -shared resource accessible to the task. This trait has only one method, [`lock`], which runs its -closure argument in a critical section. +Critical sections are required to access `#[shared]` resources in a data race-free manner and to achieve this the `shared` field of the passed `Context` implements the [`Mutex`] trait for each shared resource accessible to the task. This trait has only one method, [`lock`], which runs its closure argument in a critical section. [`Mutex`]: ../../../api/rtic/trait.Mutex.html [`lock`]: ../../../api/rtic/trait.Mutex.html#method.lock -The critical section created by the `lock` API is based on dynamic priorities: it temporarily -raises the dynamic priority of the context to a *ceiling* priority that prevents other tasks from -preempting the critical section. This synchronization protocol is known as the -[Immediate Ceiling Priority Protocol (ICPP)][icpp], and complies with -[Stack Resource Policy (SRP)][srp] based scheduling of RTIC. +The critical section created by the `lock` API is based on dynamic priorities: it temporarily raises the dynamic priority of the context to a *ceiling* priority that prevents other tasks from preempting the critical section. This synchronization protocol is known as the [Immediate Ceiling Priority Protocol (ICPP)][icpp], and complies with [Stack Resource Policy (SRP)][srp] based scheduling of RTIC. [icpp]: https://en.wikipedia.org/wiki/Priority_ceiling_protocol [srp]: https://en.wikipedia.org/wiki/Stack_Resource_Policy -In the example below we have three interrupt handlers with priorities ranging from one to three. -The two handlers with the lower priorities contend for a `shared` resource and need to succeed in locking the -resource in order to access its data. The highest priority handler, which does not access the `shared` -resource, is free to preempt a critical section created by the lowest priority handler. +In the example below we have three interrupt handlers with priorities ranging from one to three. The two handlers with the lower priorities contend for the `shared` resource and need to lock the resource for accessing the data. The highest priority handler, which do not access the `shared` resource, is free to preempt the critical section created by the lowest priority handler. ``` rust -{{#include ../../../../examples/lock.rs}} +{{#include ../../../../rtic/examples/lock.rs}} ``` ``` console $ cargo run --target thumbv7m-none-eabi --example lock -{{#include ../../../../ci/expected/lock.run}} +{{#include ../../../../rtic/ci/expected/lock.run}} ``` Types of `#[shared]` resources have to be [`Send`]. ## Multi-lock -As an extension to `lock`, and to reduce rightward drift, locks can be taken as tuples. The -following examples show this in use: +As an extension to `lock`, and to reduce rightward drift, locks can be taken as tuples. The following examples show this in use: ``` rust -{{#include ../../../../examples/multilock.rs}} +{{#include ../../../../rtic/examples/multilock.rs}} ``` ``` console $ cargo run --target thumbv7m-none-eabi --example multilock -{{#include ../../../../ci/expected/multilock.run}} +{{#include ../../../../rtic/ci/expected/multilock.run}} ``` ## Only shared (`&-`) access -By default, the framework assumes that all tasks require exclusive access (`&mut-`) to resources, -but it is possible to specify that a task only requires shared access (`&-`) to a resource using the -`&resource_name` syntax in the `shared` list. +By default, the framework assumes that all tasks require exclusive mutable access (`&mut-`) to resources, but it is possible to specify that a task only requires shared access (`&-`) to a resource using the `&resource_name` syntax in the `shared` list. -The advantage of specifying shared access (`&-`) to a resource is that no locks are required to -access the resource even if the resource is contended by more than one task running at different -priorities. The downside is that the task only gets a shared reference (`&-`) to the resource, -limiting the operations it can perform on it, but where a shared reference is enough this approach -reduces the number of required locks. In addition to simple immutable data, this shared access can -be useful where the resource type safely implements interior mutability, with appropriate locking -or atomic operations of its own. +The advantage of specifying shared access (`&-`) to a resource is that no locks are required to access the resource even if the resource is contended by more than one task running at different priorities. The downside is that the task only gets a shared reference (`&-`) to the resource, limiting the operations it can perform on it, but where a shared reference is enough this approach reduces the number of required locks. In addition to simple immutable data, this shared access can be useful where the resource type safely implements interior mutability, with appropriate locking or atomic operations of its own. -Note that in this release of RTIC it is not possible to request both exclusive access (`&mut-`) -and shared access (`&-`) to the *same* resource from different tasks. Attempting to do so will -result in a compile error. +Note that in this release of RTIC it is not possible to request both exclusive access (`&mut-`) and shared access (`&-`) to the *same* resource from different tasks. Attempting to do so will result in a compile error. -In the example below a key (e.g. a cryptographic key) is loaded (or created) at runtime and then -used from two tasks that run at different priorities without any kind of lock. +In the example below a key (e.g. a cryptographic key) is loaded (or created) at runtime (returned by `init`) and then used from two tasks that run at different priorities without any kind of lock. ``` rust -{{#include ../../../../examples/only-shared-access.rs}} +{{#include ../../../../rtic/examples/only-shared-access.rs}} ``` ``` console $ cargo run --target thumbv7m-none-eabi --example only-shared-access -{{#include ../../../../ci/expected/only-shared-access.run}} +{{#include ../../../../rtic/ci/expected/only-shared-access.run}} ``` -## Lock-free resource access of shared resources +## Lock-free access of shared resources -A critical section is *not* required to access a `#[shared]` resource that's only accessed by tasks -running at the *same* priority. In this case, you can opt out of the `lock` API by adding the -`#[lock_free]` field-level attribute to the resource declaration (see example below). Note that -this is merely a convenience to reduce needless resource locking code, because even if the +A critical section is *not* required to access a `#[shared]` resource that's only accessed by tasks running at the *same* priority. In this case, you can opt out of the `lock` API by adding the `#[lock_free]` field-level attribute to the resource declaration (see example below). + + + +To adhere to the Rust [aliasing] rule, a resource may be either accessed through multiple immutable references or a singe mutable reference (but not both at the same time). + +[aliasing]: https://doc.rust-lang.org/nomicon/aliasing.html -Also worth noting: using `#[lock_free]` on resources shared by -tasks running at different priorities will result in a *compile-time* error -- not using the `lock` -API would be a data race in that case. +Using `#[lock_free]` on resources shared by tasks running at different priorities will result in a *compile-time* error -- not using the `lock` API would violate the aforementioned alias rule. Similarly, for each priority there can be only a single *software* task accessing a shared resource (as an `async` task may yield execution to other *software* or *hardware* tasks running at the same priority). However, under this single-task restriction, we make the observation that the resource is in effect no longer `shared` but rather `local`. Thus, using a `#[lock_free]` shared resource will result in a *compile-time* error -- where applicable, use a `#[local]` resource instead. ``` rust -{{#include ../../../../examples/lock-free.rs}} +{{#include ../../../../rtic/examples/lock-free.rs}} ``` ``` console $ cargo run --target thumbv7m-none-eabi --example lock-free -{{#include ../../../../ci/expected/lock-free.run}} +{{#include ../../../../rtic/ci/expected/lock-free.run}} ``` diff --git a/book/en/src/by-example/software_tasks.md b/book/en/src/by-example/software_tasks.md index 8ee185b..2752707 100644 --- a/book/en/src/by-example/software_tasks.md +++ b/book/en/src/by-example/software_tasks.md @@ -1,47 +1,99 @@ # Software tasks & spawn -The RTIC concept of a software task shares a lot with that of [hardware tasks](./hardware_tasks.md) -with the core difference that a software task is not explicitly bound to a specific -interrupt vector, but rather bound to a “dispatcher” interrupt vector running -at the intended priority of the software task (see below). +The RTIC concept of a *software* task shares a lot with that of [hardware tasks](./hardware_tasks.md) with the core difference that a software task is not explicitly bound to a specific +interrupt vector, but rather to a “dispatcher” interrupt vector running at the same priority as the software task. -Thus, software tasks are tasks which are not *directly* bound to an interrupt vector. +Similarly to *hardware* tasks, the `#[task]` attribute used on a function declare it as a task. The absence of a `binds = InterruptName` argument to the attribute declares the function as a *software task*. -The `#[task]` attributes used on a function determine if it is -software tasks, specifically the absence of a `binds = InterruptName` -argument to the attribute definition. +The static method `task_name::spawn()` spawns (starts) a software task and given that there are no higher priority tasks running the task will start executing directly. -The static method `task_name::spawn()` spawns (schedules) a software -task by registering it with a specific dispatcher. If there are no -higher priority tasks available to the scheduler (which serves a set -of dispatchers), the task will start executing directly. +The *software* task itself is given as an `async` Rust function, which allows the user to optionally `await` future events. This allows to blend reactive programming (by means of *hardware* tasks) with sequential programming (by means of *software* tasks). -All software tasks at the same priority level share an interrupt handler bound to their dispatcher. -What differentiates software and hardware tasks is the usage of either a dispatcher or a bound interrupt vector. +Whereas, *hardware* tasks are assumed to run-to-completion (and return), *software* tasks may be started (`spawned`) once and run forever, with the side condition that any loop (execution path) is broken by at least one `await` (yielding operation). -The interrupt vectors used as dispatchers cannot be used by hardware tasks. +All *software* tasks at the same priority level shares an interrupt handler acting as an async executor dispatching the software tasks. -Availability of a set of “free” (not in use by hardware tasks) and usable interrupt vectors allows the framework -to dispatch software tasks via dedicated interrupt handlers. +This list of dispatchers, `dispatchers = [FreeInterrupt1, FreeInterrupt2, ...]` is an argument to the `#[app]` attribute, where you define the set of free and usable interrupts. -This set of dispatchers, `dispatchers = [FreeInterrupt1, FreeInterrupt2, ...]` is an -argument to the `#[app]` attribute. +Each interrupt vector acting as dispatcher gets assigned to one priority level meaning that the list of dispatchers need to cover all priority levels used by software tasks. -Each interrupt vector acting as dispatcher gets assigned to a unique priority level meaning that -the list of dispatchers needs to cover all priority levels used by software tasks. +Example: The `dispatchers =` argument needs to have at least 3 entries for an application using three different priorities for software tasks. -Example: The `dispatchers =` argument needs to have at least 3 entries for an application using -three different priorities for software tasks. - -The framework will give a compilation error if there are not enough dispatchers provided. +The framework will give a compilation error if there are not enough dispatchers provided, or if a clash occurs between the list of dispatchers and interrupts bound to *hardware* tasks. See the following example: ``` rust -{{#include ../../../../examples/spawn.rs}} +{{#include ../../../../rtic/examples/spawn.rs}} ``` ``` console $ cargo run --target thumbv7m-none-eabi --example spawn -{{#include ../../../../ci/expected/spawn.run}} +{{#include ../../../../rtic/ci/expected/spawn.run}} +``` +You may `spawn` a *software* task again, given that it has run-to-completion (returned). + +In the below example, we `spawn` the *software* task `foo` from the `idle` task. Since the default priority of the *software* task is 1 (higher than `idle`), the dispatcher will execute `foo` (preempting `idle`). Since `foo` runs-to-completion. It is ok to `spawn` the `foo` task again. + +Technically the async executor will `poll` the `foo` *future* which in this case leaves the *future* in a *completed* state. + +``` rust +{{#include ../../../../rtic/examples/spawn_loop.rs}} +``` + +``` console +$ cargo run --target thumbv7m-none-eabi --example spawn_loop +{{#include ../../../../rtic/ci/expected/spawn_loop.run}} +``` + +An attempt to `spawn` an already spawned task (running) task will result in an error. Notice, the that the error is reported before the `foo` task is actually run. This is since, the actual execution of the *software* task is handled by the dispatcher interrupt (`SSIO`), which is not enabled until we exit the `init` task. (Remember, `init` runs in a critical section, i.e. all interrupts being disabled.) + +Technically, a `spawn` to a *future* that is not in *completed* state is considered an error. + +``` rust +{{#include ../../../../rtic/examples/spawn_err.rs}} +``` + +``` console +$ cargo run --target thumbv7m-none-eabi --example spawn_err +{{#include ../../../../rtic/ci/expected/spawn_err.run}} +``` + +## Passing arguments +You can also pass arguments at spawn as follows. + +``` rust +{{#include ../../../../rtic/examples/spawn_arguments.rs}} ``` + +``` console +$ cargo run --target thumbv7m-none-eabi --example spawn_arguments +{{#include ../../../../rtic/ci/expected/spawn_arguments.run}} +``` + +## Priority zero tasks + +In RTIC tasks run preemptively to each other, with priority zero (0) the lowest priority. You can use priority zero tasks for background work, without any strict real-time requirements. + +Conceptually, one can see such tasks as running in the `main` thread of the application, thus the resources associated are not required the [Send] bound. + +[Send]: https://doc.rust-lang.org/nomicon/send-and-sync.html + + +``` rust +{{#include ../../../../rtic/examples/zero-prio-task.rs}} +``` + +``` console +$ cargo run --target thumbv7m-none-eabi --example zero-prio-task +{{#include ../../../../rtic/ci/expected/zero-prio-task.run}} +``` + +> **Notice**: *software* task at zero priority cannot co-exist with the [idle] task. The reason is that `idle` is running as a non-returning Rust function at priority zero. Thus there would be no way for an executor at priority zero to give control to *software* tasks at the same priority. + +--- + +Application side safety: Technically, the RTIC framework ensures that `poll` is never executed on any *software* task with *completed* future, thus adhering to the soundness rules of async Rust. + + + diff --git a/book/en/src/by-example/starting_a_project.md b/book/en/src/by-example/starting_a_project.md index fe7be57..8638f90 100644 --- a/book/en/src/by-example/starting_a_project.md +++ b/book/en/src/by-example/starting_a_project.md @@ -10,6 +10,8 @@ If you are targeting ARMv6-M or ARMv8-M-base architecture, check out the section This will give you an RTIC application with support for RTT logging with [`defmt`] and stack overflow protection using [`flip-link`]. There is also a multitude of examples provided by the community: +For inspiration you may look at the below resources. For now they cover RTIC 1.0.x, but will be updated with RTIC 2.0.x examples over time. + - [`rtic-examples`] - Multiple projects - [https://github.com/kalkyl/f411-rtic](https://github.com/kalkyl/f411-rtic) - ... More to come diff --git a/book/en/src/preface.md b/book/en/src/preface.md index 6041dfe..3f47cb3 100644 --- a/book/en/src/preface.md +++ b/book/en/src/preface.md @@ -1,7 +1,7 @@

RTIC
-

Real-Time Interrupt-driven Concurrency

+

The Embedded Rust RTOS

A concurrency framework for building real-time systems

@@ -10,29 +10,160 @@ This book contains user level documentation for the Real-Time Interrupt-driven Concurrency (RTIC) framework. The API reference is available [here](../../api/). -Formerly known as Real-Time For the Masses. + -This is the documentation of v1.0.x of RTIC; for the documentation of version +This is the documentation of v2.0.x (pre-release) of RTIC 2. -* v0.5.x go [here](/0.5). -* v0.4.x go [here](/0.4). +## RTIC - The Past, current and Future + +This section gives a background to the RTIC model. Feel free to skip to section [RTIC the model](preface.md#rtic-the-model) for a TL;DR. + +The RTIC framework takes the outset from real-time systems research at Luleå University of Technology (LTU) Sweden. RTIC is inspired by the concurrency model of the [Timber] language, the [RTFM-SRP] based scheduler, the [RTFM-core] language and [Abstract Timer] implementation. For a full list of related research see [TODO]. + +[Timber]: https://timber-lang.org/ +[RTFM-SRP]: https://www.diva-portal.org/smash/get/diva2:1005680/FULLTEXT01.pdf +[RTFM-core]: https://ltu.diva-portal.org/smash/get/diva2:1013248/FULLTEXT01.pdf +[AbstractTimer]: https://ltu.diva-portal.org/smash/get/diva2:1013030/FULLTEXT01.pdf + +## Stack Resource Policy based Scheduling + +Stack Resource Policy (SRP) based concurrency and resource management is at heart of the RTIC framework. The [SRP] model itself extends on [Priority Inheritance Protocols], and provides a set of outstanding properties for single core scheduling. To name a few: + +- preemptive deadlock and race-free scheduling +- resource efficiency + - tasks execute on a single shared stack + - tasks run-to-completion with wait free access to shared resources +- predictable scheduling, with bounded priority inversion by a single (named) critical section +- theoretical underpinning amenable to static analysis (e.g., for task response times and overall schedulability) + +SRP comes with a set of system wide requirements: +- each task is associated a static priority, +- tasks execute on a single-core, +- tasks must be run-to-completion, and +- resources must be claimed/locked in LIFO order. + +[SRP]: https://link.springer.com/article/10.1007/BF00365393 +[Priority Inheritance Protocols]: https://ieeexplore.ieee.org/document/57058 + +## SRP analysis + +SRP based scheduling requires the set of static priority tasks and their access to shared resources to be known in order to compute a static *ceiling* (𝝅) for each resource. The static resource *ceiling* 𝝅(r) reflects the maximum static priority of any task that accesses the resource `r`. + +### Example + +Assume two tasks `A` (with priority `p(A) = 2`) and `B` (with priority `p(B) = 4`) both accessing the shared resource `R`. The static ceiling of `R` is 4 (computed from `𝝅(R) = max(p(A) = 2, p(B) = 4) = 4`). + +A graph representation of the example: + +```mermaid +graph LR + A["p(A) = 2"] --> R + B["p(A) = 4"] --> R + R["𝝅(R) = 4"] +``` + +## RTIC the hardware accelerated real-time scheduler + +SRP itself is compatible both to dynamic and static priority scheduling. For the implementation of RTIC we leverage on the underlying hardware for accelerated static priority scheduling. + +In the case of the `ARM Cortex-M` architecture, each interrupt vector entry `v[i]` is associated a function pointer (`v[i].fn`), and a static priority (`v[i].priority`), an enabled- (`v[i].enabled`) and a pending-bit (`v[i].pending`). + +An interrupt `i` is scheduled (run) by the hardware under the conditions: +1. is `pended` and `enabled` and has a priority higher than the (optional `BASEPRI`) register, and +1. has the highest priority among interrupts meeting 1. + +The first condition (1) can be seen a filter allowing RTIC to take control over which tasks should be allowed to start (and which should be prevented from starting). + +The SPR model for single-core static scheduling on the other hand states that a task should be scheduled (run) under the conditions: +1. it is `requested` to run and has a static priority higher than the current system ceiling (𝜫) +1. it has the highest static priority among tasks meeting 1. + +The similarities are striking and it is not by chance/luck/coincidence. The hardware was cleverly designed with real-time scheduling in mind. + +In order to map the SRP scheduling onto the hardware we need to have a closer look on the system ceiling (𝜫). Under SRP 𝜫 is computed as the maximum priority ceiling of the currently held resources, and will thus change dynamically during the system operation. + +## Example + +Assume the task model above. Starting from an idle system, 𝜫 is 0, (no task is holding any resource). Assume that `A` is requested for execution, it will immediately be scheduled. Assume that `A` claims (locks) the resource `R`. During the claim (lock of `R`) any request `B` will be blocked from starting (by 𝜫 = `max(𝝅(R) = 4) = 4`, `p(B) = 4`, thus SRP scheduling condition 1 is not met). + +## Mapping + +The mapping of static priority SRP based scheduling to the Cortex M hardware is straightforward: -## Is RTIC an RTOS? +- each task `t` are mapped to an interrupt vector index `i` with a corresponding function `v[i].fn = t` and given the static priority `v[i].priority = p(t)`. +- the current system ceiling is mapped to the `BASEPRI` register or implemented through masking the interrupt enable bits accordingly. -A common question is whether RTIC is an RTOS or not, and depending on your background the -answer may vary. From RTIC's developers point of view; RTIC is a hardware accelerated -RTOS that utilizes the NVIC in Cortex-M MCUs to perform scheduling, rather than the more -classical software kernel. +## Example -Another common view from the community is that RTIC is a concurrency framework as there -is no software kernel and that it relies on external HALs. +For the running example, a snapshot of the ARM Cortex M [NVIC] may have the following configuration (after task `A` has been pended for execution.) ---- +| Index | Fn | Priority | Enabled | Pended | +| ----- | --- | -------- | ------- | ------ | +| 0 | A | 2 | true | true | +| 1 | B | 4 | true | false | + +[NVIC]: https://developer.arm.com/documentation/ddi0337/h/nested-vectored-interrupt-controller/about-the-nvic + +(As discussed later, the assignment of interrupt and exception vectors is up to the user.) + + +A claim (lock(r)) will change the current system ceiling (𝜫) and can be implemented as a *named* critical section: + - old_ceiling = 𝜫, 𝜫 = 𝝅(r) + - execute code within critical section + - old_ceiling = 𝜫 + +This amounts to a resource protection mechanism requiring only two machine instructions on enter and one on exit the critical section for managing the `BASEPRI` register. For architectures lacking `BASEPRI`, we can implement the system ceiling through a set of machine instructions for disabling/enabling interrupts on entry/exit for the named critical section. The number of machine instructions vary depending on the number of mask registers that needs to be updated (a single machine operation can operate on up to 32 interrupts, so for the M0/M0+ architecture a single instruction suffice). RTIC will determine the ceiling values and masking constants at compile time, thus all operations is in Rust terms zero-cost. + +In this way RTIC fuses SRP based preemptive scheduling with a zero-cost hardware accelerated implementation, resulting in "best in class" guarantees and performance. + +Given that the approach is dead simple, how come SRP and hardware accelerated scheduling is not adopted by any other mainstream RTOS? + +The answer is simple, the commonly adopted threading model does not lend itself well to static analysis - there is no known way to extract the task/resource dependencies from the source code at compile time (thus ceilings cannot be efficiently computed and the LIFO resource locking requirement cannot be ensured). Thus SRP based scheduling is in the general case out of reach for any thread based RTOS. + +## RTIC into the Future + +Asynchronous programming in various forms are getting increased popularity and language support. Rust natively provides an `async`/`await` API for cooperative multitasking and the compiler generates the necessary boilerplate for storing and retrieving execution contexts (i.e., managing the set of local variables that spans each `await`). + +The Rust standard library provides collections for dynamically allocated data-structures (useful to manage execution contexts at run-time. However, in the setting of resource constrained real-time systems, dynamic allocations are problematic (both regarding performance and reliability - Rust runs into a *panic* on an out-of-memory condition). Thus, static allocation is king! + +RTIC provides a mechanism for `async`/`await` that relies solely on static allocations. However, the implementation relies on the `#![feature(type_alias_impl_trait)]` (TAIT) which is undergoing stabilization (thus RTIC 2.0.x currently requires a *nightly* toolchain). Technically, using TAIT, the compiler determines the size of each execution context allowing static allocation. + +From a modelling perspective `async/await` lifts the run-to-completion requirement of SRP, and each section of code between two yield points (`await`s) can be seen as an individual task. The compiler will reject any attempt to `await` while holding a resource (not doing so would break the strict LIFO requirement on resource usage under SRP). + +So with the technical stuff out of the way, what does `async/await` bring to the RTIC table? + +The answer is - improved ergonomics! In cases you want a task to perform a sequence of requests (and await their results in order to progress). Without `async`/`await` the programmer would be forced to split the task into individual sub-tasks and maintain some sort of state encoding (and manually progress by selecting sub-task). Using `async/await` each yield point (`await`) essentially represents a state, and the progression mechanism is built automatically for you at compile time by means of `Futures`. + +Rust `async`/`await` support is still incomplete and/or under development (e.g., there are no stable way to express `async` closures, precluding use in iterator patterns). Nevertheless, Rust `async`/`await` is production ready and covers most common use cases. + +An important property is that futures are composable, thus you can await either, all, or any combination of possible futures (allowing e.g., timeouts and/or asynchronous errors to be promptly handled). For more details and examples see Section [todo]. + +## RTIC the model + +An RTIC `app` is a declarative and executable system model for single-core applications, defining a set of (`local` and `shared`) resources operated on by a set of (`init`, `idle`, *hardware* and *software*) tasks. In short the `init` task runs before any other task returning a set of resources (`local` and `shared`). Tasks run preemptively based on their associated static priority, `idle` has the lowest priority (and can be used for background work, and/or to put the system to sleep until woken by some event). Hardware tasks are bound to underlying hardware interrupts, while software tasks are scheduled by asynchronous executors (one for each software task priority). + +At compile time the task/resource model is analyzed under SRP and executable code generated with the following outstanding properties: + +- guaranteed race-free resource access and deadlock-free execution on a single-shared stack (thanks to SRP) + - hardware task scheduling is performed directly by the hardware, and + - software task scheduling is performed by auto generated async executors tailored to the application. + +The RTIC API design ensures that both SRP requirements and Rust soundness rules are upheld at all times, thus the executable model is correct by construction. Overall, the generated code infers no additional overhead in comparison to a hand-written implementation, thus in Rust terms RTIC offers a zero-cost abstraction to concurrency. + + + diff --git a/book/en/src/rtic_vs.md b/book/en/src/rtic_vs.md new file mode 100644 index 0000000..2f8c8d5 --- /dev/null +++ b/book/en/src/rtic_vs.md @@ -0,0 +1,31 @@ +# RTIC vs. the world + +RTIC aims to provide the lowest level of abstraction needed for developing robust and reliable embedded software. + +It provides a minimal set of required mechanisms for safe sharing of mutable resources among interrupts and asynchronously executing tasks. The scheduling primitives leverages on the underlying hardware for unparalleled performance and predictability, in effect RTIC provides in Rust terms a zero-cost abstraction to concurrent real-time programming. + + + +## Comparison regarding safety and security + +Comparing RTIC to traditional a Real-Time Operating System (RTOS) is hard. Firstly, a traditional RTOS typically comes with no guarantees regarding system safety, even the most hardened kernels like the formally verified [seL4] kernel. Their claims to integrity, confidentiality, and availability regards only the kernel itself (under additional assumptions its configuration and environment). They even state: + +"An OS kernel, verified or not, does not automatically make a system secure. In fact, any system, no matter how secure, can be used in insecure ways." + +[seL4]: https://sel4.systems/ + +### Security by design + +In the world of information security we commonly find: + +- confidentiality, protecting the information from being exposed to an unauthorized party, +- integrity, referring to accuracy and completeness of data, and +- availability, referring to data being accessible to authorized users. + +Obviously, a traditional OS can guarantee neither confidentiality nor integrity, as both requires the security critical code to be trusted. Regarding availability, this typically boils down to the usage of system resources. Any OS that allows for dynamic allocation of resources, relies on that the application correctly handles allocations/de-allocations, and cases of allocation failures. + +Thus their claim is correct, security is completely out of hands for the OS, the best we can hope for is that it does not add further vulnerabilities. + +RTIC on the other hand holds your back. The declarative system wide model gives you a static set of tasks and resources, with precise control over what data is shared and between which parties. Moreover, Rust as a programming language comes with strong properties regarding integrity (compile time aliasing, mutability and lifetime guarantees, together with ensured data validity). + +Using RTIC these properties propagate to the system wide model, without interference of other applications running. The RTIC kernel is internally infallible without any need of dynamically allocated data. \ No newline at end of file diff --git a/rtic/Cargo.toml b/rtic/Cargo.toml index 12a34da..9b8a291 100644 --- a/rtic/Cargo.toml +++ b/rtic/Cargo.toml @@ -10,7 +10,19 @@ categories = ["concurrency", "embedded", "no-std", "asynchronous"] description = "Real-Time Interrupt-driven Concurrency (RTIC): a concurrency framework for building real-time systems" documentation = "https://rtic.rs/" edition = "2021" -keywords = ["arm", "cortex-m", "risc-v", "embedded", "async", "runtime", "futures", "await", "no-std", "rtos", "bare-metal"] +keywords = [ + "arm", + "cortex-m", + "risc-v", + "embedded", + "async", + "runtime", + "futures", + "await", + "no-std", + "rtos", + "bare-metal", +] license = "MIT OR Apache-2.0" name = "rtic" readme = "README.md" @@ -31,6 +43,7 @@ bare-metal = "1.0.0" #portable-atomic = { version = "0.3.19" } atomic-polyfill = "1" + [build-dependencies] version_check = "0.9" @@ -42,6 +55,11 @@ rtic-time = { path = "../rtic-time" } rtic-channel = { path = "../rtic-channel" } rtic-monotonics = { path = "../rtic-monotonics" } +[dev-dependencies.futures] +version = "0.3.26" +default-features = false +features = ["async-await"] + [dev-dependencies.panic-semihosting] features = ["exit"] version = "0.6.0" diff --git a/rtic/ci/expected/async-channel-done.run b/rtic/ci/expected/async-channel-done.run new file mode 100644 index 0000000..525962a --- /dev/null +++ b/rtic/ci/expected/async-channel-done.run @@ -0,0 +1,9 @@ +Sender 1 sending: 1 +Sender 1 done +Sender 2 sending: 2 +Sender 3 sending: 3 +Receiver got: 1 +Sender 2 done +Receiver got: 2 +Sender 3 done +Receiver got: 3 diff --git a/rtic/ci/expected/async-channel-no-receiver.run b/rtic/ci/expected/async-channel-no-receiver.run new file mode 100644 index 0000000..34624e1 --- /dev/null +++ b/rtic/ci/expected/async-channel-no-receiver.run @@ -0,0 +1 @@ +Sender 1 sending: 1 Err(NoReceiver(1)) diff --git a/rtic/ci/expected/async-channel-no-sender.run b/rtic/ci/expected/async-channel-no-sender.run new file mode 100644 index 0000000..237f2f1 --- /dev/null +++ b/rtic/ci/expected/async-channel-no-sender.run @@ -0,0 +1 @@ +Receiver got: Err(NoSender) diff --git a/rtic/ci/expected/async-channel-try.run b/rtic/ci/expected/async-channel-try.run new file mode 100644 index 0000000..c3a4092 --- /dev/null +++ b/rtic/ci/expected/async-channel-try.run @@ -0,0 +1,2 @@ +Sender 1 sending: 1 +Sender 1 try sending: 2 Err(Full(2)) diff --git a/rtic/ci/expected/async-channel.run b/rtic/ci/expected/async-channel.run index 3e3c232..4e313a1 100644 --- a/rtic/ci/expected/async-channel.run +++ b/rtic/ci/expected/async-channel.run @@ -3,4 +3,4 @@ Sender 2 sending: 2 Sender 3 sending: 3 Receiver got: 1 Receiver got: 2 -Receiver got: 5 +Receiver got: 3 diff --git a/rtic/ci/expected/async-timeout.run b/rtic/ci/expected/async-timeout.run index a807423..ca929c7 100644 --- a/rtic/ci/expected/async-timeout.run +++ b/rtic/ci/expected/async-timeout.run @@ -1,5 +1,16 @@ init -hello from bar -hello from foo -foo no timeout -bar timeout +the hal takes a duration of Duration { ticks: 450 } +timeout +the hal takes a duration of Duration { ticks: 450 } +hal returned 5 +the hal takes a duration of Duration { ticks: 450 } +hal returned 5 +now is Instant { ticks: 2102 }, timeout at Instant { ticks: 2602 } +the hal takes a duration of Duration { ticks: 350 } +hal returned 5 at time Instant { ticks: 2452 } +now is Instant { ticks: 3102 }, timeout at Instant { ticks: 3602 } +the hal takes a duration of Duration { ticks: 450 } +hal returned 5 at time Instant { ticks: 3552 } +now is Instant { ticks: 4102 }, timeout at Instant { ticks: 4602 } +the hal takes a duration of Duration { ticks: 550 } +timeout diff --git a/rtic/ci/expected/common.run b/rtic/ci/expected/common.run index e69de29..4f1d350 100644 --- a/rtic/ci/expected/common.run +++ b/rtic/ci/expected/common.run @@ -0,0 +1,3 @@ +bar: local_to_bar = 1 +foo: local_to_foo = 1 +idle: local_to_idle = 1 diff --git a/rtic/ci/expected/message_passing.run b/rtic/ci/expected/message_passing.run deleted file mode 100644 index 9085e13..0000000 --- a/rtic/ci/expected/message_passing.run +++ /dev/null @@ -1 +0,0 @@ -foo 1, 1 diff --git a/rtic/ci/expected/spawn_arguments.run b/rtic/ci/expected/spawn_arguments.run new file mode 100644 index 0000000..9085e13 --- /dev/null +++ b/rtic/ci/expected/spawn_arguments.run @@ -0,0 +1 @@ +foo 1, 1 diff --git a/rtic/ci/expected/spawn_err.run b/rtic/ci/expected/spawn_err.run new file mode 100644 index 0000000..97c4112 --- /dev/null +++ b/rtic/ci/expected/spawn_err.run @@ -0,0 +1,3 @@ +init +Cannot spawn a spawned (running) task! +foo diff --git a/rtic/ci/expected/spawn_loop.run b/rtic/ci/expected/spawn_loop.run new file mode 100644 index 0000000..ee6e1e3 --- /dev/null +++ b/rtic/ci/expected/spawn_loop.run @@ -0,0 +1,7 @@ +init +foo +idle +foo +idle +foo +idle diff --git a/rtic/examples/async-channel-done.rs b/rtic/examples/async-channel-done.rs new file mode 100644 index 0000000..1c32d32 --- /dev/null +++ b/rtic/examples/async-channel-done.rs @@ -0,0 +1,65 @@ +//! examples/async-channel-done.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use rtic_channel::*; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + const CAPACITY: usize = 1; + #[init] + fn init(_: init::Context) -> (Shared, Local) { + let (s, r) = make_channel!(u32, CAPACITY); + + receiver::spawn(r).unwrap(); + sender1::spawn(s.clone()).unwrap(); + sender2::spawn(s.clone()).unwrap(); + sender3::spawn(s).unwrap(); + + (Shared {}, Local {}) + } + + #[task] + async fn receiver(_c: receiver::Context, mut receiver: Receiver<'static, u32, CAPACITY>) { + while let Ok(val) = receiver.recv().await { + hprintln!("Receiver got: {}", val); + if val == 3 { + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } + } + } + + #[task] + async fn sender1(_c: sender1::Context, mut sender: Sender<'static, u32, CAPACITY>) { + hprintln!("Sender 1 sending: 1"); + sender.send(1).await.unwrap(); + hprintln!("Sender 1 done"); + } + + #[task] + async fn sender2(_c: sender2::Context, mut sender: Sender<'static, u32, CAPACITY>) { + hprintln!("Sender 2 sending: 2"); + sender.send(2).await.unwrap(); + hprintln!("Sender 2 done"); + } + + #[task] + async fn sender3(_c: sender3::Context, mut sender: Sender<'static, u32, CAPACITY>) { + hprintln!("Sender 3 sending: 3"); + sender.send(3).await.unwrap(); + hprintln!("Sender 3 done"); + } +} diff --git a/rtic/examples/async-channel-no-receiver.rs b/rtic/examples/async-channel-no-receiver.rs new file mode 100644 index 0000000..ffb78e4 --- /dev/null +++ b/rtic/examples/async-channel-no-receiver.rs @@ -0,0 +1,40 @@ +//! examples/async-channel-no-receiver.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use rtic_channel::*; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + const CAPACITY: usize = 1; + #[init] + fn init(_: init::Context) -> (Shared, Local) { + let (s, _r) = make_channel!(u32, CAPACITY); + + sender1::spawn(s.clone()).unwrap(); + + (Shared {}, Local {}) + } + + + #[task] + async fn sender1(_c: sender1::Context, mut sender: Sender<'static, u32, CAPACITY>) { + + hprintln!("Sender 1 sending: 1 {:?}", sender.send(1).await); + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } + +} diff --git a/rtic/examples/async-channel-no-sender.rs b/rtic/examples/async-channel-no-sender.rs new file mode 100644 index 0000000..58e010d --- /dev/null +++ b/rtic/examples/async-channel-no-sender.rs @@ -0,0 +1,40 @@ +//! examples/async-channel-no-sender.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use rtic_channel::*; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + const CAPACITY: usize = 1; + #[init] + fn init(_: init::Context) -> (Shared, Local) { + let (_s, r) = make_channel!(u32, CAPACITY); + + receiver::spawn(r).unwrap(); + + (Shared {}, Local {}) + } + + #[task] + async fn receiver(_c: receiver::Context, mut receiver: Receiver<'static, u32, CAPACITY>) { + hprintln!("Receiver got: {:?}", receiver.recv().await); + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } + + +} diff --git a/rtic/examples/async-channel-try.rs b/rtic/examples/async-channel-try.rs new file mode 100644 index 0000000..4a79935 --- /dev/null +++ b/rtic/examples/async-channel-try.rs @@ -0,0 +1,48 @@ +//! examples/async-channel-try.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use rtic_channel::*; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + const CAPACITY: usize = 1; + #[init] + fn init(_: init::Context) -> (Shared, Local) { + let (s, r) = make_channel!(u32, CAPACITY); + + receiver::spawn(r).unwrap(); + sender1::spawn(s.clone()).unwrap(); + + (Shared {}, Local {}) + } + + #[task] + async fn receiver(_c: receiver::Context, mut receiver: Receiver<'static, u32, CAPACITY>) { + while let Ok(val) = receiver.recv().await { + hprintln!("Receiver got: {}", val); + } + } + + #[task] + async fn sender1(_c: sender1::Context, mut sender: Sender<'static, u32, CAPACITY>) { + hprintln!("Sender 1 sending: 1"); + sender.send(1).await.unwrap(); + hprintln!("Sender 1 try sending: 2 {:?}", sender.try_send(2)); + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } + +} diff --git a/rtic/examples/async-channel.rs b/rtic/examples/async-channel.rs index 3a2e491..58eeee8 100644 --- a/rtic/examples/async-channel.rs +++ b/rtic/examples/async-channel.rs @@ -19,9 +19,10 @@ mod app { #[local] struct Local {} + const CAPACITY: usize = 5; #[init] fn init(_: init::Context) -> (Shared, Local) { - let (s, r) = make_channel!(u32, 5); + let (s, r) = make_channel!(u32, CAPACITY); receiver::spawn(r).unwrap(); sender1::spawn(s.clone()).unwrap(); @@ -32,30 +33,30 @@ mod app { } #[task] - async fn receiver(_c: receiver::Context, mut receiver: Receiver<'static, u32, 5>) { + async fn receiver(_c: receiver::Context, mut receiver: Receiver<'static, u32, CAPACITY>) { while let Ok(val) = receiver.recv().await { hprintln!("Receiver got: {}", val); - if val == 5 { + if val == 3 { debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator } } } #[task] - async fn sender1(_c: sender1::Context, mut sender: Sender<'static, u32, 5>) { + async fn sender1(_c: sender1::Context, mut sender: Sender<'static, u32, CAPACITY>) { hprintln!("Sender 1 sending: 1"); sender.send(1).await.unwrap(); } #[task] - async fn sender2(_c: sender2::Context, mut sender: Sender<'static, u32, 5>) { + async fn sender2(_c: sender2::Context, mut sender: Sender<'static, u32, CAPACITY>) { hprintln!("Sender 2 sending: 2"); sender.send(2).await.unwrap(); } #[task] - async fn sender3(_c: sender3::Context, mut sender: Sender<'static, u32, 5>) { + async fn sender3(_c: sender3::Context, mut sender: Sender<'static, u32, CAPACITY>) { hprintln!("Sender 3 sending: 3"); - sender.send(5).await.unwrap(); + sender.send(3).await.unwrap(); } } diff --git a/rtic/examples/async-delay.rs b/rtic/examples/async-delay.rs index 0440f77..6c79cf5 100644 --- a/rtic/examples/async-delay.rs +++ b/rtic/examples/async-delay.rs @@ -1,3 +1,5 @@ +// examples/async-delay.rs +// #![no_main] #![no_std] #![feature(type_alias_impl_trait)] diff --git a/rtic/examples/async-timeout.rs b/rtic/examples/async-timeout.rs new file mode 100644 index 0000000..30fee43 --- /dev/null +++ b/rtic/examples/async-timeout.rs @@ -0,0 +1,87 @@ +// examples/async-timeout.rs +// +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use cortex_m_semihosting::{debug, hprintln}; +use panic_semihosting as _; +use rtic_monotonics::systick_monotonic::*; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0, UART0], peripherals = true)] +mod app { + use super::*; + use futures::{future::FutureExt, select_biased}; + + rtic_monotonics::make_systick_timer_queue!(TIMER); + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(cx: init::Context) -> (Shared, Local) { + hprintln!("init"); + + let systick = Systick::start(cx.core.SYST, 12_000_000); + TIMER.initialize(systick); + + foo::spawn().ok(); + + (Shared {}, Local {}) + } + + #[task] + async fn foo(_cx: foo::Context) { + // Call hal with short relative timeout using `select_biased` + select_biased! { + v = hal_get(&TIMER, 1).fuse() => hprintln!("hal returned {}", v), + _ = TIMER.delay(200.millis()).fuse() => hprintln!("timeout", ), // this will finish first + } + + // Call hal with long relative timeout using `select_biased` + select_biased! { + v = hal_get(&TIMER, 1).fuse() => hprintln!("hal returned {}", v), // hal finish first + _ = TIMER.delay(1000.millis()).fuse() => hprintln!("timeout", ), + } + + // Call hal with long relative timeout using monotonic `timeout_after` + match TIMER.timeout_after(1000.millis(), hal_get(&TIMER, 1)).await { + Ok(v) => hprintln!("hal returned {}", v), + _ => hprintln!("timeout"), + } + + // get the current time instance + let mut instant = TIMER.now(); + + // do this 3 times + for n in 0..3 { + // exact point in time without drift + instant += 1000.millis(); + TIMER.delay_until(instant).await; + + // exact point it time for timeout + let timeout = instant + 500.millis(); + hprintln!("now is {:?}, timeout at {:?}", TIMER.now(), timeout); + + match TIMER.timeout_at(timeout, hal_get(&TIMER, n)).await { + Ok(v) => hprintln!("hal returned {} at time {:?}", v, TIMER.now()), + _ => hprintln!("timeout"), + } + } + + debug::exit(debug::EXIT_SUCCESS); + } +} + +// Emulate some hal +async fn hal_get(timer: &'static SystickTimerQueue, n: u32) -> u32 { + // emulate some delay time dependent on n + let d = 350.millis() + n * 100.millis(); + hprintln!("the hal takes a duration of {:?}", d); + timer.delay(d).await; + // emulate some return value + 5 +} diff --git a/rtic/examples/common.rs b/rtic/examples/common.rs new file mode 100644 index 0000000..f07b69c --- /dev/null +++ b/rtic/examples/common.rs @@ -0,0 +1,86 @@ +//! examples/common.rs +#![feature(type_alias_impl_trait)] +#![deny(unsafe_code)] +#![deny(missing_docs)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [UART0, UART1])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + + #[shared] + struct Shared {} + + #[local] + struct Local { + local_to_foo: i64, + local_to_bar: i64, + local_to_idle: i64, + } + + // `#[init]` cannot access locals from the `#[local]` struct as they are initialized here. + #[init] + fn init(_: init::Context) -> (Shared, Local) { + foo::spawn().unwrap(); + bar::spawn().unwrap(); + + ( + Shared {}, + // initial values for the `#[local]` resources + Local { + local_to_foo: 0, + local_to_bar: 0, + local_to_idle: 0, + }, + ) + } + + // `local_to_idle` can only be accessed from this context + #[idle(local = [local_to_idle])] + fn idle(cx: idle::Context) -> ! { + let local_to_idle = cx.local.local_to_idle; + *local_to_idle += 1; + + hprintln!("idle: local_to_idle = {}", local_to_idle); + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + + // error: no `local_to_foo` field in `idle::LocalResources` + // _cx.local.local_to_foo += 1; + + // error: no `local_to_bar` field in `idle::LocalResources` + // _cx.local.local_to_bar += 1; + + loop { + cortex_m::asm::nop(); + } + } + + // `local_to_foo` can only be accessed from this context + #[task(local = [local_to_foo])] + async fn foo(cx: foo::Context) { + let local_to_foo = cx.local.local_to_foo; + *local_to_foo += 1; + + // error: no `local_to_bar` field in `foo::LocalResources` + // cx.local.local_to_bar += 1; + + hprintln!("foo: local_to_foo = {}", local_to_foo); + } + + // `local_to_bar` can only be accessed from this context + #[task(local = [local_to_bar])] + async fn bar(cx: bar::Context) { + let local_to_bar = cx.local.local_to_bar; + *local_to_bar += 1; + + // error: no `local_to_foo` field in `bar::LocalResources` + // cx.local.local_to_foo += 1; + + hprintln!("bar: local_to_bar = {}", local_to_bar); + } +} diff --git a/rtic/examples/lock-free.rs b/rtic/examples/lock-free.rs new file mode 100644 index 0000000..3e09604 --- /dev/null +++ b/rtic/examples/lock-free.rs @@ -0,0 +1,50 @@ +//! examples/lock-free.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965)] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use lm3s6965::Interrupt; + + #[shared] + struct Shared { + #[lock_free] // <- lock-free shared resource + counter: u64, + } + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + rtic::pend(Interrupt::UART0); + + (Shared { counter: 0 }, Local {}) + } + + #[task(binds = UART0, shared = [counter])] // <- same priority + fn foo(c: foo::Context) { + rtic::pend(Interrupt::UART1); + + *c.shared.counter += 1; // <- no lock API required + let counter = *c.shared.counter; + hprintln!(" foo = {}", counter); + } + + #[task(binds = UART1, shared = [counter])] // <- same priority + fn bar(c: bar::Context) { + rtic::pend(Interrupt::UART0); + *c.shared.counter += 1; // <- no lock API required + let counter = *c.shared.counter; + hprintln!(" bar = {}", counter); + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } +} diff --git a/rtic/examples/message_passing.rs b/rtic/examples/message_passing.rs deleted file mode 100644 index 01f1a65..0000000 --- a/rtic/examples/message_passing.rs +++ /dev/null @@ -1,34 +0,0 @@ -//! examples/message_passing.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] -#![feature(type_alias_impl_trait)] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) { - foo::spawn(1, 1).unwrap(); - assert!(foo::spawn(1, 4).is_err()); // The capacity of `foo` is reached - - (Shared {}, Local {}) - } - - #[task] - async fn foo(_c: foo::Context, x: i32, y: u32) { - hprintln!("foo {}, {}", x, y); - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } -} diff --git a/rtic/examples/spawn_arguments.rs b/rtic/examples/spawn_arguments.rs new file mode 100644 index 0000000..01f1a65 --- /dev/null +++ b/rtic/examples/spawn_arguments.rs @@ -0,0 +1,34 @@ +//! examples/message_passing.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + foo::spawn(1, 1).unwrap(); + assert!(foo::spawn(1, 4).is_err()); // The capacity of `foo` is reached + + (Shared {}, Local {}) + } + + #[task] + async fn foo(_c: foo::Context, x: i32, y: u32) { + hprintln!("foo {}, {}", x, y); + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } +} diff --git a/rtic/examples/spawn_err.rs b/rtic/examples/spawn_err.rs new file mode 100644 index 0000000..ee9904a --- /dev/null +++ b/rtic/examples/spawn_err.rs @@ -0,0 +1,40 @@ +//! examples/spawn.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![deny(missing_docs)] +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + hprintln!("init"); + foo::spawn().unwrap(); + match foo::spawn() { + Ok(_) => {} + Err(()) => hprintln!("Cannot spawn a spawned (running) task!"), + } + + (Shared {}, Local {}) + } + + #[task] + async fn foo(_: foo::Context) { + hprintln!("foo"); + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } +} diff --git a/rtic/examples/spawn_loop.rs b/rtic/examples/spawn_loop.rs new file mode 100644 index 0000000..c1ad04e --- /dev/null +++ b/rtic/examples/spawn_loop.rs @@ -0,0 +1,42 @@ +//! examples/spawn.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![deny(missing_docs)] +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + hprintln!("init"); + + (Shared {}, Local {}) + } + #[idle] + fn idle(_: idle::Context) -> ! { + for _ in 0..3 { + foo::spawn().unwrap(); + hprintln!("idle"); + } + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + loop {} + } + + #[task] + async fn foo(_: foo::Context) { + hprintln!("foo"); + } +} diff --git a/rtic/examples/zero-prio-task_err.no_rs b/rtic/examples/zero-prio-task_err.no_rs new file mode 100644 index 0000000..ab67d82 --- /dev/null +++ b/rtic/examples/zero-prio-task_err.no_rs @@ -0,0 +1,66 @@ +//! examples/zero-prio-task.rs + +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] +#![deny(missing_docs)] + +use core::marker::PhantomData; +use panic_semihosting as _; + +/// Does not impl send +pub struct NotSend { + _0: PhantomData<*const ()>, +} + +#[rtic::app(device = lm3s6965, peripherals = true)] +mod app { + use super::NotSend; + use core::marker::PhantomData; + use cortex_m_semihosting::{debug, hprintln}; + + #[shared] + struct Shared { + x: NotSend, + } + + #[local] + struct Local { + y: NotSend, + } + + #[init] + fn init(_cx: init::Context) -> (Shared, Local) { + hprintln!("init"); + + async_task::spawn().unwrap(); + async_task2::spawn().unwrap(); + + ( + Shared { + x: NotSend { _0: PhantomData }, + }, + Local { + y: NotSend { _0: PhantomData }, + }, + ) + } + + #[task(priority = 0, shared = [x], local = [y])] + async fn async_task(_: async_task::Context) { + hprintln!("hello from async"); + } + + #[task(priority = 0, shared = [x])] + async fn async_task2(_: async_task2::Context) { + hprintln!("hello from async2"); + } + + #[idle(shared = [x])] + fn idle(_: idle::Context) -> ! { + hprintln!("hello from idle"); + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + loop {} + } +} -- cgit v1.2.3 From 274de31a78a47b4391987e9951e6d7cef56fb0d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Tj=C3=A4der?= Date: Mon, 30 Jan 2023 22:15:43 +0100 Subject: CI: Add mdbook-mermaid --- .github/workflows/build.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3ef7d52..7df817c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -684,6 +684,9 @@ jobs: - name: Install dependencies run: pip install git+https://github.com/linkchecker/linkchecker.git + - name: Install mdbook-mermaid + run: cargo install mdbook-mermaid + - name: mdBook Action uses: peaceiris/actions-mdbook@v1 with: @@ -774,6 +777,9 @@ jobs: # - name: Display Python version # run: python -c "import sys; print(sys.version)" # +# - name: Install mdbook-mermaid +# run: cargo install mdbook-mermaid +# # - name: mdBook Action # uses: peaceiris/actions-mdbook@v1 # with: -- cgit v1.2.3 From d3cadac90b109510ced86c585b57c35493d3fa2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Tj=C3=A4der?= Date: Mon, 30 Jan 2023 22:18:36 +0100 Subject: Book: Enable mermaid for mdbook --- book/en/book.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/book/en/book.toml b/book/en/book.toml index 98c5bf3..e134c29 100644 --- a/book/en/book.toml +++ b/book/en/book.toml @@ -4,6 +4,10 @@ multilingual = false src = "src" title = "Real-Time Interrupt-driven Concurrency" +[preprocessor.mermaid] +command = "mdbook-mermaid" + [output.html] git-repository-url = "https://github.com/rtic-rs/cortex-m-rtic" git-repository-icon = "fa-github" +additional-js = ["mermaid.min.js", "mermaid-init.js"] -- cgit v1.2.3 From 8d46fb9cf9f2b9cdd14844672ad840d777739cb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Tj=C3=A4der?= Date: Mon, 30 Jan 2023 22:19:53 +0100 Subject: Book: Add mermaid files --- book/en/mermaid-init.js | 1 + book/en/mermaid.min.js | 1282 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1283 insertions(+) create mode 100644 book/en/mermaid-init.js create mode 100644 book/en/mermaid.min.js diff --git a/book/en/mermaid-init.js b/book/en/mermaid-init.js new file mode 100644 index 0000000..313a6e8 --- /dev/null +++ b/book/en/mermaid-init.js @@ -0,0 +1 @@ +mermaid.initialize({startOnLoad:true}); diff --git a/book/en/mermaid.min.js b/book/en/mermaid.min.js new file mode 100644 index 0000000..8c7ea4e --- /dev/null +++ b/book/en/mermaid.min.js @@ -0,0 +1,1282 @@ +/* MIT Licensed. Copyright (c) 2014 - 2022 Knut Sveidqvist */ +/* For license information please see https://github.com/mermaid-js/mermaid/blob/v9.2.2/LICENSE */ +(function(jr,wn){typeof exports=="object"&&typeof module<"u"?module.exports=wn():typeof define=="function"&&define.amd?define(wn):(jr=typeof globalThis<"u"?globalThis:jr||self,jr.mermaid=wn())})(this,function(){"use strict";var Ost=Object.defineProperty;var Fst=(jr,wn,fn)=>wn in jr?Ost(jr,wn,{enumerable:!0,configurable:!0,writable:!0,value:fn}):jr[wn]=fn;var vl=(jr,wn,fn)=>(Fst(jr,typeof wn!="symbol"?wn+"":wn,fn),fn);var jr=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{};function wn(t){var e=t.default;if(typeof e=="function"){var r=function(){return e.apply(this,arguments)};r.prototype=e.prototype}else r={};return Object.defineProperty(r,"__esModule",{value:!0}),Object.keys(t).forEach(function(n){var i=Object.getOwnPropertyDescriptor(t,n);Object.defineProperty(r,n,i.get?i:{enumerable:!0,get:function(){return t[n]}})}),r}function fn(t){throw new Error('Could not dynamically require "'+t+'". Please configure the dynamicRequireTargets or/and ignoreDynamicRequires option of @rollup/plugin-commonjs appropriately for this require call to work.')}var y_={exports:{}};(function(t,e){(function(r,n){t.exports=n()})(jr,function(){var r;function n(){return r.apply(null,arguments)}function i(g){return g instanceof Array||Object.prototype.toString.call(g)==="[object Array]"}function a(g){return g!=null&&Object.prototype.toString.call(g)==="[object Object]"}function s(g,E){return Object.prototype.hasOwnProperty.call(g,E)}function o(g){if(Object.getOwnPropertyNames)return Object.getOwnPropertyNames(g).length===0;for(var E in g)if(s(g,E))return;return 1}function l(g){return g===void 0}function u(g){return typeof g=="number"||Object.prototype.toString.call(g)==="[object Number]"}function h(g){return g instanceof Date||Object.prototype.toString.call(g)==="[object Date]"}function d(g,E){for(var I=[],O=g.length,G=0;G>>0,O=0;Oue(g)?(ht=g+1,xt-ue(g)):(ht=g,xt);return{year:ht,dayOfYear:Mt}}function Ke(g,E,I){var O,G,ht=Ie(g.year(),E,I),ht=Math.floor((g.dayOfYear()-ht-1)/7)+1;return ht<1?O=ht+wr(G=g.year()-1,E,I):ht>wr(g.year(),E,I)?(O=ht-wr(g.year(),E,I),G=g.year()+1):(G=g.year(),O=ht),{week:O,year:G}}function wr(g,G,I){var O=Ie(g,G,I),G=Ie(g+1,G,I);return(ue(g)-O+G)/7}Y("w",["ww",2],"wo","week"),Y("W",["WW",2],"Wo","isoWeek"),W("week","w"),W("isoWeek","W"),Z("week",5),Z("isoWeek",5),ft("w",at),ft("ww",at,fe),ft("W",at),ft("WW",at,fe),we(["w","ww","W","WW"],function(g,E,I,O){E[O.substr(0,1)]=q(g)});function Ge(g,E){return g.slice(E,7).concat(g.slice(0,E))}Y("d",0,"do","day"),Y("dd",0,0,function(g){return this.localeData().weekdaysMin(this,g)}),Y("ddd",0,0,function(g){return this.localeData().weekdaysShort(this,g)}),Y("dddd",0,0,function(g){return this.localeData().weekdays(this,g)}),Y("e",0,0,"weekday"),Y("E",0,0,"isoWeekday"),W("day","d"),W("weekday","e"),W("isoWeekday","E"),Z("day",11),Z("weekday",11),Z("isoWeekday",11),ft("d",at),ft("e",at),ft("E",at),ft("dd",function(g,E){return E.weekdaysMinRegex(g)}),ft("ddd",function(g,E){return E.weekdaysShortRegex(g)}),ft("dddd",function(g,E){return E.weekdaysRegex(g)}),we(["dd","ddd","dddd"],function(g,E,I,O){O=I._locale.weekdaysParse(g,O,I._strict),O!=null?E.d=O:m(I).invalidWeekday=g}),we(["d","e","E"],function(g,E,I,O){E[O]=q(g)});var Ze="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),qt="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),st="Su_Mo_Tu_We_Th_Fr_Sa".split("_"),At=Tt,Nt=Tt,Jt=Tt;function ze(){function g(Ot,de){return de.length-Ot.length}for(var E,I,O,G=[],ht=[],xt=[],Mt=[],Vt=0;Vt<7;Vt++)O=p([2e3,1]).day(Vt),E=Dt(this.weekdaysMin(O,"")),I=Dt(this.weekdaysShort(O,"")),O=Dt(this.weekdays(O,"")),G.push(E),ht.push(I),xt.push(O),Mt.push(E),Mt.push(I),Mt.push(O);G.sort(g),ht.sort(g),xt.sort(g),Mt.sort(g),this._weekdaysRegex=new RegExp("^("+Mt.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+xt.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+ht.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+G.join("|")+")","i")}function Pe(){return this.hours()%12||12}function qe(g,E){Y(g,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),E)})}function Tr(g,E){return E._meridiemParse}Y("H",["HH",2],0,"hour"),Y("h",["hh",2],0,Pe),Y("k",["kk",2],0,function(){return this.hours()||24}),Y("hmm",0,0,function(){return""+Pe.apply(this)+N(this.minutes(),2)}),Y("hmmss",0,0,function(){return""+Pe.apply(this)+N(this.minutes(),2)+N(this.seconds(),2)}),Y("Hmm",0,0,function(){return""+this.hours()+N(this.minutes(),2)}),Y("Hmmss",0,0,function(){return""+this.hours()+N(this.minutes(),2)+N(this.seconds(),2)}),qe("a",!0),qe("A",!1),W("hour","h"),Z("hour",13),ft("a",Tr),ft("A",Tr),ft("H",at),ft("h",at),ft("k",at),ft("HH",at,fe),ft("hh",at,fe),ft("kk",at,fe),ft("hmm",It),ft("hmmss",Lt),ft("Hmm",It),ft("Hmmss",Lt),Qt(["H","HH"],bt),Qt(["k","kk"],function(g,E,I){g=q(g),E[bt]=g===24?0:g}),Qt(["a","A"],function(g,E,I){I._isPm=I._locale.isPM(g),I._meridiem=g}),Qt(["h","hh"],function(g,E,I){E[bt]=q(g),m(I).bigHour=!0}),Qt("hmm",function(g,E,I){var O=g.length-2;E[bt]=q(g.substr(0,O)),E[Et]=q(g.substr(O)),m(I).bigHour=!0}),Qt("hmmss",function(g,E,I){var O=g.length-4,G=g.length-2;E[bt]=q(g.substr(0,O)),E[Et]=q(g.substr(O,2)),E[kt]=q(g.substr(G)),m(I).bigHour=!0}),Qt("Hmm",function(g,E,I){var O=g.length-2;E[bt]=q(g.substr(0,O)),E[Et]=q(g.substr(O))}),Qt("Hmmss",function(g,E,I){var O=g.length-4,G=g.length-2;E[bt]=q(g.substr(0,O)),E[Et]=q(g.substr(O,2)),E[kt]=q(g.substr(G))}),Tt=U("Hours",!0);var Ve,va={calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},longDateFormat:{LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},invalidDate:"Invalid date",ordinal:"%d",dayOfMonthOrdinalParse:/\d{1,2}/,relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",w:"a week",ww:"%d weeks",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},months:ne,monthsShort:ve,week:{dow:0,doy:6},weekdays:Ze,weekdaysMin:st,weekdaysShort:qt,meridiemParse:/[ap]\.?m?\.?/i},Ce={},Wi={};function E0(g){return g&&g.toLowerCase().replace("_","-")}function _u(g){for(var E,I,O,G,ht=0;ht=E&&function(xt,Mt){for(var Vt=Math.min(xt.length,Mt.length),Ot=0;Ot=E-1)break;E--}ht++}return Ve}function Ln(g){var E;if(Ce[g]===void 0&&!0&&t&&t.exports&&g.match("^[^/\\\\]*$")!=null)try{E=Ve._abbr,fn("./locale/"+g),Xt(E)}catch{Ce[g]=null}return Ce[g]}function Xt(g,E){return g&&((E=l(E)?ce(g):ee(g,E))?Ve=E:typeof console<"u"&&console.warn&&console.warn("Locale "+g+" not found. Did you forget to load it?")),Ve._abbr}function ee(g,E){if(E===null)return delete Ce[g],null;var I,O=va;if(E.abbr=g,Ce[g]!=null)L("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info."),O=Ce[g]._config;else if(E.parentLocale!=null)if(Ce[E.parentLocale]!=null)O=Ce[E.parentLocale]._config;else{if((I=Ln(E.parentLocale))==null)return Wi[E.parentLocale]||(Wi[E.parentLocale]=[]),Wi[E.parentLocale].push({name:g,config:E}),null;O=I._config}return Ce[g]=new w(B(O,E)),Wi[g]&&Wi[g].forEach(function(G){ee(G.name,G.config)}),Xt(g),Ce[g]}function ce(g){var E;if(!(g=g&&g._locale&&g._locale._abbr?g._locale._abbr:g))return Ve;if(!i(g)){if(E=Ln(g))return E;g=[g]}return _u(g)}function Pt(g){var E=g._a;return E&&m(g).overflow===-2&&(E=E[zt]<0||11yt(E[Ft],E[zt])?wt:E[bt]<0||24wr(ht,Vt,Ot)?m(O)._overflowWeeks=!0:de!=null?m(O)._overflowWeekday=!0:(ie=oe(ht,xt,Mt,Vt,Ot),O._a[Ft]=ie.year,O._dayOfYear=ie.dayOfYear)),g._dayOfYear!=null&&(G=Gi(g._a[Ft],I[Ft]),(g._dayOfYear>ue(G)||g._dayOfYear===0)&&(m(g)._overflowDayOfYear=!0),de=Hr(G,0,g._dayOfYear),g._a[zt]=de.getUTCMonth(),g._a[wt]=de.getUTCDate()),E=0;E<3&&g._a[E]==null;++E)g._a[E]=er[E]=I[E];for(;E<7;E++)g._a[E]=er[E]=g._a[E]==null?E===2?1:0:g._a[E];g._a[bt]===24&&g._a[Et]===0&&g._a[kt]===0&&g._a[Ut]===0&&(g._nextDay=!0,g._a[bt]=0),g._d=(g._useUTC?Hr:_a).apply(null,er),ht=g._useUTC?g._d.getUTCDay():g._d.getDay(),g._tzm!=null&&g._d.setUTCMinutes(g._d.getUTCMinutes()-g._tzm),g._nextDay&&(g._a[bt]=24),g._w&&g._w.d!==void 0&&g._w.d!==ht&&(m(g).weekdayMismatch=!0)}}function vu(g){if(g._f===n.ISO_8601)A0(g);else if(g._f===n.RFC_2822)Hi(g);else{g._a=[],m(g).empty=!0;for(var E,I,O,G,ht,xt=""+g._i,Mt=xt.length,Vt=0,Ot=lt(g._f,g._locale).match(z)||[],de=Ot.length,ie=0;ieg.valueOf():g.valueOf()"}),P.toJSON=function(){return this.isValid()?this.toISOString():null},P.toString=function(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")},P.unix=function(){return Math.floor(this.valueOf()/1e3)},P.valueOf=function(){return this._d.valueOf()-6e4*(this._offset||0)},P.creationData=function(){return{input:this._i,format:this._f,locale:this._locale,isUTC:this._isUTC,strict:this._strict}},P.eraName=function(){for(var g,E=this.localeData().eras(),I=0,O=E.length;Ithis.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()},P.isLocal=function(){return!!this.isValid()&&!this._isUTC},P.isUtcOffset=function(){return!!this.isValid()&&this._isUTC},P.isUtc=rR,P.isUTC=rR,P.zoneAbbr=function(){return this._isUTC?"UTC":""},P.zoneName=function(){return this._isUTC?"Coordinated Universal Time":""},P.dates=R("dates accessor is deprecated. Use date instead.",ls),P.months=R("months accessor is deprecated. Use month instead",se),P.years=R("years accessor is deprecated. Use year instead",N0),P.zone=R("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",function(g,E){return g!=null?(this.utcOffset(g=typeof g!="string"?-g:g,E),this):-this.utcOffset()}),P.isDSTShifted=R("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",function(){if(!l(this._isDSTShifted))return this._isDSTShifted;var g,E={};return T(E,this),(E=M0(E))._a?(g=(E._isUTC?p:De)(E._a),this._isDSTShifted=this.isValid()&&0{},debug:(...t)=>{},info:(...t)=>{},warn:(...t)=>{},error:(...t)=>{},fatal:(...t)=>{}},D0=function(t="fatal"){let e=ji.fatal;typeof t=="string"?(t=t.toLowerCase(),t in ji&&(e=ji[t])):typeof t=="number"&&(e=t),H.trace=()=>{},H.debug=()=>{},H.info=()=>{},H.warn=()=>{},H.error=()=>{},H.fatal=()=>{},e<=ji.fatal&&(H.fatal=console.error?console.error.bind(console,Nn("FATAL"),"color: orange"):console.log.bind(console,"\x1B[35m",Nn("FATAL"))),e<=ji.error&&(H.error=console.error?console.error.bind(console,Nn("ERROR"),"color: orange"):console.log.bind(console,"\x1B[31m",Nn("ERROR"))),e<=ji.warn&&(H.warn=console.warn?console.warn.bind(console,Nn("WARN"),"color: orange"):console.log.bind(console,"\x1B[33m",Nn("WARN"))),e<=ji.info&&(H.info=console.info?console.info.bind(console,Nn("INFO"),"color: lightblue"):console.log.bind(console,"\x1B[34m",Nn("INFO"))),e<=ji.debug&&(H.debug=console.debug?console.debug.bind(console,Nn("DEBUG"),"color: lightgreen"):console.log.bind(console,"\x1B[32m",Nn("DEBUG"))),e<=ji.trace&&(H.trace=console.debug?console.debug.bind(console,Nn("TRACE"),"color: lightgreen"):console.log.bind(console,"\x1B[32m",Nn("TRACE")))},Nn=t=>`%c${Xn().format("ss.SSS")} : ${t} : `;var O0={};Object.defineProperty(O0,"__esModule",{value:!0});var ki=O0.sanitizeUrl=void 0,bR=/^([^\w]*)(javascript|data|vbscript)/im,_R=/&#(\w+)(^\w|;)?/g,vR=/[\u0000-\u001F\u007F-\u009F\u2000-\u200D\uFEFF]/gim,xR=/^([^:]+):/gm,kR=[".","/"];function wR(t){return kR.indexOf(t[0])>-1}function TR(t){return t.replace(_R,function(e,r){return String.fromCharCode(r)})}function ER(t){var e=TR(t||"").replace(vR,"").trim();if(!e)return"about:blank";if(wR(e))return e;var r=e.match(xR);if(!r)return e;var n=r[0];return bR.test(n)?"about:blank":e}ki=O0.sanitizeUrl=ER;function Qe(t,e){return t==null||e==null?NaN:te?1:t>=e?0:NaN}function m_(t,e){return t==null||e==null?NaN:et?1:e>=t?0:NaN}function ku(t){let e,r,n;t.length!==2?(e=Qe,r=(o,l)=>Qe(t(o),l),n=(o,l)=>t(o)-l):(e=t===Qe||t===m_?t:CR,r=t,n=t);function i(o,l,u=0,h=o.length){if(u>>1;r(o[d],l)<0?u=d+1:h=d}while(u>>1;r(o[d],l)<=0?u=d+1:h=d}while(uu&&n(o[d-1],l)>-n(o[d],l)?d-1:d}return{left:i,center:s,right:a}}function CR(){return 0}function b_(t){return t===null?NaN:+t}function*__(t,e){if(e===void 0)for(let r of t)r!=null&&(r=+r)>=r&&(yield r);else{let r=-1;for(let n of t)(n=e(n,++r,t))!=null&&(n=+n)>=n&&(yield n)}}const v_=ku(Qe),x_=v_.right,SR=v_.left,AR=ku(b_).center,cs=x_;function MR(t,e){if(!((e=+e)>=0))throw new RangeError("invalid r");let r=t.length;if(!((r=Math.floor(r))>=0))throw new RangeError("invalid length");if(!r||!e)return t;const n=F0(e),i=t.slice();return n(t,i,0,r,1),n(i,t,0,r,1),n(t,i,0,r,1),t}const k_=w_(F0),LR=w_(RR);function w_(t){return function(e,r,n=r){if(!((r=+r)>=0))throw new RangeError("invalid rx");if(!((n=+n)>=0))throw new RangeError("invalid ry");let{data:i,width:a,height:s}=e;if(!((a=Math.floor(a))>=0))throw new RangeError("invalid width");if(!((s=Math.floor(s!==void 0?s:i.length/a))>=0))throw new RangeError("invalid height");if(!a||!s||!r&&!n)return e;const o=r&&t(r),l=n&&t(n),u=i.slice();return o&&l?(ro(o,u,i,a,s),ro(o,i,u,a,s),ro(o,u,i,a,s),no(l,i,u,a,s),no(l,u,i,a,s),no(l,i,u,a,s)):o?(ro(o,i,u,a,s),ro(o,u,i,a,s),ro(o,i,u,a,s)):l&&(no(l,i,u,a,s),no(l,u,i,a,s),no(l,i,u,a,s)),e}}function ro(t,e,r,n,i){for(let a=0,s=n*i;a{i<<=2,a<<=2,s<<=2,e(r,n,i+0,a+0,s),e(r,n,i+1,a+1,s),e(r,n,i+2,a+2,s),e(r,n,i+3,a+3,s)}}function F0(t){const e=Math.floor(t);if(e===t)return IR(t);const r=t-e,n=2*t+1;return(i,a,s,o,l)=>{if(!((o-=l)>=s))return;let u=e*a[s];const h=l*e,d=h+l;for(let f=s,p=s+h;f{if(!((a-=s)>=i))return;let o=t*n[i];const l=s*t;for(let u=i,h=i+l;u=n&&++r;else{let n=-1;for(let i of t)(i=e(i,++n,t))!=null&&(i=+i)>=i&&++r}return r}function NR(t){return t.length|0}function BR(t){return!(t>0)}function DR(t){return typeof t!="object"||"length"in t?t:Array.from(t)}function OR(t){return e=>t(...e)}function FR(...t){const e=typeof t[t.length-1]=="function"&&OR(t.pop());t=t.map(DR);const r=t.map(NR),n=t.length-1,i=new Array(n+1).fill(0),a=[];if(n<0||r.some(BR))return a;for(;;){a.push(i.map((o,l)=>t[l][o]));let s=n;for(;++i[s]===r[s];){if(s===0)return e?a.map(e):a;i[s--]=0}}}function PR(t,e){var r=0,n=0;return Float64Array.from(t,e===void 0?i=>r+=+i||0:i=>r+=+e(i,n++,t)||0)}function T_(t,e){let r=0,n,i=0,a=0;if(e===void 0)for(let s of t)s!=null&&(s=+s)>=s&&(n=s-i,i+=n/++r,a+=n*(s-i));else{let s=-1;for(let o of t)(o=e(o,++s,t))!=null&&(o=+o)>=o&&(n=o-i,i+=n/++r,a+=n*(o-i))}if(r>1)return a/(r-1)}function E_(t,e){const r=T_(t,e);return r&&Math.sqrt(r)}function xl(t,e){let r,n;if(e===void 0)for(const i of t)i!=null&&(r===void 0?i>=i&&(r=n=i):(r>i&&(r=i),n=a&&(r=n=a):(r>a&&(r=a),n0){for(s=e[--r];r>0&&(n=s,i=e[--r],s=n+i,a=i-(s-n),!a););r>0&&(a<0&&e[r-1]<0||a>0&&e[r-1]>0)&&(i=a*2,n=s+i,i==n-s&&(s=n))}return s}}function qR(t,e){const r=new _r;if(e===void 0)for(let n of t)(n=+n)&&r.add(n);else{let n=-1;for(let i of t)(i=+e(i,++n,t))&&r.add(i)}return+r}function VR(t,e){const r=new _r;let n=-1;return Float64Array.from(t,e===void 0?i=>r.add(+i||0):i=>r.add(+e(i,++n,t)||0))}class kl extends Map{constructor(e,r=A_){if(super(),Object.defineProperties(this,{_intern:{value:new Map},_key:{value:r}}),e!=null)for(const[n,i]of e)this.set(n,i)}get(e){return super.get(P0(this,e))}has(e){return super.has(P0(this,e))}set(e,r){return super.set(C_(this,e),r)}delete(e){return super.delete(S_(this,e))}}class us extends Set{constructor(e,r=A_){if(super(),Object.defineProperties(this,{_intern:{value:new Map},_key:{value:r}}),e!=null)for(const n of e)this.add(n)}has(e){return super.has(P0(this,e))}add(e){return super.add(C_(this,e))}delete(e){return super.delete(S_(this,e))}}function P0({_intern:t,_key:e},r){const n=e(r);return t.has(n)?t.get(n):r}function C_({_intern:t,_key:e},r){const n=e(r);return t.has(n)?t.get(n):(t.set(n,r),r)}function S_({_intern:t,_key:e},r){const n=e(r);return t.has(n)&&(r=t.get(n),t.delete(n)),r}function A_(t){return t!==null&&typeof t=="object"?t.valueOf():t}function io(t){return t}function M_(t,...e){return ao(t,io,io,e)}function L_(t,...e){return ao(t,Array.from,io,e)}function R_(t,e){for(let r=1,n=e.length;ri.pop().map(([a,s])=>[...i,a,s]));return t}function zR(t,...e){return R_(L_(t,...e),e)}function YR(t,e,...r){return R_(N_(t,e,...r),r)}function I_(t,e,...r){return ao(t,io,e,r)}function N_(t,e,...r){return ao(t,Array.from,e,r)}function UR(t,...e){return ao(t,io,B_,e)}function WR(t,...e){return ao(t,Array.from,B_,e)}function B_(t){if(t.length!==1)throw new Error("duplicate key");return t[0]}function ao(t,e,r,n){return function i(a,s){if(s>=n.length)return r(a);const o=new kl,l=n[s++];let u=-1;for(const h of a){const d=l(h,++u,a),f=o.get(d);f?f.push(h):o.set(d,[h])}for(const[h,d]of o)o.set(h,i(d,s));return e(o)}(t,0)}function D_(t,e){return Array.from(e,r=>t[r])}function q0(t,...e){if(typeof t[Symbol.iterator]!="function")throw new TypeError("values is not iterable");t=Array.from(t);let[r]=e;if(r&&r.length!==2||e.length>1){const n=Uint32Array.from(t,(i,a)=>a);return e.length>1?(e=e.map(i=>t.map(i)),n.sort((i,a)=>{for(const s of e){const o=so(s[i],s[a]);if(o)return o}})):(r=t.map(r),n.sort((i,a)=>so(r[i],r[a]))),D_(t,n)}return t.sort(V0(r))}function V0(t=Qe){if(t===Qe)return so;if(typeof t!="function")throw new TypeError("compare is not a function");return(e,r)=>{const n=t(e,r);return n||n===0?n:(t(r,r)===0)-(t(e,e)===0)}}function so(t,e){return(t==null||!(t>=t))-(e==null||!(e>=e))||(te?1:0)}function HR(t,e,r){return(e.length!==2?q0(I_(t,e,r),([n,i],[a,s])=>Qe(i,s)||Qe(n,a)):q0(M_(t,r),([n,i],[a,s])=>e(i,s)||Qe(n,a))).map(([n])=>n)}var GR=Array.prototype,jR=GR.slice;function Tu(t){return()=>t}var z0=Math.sqrt(50),Y0=Math.sqrt(10),U0=Math.sqrt(2);function hs(t,e,r){var n,i=-1,a,s,o;if(e=+e,t=+t,r=+r,t===e&&r>0)return[t];if((n=e0){let l=Math.round(t/o),u=Math.round(e/o);for(l*oe&&--u,s=new Array(a=u-l+1);++ie&&--u,s=new Array(a=u-l+1);++i=0?(a>=z0?10:a>=Y0?5:a>=U0?2:1)*Math.pow(10,i):-Math.pow(10,-i)/(a>=z0?10:a>=Y0?5:a>=U0?2:1)}function wl(t,e,r){var n=Math.abs(e-t)/Math.max(0,r),i=Math.pow(10,Math.floor(Math.log(n)/Math.LN10)),a=n/i;return a>=z0?i*=10:a>=Y0?i*=5:a>=U0&&(i*=2),e0?(t=Math.floor(t/i)*i,e=Math.ceil(e/i)*i):i<0&&(t=Math.ceil(t*i)/i,e=Math.floor(e*i)/i),n=i}}function W0(t){return Math.ceil(Math.log(wu(t))/Math.LN2)+1}function F_(){var t=io,e=xl,r=W0;function n(i){Array.isArray(i)||(i=Array.from(i));var a,s=i.length,o,l,u=new Array(s);for(a=0;a=f)if(b>=f&&e===xl){const k=oo(d,f,x);isFinite(k)&&(k>0?f=(Math.floor(f/k)+1)*k:k<0&&(f=(Math.ceil(f*-k)+1)/-k))}else p.pop()}for(var m=p.length;p[0]<=d;)p.shift(),--m;for(;p[m-1]>f;)p.pop(),--m;var _=new Array(m+1),y;for(a=0;a<=m;++a)y=_[a]=[],y.x0=a>0?p[a-1]:d,y.x1=a0)for(a=0;a=n)&&(r=n);else{let n=-1;for(let i of t)(i=e(i,++n,t))!=null&&(r=i)&&(r=i)}return r}function H0(t,e){let r,n=-1,i=-1;if(e===void 0)for(const a of t)++i,a!=null&&(r=a)&&(r=a,n=i);else for(let a of t)(a=e(a,++i,t))!=null&&(r=a)&&(r=a,n=i);return n}function Tl(t,e){let r;if(e===void 0)for(const n of t)n!=null&&(r>n||r===void 0&&n>=n)&&(r=n);else{let n=-1;for(let i of t)(i=e(i,++n,t))!=null&&(r>i||r===void 0&&i>=i)&&(r=i)}return r}function G0(t,e){let r,n=-1,i=-1;if(e===void 0)for(const a of t)++i,a!=null&&(r>a||r===void 0&&a>=a)&&(r=a,n=i);else for(let a of t)(a=e(a,++i,t))!=null&&(r>a||r===void 0&&a>=a)&&(r=a,n=i);return n}function Eu(t,e,r=0,n=t.length-1,i){for(i=i===void 0?so:V0(i);n>r;){if(n-r>600){const l=n-r+1,u=e-r+1,h=Math.log(l),d=.5*Math.exp(2*h/3),f=.5*Math.sqrt(h*d*(l-d)/l)*(u-l/2<0?-1:1),p=Math.max(r,Math.floor(e-u*d/l+f)),m=Math.min(n,Math.floor(e+(l-u)*d/l+f));Eu(t,e,p,m,i)}const a=t[e];let s=r,o=n;for(El(t,r,e),i(t[n],a)>0&&El(t,r,n);s0;)--o}i(t[r],a)===0?El(t,r,o):(++o,El(t,o,n)),o<=e&&(r=o+1),e<=o&&(n=o-1)}return t}function El(t,e,r){const n=t[e];t[e]=t[r],t[r]=n}function P_(t,e=Qe){let r,n=!1;if(e.length===1){let i;for(const a of t){const s=e(a);(n?Qe(s,i)>0:Qe(s,s)===0)&&(r=a,i=s,n=!0)}}else for(const i of t)(n?e(i,r)>0:e(i,i)===0)&&(r=i,n=!0);return r}function Cl(t,e,r){if(t=Float64Array.from(__(t,r)),!!(n=t.length)){if((e=+e)<=0||n<2)return Tl(t);if(e>=1)return lo(t);var n,i=(n-1)*e,a=Math.floor(i),s=lo(Eu(t,a).subarray(0,a+1)),o=Tl(t.subarray(a+1));return s+(o-s)*(i-a)}}function q_(t,e,r=b_){if(!!(n=t.length)){if((e=+e)<=0||n<2)return+r(t[0],0,t);if(e>=1)return+r(t[n-1],n-1,t);var n,i=(n-1)*e,a=Math.floor(i),s=+r(t[a],a,t),o=+r(t[a+1],a+1,t);return s+(o-s)*(i-a)}}function V_(t,e,r){if(t=Float64Array.from(__(t,r)),!!(n=t.length)){if((e=+e)<=0||n<2)return G0(t);if(e>=1)return H0(t);var n,i=Math.floor((n-1)*e),a=(o,l)=>so(t[o],t[l]),s=Eu(Uint32Array.from(t,(o,l)=>l),i,0,n-1,a);return P_(s.subarray(0,i+1),o=>t[o])}}function $R(t,e,r){return Math.ceil((r-e)/(2*(Cl(t,.75)-Cl(t,.25))*Math.pow(wu(t),-1/3)))}function XR(t,e,r){return Math.ceil((r-e)*Math.cbrt(wu(t))/(3.49*E_(t)))}function KR(t,e){let r=0,n=0;if(e===void 0)for(let i of t)i!=null&&(i=+i)>=i&&(++r,n+=i);else{let i=-1;for(let a of t)(a=e(a,++i,t))!=null&&(a=+a)>=a&&(++r,n+=a)}if(r)return n/r}function ZR(t,e){return Cl(t,.5,e)}function QR(t,e){return V_(t,.5,e)}function*JR(t){for(const e of t)yield*e}function j0(t){return Array.from(JR(t))}function tI(t,e){const r=new kl;if(e===void 0)for(let a of t)a!=null&&a>=a&&r.set(a,(r.get(a)||0)+1);else{let a=-1;for(let s of t)(s=e(s,++a,t))!=null&&s>=s&&r.set(s,(r.get(s)||0)+1)}let n,i=0;for(const[a,s]of r)s>i&&(i=s,n=a);return n}function eI(t,e=rI){const r=[];let n,i=!1;for(const a of t)i&&r.push(e(n,a)),n=a,i=!0;return r}function rI(t,e){return[t,e]}function Ca(t,e,r){t=+t,e=+e,r=(i=arguments.length)<2?(e=t,t=0,1):i<3?1:+r;for(var n=-1,i=Math.max(0,Math.ceil((e-t)/r))|0,a=new Array(i);++ne(r[o],r[l]);let a,s;return Uint32Array.from(r,(o,l)=>l).sort(e===Qe?(o,l)=>so(r[o],r[l]):V0(i)).forEach((o,l)=>{const u=i(o,a===void 0?o:a);u>=0?((a===void 0||u>0)&&(a=o,s=l),n[o]=s):n[o]=NaN}),n}function iI(t,e=Qe){let r,n=!1;if(e.length===1){let i;for(const a of t){const s=e(a);(n?Qe(s,i)<0:Qe(s,s)===0)&&(r=a,i=s,n=!0)}}else for(const i of t)(n?e(i,r)<0:e(i,i)===0)&&(r=i,n=!0);return r}function z_(t,e=Qe){if(e.length===1)return G0(t,e);let r,n=-1,i=-1;for(const a of t)++i,(n<0?e(a,a)===0:e(a,r)<0)&&(r=a,n=i);return n}function aI(t,e=Qe){if(e.length===1)return H0(t,e);let r,n=-1,i=-1;for(const a of t)++i,(n<0?e(a,a)===0:e(a,r)>0)&&(r=a,n=i);return n}function sI(t,e){const r=z_(t,e);return r<0?void 0:r}const oI=Y_(Math.random);function Y_(t){return function(r,n=0,i=r.length){let a=i-(n=+n);for(;a;){const s=t()*a--|0,o=r[a+n];r[a+n]=r[s+n],r[s+n]=o}return r}}function lI(t,e){let r=0;if(e===void 0)for(let n of t)(n=+n)&&(r+=n);else{let n=-1;for(let i of t)(i=+e(i,++n,t))&&(r+=i)}return r}function U_(t){if(!(a=t.length))return[];for(var e=-1,r=Tl(t,cI),n=new Array(r);++ee(r,n,t))}function gI(t,e,r){if(typeof e!="function")throw new TypeError("reducer is not a function");const n=t[Symbol.iterator]();let i,a,s=-1;if(arguments.length<3){if({done:i,value:r}=n.next(),i)return;++s}for(;{done:i,value:a}=n.next(),!i;)r=e(r,a,++s,t);return r}function yI(t){if(typeof t[Symbol.iterator]!="function")throw new TypeError("values is not iterable");return Array.from(t).reverse()}function mI(t,...e){t=new us(t);for(const r of e)for(const n of r)t.delete(n);return t}function bI(t,e){const r=e[Symbol.iterator](),n=new us;for(const i of t){if(n.has(i))return!1;let a,s;for(;({value:a,done:s}=r.next())&&!s;){if(Object.is(i,a))return!1;n.add(a)}}return!0}function _I(t,...e){t=new us(t),e=e.map(vI);t:for(const r of t)for(const n of e)if(!n.has(r)){t.delete(r);continue t}return t}function vI(t){return t instanceof us?t:new us(t)}function W_(t,e){const r=t[Symbol.iterator](),n=new Set;for(const i of e){const a=H_(i);if(n.has(a))continue;let s,o;for(;{value:s,done:o}=r.next();){if(o)return!1;const l=H_(s);if(n.add(l),Object.is(a,l))break}}return!0}function H_(t){return t!==null&&typeof t=="object"?t.valueOf():t}function xI(t,e){return W_(e,t)}function kI(...t){const e=new us;for(const r of t)for(const n of r)e.add(n);return e}function wI(t){return t}var Cu=1,Su=2,$0=3,Sl=4,G_=1e-6;function TI(t){return"translate("+t+",0)"}function EI(t){return"translate(0,"+t+")"}function CI(t){return e=>+t(e)}function SI(t,e){return e=Math.max(0,t.bandwidth()-e*2)/2,t.round()&&(e=Math.round(e)),r=>+t(r)+e}function AI(){return!this.__axis}function Au(t,e){var r=[],n=null,i=null,a=6,s=6,o=3,l=typeof window<"u"&&window.devicePixelRatio>1?0:.5,u=t===Cu||t===Sl?-1:1,h=t===Sl||t===Su?"x":"y",d=t===Cu||t===$0?TI:EI;function f(p){var m=n==null?e.ticks?e.ticks.apply(e,r):e.domain():n,_=i==null?e.tickFormat?e.tickFormat.apply(e,r):wI:i,y=Math.max(a,0)+o,b=e.range(),x=+b[0]+l,k=+b[b.length-1]+l,T=(e.bandwidth?SI:CI)(e.copy(),l),C=p.selection?p.selection():p,M=C.selectAll(".domain").data([null]),S=C.selectAll(".tick").data(m,e).order(),R=S.exit(),A=S.enter().append("g").attr("class","tick"),L=S.select("line"),v=S.select("text");M=M.merge(M.enter().insert("path",".tick").attr("class","domain").attr("stroke","currentColor")),S=S.merge(A),L=L.merge(A.append("line").attr("stroke","currentColor").attr(h+"2",u*a)),v=v.merge(A.append("text").attr("fill","currentColor").attr(h,u*y).attr("dy",t===Cu?"0em":t===$0?"0.71em":"0.32em")),p!==C&&(M=M.transition(p),S=S.transition(p),L=L.transition(p),v=v.transition(p),R=R.transition(p).attr("opacity",G_).attr("transform",function(B){return isFinite(B=T(B))?d(B+l):this.getAttribute("transform")}),A.attr("opacity",G_).attr("transform",function(B){var w=this.parentNode.__axis;return d((w&&isFinite(w=w(B))?w:T(B))+l)})),R.remove(),M.attr("d",t===Sl||t===Su?s?"M"+u*s+","+x+"H"+l+"V"+k+"H"+u*s:"M"+l+","+x+"V"+k:s?"M"+x+","+u*s+"V"+l+"H"+k+"V"+u*s:"M"+x+","+l+"H"+k),S.attr("opacity",1).attr("transform",function(B){return d(T(B)+l)}),L.attr(h+"2",u*a),v.attr(h,u*y).text(_),C.filter(AI).attr("fill","none").attr("font-size",10).attr("font-family","sans-serif").attr("text-anchor",t===Su?"start":t===Sl?"end":"middle"),C.each(function(){this.__axis=T})}return f.scale=function(p){return arguments.length?(e=p,f):e},f.ticks=function(){return r=Array.from(arguments),f},f.tickArguments=function(p){return arguments.length?(r=p==null?[]:Array.from(p),f):r.slice()},f.tickValues=function(p){return arguments.length?(n=p==null?null:Array.from(p),f):n&&n.slice()},f.tickFormat=function(p){return arguments.length?(i=p,f):i},f.tickSize=function(p){return arguments.length?(a=s=+p,f):a},f.tickSizeInner=function(p){return arguments.length?(a=+p,f):a},f.tickSizeOuter=function(p){return arguments.length?(s=+p,f):s},f.tickPadding=function(p){return arguments.length?(o=+p,f):o},f.offset=function(p){return arguments.length?(l=+p,f):l},f}function j_(t){return Au(Cu,t)}function MI(t){return Au(Su,t)}function $_(t){return Au($0,t)}function LI(t){return Au(Sl,t)}var RI={value:()=>{}};function fs(){for(var t=0,e=arguments.length,r={},n;t=0&&(n=r.slice(i+1),r=r.slice(0,i)),r&&!e.hasOwnProperty(r))throw new Error("unknown type: "+r);return{type:r,name:n}})}Mu.prototype=fs.prototype={constructor:Mu,on:function(t,e){var r=this._,n=II(t+"",r),i,a=-1,s=n.length;if(arguments.length<2){for(;++a0)for(var r=new Array(i),n=0,i,a;n=0&&(e=t.slice(0,r))!=="xmlns"&&(t=t.slice(r+1)),K0.hasOwnProperty(e)?{space:K0[e],local:t}:t}function BI(t){return function(){var e=this.ownerDocument,r=this.namespaceURI;return r===X0&&e.documentElement.namespaceURI===X0?e.createElement(t):e.createElementNS(r,t)}}function DI(t){return function(){return this.ownerDocument.createElementNS(t.space,t.local)}}function Lu(t){var e=Al(t);return(e.local?DI:BI)(e)}function OI(){}function Ru(t){return t==null?OI:function(){return this.querySelector(t)}}function FI(t){typeof t!="function"&&(t=Ru(t));for(var e=this._groups,r=e.length,n=new Array(r),i=0;i=k&&(k=x+1);!(C=y[k])&&++k=0;)(s=n[i])&&(a&&s.compareDocumentPosition(a)^4&&a.parentNode.insertBefore(s,a),a=s);return this}function oN(t){t||(t=lN);function e(d,f){return d&&f?t(d.__data__,f.__data__):!d-!f}for(var r=this._groups,n=r.length,i=new Array(n),a=0;ae?1:t>=e?0:NaN}function cN(){var t=arguments[0];return arguments[0]=this,t.apply(null,arguments),this}function uN(){return Array.from(this)}function hN(){for(var t=this._groups,e=0,r=t.length;e1?this.each((e==null?kN:typeof e=="function"?TN:wN)(t,e,r==null?"":r)):ds(this.node(),t)}function ds(t,e){return t.style.getPropertyValue(e)||J0(t).getComputedStyle(t,null).getPropertyValue(e)}function CN(t){return function(){delete this[t]}}function SN(t,e){return function(){this[t]=e}}function AN(t,e){return function(){var r=e.apply(this,arguments);r==null?delete this[t]:this[t]=r}}function MN(t,e){return arguments.length>1?this.each((e==null?CN:typeof e=="function"?AN:SN)(t,e)):this.node()[t]}function J_(t){return t.trim().split(/^|\s+/)}function td(t){return t.classList||new t5(t)}function t5(t){this._node=t,this._names=J_(t.getAttribute("class")||"")}t5.prototype={add:function(t){var e=this._names.indexOf(t);e<0&&(this._names.push(t),this._node.setAttribute("class",this._names.join(" ")))},remove:function(t){var e=this._names.indexOf(t);e>=0&&(this._names.splice(e,1),this._node.setAttribute("class",this._names.join(" ")))},contains:function(t){return this._names.indexOf(t)>=0}};function e5(t,e){for(var r=td(t),n=-1,i=e.length;++n=0&&(r=e.slice(n+1),e=e.slice(0,n)),{type:e,name:r}})}function nB(t){return function(){var e=this.__on;if(!!e){for(var r=0,n=-1,i=e.length,a;rTn(r,e))}function Nu(t){return typeof t=="string"?new $r([document.querySelectorAll(t)],[document.documentElement]):new $r([K_(t)],ed)}const pB={passive:!1},Ml={capture:!0,passive:!1};function nd(t){t.stopImmediatePropagation()}function co(t){t.preventDefault(),t.stopImmediatePropagation()}function Bu(t){var e=t.document.documentElement,r=St(t).on("dragstart.drag",co,Ml);"onselectstart"in e?r.on("selectstart.drag",co,Ml):(e.__noselect=e.style.MozUserSelect,e.style.MozUserSelect="none")}function Du(t,e){var r=t.document.documentElement,n=St(t).on("dragstart.drag",null);e&&(n.on("click.drag",co,Ml),setTimeout(function(){n.on("click.drag",null)},0)),"onselectstart"in r?n.on("selectstart.drag",null):(r.style.MozUserSelect=r.__noselect,delete r.__noselect)}const Ou=t=>()=>t;function id(t,{sourceEvent:e,subject:r,target:n,identifier:i,active:a,x:s,y:o,dx:l,dy:u,dispatch:h}){Object.defineProperties(this,{type:{value:t,enumerable:!0,configurable:!0},sourceEvent:{value:e,enumerable:!0,configurable:!0},subject:{value:r,enumerable:!0,configurable:!0},target:{value:n,enumerable:!0,configurable:!0},identifier:{value:i,enumerable:!0,configurable:!0},active:{value:a,enumerable:!0,configurable:!0},x:{value:s,enumerable:!0,configurable:!0},y:{value:o,enumerable:!0,configurable:!0},dx:{value:l,enumerable:!0,configurable:!0},dy:{value:u,enumerable:!0,configurable:!0},_:{value:h}})}id.prototype.on=function(){var t=this._.on.apply(this._,arguments);return t===this._?this:t};function gB(t){return!t.ctrlKey&&!t.button}function yB(){return this.parentNode}function mB(t,e){return e==null?{x:t.x,y:t.y}:e}function bB(){return navigator.maxTouchPoints||"ontouchstart"in this}function _B(){var t=gB,e=yB,r=mB,n=bB,i={},a=fs("start","drag","end"),s=0,o,l,u,h,d=0;function f(T){T.on("mousedown.drag",p).filter(n).on("touchstart.drag",y).on("touchmove.drag",b,pB).on("touchend.drag touchcancel.drag",x).style("touch-action","none").style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}function p(T,C){if(!(h||!t.call(this,T,C))){var M=k(this,e.call(this,T,C),T,C,"mouse");!M||(St(T.view).on("mousemove.drag",m,Ml).on("mouseup.drag",_,Ml),Bu(T.view),nd(T),u=!1,o=T.clientX,l=T.clientY,M("start",T))}}function m(T){if(co(T),!u){var C=T.clientX-o,M=T.clientY-l;u=C*C+M*M>d}i.mouse("drag",T)}function _(T){St(T.view).on("mousemove.drag mouseup.drag",null),Du(T.view,u),co(T),i.mouse("end",T)}function y(T,C){if(!!t.call(this,T,C)){var M=T.changedTouches,S=e.call(this,T,C),R=M.length,A,L;for(A=0;A>8&15|e>>4&240,e>>4&15|e&240,(e&15)<<4|e&15,1):r===8?Fu(e>>24&255,e>>16&255,e>>8&255,(e&255)/255):r===4?Fu(e>>12&15|e>>8&240,e>>8&15|e>>4&240,e>>4&15|e&240,((e&15)<<4|e&15)/255):null):(e=xB.exec(t))?new Er(e[1],e[2],e[3],1):(e=kB.exec(t))?new Er(e[1]*255/100,e[2]*255/100,e[3]*255/100,1):(e=wB.exec(t))?Fu(e[1],e[2],e[3],e[4]):(e=TB.exec(t))?Fu(e[1]*255/100,e[2]*255/100,e[3]*255/100,e[4]):(e=EB.exec(t))?f5(e[1],e[2]/100,e[3]/100,1):(e=CB.exec(t))?f5(e[1],e[2]/100,e[3]/100,e[4]):s5.hasOwnProperty(t)?c5(s5[t]):t==="transparent"?new Er(NaN,NaN,NaN,0):null}function c5(t){return new Er(t>>16&255,t>>8&255,t&255,1)}function Fu(t,e,r,n){return n<=0&&(t=e=r=NaN),new Er(t,e,r,n)}function ad(t){return t instanceof Sa||(t=Aa(t)),t?(t=t.rgb(),new Er(t.r,t.g,t.b,t.opacity)):new Er}function po(t,e,r,n){return arguments.length===1?ad(t):new Er(t,e,r,n==null?1:n)}function Er(t,e,r,n){this.r=+t,this.g=+e,this.b=+r,this.opacity=+n}uo(Er,po,Ll(Sa,{brighter(t){return t=t==null?ho:Math.pow(ho,t),new Er(this.r*t,this.g*t,this.b*t,this.opacity)},darker(t){return t=t==null?gs:Math.pow(gs,t),new Er(this.r*t,this.g*t,this.b*t,this.opacity)},rgb(){return this},clamp(){return new Er(ys(this.r),ys(this.g),ys(this.b),Pu(this.opacity))},displayable(){return-.5<=this.r&&this.r<255.5&&-.5<=this.g&&this.g<255.5&&-.5<=this.b&&this.b<255.5&&0<=this.opacity&&this.opacity<=1},hex:u5,formatHex:u5,formatHex8:MB,formatRgb:h5,toString:h5}));function u5(){return`#${ms(this.r)}${ms(this.g)}${ms(this.b)}`}function MB(){return`#${ms(this.r)}${ms(this.g)}${ms(this.b)}${ms((isNaN(this.opacity)?1:this.opacity)*255)}`}function h5(){const t=Pu(this.opacity);return`${t===1?"rgb(":"rgba("}${ys(this.r)}, ${ys(this.g)}, ${ys(this.b)}${t===1?")":`, ${t})`}`}function Pu(t){return isNaN(t)?1:Math.max(0,Math.min(1,t))}function ys(t){return Math.max(0,Math.min(255,Math.round(t)||0))}function ms(t){return t=ys(t),(t<16?"0":"")+t.toString(16)}function f5(t,e,r,n){return n<=0?t=e=r=NaN:r<=0||r>=1?t=e=NaN:e<=0&&(t=NaN),new Kn(t,e,r,n)}function d5(t){if(t instanceof Kn)return new Kn(t.h,t.s,t.l,t.opacity);if(t instanceof Sa||(t=Aa(t)),!t)return new Kn;if(t instanceof Kn)return t;t=t.rgb();var e=t.r/255,r=t.g/255,n=t.b/255,i=Math.min(e,r,n),a=Math.max(e,r,n),s=NaN,o=a-i,l=(a+i)/2;return o?(e===a?s=(r-n)/o+(r0&&l<1?0:s,new Kn(s,o,l,t.opacity)}function qu(t,e,r,n){return arguments.length===1?d5(t):new Kn(t,e,r,n==null?1:n)}function Kn(t,e,r,n){this.h=+t,this.s=+e,this.l=+r,this.opacity=+n}uo(Kn,qu,Ll(Sa,{brighter(t){return t=t==null?ho:Math.pow(ho,t),new Kn(this.h,this.s,this.l*t,this.opacity)},darker(t){return t=t==null?gs:Math.pow(gs,t),new Kn(this.h,this.s,this.l*t,this.opacity)},rgb(){var t=this.h%360+(this.h<0)*360,e=isNaN(t)||isNaN(this.s)?0:this.s,r=this.l,n=r+(r<.5?r:1-r)*e,i=2*r-n;return new Er(sd(t>=240?t-240:t+120,i,n),sd(t,i,n),sd(t<120?t+240:t-120,i,n),this.opacity)},clamp(){return new Kn(p5(this.h),Vu(this.s),Vu(this.l),Pu(this.opacity))},displayable(){return(0<=this.s&&this.s<=1||isNaN(this.s))&&0<=this.l&&this.l<=1&&0<=this.opacity&&this.opacity<=1},formatHsl(){const t=Pu(this.opacity);return`${t===1?"hsl(":"hsla("}${p5(this.h)}, ${Vu(this.s)*100}%, ${Vu(this.l)*100}%${t===1?")":`, ${t})`}`}}));function p5(t){return t=(t||0)%360,t<0?t+360:t}function Vu(t){return Math.max(0,Math.min(1,t||0))}function sd(t,e,r){return(t<60?e+(r-e)*t/60:t<180?r:t<240?e+(r-e)*(240-t)/60:e)*255}const g5=Math.PI/180,y5=180/Math.PI,zu=18,m5=.96422,b5=1,_5=.82521,v5=4/29,go=6/29,x5=3*go*go,LB=go*go*go;function k5(t){if(t instanceof Zn)return new Zn(t.l,t.a,t.b,t.opacity);if(t instanceof Ti)return T5(t);t instanceof Er||(t=ad(t));var e=ud(t.r),r=ud(t.g),n=ud(t.b),i=od((.2225045*e+.7168786*r+.0606169*n)/b5),a,s;return e===r&&r===n?a=s=i:(a=od((.4360747*e+.3850649*r+.1430804*n)/m5),s=od((.0139322*e+.0971045*r+.7141733*n)/_5)),new Zn(116*i-16,500*(a-i),200*(i-s),t.opacity)}function RB(t,e){return new Zn(t,0,0,e==null?1:e)}function Yu(t,e,r,n){return arguments.length===1?k5(t):new Zn(t,e,r,n==null?1:n)}function Zn(t,e,r,n){this.l=+t,this.a=+e,this.b=+r,this.opacity=+n}uo(Zn,Yu,Ll(Sa,{brighter(t){return new Zn(this.l+zu*(t==null?1:t),this.a,this.b,this.opacity)},darker(t){return new Zn(this.l-zu*(t==null?1:t),this.a,this.b,this.opacity)},rgb(){var t=(this.l+16)/116,e=isNaN(this.a)?t:t+this.a/500,r=isNaN(this.b)?t:t-this.b/200;return e=m5*ld(e),t=b5*ld(t),r=_5*ld(r),new Er(cd(3.1338561*e-1.6168667*t-.4906146*r),cd(-.9787684*e+1.9161415*t+.033454*r),cd(.0719453*e-.2289914*t+1.4052427*r),this.opacity)}}));function od(t){return t>LB?Math.pow(t,1/3):t/x5+v5}function ld(t){return t>go?t*t*t:x5*(t-v5)}function cd(t){return 255*(t<=.0031308?12.92*t:1.055*Math.pow(t,1/2.4)-.055)}function ud(t){return(t/=255)<=.04045?t/12.92:Math.pow((t+.055)/1.055,2.4)}function w5(t){if(t instanceof Ti)return new Ti(t.h,t.c,t.l,t.opacity);if(t instanceof Zn||(t=k5(t)),t.a===0&&t.b===0)return new Ti(NaN,0=1?(r=1,e-1):Math.floor(r*e),i=t[n],a=t[n+1],s=n>0?t[n-1]:2*i-a,o=n()=>t;function I5(t,e){return function(r){return t+r*e}}function BB(t,e,r){return t=Math.pow(t,r),e=Math.pow(e,r)-t,r=1/r,function(n){return Math.pow(t+n*e,r)}}function Gu(t,e){var r=e-t;return r?I5(t,r>180||r<-180?r-360*Math.round(r/360):r):Hu(isNaN(t)?e:t)}function DB(t){return(t=+t)==1?Cr:function(e,r){return r-e?BB(e,r,t):Hu(isNaN(e)?r:e)}}function Cr(t,e){var r=e-t;return r?I5(t,r):Hu(isNaN(t)?e:t)}const Nl=function t(e){var r=DB(e);function n(i,a){var s=r((i=po(i)).r,(a=po(a)).r),o=r(i.g,a.g),l=r(i.b,a.b),u=Cr(i.opacity,a.opacity);return function(h){return i.r=s(h),i.g=o(h),i.b=l(h),i.opacity=u(h),i+""}}return n.gamma=t,n}(1);function N5(t){return function(e){var r=e.length,n=new Array(r),i=new Array(r),a=new Array(r),s,o;for(s=0;sr&&(a=e.slice(r,a),o[s]?o[s]+=a:o[++s]=a),(n=n[0])===(i=i[0])?o[s]?o[s]+=i:o[++s]=i:(o[++s]=null,l.push({i:s,x:Bn(n,i)})),r=gd.lastIndex;return r180?h+=360:h-u>180&&(u+=360),f.push({i:d.push(i(d)+"rotate(",null,n)-2,x:Bn(u,h)})):h&&d.push(i(d)+"rotate("+h+n)}function o(u,h,d,f){u!==h?f.push({i:d.push(i(d)+"skewX(",null,n)-2,x:Bn(u,h)}):h&&d.push(i(d)+"skewX("+h+n)}function l(u,h,d,f,p,m){if(u!==d||h!==f){var _=p.push(i(p)+"scale(",null,",",null,")");m.push({i:_-4,x:Bn(u,d)},{i:_-2,x:Bn(h,f)})}else(d!==1||f!==1)&&p.push(i(p)+"scale("+d+","+f+")")}return function(u,h){var d=[],f=[];return u=t(u),h=t(h),a(u.translateX,u.translateY,h.translateX,h.translateY,d,f),s(u.rotate,h.rotate,d,f),o(u.skewX,h.skewX,d,f),l(u.scaleX,u.scaleY,h.scaleX,h.scaleY,d,f),u=h=null,function(p){for(var m=-1,_=f.length,y;++m<_;)d[(y=f[m]).i]=y.x(p);return d.join("")}}}var Y5=z5(YB,"px, ","px)","deg)"),U5=z5(UB,", ",")",")"),WB=1e-12;function W5(t){return((t=Math.exp(t))+1/t)/2}function HB(t){return((t=Math.exp(t))-1/t)/2}function GB(t){return((t=Math.exp(2*t))-1)/(t+1)}const H5=function t(e,r,n){function i(a,s){var o=a[0],l=a[1],u=a[2],h=s[0],d=s[1],f=s[2],p=h-o,m=d-l,_=p*p+m*m,y,b;if(_=0&&t._call.call(void 0,e),t=t._next;--yo}function tv(){_s=(Zu=Fl.now())+Qu,yo=Bl=0;try{J5()}finally{yo=0,eD(),_s=0}}function tD(){var t=Fl.now(),e=t-Zu;e>Z5&&(Qu-=e,Zu=t)}function eD(){for(var t,e=Ku,r,n=1/0;e;)e._call?(n>e._time&&(n=e._time),t=e,e=e._next):(r=e._next,e._next=null,e=t?t._next=r:Ku=r);Ol=t,bd(n)}function bd(t){if(!yo){Bl&&(Bl=clearTimeout(Bl));var e=t-_s;e>24?(t<1/0&&(Bl=setTimeout(tv,t-Fl.now()-Qu)),Dl&&(Dl=clearInterval(Dl))):(Dl||(Zu=Fl.now(),Dl=setInterval(tD,Z5)),yo=1,Q5(tv))}}function _d(t,e,r){var n=new ql;return e=e==null?0:+e,n.restart(i=>{n.stop(),t(i+e)},e,r),n}function rD(t,e,r){var n=new ql,i=e;return e==null?(n.restart(t,e,r),n):(n._restart=n.restart,n.restart=function(a,s,o){s=+s,o=o==null?Pl():+o,n._restart(function l(u){u+=i,n._restart(l,i+=s,o),a(u)},s,o)},n.restart(t,e,r),n)}var nD=fs("start","end","cancel","interrupt"),iD=[],ev=0,vd=1,xd=2,th=3,rv=4,kd=5,eh=6;function rh(t,e,r,n,i,a){var s=t.__transition;if(!s)t.__transition={};else if(r in s)return;aD(t,r,{name:e,index:n,group:i,on:nD,tween:iD,time:a.time,delay:a.delay,duration:a.duration,ease:a.ease,timer:null,state:ev})}function wd(t,e){var r=Jn(t,e);if(r.state>ev)throw new Error("too late; already scheduled");return r}function Ei(t,e){var r=Jn(t,e);if(r.state>th)throw new Error("too late; already running");return r}function Jn(t,e){var r=t.__transition;if(!r||!(r=r[e]))throw new Error("transition not found");return r}function aD(t,e,r){var n=t.__transition,i;n[e]=r,r.timer=Ju(a,0,r.time);function a(u){r.state=vd,r.timer.restart(s,r.delay,r.time),r.delay<=u&&s(u-r.delay)}function s(u){var h,d,f,p;if(r.state!==vd)return l();for(h in n)if(p=n[h],p.name===r.name){if(p.state===th)return _d(s);p.state===rv?(p.state=eh,p.timer.stop(),p.on.call("interrupt",t,t.__data__,p.index,p.group),delete n[h]):+hxd&&n.state=0&&(e=e.slice(0,r)),!e||e==="start"})}function DD(t,e,r){var n,i,a=BD(e)?wd:Ei;return function(){var s=a(this,t),o=s.on;o!==n&&(i=(n=o).copy()).on(e,r),s.on=i}}function OD(t,e){var r=this._id;return arguments.length<2?Jn(this.node(),r).on.on(t):this.each(DD(r,t,e))}function FD(t){return function(){var e=this.parentNode;for(var r in this.__transition)if(+r!==t)return;e&&e.removeChild(this)}}function PD(){return this.on("end.remove",FD(this._id))}function qD(t){var e=this._name,r=this._id;typeof t!="function"&&(t=Ru(t));for(var n=this._groups,i=n.length,a=new Array(i),s=0;s+t;function oO(t){return t*t}function lO(t){return t*(2-t)}function ov(t){return((t*=2)<=1?t*t:--t*(2-t)+1)/2}function cO(t){return t*t*t}function uO(t){return--t*t*t+1}function Ed(t){return((t*=2)<=1?t*t*t:(t-=2)*t*t+2)/2}var Cd=3,hO=function t(e){e=+e;function r(n){return Math.pow(n,e)}return r.exponent=t,r}(Cd),fO=function t(e){e=+e;function r(n){return 1-Math.pow(1-n,e)}return r.exponent=t,r}(Cd),lv=function t(e){e=+e;function r(n){return((n*=2)<=1?Math.pow(n,e):2-Math.pow(2-n,e))/2}return r.exponent=t,r}(Cd),cv=Math.PI,uv=cv/2;function dO(t){return+t==1?1:1-Math.cos(t*uv)}function pO(t){return Math.sin(t*uv)}function hv(t){return(1-Math.cos(cv*t))/2}function La(t){return(Math.pow(2,-10*t)-.0009765625)*1.0009775171065494}function gO(t){return La(1-+t)}function yO(t){return 1-La(t)}function fv(t){return((t*=2)<=1?La(1-t):2-La(t-1))/2}function mO(t){return 1-Math.sqrt(1-t*t)}function bO(t){return Math.sqrt(1- --t*t)}function dv(t){return((t*=2)<=1?1-Math.sqrt(1-t*t):Math.sqrt(1-(t-=2)*t)+1)/2}var Sd=4/11,_O=6/11,vO=8/11,xO=3/4,kO=9/11,wO=10/11,TO=15/16,EO=21/22,CO=63/64,nh=1/Sd/Sd;function SO(t){return 1-Vl(1-t)}function Vl(t){return(t=+t)vd&&n.name===e)return new Ci([[t]],OO,e,+i)}return null}const Rd=t=>()=>t;function PO(t,{sourceEvent:e,target:r,selection:n,mode:i,dispatch:a}){Object.defineProperties(this,{type:{value:t,enumerable:!0,configurable:!0},sourceEvent:{value:e,enumerable:!0,configurable:!0},target:{value:r,enumerable:!0,configurable:!0},selection:{value:n,enumerable:!0,configurable:!0},mode:{value:i,enumerable:!0,configurable:!0},_:{value:a}})}function qO(t){t.stopImmediatePropagation()}function Id(t){t.preventDefault(),t.stopImmediatePropagation()}var yv={name:"drag"},Nd={name:"space"},bo={name:"handle"},_o={name:"center"};const{abs:mv,max:Or,min:Fr}=Math;function bv(t){return[+t[0],+t[1]]}function Bd(t){return[bv(t[0]),bv(t[1])]}var ih={name:"x",handles:["w","e"].map(zl),input:function(t,e){return t==null?null:[[+t[0],e[0][1]],[+t[1],e[1][1]]]},output:function(t){return t&&[t[0][0],t[1][0]]}},ah={name:"y",handles:["n","s"].map(zl),input:function(t,e){return t==null?null:[[e[0][0],+t[0]],[e[1][0],+t[1]]]},output:function(t){return t&&[t[0][1],t[1][1]]}},VO={name:"xy",handles:["n","w","e","s","nw","ne","sw","se"].map(zl),input:function(t){return t==null?null:Bd(t)},output:function(t){return t}},Xi={overlay:"crosshair",selection:"move",n:"ns-resize",e:"ew-resize",s:"ns-resize",w:"ew-resize",nw:"nwse-resize",ne:"nesw-resize",se:"nwse-resize",sw:"nesw-resize"},_v={e:"w",w:"e",nw:"ne",ne:"nw",se:"sw",sw:"se"},vv={n:"s",s:"n",nw:"sw",ne:"se",se:"ne",sw:"nw"},zO={overlay:1,selection:1,n:null,e:1,s:null,w:-1,nw:-1,ne:1,se:1,sw:-1},YO={overlay:1,selection:1,n:-1,e:null,s:1,w:null,nw:-1,ne:-1,se:1,sw:1};function zl(t){return{type:t}}function UO(t){return!t.ctrlKey&&!t.button}function WO(){var t=this.ownerSVGElement||this;return t.hasAttribute("viewBox")?(t=t.viewBox.baseVal,[[t.x,t.y],[t.x+t.width,t.y+t.height]]):[[0,0],[t.width.baseVal.value,t.height.baseVal.value]]}function HO(){return navigator.maxTouchPoints||"ontouchstart"in this}function Dd(t){for(;!t.__brush;)if(!(t=t.parentNode))return;return t.__brush}function GO(t){return t[0][0]===t[1][0]||t[0][1]===t[1][1]}function jO(t){var e=t.__brush;return e?e.dim.output(e.selection):null}function $O(){return Od(ih)}function XO(){return Od(ah)}function KO(){return Od(VO)}function Od(t){var e=WO,r=UO,n=HO,i=!0,a=fs("start","brush","end"),s=6,o;function l(y){var b=y.property("__brush",_).selectAll(".overlay").data([zl("overlay")]);b.enter().append("rect").attr("class","overlay").attr("pointer-events","all").attr("cursor",Xi.overlay).merge(b).each(function(){var k=Dd(this).extent;St(this).attr("x",k[0][0]).attr("y",k[0][1]).attr("width",k[1][0]-k[0][0]).attr("height",k[1][1]-k[0][1])}),y.selectAll(".selection").data([zl("selection")]).enter().append("rect").attr("class","selection").attr("cursor",Xi.selection).attr("fill","#777").attr("fill-opacity",.3).attr("stroke","#fff").attr("shape-rendering","crispEdges");var x=y.selectAll(".handle").data(t.handles,function(k){return k.type});x.exit().remove(),x.enter().append("rect").attr("class",function(k){return"handle handle--"+k.type}).attr("cursor",function(k){return Xi[k.type]}),y.each(u).attr("fill","none").attr("pointer-events","all").on("mousedown.brush",f).filter(n).on("touchstart.brush",f).on("touchmove.brush",p).on("touchend.brush touchcancel.brush",m).style("touch-action","none").style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}l.move=function(y,b,x){y.tween?y.on("start.brush",function(k){h(this,arguments).beforestart().start(k)}).on("interrupt.brush end.brush",function(k){h(this,arguments).end(k)}).tween("brush",function(){var k=this,T=k.__brush,C=h(k,arguments),M=T.selection,S=t.input(typeof b=="function"?b.apply(this,arguments):b,T.extent),R=Ma(M,S);function A(L){T.selection=L===1&&S===null?null:R(L),u.call(k),C.brush()}return M!==null&&S!==null?A:A(1)}):y.each(function(){var k=this,T=arguments,C=k.__brush,M=t.input(typeof b=="function"?b.apply(k,T):b,C.extent),S=h(k,T).beforestart();vs(k),C.selection=M===null?null:M,u.call(k),S.start(x).brush(x).end(x)})},l.clear=function(y,b){l.move(y,null,b)};function u(){var y=St(this),b=Dd(this).selection;b?(y.selectAll(".selection").style("display",null).attr("x",b[0][0]).attr("y",b[0][1]).attr("width",b[1][0]-b[0][0]).attr("height",b[1][1]-b[0][1]),y.selectAll(".handle").style("display",null).attr("x",function(x){return x.type[x.type.length-1]==="e"?b[1][0]-s/2:b[0][0]-s/2}).attr("y",function(x){return x.type[0]==="s"?b[1][1]-s/2:b[0][1]-s/2}).attr("width",function(x){return x.type==="n"||x.type==="s"?b[1][0]-b[0][0]+s:s}).attr("height",function(x){return x.type==="e"||x.type==="w"?b[1][1]-b[0][1]+s:s})):y.selectAll(".selection,.handle").style("display","none").attr("x",null).attr("y",null).attr("width",null).attr("height",null)}function h(y,b,x){var k=y.__brush.emitter;return k&&(!x||!k.clean)?k:new d(y,b,x)}function d(y,b,x){this.that=y,this.args=b,this.state=y.__brush,this.active=0,this.clean=x}d.prototype={beforestart:function(){return++this.active===1&&(this.state.emitter=this,this.starting=!0),this},start:function(y,b){return this.starting?(this.starting=!1,this.emit("start",y,b)):this.emit("brush",y),this},brush:function(y,b){return this.emit("brush",y,b),this},end:function(y,b){return--this.active===0&&(delete this.state.emitter,this.emit("end",y,b)),this},emit:function(y,b,x){var k=St(this.that).datum();a.call(y,this.that,new PO(y,{sourceEvent:b,target:l,selection:t.output(this.state.selection),mode:x,dispatch:a}),k)}};function f(y){if(o&&!y.touches||!r.apply(this,arguments))return;var b=this,x=y.target.__data__.type,k=(i&&y.metaKey?x="overlay":x)==="selection"?yv:i&&y.altKey?_o:bo,T=t===ah?null:zO[x],C=t===ih?null:YO[x],M=Dd(b),S=M.extent,R=M.selection,A=S[0][0],L,v,B=S[0][1],w,D,N=S[1][0],z,X,ct=S[1][1],J,Y,$=0,lt=0,ut,W=T&&C&&i&&y.shiftKey,tt,K,it=Array.from(y.touches||[y],at=>{const It=at.identifier;return at=Tn(at,b),at.point0=at.slice(),at.identifier=It,at});vs(b);var Z=h(b,arguments,!0).beforestart();if(x==="overlay"){R&&(ut=!0);const at=[it[0],it[1]||it[0]];M.selection=R=[[L=t===ah?A:Fr(at[0][0],at[1][0]),w=t===ih?B:Fr(at[0][1],at[1][1])],[z=t===ah?N:Or(at[0][0],at[1][0]),J=t===ih?ct:Or(at[0][1],at[1][1])]],it.length>1&&F(y)}else L=R[0][0],w=R[0][1],z=R[1][0],J=R[1][1];v=L,D=w,X=z,Y=J;var V=St(b).attr("pointer-events","none"),Q=V.selectAll(".overlay").attr("cursor",Xi[x]);if(y.touches)Z.moved=U,Z.ended=j;else{var q=St(y.view).on("mousemove.brush",U,!0).on("mouseup.brush",j,!0);i&&q.on("keydown.brush",P,!0).on("keyup.brush",et,!0),Bu(y.view)}u.call(b),Z.start(y,k.name);function U(at){for(const It of at.changedTouches||[at])for(const Lt of it)Lt.identifier===It.identifier&&(Lt.cur=Tn(It,b));if(W&&!tt&&!K&&it.length===1){const It=it[0];mv(It.cur[0]-It[0])>mv(It.cur[1]-It[1])?K=!0:tt=!0}for(const It of it)It.cur&&(It[0]=It.cur[0],It[1]=It.cur[1]);ut=!0,Id(at),F(at)}function F(at){const It=it[0],Lt=It.point0;var Rt;switch($=It[0]-Lt[0],lt=It[1]-Lt[1],k){case Nd:case yv:{T&&($=Or(A-L,Fr(N-z,$)),v=L+$,X=z+$),C&&(lt=Or(B-w,Fr(ct-J,lt)),D=w+lt,Y=J+lt);break}case bo:{it[1]?(T&&(v=Or(A,Fr(N,it[0][0])),X=Or(A,Fr(N,it[1][0])),T=1),C&&(D=Or(B,Fr(ct,it[0][1])),Y=Or(B,Fr(ct,it[1][1])),C=1)):(T<0?($=Or(A-L,Fr(N-L,$)),v=L+$,X=z):T>0&&($=Or(A-z,Fr(N-z,$)),v=L,X=z+$),C<0?(lt=Or(B-w,Fr(ct-w,lt)),D=w+lt,Y=J):C>0&&(lt=Or(B-J,Fr(ct-J,lt)),D=w,Y=J+lt));break}case _o:{T&&(v=Or(A,Fr(N,L-$*T)),X=Or(A,Fr(N,z+$*T))),C&&(D=Or(B,Fr(ct,w-lt*C)),Y=Or(B,Fr(ct,J+lt*C)));break}}X0&&(L=v-$),C<0?J=Y-lt:C>0&&(w=D-lt),k=Nd,Q.attr("cursor",Xi.selection),F(at));break}default:return}Id(at)}function et(at){switch(at.keyCode){case 16:{W&&(tt=K=W=!1,F(at));break}case 18:{k===_o&&(T<0?z=X:T>0&&(L=v),C<0?J=Y:C>0&&(w=D),k=bo,F(at));break}case 32:{k===Nd&&(at.altKey?(T&&(z=X-$*T,L=v+$*T),C&&(J=Y-lt*C,w=D+lt*C),k=_o):(T<0?z=X:T>0&&(L=v),C<0?J=Y:C>0&&(w=D),k=bo),Q.attr("cursor",Xi[x]),F(at));break}default:return}Id(at)}}function p(y){h(this,arguments).moved(y)}function m(y){h(this,arguments).ended(y)}function _(){var y=this.__brush||{selection:null};return y.extent=Bd(e.apply(this,arguments)),y.dim=t,y}return l.extent=function(y){return arguments.length?(e=typeof y=="function"?y:Rd(Bd(y)),l):e},l.filter=function(y){return arguments.length?(r=typeof y=="function"?y:Rd(!!y),l):r},l.touchable=function(y){return arguments.length?(n=typeof y=="function"?y:Rd(!!y),l):n},l.handleSize=function(y){return arguments.length?(s=+y,l):s},l.keyModifiers=function(y){return arguments.length?(i=!!y,l):i},l.on=function(){var y=a.on.apply(a,arguments);return y===a?l:y},l}var xv=Math.abs,vo=Math.cos,xo=Math.sin,kv=Math.PI,sh=kv/2,wv=kv*2,Tv=Math.max,Fd=1e-12;function Pd(t,e){return Array.from({length:e-t},(r,n)=>t+n)}function ZO(t){return function(e,r){return t(e.source.value+e.target.value,r.source.value+r.target.value)}}function QO(){return qd(!1,!1)}function JO(){return qd(!1,!0)}function tF(){return qd(!0,!1)}function qd(t,e){var r=0,n=null,i=null,a=null;function s(o){var l=o.length,u=new Array(l),h=Pd(0,l),d=new Array(l*l),f=new Array(l),p=0,m;o=Float64Array.from({length:l*l},e?(_,y)=>o[y%l][y/l|0]:(_,y)=>o[y/l|0][y%l]);for(let _=0;_n(u[y],u[b]));for(const y of h){const b=_;if(t){const x=Pd(~l+1,l).filter(k=>k<0?o[~k*l+y]:o[y*l+k]);i&&x.sort((k,T)=>i(k<0?-o[~k*l+y]:o[y*l+k],T<0?-o[~T*l+y]:o[y*l+T]));for(const k of x)if(k<0){const T=d[~k*l+y]||(d[~k*l+y]={source:null,target:null});T.target={index:y,startAngle:_,endAngle:_+=o[~k*l+y]*p,value:o[~k*l+y]}}else{const T=d[y*l+k]||(d[y*l+k]={source:null,target:null});T.source={index:y,startAngle:_,endAngle:_+=o[y*l+k]*p,value:o[y*l+k]}}f[y]={index:y,startAngle:b,endAngle:_,value:u[y]}}else{const x=Pd(0,l).filter(k=>o[y*l+k]||o[k*l+y]);i&&x.sort((k,T)=>i(o[y*l+k],o[y*l+T]));for(const k of x){let T;if(yxs)if(!(Math.abs(h*o-l*u)>xs)||!i)this._+="L"+(this._x1=t)+","+(this._y1=e);else{var f=r-a,p=n-s,m=o*o+l*l,_=f*f+p*p,y=Math.sqrt(m),b=Math.sqrt(d),x=i*Math.tan((Vd-Math.acos((m+d-_)/(2*y*b)))/2),k=x/b,T=x/y;Math.abs(k-1)>xs&&(this._+="L"+(t+k*u)+","+(e+k*h)),this._+="A"+i+","+i+",0,0,"+ +(h*f>u*p)+","+(this._x1=t+T*o)+","+(this._y1=e+T*l)}},arc:function(t,e,r,n,i,a){t=+t,e=+e,r=+r,a=!!a;var s=r*Math.cos(n),o=r*Math.sin(n),l=t+s,u=e+o,h=1^a,d=a?n-i:i-n;if(r<0)throw new Error("negative radius: "+r);this._x1===null?this._+="M"+l+","+u:(Math.abs(this._x1-l)>xs||Math.abs(this._y1-u)>xs)&&(this._+="L"+l+","+u),r&&(d<0&&(d=d%zd+zd),d>eF?this._+="A"+r+","+r+",0,1,"+h+","+(t-s)+","+(e-o)+"A"+r+","+r+",0,1,"+h+","+(this._x1=l)+","+(this._y1=u):d>xs&&(this._+="A"+r+","+r+",0,"+ +(d>=Vd)+","+h+","+(this._x1=t+r*Math.cos(i))+","+(this._y1=e+r*Math.sin(i))))},rect:function(t,e,r,n){this._+="M"+(this._x0=this._x1=+t)+","+(this._y0=this._y1=+e)+"h"+ +r+"v"+ +n+"h"+-r+"Z"},toString:function(){return this._}};var rF=Array.prototype.slice;function ks(t){return function(){return t}}function nF(t){return t.source}function iF(t){return t.target}function Ev(t){return t.radius}function aF(t){return t.startAngle}function sF(t){return t.endAngle}function oF(){return 0}function lF(){return 10}function Cv(t){var e=nF,r=iF,n=Ev,i=Ev,a=aF,s=sF,o=oF,l=null;function u(){var h,d=e.apply(this,arguments),f=r.apply(this,arguments),p=o.apply(this,arguments)/2,m=rF.call(arguments),_=+n.apply(this,(m[0]=d,m)),y=a.apply(this,m)-sh,b=s.apply(this,m)-sh,x=+i.apply(this,(m[0]=f,m)),k=a.apply(this,m)-sh,T=s.apply(this,m)-sh;if(l||(l=h=Ra()),p>Fd&&(xv(b-y)>p*2+Fd?b>y?(y+=p,b-=p):(y-=p,b+=p):y=b=(y+b)/2,xv(T-k)>p*2+Fd?T>k?(k+=p,T-=p):(k-=p,T+=p):k=T=(k+T)/2),l.moveTo(_*vo(y),_*xo(y)),l.arc(0,0,_,y,b),y!==k||b!==T)if(t){var C=+t.apply(this,arguments),M=x-C,S=(k+T)/2;l.quadraticCurveTo(0,0,M*vo(k),M*xo(k)),l.lineTo(x*vo(S),x*xo(S)),l.lineTo(M*vo(T),M*xo(T))}else l.quadraticCurveTo(0,0,x*vo(k),x*xo(k)),l.arc(0,0,x,k,T);if(l.quadraticCurveTo(0,0,_*vo(y),_*xo(y)),l.closePath(),h)return l=null,h+""||null}return t&&(u.headRadius=function(h){return arguments.length?(t=typeof h=="function"?h:ks(+h),u):t}),u.radius=function(h){return arguments.length?(n=i=typeof h=="function"?h:ks(+h),u):n},u.sourceRadius=function(h){return arguments.length?(n=typeof h=="function"?h:ks(+h),u):n},u.targetRadius=function(h){return arguments.length?(i=typeof h=="function"?h:ks(+h),u):i},u.startAngle=function(h){return arguments.length?(a=typeof h=="function"?h:ks(+h),u):a},u.endAngle=function(h){return arguments.length?(s=typeof h=="function"?h:ks(+h),u):s},u.padAngle=function(h){return arguments.length?(o=typeof h=="function"?h:ks(+h),u):o},u.source=function(h){return arguments.length?(e=h,u):e},u.target=function(h){return arguments.length?(r=h,u):r},u.context=function(h){return arguments.length?(l=h==null?null:h,u):l},u}function cF(){return Cv()}function uF(){return Cv(lF)}var hF=Array.prototype,Sv=hF.slice;function fF(t,e){return t-e}function dF(t){for(var e=0,r=t.length,n=t[r-1][1]*t[0][0]-t[r-1][0]*t[0][1];++e()=>t;function pF(t,e){for(var r=-1,n=e.length,i;++rn!=p>n&&r<(f-u)*(n-h)/(p-h)+u&&(i=-i)}return i}function yF(t,e,r){var n;return mF(t,e,r)&&bF(t[n=+(t[0]===e[0])],r[n],e[n])}function mF(t,e,r){return(e[0]-t[0])*(r[1]-t[1])===(r[0]-t[0])*(e[1]-t[1])}function bF(t,e,r){return t<=e&&e<=r||r<=e&&e<=t}function _F(){}var Ki=[[],[[[1,1.5],[.5,1]]],[[[1.5,1],[1,1.5]]],[[[1.5,1],[.5,1]]],[[[1,.5],[1.5,1]]],[[[1,1.5],[.5,1]],[[1,.5],[1.5,1]]],[[[1,.5],[1,1.5]]],[[[1,.5],[.5,1]]],[[[.5,1],[1,.5]]],[[[1,1.5],[1,.5]]],[[[.5,1],[1,.5]],[[1.5,1],[1,1.5]]],[[[1.5,1],[1,.5]]],[[[.5,1],[1.5,1]]],[[[1,1.5],[1.5,1]]],[[[.5,1],[1,1.5]]],[]];function Ud(){var t=1,e=1,r=W0,n=l;function i(u){var h=r(u);if(Array.isArray(h))h=h.slice().sort(fF);else{const d=xl(u),f=wl(d[0],d[1],h);h=hs(Math.floor(d[0]/f)*f,Math.floor(d[1]/f-1)*f,h)}return h.map(d=>a(u,d))}function a(u,h){var d=[],f=[];return s(u,h,function(p){n(p,u,h),dF(p)>0?d.push([p]):f.push(p)}),f.forEach(function(p){for(var m=0,_=d.length,y;m<_;++m)if(pF((y=d[m])[0],p)!==-1){y.push(p);return}}),{type:"MultiPolygon",value:h,coordinates:d}}function s(u,h,d){var f=new Array,p=new Array,m,_,y,b,x,k;for(m=_=-1,b=u[0]>=h,Ki[b<<1].forEach(T);++m=h,Ki[y|b<<1].forEach(T);for(Ki[b<<0].forEach(T);++_=h,x=u[_*t]>=h,Ki[b<<1|x<<2].forEach(T);++m=h,k=x,x=u[_*t+m+1]>=h,Ki[y|b<<1|x<<2|k<<3].forEach(T);Ki[b|x<<3].forEach(T)}for(m=-1,x=u[_*t]>=h,Ki[x<<2].forEach(T);++m=h,Ki[x<<2|k<<3].forEach(T);Ki[x<<3].forEach(T);function T(C){var M=[C[0][0]+m,C[0][1]+_],S=[C[1][0]+m,C[1][1]+_],R=o(M),A=o(S),L,v;(L=p[R])?(v=f[A])?(delete p[L.end],delete f[v.start],L===v?(L.ring.push(S),d(L.ring)):f[L.start]=p[v.end]={start:L.start,end:v.end,ring:L.ring.concat(v.ring)}):(delete p[L.end],L.ring.push(S),p[L.end=A]=L):(L=f[A])?(v=p[R])?(delete f[L.start],delete p[v.end],L===v?(L.ring.push(S),d(L.ring)):f[v.start]=p[L.end]={start:v.start,end:L.end,ring:v.ring.concat(L.ring)}):(delete f[L.start],L.ring.unshift(M),f[L.start=R]=L):f[R]=p[A]={start:R,end:A,ring:[M,S]}}}function o(u){return u[0]*2+u[1]*(t+1)*4}function l(u,h,d){u.forEach(function(f){var p=f[0],m=f[1],_=p|0,y=m|0,b,x=h[y*t+_];p>0&&p0&&m=0&&d>=0))throw new Error("invalid size");return t=h,e=d,i},i.thresholds=function(u){return arguments.length?(r=typeof u=="function"?u:Array.isArray(u)?Ia(Sv.call(u)):Ia(u),i):r},i.smooth=function(u){return arguments.length?(n=u?l:_F,i):n===l},i}function vF(t){return t[0]}function xF(t){return t[1]}function kF(){return 1}function wF(){var t=vF,e=xF,r=kF,n=960,i=500,a=20,s=2,o=a*3,l=n+o*2>>s,u=i+o*2>>s,h=Ia(20);function d(x){var k=new Float32Array(l*u),T=Math.pow(2,-s),C=-1;for(const w of x){var M=(t(w,++C,x)+o)*T,S=(e(w,C,x)+o)*T,R=+r(w,C,x);if(M>=0&&M=0&&SM*C))(k).map((M,S)=>(M.value=+T[S],p(M)))}f.contours=function(x){var k=d(x),T=Ud().size([l,u]),C=Math.pow(2,2*s),M=S=>{S=+S;var R=p(T.contour(k,S*C));return R.value=S,R};return Object.defineProperty(M,"max",{get:()=>lo(k)/C}),M};function p(x){return x.coordinates.forEach(m),x}function m(x){x.forEach(_)}function _(x){x.forEach(y)}function y(x){x[0]=x[0]*Math.pow(2,s)-o,x[1]=x[1]*Math.pow(2,s)-o}function b(){return o=a*3,l=n+o*2>>s,u=i+o*2>>s,f}return f.x=function(x){return arguments.length?(t=typeof x=="function"?x:Ia(+x),f):t},f.y=function(x){return arguments.length?(e=typeof x=="function"?x:Ia(+x),f):e},f.weight=function(x){return arguments.length?(r=typeof x=="function"?x:Ia(+x),f):r},f.size=function(x){if(!arguments.length)return[n,i];var k=+x[0],T=+x[1];if(!(k>=0&&T>=0))throw new Error("invalid size");return n=k,i=T,b()},f.cellSize=function(x){if(!arguments.length)return 1<=1))throw new Error("invalid cell size");return s=Math.floor(Math.log(x)/Math.LN2),b()},f.thresholds=function(x){return arguments.length?(h=typeof x=="function"?x:Array.isArray(x)?Ia(Sv.call(x)):Ia(x),f):h},f.bandwidth=function(x){if(!arguments.length)return Math.sqrt(a*(a+1));if(!((x=+x)>=0))throw new Error("invalid bandwidth");return a=(Math.sqrt(4*x*x+1)-1)/2,b()},f}const Zi=11102230246251565e-32,Pr=134217729,TF=(3+8*Zi)*Zi;function Wd(t,e,r,n,i){let a,s,o,l,u=e[0],h=n[0],d=0,f=0;h>u==h>-u?(a=u,u=e[++d]):(a=h,h=n[++f]);let p=0;if(du==h>-u?(s=u+a,o=a-(s-u),u=e[++d]):(s=h+a,o=a-(s-h),h=n[++f]),a=s,o!==0&&(i[p++]=o);du==h>-u?(s=a+u,l=s-a,o=a-(s-l)+(u-l),u=e[++d]):(s=a+h,l=s-a,o=a-(s-l)+(h-l),h=n[++f]),a=s,o!==0&&(i[p++]=o);for(;d=D||-w>=D||(d=t-A,o=t-(A+d)+(d-i),d=r-L,u=r-(L+d)+(d-i),d=e-v,l=e-(v+d)+(d-a),d=n-B,h=n-(B+d)+(d-a),o===0&&l===0&&u===0&&h===0)||(D=AF*s+TF*Math.abs(w),w+=A*h+B*o-(v*u+L*l),w>=D||-w>=D))return w;T=o*B,f=Pr*o,p=f-(f-o),m=o-p,f=Pr*B,_=f-(f-B),y=B-_,C=m*y-(T-p*_-m*_-p*y),M=l*L,f=Pr*l,p=f-(f-l),m=l-p,f=Pr*L,_=f-(f-L),y=L-_,S=m*y-(M-p*_-m*_-p*y),b=C-S,d=C-b,Xr[0]=C-(b+d)+(d-S),x=T+b,d=x-T,k=T-(x-d)+(b-d),b=k-M,d=k-b,Xr[1]=k-(b+d)+(d-M),R=x+b,d=R-x,Xr[2]=x-(R-d)+(b-d),Xr[3]=R;const N=Wd(4,ko,4,Xr,Av);T=A*h,f=Pr*A,p=f-(f-A),m=A-p,f=Pr*h,_=f-(f-h),y=h-_,C=m*y-(T-p*_-m*_-p*y),M=v*u,f=Pr*v,p=f-(f-v),m=v-p,f=Pr*u,_=f-(f-u),y=u-_,S=m*y-(M-p*_-m*_-p*y),b=C-S,d=C-b,Xr[0]=C-(b+d)+(d-S),x=T+b,d=x-T,k=T-(x-d)+(b-d),b=k-M,d=k-b,Xr[1]=k-(b+d)+(d-M),R=x+b,d=R-x,Xr[2]=x-(R-d)+(b-d),Xr[3]=R;const z=Wd(N,Av,4,Xr,Mv);T=o*h,f=Pr*o,p=f-(f-o),m=o-p,f=Pr*h,_=f-(f-h),y=h-_,C=m*y-(T-p*_-m*_-p*y),M=l*u,f=Pr*l,p=f-(f-l),m=l-p,f=Pr*u,_=f-(f-u),y=u-_,S=m*y-(M-p*_-m*_-p*y),b=C-S,d=C-b,Xr[0]=C-(b+d)+(d-S),x=T+b,d=x-T,k=T-(x-d)+(b-d),b=k-M,d=k-b,Xr[1]=k-(b+d)+(d-M),R=x+b,d=R-x,Xr[2]=x-(R-d)+(b-d),Xr[3]=R;const X=Wd(z,Mv,4,Xr,Lv);return Lv[X-1]}function oh(t,e,r,n,i,a){const s=(e-a)*(r-i),o=(t-i)*(n-a),l=s-o;if(s===0||o===0||s>0!=o>0)return l;const u=Math.abs(s+o);return Math.abs(l)>=CF*u?l:-MF(t,e,r,n,i,a,u)}const Rv=Math.pow(2,-52),lh=new Uint32Array(512);class ch{static from(e,r=BF,n=DF){const i=e.length,a=new Float64Array(i*2);for(let s=0;s>1;if(r>0&&typeof e[0]!="number")throw new Error("Expected coords to contain numbers.");this.coords=e;const n=Math.max(2*r-5,0);this._triangles=new Uint32Array(n*3),this._halfedges=new Int32Array(n*3),this._hashSize=Math.ceil(Math.sqrt(r)),this._hullPrev=new Uint32Array(r),this._hullNext=new Uint32Array(r),this._hullTri=new Uint32Array(r),this._hullHash=new Int32Array(this._hashSize).fill(-1),this._ids=new Uint32Array(r),this._dists=new Float64Array(r),this.update()}update(){const{coords:e,_hullPrev:r,_hullNext:n,_hullTri:i,_hullHash:a}=this,s=e.length>>1;let o=1/0,l=1/0,u=-1/0,h=-1/0;for(let L=0;Lu&&(u=v),B>h&&(h=B),this._ids[L]=L}const d=(o+u)/2,f=(l+h)/2;let p=1/0,m,_,y;for(let L=0;L0&&(_=L,p=v)}let k=e[2*_],T=e[2*_+1],C=1/0;for(let L=0;Lw&&(L[v++]=D,w=this._dists[D])}this.hull=L.subarray(0,v),this.triangles=new Uint32Array(0),this.halfedges=new Uint32Array(0);return}if(oh(b,x,k,T,M,S)<0){const L=_,v=k,B=T;_=y,k=M,T=S,y=L,M=v,S=B}const R=NF(b,x,k,T,M,S);this._cx=R.x,this._cy=R.y;for(let L=0;L0&&Math.abs(D-v)<=Rv&&Math.abs(N-B)<=Rv||(v=D,B=N,w===m||w===_||w===y))continue;let z=0;for(let $=0,lt=this._hashKey(D,N);$=0;)if(X=ct,X===z){X=-1;break}if(X===-1)continue;let J=this._addTriangle(X,w,n[X],-1,-1,i[X]);i[w]=this._legalize(J+2),i[X]=J,A++;let Y=n[X];for(;ct=n[Y],oh(D,N,e[2*Y],e[2*Y+1],e[2*ct],e[2*ct+1])<0;)J=this._addTriangle(Y,w,ct,i[w],-1,i[Y]),i[w]=this._legalize(J+2),n[Y]=Y,A--,Y=ct;if(X===z)for(;ct=r[X],oh(D,N,e[2*ct],e[2*ct+1],e[2*X],e[2*X+1])<0;)J=this._addTriangle(ct,w,X,-1,i[X],i[ct]),this._legalize(J+2),i[ct]=J,n[X]=X,A--,X=ct;this._hullStart=r[w]=X,n[X]=r[Y]=w,n[w]=Y,a[this._hashKey(D,N)]=w,a[this._hashKey(e[2*X],e[2*X+1])]=X}this.hull=new Uint32Array(A);for(let L=0,v=this._hullStart;L0?3-r:1+r)/4}function Hd(t,e,r,n){const i=t-r,a=e-n;return i*i+a*a}function RF(t,e,r,n,i,a,s,o){const l=t-s,u=e-o,h=r-s,d=n-o,f=i-s,p=a-o,m=l*l+u*u,_=h*h+d*d,y=f*f+p*p;return l*(d*y-_*p)-u*(h*y-_*f)+m*(h*p-d*f)<0}function IF(t,e,r,n,i,a){const s=r-t,o=n-e,l=i-t,u=a-e,h=s*s+o*o,d=l*l+u*u,f=.5/(s*u-o*l),p=(u*h-o*d)*f,m=(s*d-l*h)*f;return p*p+m*m}function NF(t,e,r,n,i,a){const s=r-t,o=n-e,l=i-t,u=a-e,h=s*s+o*o,d=l*l+u*u,f=.5/(s*u-o*l),p=t+(u*h-o*d)*f,m=e+(s*d-l*h)*f;return{x:p,y:m}}function wo(t,e,r,n){if(n-r<=20)for(let i=r+1;i<=n;i++){const a=t[i],s=e[a];let o=i-1;for(;o>=r&&e[t[o]]>s;)t[o+1]=t[o--];t[o+1]=a}else{const i=r+n>>1;let a=r+1,s=n;Ul(t,i,a),e[t[r]]>e[t[n]]&&Ul(t,r,n),e[t[a]]>e[t[n]]&&Ul(t,a,n),e[t[r]]>e[t[a]]&&Ul(t,r,a);const o=t[a],l=e[o];for(;;){do a++;while(e[t[a]]l);if(s=s-r?(wo(t,e,a,n),wo(t,e,r,s-1)):(wo(t,e,r,s-1),wo(t,e,a,n))}}function Ul(t,e,r){const n=t[e];t[e]=t[r],t[r]=n}function BF(t){return t[0]}function DF(t){return t[1]}const Iv=1e-6;class ws{constructor(){this._x0=this._y0=this._x1=this._y1=null,this._=""}moveTo(e,r){this._+=`M${this._x0=this._x1=+e},${this._y0=this._y1=+r}`}closePath(){this._x1!==null&&(this._x1=this._x0,this._y1=this._y0,this._+="Z")}lineTo(e,r){this._+=`L${this._x1=+e},${this._y1=+r}`}arc(e,r,n){e=+e,r=+r,n=+n;const i=e+n,a=r;if(n<0)throw new Error("negative radius");this._x1===null?this._+=`M${i},${a}`:(Math.abs(this._x1-i)>Iv||Math.abs(this._y1-a)>Iv)&&(this._+="L"+i+","+a),n&&(this._+=`A${n},${n},0,1,1,${e-n},${r}A${n},${n},0,1,1,${this._x1=i},${this._y1=a}`)}rect(e,r,n,i){this._+=`M${this._x0=this._x1=+e},${this._y0=this._y1=+r}h${+n}v${+i}h${-n}Z`}value(){return this._||null}}class Gd{constructor(){this._=[]}moveTo(e,r){this._.push([e,r])}closePath(){this._.push(this._[0].slice())}lineTo(e,r){this._.push([e,r])}value(){return this._.length?this._:null}}class Nv{constructor(e,[r,n,i,a]=[0,0,960,500]){if(!((i=+i)>=(r=+r))||!((a=+a)>=(n=+n)))throw new Error("invalid bounds");this.delaunay=e,this._circumcenters=new Float64Array(e.points.length*2),this.vectors=new Float64Array(e.points.length*2),this.xmax=i,this.xmin=r,this.ymax=a,this.ymin=n,this._init()}update(){return this.delaunay.update(),this._init(),this}_init(){const{delaunay:{points:e,hull:r,triangles:n},vectors:i}=this,a=this.circumcenters=this._circumcenters.subarray(0,n.length/3*2);for(let p=0,m=0,_=n.length,y,b;p<_;p+=3,m+=2){const x=n[p]*2,k=n[p+1]*2,T=n[p+2]*2,C=e[x],M=e[x+1],S=e[k],R=e[k+1],A=e[T],L=e[T+1],v=S-C,B=R-M,w=A-C,D=L-M,N=(v*D-B*w)*2;if(Math.abs(N)<1e-9){let z=1e9;const X=n[0]*2;z*=Math.sign((e[X]-C)*D-(e[X+1]-M)*w),y=(C+A)/2-z*D,b=(M+L)/2+z*w}else{const z=1/N,X=v*v+B*B,ct=w*w+D*D;y=C+(D*X-B*ct)*z,b=M+(v*ct-w*X)*z}a[m]=y,a[m+1]=b}let s=r[r.length-1],o,l=s*4,u,h=e[2*s],d,f=e[2*s+1];i.fill(0);for(let p=0;p1;)a-=2;for(let s=2;s4)for(let s=0;s0){if(r>=this.ymax)return null;(s=(this.ymax-r)/i)0){if(e>=this.xmax)return null;(s=(this.xmax-e)/n)this.xmax?2:0)|(rthis.ymax?8:0)}}const OF=2*Math.PI,To=Math.pow;function FF(t){return t[0]}function PF(t){return t[1]}function qF(t){const{triangles:e,coords:r}=t;for(let n=0;n1e-10)return!1}return!0}function VF(t,e,r){return[t+Math.sin(t+e)*r,e+Math.cos(t-e)*r]}class jd{static from(e,r=FF,n=PF,i){return new jd("length"in e?zF(e,r,n,i):Float64Array.from(YF(e,r,n,i)))}constructor(e){this._delaunator=new ch(e),this.inedges=new Int32Array(e.length/2),this._hullIndex=new Int32Array(e.length/2),this.points=this._delaunator.coords,this._init()}update(){return this._delaunator.update(),this._init(),this}_init(){const e=this._delaunator,r=this.points;if(e.hull&&e.hull.length>2&&qF(e)){this.collinear=Int32Array.from({length:r.length/2},(f,p)=>p).sort((f,p)=>r[2*f]-r[2*p]||r[2*f+1]-r[2*p+1]);const l=this.collinear[0],u=this.collinear[this.collinear.length-1],h=[r[2*l],r[2*l+1],r[2*u],r[2*u+1]],d=1e-8*Math.hypot(h[3]-h[1],h[2]-h[0]);for(let f=0,p=r.length/2;f0&&(this.triangles=new Int32Array(3).fill(-1),this.halfedges=new Int32Array(3).fill(-1),this.triangles[0]=i[0],s[i[0]]=1,i.length===2&&(s[i[1]]=0,this.triangles[1]=i[1],this.triangles[2]=i[1]))}voronoi(e){return new Nv(this,e)}*neighbors(e){const{inedges:r,hull:n,_hullIndex:i,halfedges:a,triangles:s,collinear:o}=this;if(o){const d=o.indexOf(e);d>0&&(yield o[d-1]),d=0&&a!==n&&a!==i;)n=a;return a}_step(e,r,n){const{inedges:i,hull:a,_hullIndex:s,halfedges:o,triangles:l,points:u}=this;if(i[e]===-1||!u.length)return(e+1)%(u.length>>1);let h=e,d=To(r-u[e*2],2)+To(n-u[e*2+1],2);const f=i[e];let p=f;do{let m=l[p];const _=To(r-u[m*2],2)+To(n-u[m*2+1],2);if(_9999?"+"+dn(t,6):dn(t,4)}function HF(t){var e=t.getUTCHours(),r=t.getUTCMinutes(),n=t.getUTCSeconds(),i=t.getUTCMilliseconds();return isNaN(t)?"Invalid Date":WF(t.getUTCFullYear())+"-"+dn(t.getUTCMonth()+1,2)+"-"+dn(t.getUTCDate(),2)+(i?"T"+dn(e,2)+":"+dn(r,2)+":"+dn(n,2)+"."+dn(i,3)+"Z":n?"T"+dn(e,2)+":"+dn(r,2)+":"+dn(n,2)+"Z":r||e?"T"+dn(e,2)+":"+dn(r,2)+"Z":"")}function uh(t){var e=new RegExp('["'+t+` +\r]`),r=t.charCodeAt(0);function n(d,f){var p,m,_=i(d,function(y,b){if(p)return p(y,b-1);m=y,p=f?UF(y,f):Dv(y)});return _.columns=m||[],_}function i(d,f){var p=[],m=d.length,_=0,y=0,b,x=m<=0,k=!1;d.charCodeAt(m-1)===Wl&&--m,d.charCodeAt(m-1)===Kd&&--m;function T(){if(x)return $d;if(k)return k=!1,Bv;var M,S=_,R;if(d.charCodeAt(S)===Xd){for(;_++=m?x=!0:(R=d.charCodeAt(_++))===Wl?k=!0:R===Kd&&(k=!0,d.charCodeAt(_)===Wl&&++_),d.slice(S+1,M-1).replace(/""/g,'"')}for(;_hh(e,r).then(n=>new DOMParser().parseFromString(n,t))}const mP=Zd("application/xml");var bP=Zd("text/html"),_P=Zd("image/svg+xml");function vP(t,e){var r,n=1;t==null&&(t=0),e==null&&(e=0);function i(){var a,s=r.length,o,l=0,u=0;for(a=0;a=(d=(o+u)/2))?o=d:u=d,(y=r>=(f=(l+h)/2))?l=f:h=f,i=a,!(a=a[b=y<<1|_]))return i[b]=s,t;if(p=+t._x.call(null,a.data),m=+t._y.call(null,a.data),e===p&&r===m)return s.next=a,i?i[b]=s:t._root=s,t;do i=i?i[b]=new Array(4):t._root=new Array(4),(_=e>=(d=(o+u)/2))?o=d:u=d,(y=r>=(f=(l+h)/2))?l=f:h=f;while((b=y<<1|_)===(x=(m>=f)<<1|p>=d));return i[x]=a,i[b]=s,t}function kP(t){var e,r,n=t.length,i,a,s=new Array(n),o=new Array(n),l=1/0,u=1/0,h=-1/0,d=-1/0;for(r=0;rh&&(h=i),ad&&(d=a));if(l>h||u>d)return this;for(this.cover(l,u).cover(h,d),r=0;rt||t>=i||n>e||e>=a;)switch(u=(eh||(o=m.y0)>d||(l=m.x1)=b)<<1|t>=y)&&(m=f[f.length-1],f[f.length-1]=f[f.length-1-_],f[f.length-1-_]=m)}else{var x=t-+this._x.call(null,p.data),k=e-+this._y.call(null,p.data),T=x*x+k*k;if(T=(f=(s+l)/2))?s=f:l=f,(_=d>=(p=(o+u)/2))?o=p:u=p,e=r,!(r=r[y=_<<1|m]))return this;if(!r.length)break;(e[y+1&3]||e[y+2&3]||e[y+3&3])&&(n=e,b=y)}for(;r.data!==t;)if(i=r,!(r=r.next))return this;return(a=r.next)&&delete r.next,i?(a?i.next=a:delete i.next,this):e?(a?e[y]=a:delete e[y],(r=e[0]||e[1]||e[2]||e[3])&&r===(e[3]||e[2]||e[1]||e[0])&&!r.length&&(n?n[b]=r:this._root=r),this):(this._root=a,this)}function AP(t){for(var e=0,r=t.length;ef.index){var v=p-R.x-R.vx,B=m-R.y-R.vy,w=v*v+B*B;wp+L||Mm+L||Su.r&&(u.r=u[h].r)}function l(){if(!!e){var u,h=e.length,d;for(r=new Array(h),u=0;u[e(C,M,s),C])),T;for(y=0,o=new Array(b);y(t=(YP*t+UP)%Uv)/Uv}function HP(t){return t.x}function GP(t){return t.y}var jP=10,$P=Math.PI*(3-Math.sqrt(5));function XP(t){var e,r=1,n=.001,i=1-Math.pow(n,1/300),a=0,s=.6,o=new Map,l=Ju(d),u=fs("tick","end"),h=WP();t==null&&(t=[]);function d(){f(),u.call("tick",e),r1?(y==null?o.delete(_):o.set(_,m(y)),e):o.get(_)},find:function(_,y,b){var x=0,k=t.length,T,C,M,S,R;for(b==null?b=1/0:b*=b,x=0;x1?(u.on(_,y),e):u.on(_)}}}function KP(){var t,e,r,n,i=vr(-30),a,s=1,o=1/0,l=.81;function u(p){var m,_=t.length,y=fh(t,HP,GP).visitAfter(d);for(n=p,m=0;m<_;++m)e=t[m],y.visit(f)}function h(){if(!!t){var p,m=t.length,_;for(a=new Array(m),p=0;p=o)return;(p.data!==e||p.next)&&(b===0&&(b=Na(r),T+=b*b),x===0&&(x=Na(r),T+=x*x),T=1e21?t.toLocaleString("en").replace(/,/g,""):t.toString(10)}function dh(t,e){if((r=(t=e?t.toExponential(e-1):t.toExponential()).indexOf("e"))<0)return null;var r,n=t.slice(0,r);return[n.length>1?n[0]+n.slice(2):n,+t.slice(r+1)]}function Eo(t){return t=dh(Math.abs(t)),t?t[1]:NaN}function eq(t,e){return function(r,n){for(var i=r.length,a=[],s=0,o=t[0],l=0;i>0&&o>0&&(l+o+1>n&&(o=Math.max(1,n-l)),a.push(r.substring(i-=o,i+o)),!((l+=o+1)>n));)o=t[s=(s+1)%t.length];return a.reverse().join(e)}}function rq(t){return function(e){return e.replace(/[0-9]/g,function(r){return t[+r]})}}var nq=/^(?:(.)?([<>=^]))?([+\-( ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?(~)?([a-z%])?$/i;function Co(t){if(!(e=nq.exec(t)))throw new Error("invalid format: "+t);var e;return new ph({fill:e[1],align:e[2],sign:e[3],symbol:e[4],zero:e[5],width:e[6],comma:e[7],precision:e[8]&&e[8].slice(1),trim:e[9],type:e[10]})}Co.prototype=ph.prototype;function ph(t){this.fill=t.fill===void 0?" ":t.fill+"",this.align=t.align===void 0?">":t.align+"",this.sign=t.sign===void 0?"-":t.sign+"",this.symbol=t.symbol===void 0?"":t.symbol+"",this.zero=!!t.zero,this.width=t.width===void 0?void 0:+t.width,this.comma=!!t.comma,this.precision=t.precision===void 0?void 0:+t.precision,this.trim=!!t.trim,this.type=t.type===void 0?"":t.type+""}ph.prototype.toString=function(){return this.fill+this.align+this.sign+this.symbol+(this.zero?"0":"")+(this.width===void 0?"":Math.max(1,this.width|0))+(this.comma?",":"")+(this.precision===void 0?"":"."+Math.max(0,this.precision|0))+(this.trim?"~":"")+this.type};function iq(t){t:for(var e=t.length,r=1,n=-1,i;r0&&(n=0);break}return n>0?t.slice(0,n)+t.slice(i+1):t}var Wv;function aq(t,e){var r=dh(t,e);if(!r)return t+"";var n=r[0],i=r[1],a=i-(Wv=Math.max(-8,Math.min(8,Math.floor(i/3)))*3)+1,s=n.length;return a===s?n:a>s?n+new Array(a-s+1).join("0"):a>0?n.slice(0,a)+"."+n.slice(a):"0."+new Array(1-a).join("0")+dh(t,Math.max(0,e+a-1))[0]}function Hv(t,e){var r=dh(t,e);if(!r)return t+"";var n=r[0],i=r[1];return i<0?"0."+new Array(-i).join("0")+n:n.length>i+1?n.slice(0,i+1)+"."+n.slice(i+1):n+new Array(i-n.length+2).join("0")}const Gv={"%":(t,e)=>(t*100).toFixed(e),b:t=>Math.round(t).toString(2),c:t=>t+"",d:tq,e:(t,e)=>t.toExponential(e),f:(t,e)=>t.toFixed(e),g:(t,e)=>t.toPrecision(e),o:t=>Math.round(t).toString(8),p:(t,e)=>Hv(t*100,e),r:Hv,s:aq,X:t=>Math.round(t).toString(16).toUpperCase(),x:t=>Math.round(t).toString(16)};function jv(t){return t}var $v=Array.prototype.map,Xv=["y","z","a","f","p","n","\xB5","m","","k","M","G","T","P","E","Z","Y"];function Kv(t){var e=t.grouping===void 0||t.thousands===void 0?jv:eq($v.call(t.grouping,Number),t.thousands+""),r=t.currency===void 0?"":t.currency[0]+"",n=t.currency===void 0?"":t.currency[1]+"",i=t.decimal===void 0?".":t.decimal+"",a=t.numerals===void 0?jv:rq($v.call(t.numerals,String)),s=t.percent===void 0?"%":t.percent+"",o=t.minus===void 0?"\u2212":t.minus+"",l=t.nan===void 0?"NaN":t.nan+"";function u(d){d=Co(d);var f=d.fill,p=d.align,m=d.sign,_=d.symbol,y=d.zero,b=d.width,x=d.comma,k=d.precision,T=d.trim,C=d.type;C==="n"?(x=!0,C="g"):Gv[C]||(k===void 0&&(k=12),T=!0,C="g"),(y||f==="0"&&p==="=")&&(y=!0,f="0",p="=");var M=_==="$"?r:_==="#"&&/[boxX]/.test(C)?"0"+C.toLowerCase():"",S=_==="$"?n:/[%p]/.test(C)?s:"",R=Gv[C],A=/[defgprs%]/.test(C);k=k===void 0?6:/[gprs]/.test(C)?Math.max(1,Math.min(21,k)):Math.max(0,Math.min(20,k));function L(v){var B=M,w=S,D,N,z;if(C==="c")w=R(v)+w,v="";else{v=+v;var X=v<0||1/v<0;if(v=isNaN(v)?l:R(Math.abs(v),k),T&&(v=iq(v)),X&&+v==0&&m!=="+"&&(X=!1),B=(X?m==="("?m:o:m==="-"||m==="("?"":m)+B,w=(C==="s"?Xv[8+Wv/3]:"")+w+(X&&m==="("?")":""),A){for(D=-1,N=v.length;++Dz||z>57){w=(z===46?i+v.slice(D+1):v.slice(D))+w,v=v.slice(0,D);break}}}x&&!y&&(v=e(v,1/0));var ct=B.length+v.length+w.length,J=ct>1)+B+v+w+J.slice(ct);break;default:v=J+B+v+w;break}return a(v)}return L.toString=function(){return d+""},L}function h(d,f){var p=u((d=Co(d),d.type="f",d)),m=Math.max(-8,Math.min(8,Math.floor(Eo(f)/3)))*3,_=Math.pow(10,-m),y=Xv[8+m/3];return function(b){return p(_*b)+y}}return{format:u,formatPrefix:h}}var gh,yh,Jd;Zv({thousands:",",grouping:[3],currency:["$",""]});function Zv(t){return gh=Kv(t),yh=gh.format,Jd=gh.formatPrefix,gh}function Qv(t){return Math.max(0,-Eo(Math.abs(t)))}function Jv(t,e){return Math.max(0,Math.max(-8,Math.min(8,Math.floor(Eo(e)/3)))*3-Eo(Math.abs(t)))}function t6(t,e){return t=Math.abs(t),e=Math.abs(e)-t,Math.max(0,Eo(e)-Eo(t))+1}var te=1e-6,Hl=1e-12,Ae=Math.PI,rr=Ae/2,mh=Ae/4,Qr=Ae*2,Ue=180/Ae,re=Ae/180,Ne=Math.abs,So=Math.atan,Jr=Math.atan2,Kt=Math.cos,bh=Math.ceil,e6=Math.exp,t2=Math.hypot,_h=Math.log,e2=Math.pow,Ht=Math.sin,Dn=Math.sign||function(t){return t>0?1:t<0?-1:0},Sr=Math.sqrt,r2=Math.tan;function r6(t){return t>1?0:t<-1?Ae:Math.acos(t)}function tn(t){return t>1?rr:t<-1?-rr:Math.asin(t)}function n6(t){return(t=Ht(t/2))*t}function Je(){}function vh(t,e){t&&a6.hasOwnProperty(t.type)&&a6[t.type](t,e)}var i6={Feature:function(t,e){vh(t.geometry,e)},FeatureCollection:function(t,e){for(var r=t.features,n=-1,i=r.length;++n=0?1:-1,i=n*r,a=Kt(e),s=Ht(e),o=s2*s,l=a2*a+o*Kt(i),u=o*n*Ht(i);xh.add(Jr(u,l)),i2=t,a2=a,s2=s}function cq(t){return kh=new _r,ti(t,Si),kh*2}function wh(t){return[Jr(t[1],t[0]),tn(t[2])]}function Cs(t){var e=t[0],r=t[1],n=Kt(r);return[n*Kt(e),n*Ht(e),Ht(r)]}function Th(t,e){return t[0]*e[0]+t[1]*e[1]+t[2]*e[2]}function Ao(t,e){return[t[1]*e[2]-t[2]*e[1],t[2]*e[0]-t[0]*e[2],t[0]*e[1]-t[1]*e[0]]}function o2(t,e){t[0]+=e[0],t[1]+=e[1],t[2]+=e[2]}function Eh(t,e){return[t[0]*e,t[1]*e,t[2]*e]}function Ch(t){var e=Sr(t[0]*t[0]+t[1]*t[1]+t[2]*t[2]);t[0]/=e,t[1]/=e,t[2]/=e}var tr,pn,nr,En,Ss,u6,h6,Mo,Gl,Ba,Qi,Ji={point:l2,lineStart:d6,lineEnd:p6,polygonStart:function(){Ji.point=g6,Ji.lineStart=uq,Ji.lineEnd=hq,Gl=new _r,Si.polygonStart()},polygonEnd:function(){Si.polygonEnd(),Ji.point=l2,Ji.lineStart=d6,Ji.lineEnd=p6,xh<0?(tr=-(nr=180),pn=-(En=90)):Gl>te?En=90:Gl<-te&&(pn=-90),Qi[0]=tr,Qi[1]=nr},sphere:function(){tr=-(nr=180),pn=-(En=90)}};function l2(t,e){Ba.push(Qi=[tr=t,nr=t]),eEn&&(En=e)}function f6(t,e){var r=Cs([t*re,e*re]);if(Mo){var n=Ao(Mo,r),i=[n[1],-n[0],0],a=Ao(i,n);Ch(a),a=wh(a);var s=t-Ss,o=s>0?1:-1,l=a[0]*Ue*o,u,h=Ne(s)>180;h^(o*SsEn&&(En=u)):(l=(l+360)%360-180,h^(o*SsEn&&(En=e))),h?tCn(tr,nr)&&(nr=t):Cn(t,nr)>Cn(tr,nr)&&(tr=t):nr>=tr?(tnr&&(nr=t)):t>Ss?Cn(tr,t)>Cn(tr,nr)&&(nr=t):Cn(t,nr)>Cn(tr,nr)&&(tr=t)}else Ba.push(Qi=[tr=t,nr=t]);eEn&&(En=e),Mo=r,Ss=t}function d6(){Ji.point=f6}function p6(){Qi[0]=tr,Qi[1]=nr,Ji.point=l2,Mo=null}function g6(t,e){if(Mo){var r=t-Ss;Gl.add(Ne(r)>180?r+(r>0?360:-360):r)}else u6=t,h6=e;Si.point(t,e),f6(t,e)}function uq(){Si.lineStart()}function hq(){g6(u6,h6),Si.lineEnd(),Ne(Gl)>te&&(tr=-(nr=180)),Qi[0]=tr,Qi[1]=nr,Mo=null}function Cn(t,e){return(e-=t)<0?e+360:e}function fq(t,e){return t[0]-e[0]}function y6(t,e){return t[0]<=t[1]?t[0]<=e&&e<=t[1]:eCn(n[0],n[1])&&(n[1]=i[1]),Cn(i[0],n[1])>Cn(n[0],n[1])&&(n[0]=i[0])):a.push(n=i);for(s=-1/0,r=a.length-1,e=0,n=a[r];e<=r;n=i,++e)i=a[e],(o=Cn(n[1],i[0]))>s&&(s=o,tr=i[0],nr=n[1])}return Ba=Qi=null,tr===1/0||pn===1/0?[[NaN,NaN],[NaN,NaN]]:[[tr,pn],[nr,En]]}var jl,Sh,Ah,Mh,Lh,Rh,Ih,Nh,c2,u2,h2,m6,b6,en,rn,nn,ei={sphere:Je,point:f2,lineStart:_6,lineEnd:v6,polygonStart:function(){ei.lineStart=yq,ei.lineEnd=mq},polygonEnd:function(){ei.lineStart=_6,ei.lineEnd=v6}};function f2(t,e){t*=re,e*=re;var r=Kt(e);$l(r*Kt(t),r*Ht(t),Ht(e))}function $l(t,e,r){++jl,Ah+=(t-Ah)/jl,Mh+=(e-Mh)/jl,Lh+=(r-Lh)/jl}function _6(){ei.point=pq}function pq(t,e){t*=re,e*=re;var r=Kt(e);en=r*Kt(t),rn=r*Ht(t),nn=Ht(e),ei.point=gq,$l(en,rn,nn)}function gq(t,e){t*=re,e*=re;var r=Kt(e),n=r*Kt(t),i=r*Ht(t),a=Ht(e),s=Jr(Sr((s=rn*a-nn*i)*s+(s=nn*n-en*a)*s+(s=en*i-rn*n)*s),en*n+rn*i+nn*a);Sh+=s,Rh+=s*(en+(en=n)),Ih+=s*(rn+(rn=i)),Nh+=s*(nn+(nn=a)),$l(en,rn,nn)}function v6(){ei.point=f2}function yq(){ei.point=bq}function mq(){x6(m6,b6),ei.point=f2}function bq(t,e){m6=t,b6=e,t*=re,e*=re,ei.point=x6;var r=Kt(e);en=r*Kt(t),rn=r*Ht(t),nn=Ht(e),$l(en,rn,nn)}function x6(t,e){t*=re,e*=re;var r=Kt(e),n=r*Kt(t),i=r*Ht(t),a=Ht(e),s=rn*a-nn*i,o=nn*n-en*a,l=en*i-rn*n,u=t2(s,o,l),h=tn(u),d=u&&-h/u;c2.add(d*s),u2.add(d*o),h2.add(d*l),Sh+=h,Rh+=h*(en+(en=n)),Ih+=h*(rn+(rn=i)),Nh+=h*(nn+(nn=a)),$l(en,rn,nn)}function _q(t){jl=Sh=Ah=Mh=Lh=Rh=Ih=Nh=0,c2=new _r,u2=new _r,h2=new _r,ti(t,ei);var e=+c2,r=+u2,n=+h2,i=t2(e,r,n);return iAe?t+Math.round(-t/Qr)*Qr:t,e]}p2.invert=p2;function g2(t,e,r){return(t%=Qr)?e||r?d2(w6(t),T6(e,r)):w6(t):e||r?T6(e,r):p2}function k6(t){return function(e,r){return e+=t,[e>Ae?e-Qr:e<-Ae?e+Qr:e,r]}}function w6(t){var e=k6(t);return e.invert=k6(-t),e}function T6(t,e){var r=Kt(t),n=Ht(t),i=Kt(e),a=Ht(e);function s(o,l){var u=Kt(l),h=Kt(o)*u,d=Ht(o)*u,f=Ht(l),p=f*r+h*n;return[Jr(d*i-p*a,h*r-f*n),tn(p*i+d*a)]}return s.invert=function(o,l){var u=Kt(l),h=Kt(o)*u,d=Ht(o)*u,f=Ht(l),p=f*i-d*a;return[Jr(d*i+f*a,h*r+p*n),tn(p*r-h*n)]},s}function E6(t){t=g2(t[0]*re,t[1]*re,t.length>2?t[2]*re:0);function e(r){return r=t(r[0]*re,r[1]*re),r[0]*=Ue,r[1]*=Ue,r}return e.invert=function(r){return r=t.invert(r[0]*re,r[1]*re),r[0]*=Ue,r[1]*=Ue,r},e}function C6(t,e,r,n,i,a){if(!!r){var s=Kt(e),o=Ht(e),l=n*r;i==null?(i=e+n*Qr,a=e-l/2):(i=S6(s,i),a=S6(s,a),(n>0?ia)&&(i+=n*Qr));for(var u,h=i;n>0?h>a:h1&&t.push(t.pop().concat(t.shift()))},result:function(){var r=t;return t=[],e=null,r}}}function Bh(t,e){return Ne(t[0]-e[0])=0;--o)i.point((d=h[o])[0],d[1]);else n(f.x,f.p.x,-1,i);f=f.p}f=f.o,h=f.z,p=!p}while(!f.v);i.lineEnd()}}}function L6(t){if(!!(e=t.length)){for(var e,r=0,n=t[0],i;++r=0?1:-1,L=A*R,v=L>Ae,B=y*M;if(l.add(Jr(B*A*Ht(L),b*S+B*Kt(L))),s+=v?R+A*Qr:R,v^m>=r^T>=r){var w=Ao(Cs(p),Cs(k));Ch(w);var D=Ao(a,w);Ch(D);var N=(v^R>=0?-1:1)*tn(D[2]);(n>N||n===N&&(w[0]||w[1]))&&(o+=v^R>=0?1:-1)}}return(s<-te||s0){for(l||(i.polygonStart(),l=!0),i.lineStart(),M=0;M1&&T&2&&C.push(C.pop().concat(C.shift())),h.push(C.filter(xq))}}return f}}function xq(t){return t.length>1}function kq(t,e){return((t=t.x)[0]<0?t[1]-rr-te:rr-t[1])-((e=e.x)[0]<0?e[1]-rr-te:rr-e[1])}const m2=I6(function(){return!0},wq,Eq,[-Ae,-rr]);function wq(t){var e=NaN,r=NaN,n=NaN,i;return{lineStart:function(){t.lineStart(),i=1},point:function(a,s){var o=a>0?Ae:-Ae,l=Ne(a-e);Ne(l-Ae)0?rr:-rr),t.point(n,r),t.lineEnd(),t.lineStart(),t.point(o,r),t.point(a,r),i=0):n!==o&&l>=Ae&&(Ne(e-n)te?So((Ht(e)*(a=Kt(n))*Ht(r)-Ht(n)*(i=Kt(e))*Ht(t))/(i*a*s)):(e+n)/2}function Eq(t,e,r,n){var i;if(t==null)i=r*rr,n.point(-Ae,i),n.point(0,i),n.point(Ae,i),n.point(Ae,0),n.point(Ae,-i),n.point(0,-i),n.point(-Ae,-i),n.point(-Ae,0),n.point(-Ae,i);else if(Ne(t[0]-e[0])>te){var a=t[0]0,i=Ne(e)>te;function a(h,d,f,p){C6(p,t,r,f,h,d)}function s(h,d){return Kt(h)*Kt(d)>e}function o(h){var d,f,p,m,_;return{lineStart:function(){m=p=!1,_=1},point:function(y,b){var x=[y,b],k,T=s(y,b),C=n?T?0:u(y,b):T?u(y+(y<0?Ae:-Ae),b):0;if(!d&&(m=p=T)&&h.lineStart(),T!==p&&(k=l(d,x),(!k||Bh(d,k)||Bh(x,k))&&(x[2]=1)),T!==p)_=0,T?(h.lineStart(),k=l(x,d),h.point(k[0],k[1])):(k=l(d,x),h.point(k[0],k[1],2),h.lineEnd()),d=k;else if(i&&d&&n^T){var M;!(C&f)&&(M=l(x,d,!0))&&(_=0,n?(h.lineStart(),h.point(M[0][0],M[0][1]),h.point(M[1][0],M[1][1]),h.lineEnd()):(h.point(M[1][0],M[1][1]),h.lineEnd(),h.lineStart(),h.point(M[0][0],M[0][1],3)))}T&&(!d||!Bh(d,x))&&h.point(x[0],x[1]),d=x,p=T,f=C},lineEnd:function(){p&&h.lineEnd(),d=null},clean:function(){return _|(m&&p)<<1}}}function l(h,d,f){var p=Cs(h),m=Cs(d),_=[1,0,0],y=Ao(p,m),b=Th(y,y),x=y[0],k=b-x*x;if(!k)return!f&&h;var T=e*b/k,C=-e*x/k,M=Ao(_,y),S=Eh(_,T),R=Eh(y,C);o2(S,R);var A=M,L=Th(S,A),v=Th(A,A),B=L*L-v*(Th(S,S)-1);if(!(B<0)){var w=Sr(B),D=Eh(A,(-L-w)/v);if(o2(D,S),D=wh(D),!f)return D;var N=h[0],z=d[0],X=h[1],ct=d[1],J;z0^D[1]<(Ne(D[0]-N)Ae^(N<=D[0]&&D[0]<=z)){var ut=Eh(A,(-L+w)/v);return o2(ut,S),[D,wh(ut)]}}}function u(h,d){var f=n?t:Ae-t,p=0;return h<-f?p|=1:h>f&&(p|=2),d<-f?p|=4:d>f&&(p|=8),p}return I6(s,o,a,n?[0,-t]:[-Ae,t-Ae])}function Cq(t,e,r,n,i,a){var s=t[0],o=t[1],l=e[0],u=e[1],h=0,d=1,f=l-s,p=u-o,m;if(m=r-s,!(!f&&m>0)){if(m/=f,f<0){if(m0){if(m>d)return;m>h&&(h=m)}if(m=i-s,!(!f&&m<0)){if(m/=f,f<0){if(m>d)return;m>h&&(h=m)}else if(f>0){if(m0)){if(m/=p,p<0){if(m0){if(m>d)return;m>h&&(h=m)}if(m=a-o,!(!p&&m<0)){if(m/=p,p<0){if(m>d)return;m>h&&(h=m)}else if(p>0){if(m0&&(t[0]=s+h*f,t[1]=o+h*p),d<1&&(e[0]=s+d*f,e[1]=o+d*p),!0}}}}}var Xl=1e9,Oh=-Xl;function Fh(t,e,r,n){function i(u,h){return t<=u&&u<=r&&e<=h&&h<=n}function a(u,h,d,f){var p=0,m=0;if(u==null||(p=s(u,d))!==(m=s(h,d))||l(u,h)<0^d>0)do f.point(p===0||p===3?t:r,p>1?n:e);while((p=(p+d+4)%4)!==m);else f.point(h[0],h[1])}function s(u,h){return Ne(u[0]-t)0?0:3:Ne(u[0]-r)0?2:1:Ne(u[1]-e)0?1:0:h>0?3:2}function o(u,h){return l(u.x,h.x)}function l(u,h){var d=s(u,1),f=s(h,1);return d!==f?d-f:d===0?h[1]-u[1]:d===1?u[0]-h[0]:d===2?u[1]-h[1]:h[0]-u[0]}return function(u){var h=u,d=A6(),f,p,m,_,y,b,x,k,T,C,M,S={point:R,lineStart:B,lineEnd:w,polygonStart:L,polygonEnd:v};function R(N,z){i(N,z)&&h.point(N,z)}function A(){for(var N=0,z=0,X=p.length;zn&&(W-lt)*(n-ut)>(tt-ut)*(t-lt)&&++N:tt<=n&&(W-lt)*(n-ut)<(tt-ut)*(t-lt)&&--N;return N}function L(){h=d,f=[],p=[],M=!0}function v(){var N=A(),z=M&&N,X=(f=j0(f)).length;(z||X)&&(u.polygonStart(),z&&(u.lineStart(),a(null,null,1,u),u.lineEnd()),X&&M6(f,o,N,a,u),u.polygonEnd()),h=u,f=p=m=null}function B(){S.point=D,p&&p.push(m=[]),C=!0,T=!1,x=k=NaN}function w(){f&&(D(_,y),b&&T&&d.rejoin(),f.push(d.result())),S.point=R,T&&h.lineEnd()}function D(N,z){var X=i(N,z);if(p&&m.push([N,z]),C)_=N,y=z,b=X,C=!1,X&&(h.lineStart(),h.point(N,z));else if(X&&T)h.point(N,z);else{var ct=[x=Math.max(Oh,Math.min(Xl,x)),k=Math.max(Oh,Math.min(Xl,k))],J=[N=Math.max(Oh,Math.min(Xl,N)),z=Math.max(Oh,Math.min(Xl,z))];Cq(ct,J,t,e,r,n)?(T||(h.lineStart(),h.point(ct[0],ct[1])),h.point(J[0],J[1]),X||h.lineEnd(),M=!1):X&&(h.lineStart(),h.point(N,z),M=!1)}x=N,k=z,T=X}return S}}function Sq(){var t=0,e=0,r=960,n=500,i,a,s;return s={stream:function(o){return i&&a===o?i:i=Fh(t,e,r,n)(a=o)},extent:function(o){return arguments.length?(t=+o[0][0],e=+o[0][1],r=+o[1][0],n=+o[1][1],i=a=null,s):[[t,e],[r,n]]}}}var b2,_2,Ph,qh,Ro={sphere:Je,point:Je,lineStart:Aq,lineEnd:Je,polygonStart:Je,polygonEnd:Je};function Aq(){Ro.point=Lq,Ro.lineEnd=Mq}function Mq(){Ro.point=Ro.lineEnd=Je}function Lq(t,e){t*=re,e*=re,_2=t,Ph=Ht(e),qh=Kt(e),Ro.point=Rq}function Rq(t,e){t*=re,e*=re;var r=Ht(e),n=Kt(e),i=Ne(t-_2),a=Kt(i),s=Ht(i),o=n*s,l=qh*r-Ph*n*a,u=Ph*r+qh*n*a;b2.add(Jr(Sr(o*o+l*l),u)),_2=t,Ph=r,qh=n}function B6(t){return b2=new _r,ti(t,Ro),+b2}var v2=[null,null],Iq={type:"LineString",coordinates:v2};function Vh(t,e){return v2[0]=t,v2[1]=e,B6(Iq)}var D6={Feature:function(t,e){return zh(t.geometry,e)},FeatureCollection:function(t,e){for(var r=t.features,n=-1,i=r.length;++n0&&(i=Vh(t[a],t[a-1]),i>0&&r<=i&&n<=i&&(r+n-i)*(1-Math.pow((r-n)/i,2))te}).map(f)).concat(Ca(bh(a/u)*u,i,u).filter(function(k){return Ne(k%d)>te}).map(p))}return b.lines=function(){return x().map(function(k){return{type:"LineString",coordinates:k}})},b.outline=function(){return{type:"Polygon",coordinates:[m(n).concat(_(s).slice(1),m(r).reverse().slice(1),_(o).reverse().slice(1))]}},b.extent=function(k){return arguments.length?b.extentMajor(k).extentMinor(k):b.extentMinor()},b.extentMajor=function(k){return arguments.length?(n=+k[0][0],r=+k[1][0],o=+k[0][1],s=+k[1][1],n>r&&(k=n,n=r,r=k),o>s&&(k=o,o=s,s=k),b.precision(y)):[[n,o],[r,s]]},b.extentMinor=function(k){return arguments.length?(e=+k[0][0],t=+k[1][0],a=+k[0][1],i=+k[1][1],e>t&&(k=e,e=t,t=k),a>i&&(k=a,a=i,i=k),b.precision(y)):[[e,a],[t,i]]},b.step=function(k){return arguments.length?b.stepMajor(k).stepMinor(k):b.stepMinor()},b.stepMajor=function(k){return arguments.length?(h=+k[0],d=+k[1],b):[h,d]},b.stepMinor=function(k){return arguments.length?(l=+k[0],u=+k[1],b):[l,u]},b.precision=function(k){return arguments.length?(y=+k,f=z6(a,i,90),p=Y6(e,t,y),m=z6(o,s,90),_=Y6(n,r,y),b):y},b.extentMajor([[-180,-90+te],[180,90-te]]).extentMinor([[-180,-80-te],[180,80+te]])}function Dq(){return U6()()}function Oq(t,e){var r=t[0]*re,n=t[1]*re,i=e[0]*re,a=e[1]*re,s=Kt(n),o=Ht(n),l=Kt(a),u=Ht(a),h=s*Kt(r),d=s*Ht(r),f=l*Kt(i),p=l*Ht(i),m=2*tn(Sr(n6(a-n)+s*l*n6(i-r))),_=Ht(m),y=m?function(b){var x=Ht(b*=m)/_,k=Ht(m-b)/_,T=k*h+x*f,C=k*d+x*p,M=k*o+x*u;return[Jr(C,T)*Ue,Jr(M,Sr(T*T+C*C))*Ue]}:function(){return[r*Ue,n*Ue]};return y.distance=m,y}const Kl=t=>t;var x2=new _r,k2=new _r,W6,H6,w2,T2,Da={point:Je,lineStart:Je,lineEnd:Je,polygonStart:function(){Da.lineStart=Fq,Da.lineEnd=qq},polygonEnd:function(){Da.lineStart=Da.lineEnd=Da.point=Je,x2.add(Ne(k2)),k2=new _r},result:function(){var t=x2/2;return x2=new _r,t}};function Fq(){Da.point=Pq}function Pq(t,e){Da.point=G6,W6=w2=t,H6=T2=e}function G6(t,e){k2.add(T2*t-w2*e),w2=t,T2=e}function qq(){G6(W6,H6)}const j6=Da;var Io=1/0,Yh=Io,Zl=-Io,Uh=Zl,Vq={point:zq,lineStart:Je,lineEnd:Je,polygonStart:Je,polygonEnd:Je,result:function(){var t=[[Io,Yh],[Zl,Uh]];return Zl=Uh=-(Yh=Io=1/0),t}};function zq(t,e){tZl&&(Zl=t),eUh&&(Uh=e)}const Wh=Vq;var E2=0,C2=0,Ql=0,Hh=0,Gh=0,No=0,S2=0,A2=0,Jl=0,$6,X6,Ai,Mi,ri={point:As,lineStart:K6,lineEnd:Z6,polygonStart:function(){ri.lineStart=Wq,ri.lineEnd=Hq},polygonEnd:function(){ri.point=As,ri.lineStart=K6,ri.lineEnd=Z6},result:function(){var t=Jl?[S2/Jl,A2/Jl]:No?[Hh/No,Gh/No]:Ql?[E2/Ql,C2/Ql]:[NaN,NaN];return E2=C2=Ql=Hh=Gh=No=S2=A2=Jl=0,t}};function As(t,e){E2+=t,C2+=e,++Ql}function K6(){ri.point=Yq}function Yq(t,e){ri.point=Uq,As(Ai=t,Mi=e)}function Uq(t,e){var r=t-Ai,n=e-Mi,i=Sr(r*r+n*n);Hh+=i*(Ai+t)/2,Gh+=i*(Mi+e)/2,No+=i,As(Ai=t,Mi=e)}function Z6(){ri.point=As}function Wq(){ri.point=Gq}function Hq(){Q6($6,X6)}function Gq(t,e){ri.point=Q6,As($6=Ai=t,X6=Mi=e)}function Q6(t,e){var r=t-Ai,n=e-Mi,i=Sr(r*r+n*n);Hh+=i*(Ai+t)/2,Gh+=i*(Mi+e)/2,No+=i,i=Mi*t-Ai*e,S2+=i*(Ai+t),A2+=i*(Mi+e),Jl+=i*3,As(Ai=t,Mi=e)}const J6=ri;function tx(t){this._context=t}tx.prototype={_radius:4.5,pointRadius:function(t){return this._radius=t,this},polygonStart:function(){this._line=0},polygonEnd:function(){this._line=NaN},lineStart:function(){this._point=0},lineEnd:function(){this._line===0&&this._context.closePath(),this._point=NaN},point:function(t,e){switch(this._point){case 0:{this._context.moveTo(t,e),this._point=1;break}case 1:{this._context.lineTo(t,e);break}default:{this._context.moveTo(t+this._radius,e),this._context.arc(t,e,this._radius,0,Qr);break}}},result:Je};var M2=new _r,L2,ex,rx,tc,ec,jh={point:Je,lineStart:function(){jh.point=jq},lineEnd:function(){L2&&nx(ex,rx),jh.point=Je},polygonStart:function(){L2=!0},polygonEnd:function(){L2=null},result:function(){var t=+M2;return M2=new _r,t}};function jq(t,e){jh.point=nx,ex=tc=t,rx=ec=e}function nx(t,e){tc-=t,ec-=e,M2.add(Sr(tc*tc+ec*ec)),tc=t,ec=e}const ix=jh;function ax(){this._string=[]}ax.prototype={_radius:4.5,_circle:sx(4.5),pointRadius:function(t){return(t=+t)!==this._radius&&(this._radius=t,this._circle=null),this},polygonStart:function(){this._line=0},polygonEnd:function(){this._line=NaN},lineStart:function(){this._point=0},lineEnd:function(){this._line===0&&this._string.push("Z"),this._point=NaN},point:function(t,e){switch(this._point){case 0:{this._string.push("M",t,",",e),this._point=1;break}case 1:{this._string.push("L",t,",",e);break}default:{this._circle==null&&(this._circle=sx(this._radius)),this._string.push("M",t,",",e,this._circle);break}}},result:function(){if(this._string.length){var t=this._string.join("");return this._string=[],t}else return null}};function sx(t){return"m0,"+t+"a"+t+","+t+" 0 1,1 0,"+-2*t+"a"+t+","+t+" 0 1,1 0,"+2*t+"z"}function $q(t,e){var r=4.5,n,i;function a(s){return s&&(typeof r=="function"&&i.pointRadius(+r.apply(this,arguments)),ti(s,n(i))),i.result()}return a.area=function(s){return ti(s,n(j6)),j6.result()},a.measure=function(s){return ti(s,n(ix)),ix.result()},a.bounds=function(s){return ti(s,n(Wh)),Wh.result()},a.centroid=function(s){return ti(s,n(J6)),J6.result()},a.projection=function(s){return arguments.length?(n=s==null?(t=null,Kl):(t=s).stream,a):t},a.context=function(s){return arguments.length?(i=s==null?(e=null,new ax):new tx(e=s),typeof r!="function"&&i.pointRadius(r),a):e},a.pointRadius=function(s){return arguments.length?(r=typeof s=="function"?s:(i.pointRadius(+s),+s),a):r},a.projection(t).context(e)}function Xq(t){return{stream:rc(t)}}function rc(t){return function(e){var r=new R2;for(var n in t)r[n]=t[n];return r.stream=e,r}}function R2(){}R2.prototype={constructor:R2,point:function(t,e){this.stream.point(t,e)},sphere:function(){this.stream.sphere()},lineStart:function(){this.stream.lineStart()},lineEnd:function(){this.stream.lineEnd()},polygonStart:function(){this.stream.polygonStart()},polygonEnd:function(){this.stream.polygonEnd()}};function I2(t,e,r){var n=t.clipExtent&&t.clipExtent();return t.scale(150).translate([0,0]),n!=null&&t.clipExtent(null),ti(r,t.stream(Wh)),e(Wh.result()),n!=null&&t.clipExtent(n),t}function $h(t,e,r){return I2(t,function(n){var i=e[1][0]-e[0][0],a=e[1][1]-e[0][1],s=Math.min(i/(n[1][0]-n[0][0]),a/(n[1][1]-n[0][1])),o=+e[0][0]+(i-s*(n[1][0]+n[0][0]))/2,l=+e[0][1]+(a-s*(n[1][1]+n[0][1]))/2;t.scale(150*s).translate([o,l])},r)}function N2(t,e,r){return $h(t,[[0,0],e],r)}function B2(t,e,r){return I2(t,function(n){var i=+e,a=i/(n[1][0]-n[0][0]),s=(i-a*(n[1][0]+n[0][0]))/2,o=-a*n[0][1];t.scale(150*a).translate([s,o])},r)}function D2(t,e,r){return I2(t,function(n){var i=+e,a=i/(n[1][1]-n[0][1]),s=-a*n[0][0],o=(i-a*(n[1][1]+n[0][1]))/2;t.scale(150*a).translate([s,o])},r)}var ox=16,Kq=Kt(30*re);function lx(t,e){return+e?Qq(t,e):Zq(t)}function Zq(t){return rc({point:function(e,r){e=t(e,r),this.stream.point(e[0],e[1])}})}function Qq(t,e){function r(n,i,a,s,o,l,u,h,d,f,p,m,_,y){var b=u-n,x=h-i,k=b*b+x*x;if(k>4*e&&_--){var T=s+f,C=o+p,M=l+m,S=Sr(T*T+C*C+M*M),R=tn(M/=S),A=Ne(Ne(M)-1)e||Ne((b*w+x*D)/k-.5)>.3||s*f+o*p+l*m2?N[2]%360*re:0,w()):[o*Ue,l*Ue,u*Ue]},v.angle=function(N){return arguments.length?(d=N%360*re,w()):d*Ue},v.reflectX=function(N){return arguments.length?(f=N?-1:1,w()):f<0},v.reflectY=function(N){return arguments.length?(p=N?-1:1,w()):p<0},v.precision=function(N){return arguments.length?(M=lx(S,C=N*N),D()):Sr(C)},v.fitExtent=function(N,z){return $h(v,N,z)},v.fitSize=function(N,z){return N2(v,N,z)},v.fitWidth=function(N,z){return B2(v,N,z)},v.fitHeight=function(N,z){return D2(v,N,z)};function w(){var N=cx(r,0,0,f,p,d).apply(null,e(a,s)),z=cx(r,n-N[0],i-N[1],f,p,d);return h=g2(o,l,u),S=d2(e,z),R=d2(h,S),M=lx(S,C),D()}function D(){return A=L=null,v}return function(){return e=t.apply(this,arguments),v.invert=e.invert&&B,w()}}function F2(t){var e=0,r=Ae/3,n=O2(t),i=n(e,r);return i.parallels=function(a){return arguments.length?n(e=a[0]*re,r=a[1]*re):[e*Ue,r*Ue]},i}function rV(t){var e=Kt(t);function r(n,i){return[n*e,Ht(i)/e]}return r.invert=function(n,i){return[n/e,tn(i*e)]},r}function ux(t,e){var r=Ht(t),n=(r+Ht(e))/2;if(Ne(n)=.12&&y<.234&&_>=-.425&&_<-.214?i:y>=.166&&y<.234&&_>=-.214&&_<-.115?s:r).invert(f)},h.stream=function(f){return t&&e===f?t:t=nV([r.stream(e=f),i.stream(f),s.stream(f)])},h.precision=function(f){return arguments.length?(r.precision(f),i.precision(f),s.precision(f),d()):r.precision()},h.scale=function(f){return arguments.length?(r.scale(f),i.scale(f*.35),s.scale(f),h.translate(r.translate())):r.scale()},h.translate=function(f){if(!arguments.length)return r.translate();var p=r.scale(),m=+f[0],_=+f[1];return n=r.translate(f).clipExtent([[m-.455*p,_-.238*p],[m+.455*p,_+.238*p]]).stream(u),a=i.translate([m-.307*p,_+.201*p]).clipExtent([[m-.425*p+te,_+.12*p+te],[m-.214*p-te,_+.234*p-te]]).stream(u),o=s.translate([m-.205*p,_+.212*p]).clipExtent([[m-.214*p+te,_+.166*p+te],[m-.115*p-te,_+.234*p-te]]).stream(u),d()},h.fitExtent=function(f,p){return $h(h,f,p)},h.fitSize=function(f,p){return N2(h,f,p)},h.fitWidth=function(f,p){return B2(h,f,p)},h.fitHeight=function(f,p){return D2(h,f,p)};function d(){return t=e=null,h}return h.scale(1070)}function fx(t){return function(e,r){var n=Kt(e),i=Kt(r),a=t(n*i);return a===1/0?[2,0]:[a*i*Ht(e),a*Ht(r)]}}function nc(t){return function(e,r){var n=Sr(e*e+r*r),i=t(n),a=Ht(i),s=Kt(i);return[Jr(e*a,n*s),tn(n&&r*a/n)]}}var P2=fx(function(t){return Sr(2/(1+t))});P2.invert=nc(function(t){return 2*tn(t/2)});function aV(){return Li(P2).scale(124.75).clipAngle(180-.001)}var q2=fx(function(t){return(t=r6(t))&&t/Ht(t)});q2.invert=nc(function(t){return t});function sV(){return Li(q2).scale(79.4188).clipAngle(180-.001)}function ic(t,e){return[t,_h(r2((rr+e)/2))]}ic.invert=function(t,e){return[t,2*So(e6(e))-rr]};function oV(){return dx(ic).scale(961/Qr)}function dx(t){var e=Li(t),r=e.center,n=e.scale,i=e.translate,a=e.clipExtent,s=null,o,l,u;e.scale=function(d){return arguments.length?(n(d),h()):n()},e.translate=function(d){return arguments.length?(i(d),h()):i()},e.center=function(d){return arguments.length?(r(d),h()):r()},e.clipExtent=function(d){return arguments.length?(d==null?s=o=l=u=null:(s=+d[0][0],o=+d[0][1],l=+d[1][0],u=+d[1][1]),h()):s==null?null:[[s,o],[l,u]]};function h(){var d=Ae*n(),f=e(E6(e.rotate()).invert([0,0]));return a(s==null?[[f[0]-d,f[1]-d],[f[0]+d,f[1]+d]]:t===ic?[[Math.max(f[0]-d,s),o],[Math.min(f[0]+d,l),u]]:[[s,Math.max(f[1]-d,o)],[l,Math.min(f[1]+d,u)]])}return h()}function Kh(t){return r2((rr+t)/2)}function px(t,e){var r=Kt(t),n=t===e?Ht(t):_h(r/Kt(e))/_h(Kh(e)/Kh(t)),i=r*e2(Kh(t),n)/n;if(!n)return ic;function a(s,o){i>0?o<-rr+te&&(o=-rr+te):o>rr-te&&(o=rr-te);var l=i/e2(Kh(o),n);return[l*Ht(n*s),i-l*Kt(n*s)]}return a.invert=function(s,o){var l=i-o,u=Dn(n)*Sr(s*s+l*l),h=Jr(s,Ne(l))*Dn(l);return l*n<0&&(h-=Ae*Dn(s)*Dn(l)),[h/n,2*So(e2(i/u,1/n))-rr]},a}function lV(){return F2(px).scale(109.5).parallels([30,30])}function ac(t,e){return[t,e]}ac.invert=ac;function cV(){return Li(ac).scale(152.63)}function gx(t,e){var r=Kt(t),n=t===e?Ht(t):(r-Kt(e))/(e-t),i=r/n+t;if(Ne(n)te&&--n>0);return[t/(.8707+(a=r*r)*(-.131979+a*(-.013791+a*a*a*(.003971-.001529*a)))),r]};function gV(){return Li(Y2).scale(175.295)}function U2(t,e){return[Kt(e)*Ht(t),Ht(e)]}U2.invert=nc(tn);function yV(){return Li(U2).scale(249.5).clipAngle(90+te)}function W2(t,e){var r=Kt(e),n=1+Kt(t)*r;return[r*Ht(t)/n,Ht(e)/n]}W2.invert=nc(function(t){return 2*So(t)});function mV(){return Li(W2).scale(250).clipAngle(142)}function H2(t,e){return[_h(r2((rr+e)/2)),-t]}H2.invert=function(t,e){return[-e,2*So(e6(t))-rr]};function bV(){var t=dx(H2),e=t.center,r=t.rotate;return t.center=function(n){return arguments.length?e([-n[1],n[0]]):(n=e(),[n[1],-n[0]])},t.rotate=function(n){return arguments.length?r([n[0],n[1],n.length>2?n[2]+90:90]):(n=r(),[n[0],n[1],n[2]-90])},r([0,0,90]).scale(159.155)}function _V(t,e){return t.parent===e.parent?1:2}function vV(t){return t.reduce(xV,0)/t.length}function xV(t,e){return t+e.x}function kV(t){return 1+t.reduce(wV,0)}function wV(t,e){return Math.max(t,e.y)}function TV(t){for(var e;e=t.children;)t=e[0];return t}function EV(t){for(var e;e=t.children;)t=e[e.length-1];return t}function CV(){var t=_V,e=1,r=1,n=!1;function i(a){var s,o=0;a.eachAfter(function(f){var p=f.children;p?(f.x=vV(p),f.y=kV(p)):(f.x=s?o+=t(f,s):0,f.y=0,s=f)});var l=TV(a),u=EV(a),h=l.x-t(l,u)/2,d=u.x+t(u,l)/2;return a.eachAfter(n?function(f){f.x=(f.x-a.x)*e,f.y=(a.y-f.y)*r}:function(f){f.x=(f.x-h)/(d-h)*e,f.y=(1-(a.y?f.y/a.y:1))*r})}return i.separation=function(a){return arguments.length?(t=a,i):t},i.size=function(a){return arguments.length?(n=!1,e=+a[0],r=+a[1],i):n?null:[e,r]},i.nodeSize=function(a){return arguments.length?(n=!0,e=+a[0],r=+a[1],i):n?[e,r]:null},i}function SV(t){var e=0,r=t.children,n=r&&r.length;if(!n)e=1;else for(;--n>=0;)e+=r[n].value;t.value=e}function AV(){return this.eachAfter(SV)}function MV(t,e){let r=-1;for(const n of this)t.call(e,n,++r,this);return this}function LV(t,e){for(var r=this,n=[r],i,a,s=-1;r=n.pop();)if(t.call(e,r,++s,this),i=r.children)for(a=i.length-1;a>=0;--a)n.push(i[a]);return this}function RV(t,e){for(var r=this,n=[r],i=[],a,s,o,l=-1;r=n.pop();)if(i.push(r),a=r.children)for(s=0,o=a.length;s=0;)r+=n[i].value;e.value=r})}function BV(t){return this.eachBefore(function(e){e.children&&e.children.sort(t)})}function DV(t){for(var e=this,r=OV(e,t),n=[e];e!==r;)e=e.parent,n.push(e);for(var i=n.length;t!==r;)n.splice(i,0,t),t=t.parent;return n}function OV(t,e){if(t===e)return t;var r=t.ancestors(),n=e.ancestors(),i=null;for(t=r.pop(),e=n.pop();t===e;)i=t,t=r.pop(),e=n.pop();return i}function FV(){for(var t=this,e=[t];t=t.parent;)e.push(t);return e}function PV(){return Array.from(this)}function qV(){var t=[];return this.eachBefore(function(e){e.children||t.push(e)}),t}function VV(){var t=this,e=[];return t.each(function(r){r!==t&&e.push({source:r.parent,target:r})}),e}function*zV(){var t=this,e,r=[t],n,i,a;do for(e=r.reverse(),r=[];t=e.pop();)if(yield t,n=t.children)for(i=0,a=n.length;i=0;--o)i.push(a=s[o]=new Ms(s[o])),a.parent=n,a.depth=n.depth+1;return r.eachBefore(yx)}function YV(){return G2(this).eachBefore(HV)}function UV(t){return t.children}function WV(t){return Array.isArray(t)?t[1]:null}function HV(t){t.data.value!==void 0&&(t.value=t.data.value),t.data=t.data.data}function yx(t){var e=0;do t.height=e;while((t=t.parent)&&t.height<++e)}function Ms(t){this.data=t,this.depth=this.height=0,this.parent=null}Ms.prototype=G2.prototype={constructor:Ms,count:AV,each:MV,eachAfter:RV,eachBefore:LV,find:IV,sum:NV,sort:BV,path:DV,ancestors:FV,descendants:PV,leaves:qV,links:VV,copy:YV,[Symbol.iterator]:zV};function Qh(t){return t==null?null:mx(t)}function mx(t){if(typeof t!="function")throw new Error;return t}function Ls(){return 0}function Bo(t){return function(){return t}}const GV=1664525,jV=1013904223,bx=4294967296;function j2(){let t=1;return()=>(t=(GV*t+jV)%bx)/bx}function $V(t){return typeof t=="object"&&"length"in t?t:Array.from(t)}function XV(t,e){let r=t.length,n,i;for(;r;)i=e()*r--|0,n=t[r],t[r]=t[i],t[i]=n;return t}function KV(t){return _x(t,j2())}function _x(t,e){for(var r=0,n=(t=XV(Array.from(t),e)).length,i=[],a,s;r0&&r*r>n*n+i*i}function $2(t,e){for(var r=0;r1e-6?(v+Math.sqrt(v*v-4*L*B))/(2*L):B/v);return{x:n+M+S*w,y:i+R+A*w,r:w}}function kx(t,e,r){var n=t.x-e.x,i,a,s=t.y-e.y,o,l,u=n*n+s*s;u?(a=e.r+r.r,a*=a,l=t.r+r.r,l*=l,a>l?(i=(u+l-a)/(2*u),o=Math.sqrt(Math.max(0,l/u-i*i)),r.x=t.x-i*n-o*s,r.y=t.y-i*s+o*n):(i=(u+a-l)/(2*u),o=Math.sqrt(Math.max(0,a/u-i*i)),r.x=e.x+i*n-o*s,r.y=e.y+i*s+o*n)):(r.x=e.x+r.r,r.y=e.y)}function wx(t,e){var r=t.r+e.r-1e-6,n=e.x-t.x,i=e.y-t.y;return r>0&&r*r>n*n+i*i}function Tx(t){var e=t._,r=t.next._,n=e.r+r.r,i=(e.x*r.r+r.x*e.r)/n,a=(e.y*r.r+r.y*e.r)/n;return i*i+a*a}function tf(t){this._=t,this.next=null,this.previous=null}function Ex(t,e){if(!(a=(t=$V(t)).length))return 0;var r,n,i,a,s,o,l,u,h,d,f;if(r=t[0],r.x=0,r.y=0,!(a>1))return r.r;if(n=t[1],r.x=-n.r,n.x=r.r,n.y=0,!(a>2))return r.r+n.r;kx(n,r,i=t[2]),r=new tf(r),n=new tf(n),i=new tf(i),r.next=i.previous=n,n.next=r.previous=i,i.next=n.previous=r;t:for(l=3;llz(r(T,C,i))),x=b.map(Lx),k=new Set(b).add("");for(const T of x)k.has(T)||(k.add(T),b.push(T),x.push(Lx(T)),a.push(K2));s=(T,C)=>b[C],o=(T,C)=>x[C]}for(h=0,l=a.length;h=0&&(p=a[b],p.data===K2);--b)p.data=null}if(d.parent=iz,d.eachBefore(function(b){b.depth=b.parent.depth+1,--l}).eachBefore(yx),d.parent=null,l>0)throw new Error("cycle");return d}return n.id=function(i){return arguments.length?(t=Qh(i),n):t},n.parentId=function(i){return arguments.length?(e=Qh(i),n):e},n.path=function(i){return arguments.length?(r=Qh(i),n):r},n}function lz(t){t=`${t}`;let e=t.length;return Z2(t,e-1)&&!Z2(t,e-2)&&(t=t.slice(0,-1)),t[0]==="/"?t:`/${t}`}function Lx(t){let e=t.length;if(e<2)return"";for(;--e>1&&!Z2(t,e););return t.slice(0,e)}function Z2(t,e){if(t[e]==="/"){let r=0;for(;e>0&&t[--e]==="\\";)++r;if((r&1)===0)return!0}return!1}function cz(t,e){return t.parent===e.parent?1:2}function Q2(t){var e=t.children;return e?e[0]:t.t}function J2(t){var e=t.children;return e?e[e.length-1]:t.t}function uz(t,e,r){var n=r/(e.i-t.i);e.c-=n,e.s+=r,t.c+=n,e.z+=r,e.m+=r}function hz(t){for(var e=0,r=0,n=t.children,i=n.length,a;--i>=0;)a=n[i],a.z+=e,a.m+=e,e+=a.s+(r+=a.c)}function fz(t,e,r){return t.a.parent===e.parent?t.a:r}function ef(t,e){this._=t,this.parent=null,this.children=null,this.A=null,this.a=this,this.z=0,this.m=0,this.c=0,this.s=0,this.t=null,this.i=e}ef.prototype=Object.create(Ms.prototype);function dz(t){for(var e=new ef(t,0),r,n=[e],i,a,s,o;r=n.pop();)if(a=r._.children)for(r.children=new Array(o=a.length),s=o-1;s>=0;--s)n.push(i=r.children[s]=new ef(a[s],s)),i.parent=r;return(e.parent=new ef(null,0)).children=[e],e}function pz(){var t=cz,e=1,r=1,n=null;function i(u){var h=dz(u);if(h.eachAfter(a),h.parent.m=-h.z,h.eachBefore(s),n)u.eachBefore(l);else{var d=u,f=u,p=u;u.eachBefore(function(x){x.xf.x&&(f=x),x.depth>p.depth&&(p=x)});var m=d===f?1:t(d,f)/2,_=m-d.x,y=e/(f.x+m+_),b=r/(p.depth||1);u.eachBefore(function(x){x.x=(x.x+_)*y,x.y=x.depth*b})}return u}function a(u){var h=u.children,d=u.parent.children,f=u.i?d[u.i-1]:null;if(h){hz(u);var p=(h[0].z+h[h.length-1].z)/2;f?(u.z=f.z+t(u._,f._),u.m=u.z-p):u.z=p}else f&&(u.z=f.z+t(u._,f._));u.parent.A=o(u,f,u.parent.A||d[0])}function s(u){u._.x=u.z+u.parent.m,u.m+=u.parent.m}function o(u,h,d){if(h){for(var f=u,p=u,m=h,_=f.parent.children[0],y=f.m,b=p.m,x=m.m,k=_.m,T;m=J2(m),f=Q2(f),m&&f;)_=Q2(_),p=J2(p),p.a=u,T=m.z+x-f.z-y+t(m._,f._),T>0&&(uz(fz(m,u,d),u,T),y+=T,b+=T),x+=m.m,y+=f.m,k+=_.m,b+=p.m;m&&!J2(p)&&(p.t=m,p.m+=x-b),f&&!Q2(_)&&(_.t=f,_.m+=y-k,d=u)}return d}function l(u){u.x*=e,u.y=u.depth*r}return i.separation=function(u){return arguments.length?(t=u,i):t},i.size=function(u){return arguments.length?(n=!1,e=+u[0],r=+u[1],i):n?null:[e,r]},i.nodeSize=function(u){return arguments.length?(n=!0,e=+u[0],r=+u[1],i):n?[e,r]:null},i}function rf(t,e,r,n,i){for(var a=t.children,s,o=-1,l=a.length,u=t.value&&(i-r)/t.value;++ox&&(x=u),M=y*y*C,k=Math.max(x/M,M/b),k>T){y-=u;break}T=k}s.push(l={value:y,dice:p1?n:1)},r}(Rx);function gz(){var t=Nx,e=!1,r=1,n=1,i=[0],a=Ls,s=Ls,o=Ls,l=Ls,u=Ls;function h(f){return f.x0=f.y0=0,f.x1=r,f.y1=n,f.eachBefore(d),i=[0],e&&f.eachBefore(Ax),f}function d(f){var p=i[f.depth],m=f.x0+p,_=f.y0+p,y=f.x1-p,b=f.y1-p;y=f-1){var x=a[d];x.x0=m,x.y0=_,x.x1=y,x.y1=b;return}for(var k=u[d],T=p/2+k,C=d+1,M=f-1;C>>1;u[S]b-_){var L=p?(m*A+y*R)/p:y;h(d,C,R,m,_,L,b),h(C,f,A,L,_,y,b)}else{var v=p?(_*A+b*R)/p:b;h(d,C,R,m,_,y,v),h(C,f,A,m,v,y,b)}}}function mz(t,e,r,n,i){(t.depth&1?rf:hc)(t,e,r,n,i)}const bz=function t(e){function r(n,i,a,s,o){if((l=n._squarify)&&l.ratio===e)for(var l,u,h,d,f=-1,p,m=l.length,_=n.value;++f1?n:1)},r}(Rx);function _z(t){for(var e=-1,r=t.length,n,i=t[r-1],a=0;++e1&&xz(t[r[n-2]],t[r[n-1]],t[i])<=0;)--n;r[n++]=i}return r.slice(0,n)}function wz(t){if((r=t.length)<3)return null;var e,r,n=new Array(r),i=new Array(r);for(e=0;e=0;--e)u.push(t[n[a[e]][2]]);for(e=+o;ea!=o>a&&i<(s-l)*(a-u)/(o-u)+l&&(h=!h),s=l,o=u;return h}function Ez(t){for(var e=-1,r=t.length,n=t[r-1],i,a,s=n[0],o=n[1],l=0;++e1);return n+i*o*Math.sqrt(-2*Math.log(s)/s)}}return r.source=t,r}(Ir),Az=function t(e){var r=tp.source(e);function n(){var i=r.apply(this,arguments);return function(){return Math.exp(i())}}return n.source=t,n}(Ir),Dx=function t(e){function r(n){return(n=+n)<=0?()=>0:function(){for(var i=0,a=n;a>1;--a)i+=e();return i+a*e()}}return r.source=t,r}(Ir),Mz=function t(e){var r=Dx.source(e);function n(i){if((i=+i)==0)return e;var a=r(i);return function(){return a()/i}}return n.source=t,n}(Ir),Lz=function t(e){function r(n){return function(){return-Math.log1p(-e())/n}}return r.source=t,r}(Ir),Rz=function t(e){function r(n){if((n=+n)<0)throw new RangeError("invalid alpha");return n=1/-n,function(){return Math.pow(1-e(),n)}}return r.source=t,r}(Ir),Iz=function t(e){function r(n){if((n=+n)<0||n>1)throw new RangeError("invalid p");return function(){return Math.floor(e()+n)}}return r.source=t,r}(Ir),Ox=function t(e){function r(n){if((n=+n)<0||n>1)throw new RangeError("invalid p");return n===0?()=>1/0:n===1?()=>1:(n=Math.log1p(-n),function(){return 1+Math.floor(Math.log1p(-e())/n)})}return r.source=t,r}(Ir),ep=function t(e){var r=tp.source(e)();function n(i,a){if((i=+i)<0)throw new RangeError("invalid k");if(i===0)return()=>0;if(a=a==null?1:+a,i===1)return()=>-Math.log1p(-e())*a;var s=(i<1?i+1:i)-1/3,o=1/(3*Math.sqrt(s)),l=i<1?()=>Math.pow(e(),1/i):()=>1;return function(){do{do var u=r(),h=1+o*u;while(h<=0);h*=h*h;var d=1-e()}while(d>=1-.0331*u*u*u*u&&Math.log(d)>=.5*u*u+s*(1-h+Math.log(h)));return s*h*l()*a}}return n.source=t,n}(Ir),Fx=function t(e){var r=ep.source(e);function n(i,a){var s=r(i),o=r(a);return function(){var l=s();return l===0?0:l/(l+o())}}return n.source=t,n}(Ir),Px=function t(e){var r=Ox.source(e),n=Fx.source(e);function i(a,s){return a=+a,(s=+s)>=1?()=>a:s<=0?()=>0:function(){for(var o=0,l=a,u=s;l*u>16&&l*(1-u)>16;){var h=Math.floor((l+1)*u),d=n(h,l-h+1)();d<=u?(o+=h,l-=h,u=(u-d)/(1-d)):(l=h-1,u/=d)}for(var f=u<.5,p=f?u:1-u,m=r(p),_=m(),y=0;_<=l;++y)_+=m();return o+(f?y:l-y)}}return i.source=t,i}(Ir),Nz=function t(e){function r(n,i,a){var s;return(n=+n)==0?s=o=>-Math.log(o):(n=1/n,s=o=>Math.pow(o,n)),i=i==null?0:+i,a=a==null?1:+a,function(){return i+a*s(-Math.log1p(-e()))}}return r.source=t,r}(Ir),Bz=function t(e){function r(n,i){return n=n==null?0:+n,i=i==null?1:+i,function(){return n+i*Math.tan(Math.PI*e())}}return r.source=t,r}(Ir),Dz=function t(e){function r(n,i){return n=n==null?0:+n,i=i==null?1:+i,function(){var a=e();return n+i*Math.log(a/(1-a))}}return r.source=t,r}(Ir),Oz=function t(e){var r=ep.source(e),n=Px.source(e);function i(a){return function(){for(var s=0,o=a;o>16;){var l=Math.floor(.875*o),u=r(l)();if(u>o)return s+n(l-1,o/u)();s+=l,o-=u}for(var h=-Math.log1p(-e()),d=0;h<=o;++d)h-=Math.log1p(-e());return s+d}}return i.source=t,i}(Ir),Fz=1664525,Pz=1013904223,qx=1/4294967296;function qz(t=Math.random()){let e=(0<=t&&t<1?t/qx:Math.abs(t))|0;return()=>(e=Fz*e+Pz|0,qx*(e>>>0))}function On(t,e){switch(arguments.length){case 0:break;case 1:this.range(t);break;default:this.range(e).domain(t);break}return this}function ta(t,e){switch(arguments.length){case 0:break;case 1:{typeof t=="function"?this.interpolator(t):this.range(t);break}default:{this.domain(t),typeof e=="function"?this.interpolator(e):this.range(e);break}}return this}const rp=Symbol("implicit");function nf(){var t=new kl,e=[],r=[],n=rp;function i(a){let s=t.get(a);if(s===void 0){if(n!==rp)return n;t.set(a,s=e.push(a)-1)}return r[s%r.length]}return i.domain=function(a){if(!arguments.length)return e.slice();e=[],t=new kl;for(const s of a)t.has(s)||t.set(s,e.push(s)-1);return i},i.range=function(a){return arguments.length?(r=Array.from(a),i):r.slice()},i.unknown=function(a){return arguments.length?(n=a,i):n},i.copy=function(){return nf(e,r).unknown(n)},On.apply(i,arguments),i}function np(){var t=nf().unknown(void 0),e=t.domain,r=t.range,n=0,i=1,a,s,o=!1,l=0,u=0,h=.5;delete t.unknown;function d(){var f=e().length,p=ie&&(r=t,t=e,e=r),function(n){return Math.max(t,Math.min(e,n))}}function Uz(t,e,r){var n=t[0],i=t[1],a=e[0],s=e[1];return i2?Wz:Uz,l=u=null,d}function d(f){return f==null||isNaN(f=+f)?a:(l||(l=o(t.map(n),e,r)))(n(s(f)))}return d.invert=function(f){return s(i((u||(u=o(e,t.map(n),Bn)))(f)))},d.domain=function(f){return arguments.length?(t=Array.from(f,af),h()):t.slice()},d.range=function(f){return arguments.length?(e=Array.from(f),h()):e.slice()},d.rangeRound=function(f){return e=Array.from(f),r=ju,h()},d.clamp=function(f){return arguments.length?(s=f?!0:an,h()):s!==an},d.interpolate=function(f){return arguments.length?(r=f,h()):r},d.unknown=function(f){return arguments.length?(a=f,d):a},function(f,p){return n=f,i=p,h()}}function ap(){return sf()(an,an)}function Yx(t,e,r,n){var i=wl(t,e,r),a;switch(n=Co(n==null?",f":n),n.type){case"s":{var s=Math.max(Math.abs(t),Math.abs(e));return n.precision==null&&!isNaN(a=Jv(i,s))&&(n.precision=a),Jd(n,s)}case"":case"e":case"g":case"p":case"r":{n.precision==null&&!isNaN(a=t6(i,Math.max(Math.abs(t),Math.abs(e))))&&(n.precision=a-(n.type==="e"));break}case"f":case"%":{n.precision==null&&!isNaN(a=Qv(i))&&(n.precision=a-(n.type==="%")*2);break}}return yh(n)}function Oa(t){var e=t.domain;return t.ticks=function(r){var n=e();return hs(n[0],n[n.length-1],r==null?10:r)},t.tickFormat=function(r,n){var i=e();return Yx(i[0],i[i.length-1],r==null?10:r,n)},t.nice=function(r){r==null&&(r=10);var n=e(),i=0,a=n.length-1,s=n[i],o=n[a],l,u,h=10;for(o0;){if(u=oo(s,o,r),u===l)return n[i]=s,n[a]=o,e(n);if(u>0)s=Math.floor(s/u)*u,o=Math.ceil(o/u)*u;else if(u<0)s=Math.ceil(s*u)/u,o=Math.floor(o*u)/u;else break;l=u}return t},t}function sp(){var t=ap();return t.copy=function(){return fc(t,sp())},On.apply(t,arguments),Oa(t)}function Ux(t){var e;function r(n){return n==null||isNaN(n=+n)?e:n}return r.invert=r,r.domain=r.range=function(n){return arguments.length?(t=Array.from(n,af),r):t.slice()},r.unknown=function(n){return arguments.length?(e=n,r):e},r.copy=function(){return Ux(t).unknown(e)},t=arguments.length?Array.from(t,af):[0,1],Oa(r)}function Wx(t,e){t=t.slice();var r=0,n=t.length-1,i=t[r],a=t[n],s;return aMath.pow(t,e)}function Xz(t){return t===Math.E?Math.log:t===10&&Math.log10||t===2&&Math.log2||(t=Math.log(t),e=>Math.log(e)/t)}function jx(t){return(e,r)=>-t(-e,r)}function op(t){const e=t(Hx,Gx),r=e.domain;let n=10,i,a;function s(){return i=Xz(n),a=$z(n),r()[0]<0?(i=jx(i),a=jx(a),t(Hz,Gz)):t(Hx,Gx),e}return e.base=function(o){return arguments.length?(n=+o,s()):n},e.domain=function(o){return arguments.length?(r(o),s()):r()},e.ticks=o=>{const l=r();let u=l[0],h=l[l.length-1];const d=h0){for(;f<=p;++f)for(m=1;mh)break;b.push(_)}}else for(;f<=p;++f)for(m=n-1;m>=1;--m)if(_=f>0?m/a(-f):m*a(f),!(_h)break;b.push(_)}b.length*2{if(o==null&&(o=10),l==null&&(l=n===10?"s":","),typeof l!="function"&&(!(n%1)&&(l=Co(l)).precision==null&&(l.trim=!0),l=yh(l)),o===1/0)return l;const u=Math.max(1,n*o/e.ticks().length);return h=>{let d=h/a(Math.round(i(h)));return d*nr(Wx(r(),{floor:o=>a(Math.floor(i(o))),ceil:o=>a(Math.ceil(i(o)))})),e}function $x(){const t=op(sf()).domain([1,10]);return t.copy=()=>fc(t,$x()).base(t.base()),On.apply(t,arguments),t}function Xx(t){return function(e){return Math.sign(e)*Math.log1p(Math.abs(e/t))}}function Kx(t){return function(e){return Math.sign(e)*Math.expm1(Math.abs(e))*t}}function lp(t){var e=1,r=t(Xx(e),Kx(e));return r.constant=function(n){return arguments.length?t(Xx(e=+n),Kx(e)):e},Oa(r)}function Zx(){var t=lp(sf());return t.copy=function(){return fc(t,Zx()).constant(t.constant())},On.apply(t,arguments)}function Qx(t){return function(e){return e<0?-Math.pow(-e,t):Math.pow(e,t)}}function Kz(t){return t<0?-Math.sqrt(-t):Math.sqrt(t)}function Zz(t){return t<0?-t*t:t*t}function cp(t){var e=t(an,an),r=1;function n(){return r===1?t(an,an):r===.5?t(Kz,Zz):t(Qx(r),Qx(1/r))}return e.exponent=function(i){return arguments.length?(r=+i,n()):r},Oa(e)}function up(){var t=cp(sf());return t.copy=function(){return fc(t,up()).exponent(t.exponent())},On.apply(t,arguments),t}function Qz(){return up.apply(null,arguments).exponent(.5)}function Jx(t){return Math.sign(t)*t*t}function Jz(t){return Math.sign(t)*Math.sqrt(Math.abs(t))}function t8(){var t=ap(),e=[0,1],r=!1,n;function i(a){var s=Jz(t(a));return isNaN(s)?n:r?Math.round(s):s}return i.invert=function(a){return t.invert(Jx(a))},i.domain=function(a){return arguments.length?(t.domain(a),i):t.domain()},i.range=function(a){return arguments.length?(t.range((e=Array.from(a,af)).map(Jx)),i):e.slice()},i.rangeRound=function(a){return i.range(a).round(!0)},i.round=function(a){return arguments.length?(r=!!a,i):r},i.clamp=function(a){return arguments.length?(t.clamp(a),i):t.clamp()},i.unknown=function(a){return arguments.length?(n=a,i):n},i.copy=function(){return t8(t.domain(),e).round(r).clamp(t.clamp()).unknown(n)},On.apply(i,arguments),Oa(i)}function e8(){var t=[],e=[],r=[],n;function i(){var s=0,o=Math.max(1,e.length);for(r=new Array(o-1);++s0?r[o-1]:t[0],o=r?[n[r-1],e]:[n[u-1],n[u]]},s.unknown=function(l){return arguments.length&&(a=l),s},s.thresholds=function(){return n.slice()},s.copy=function(){return r8().domain([t,e]).range(i).unknown(a)},On.apply(Oa(s),arguments)}function n8(){var t=[.5],e=[0,1],r,n=1;function i(a){return a!=null&&a<=a?e[cs(t,a,0,n)]:r}return i.domain=function(a){return arguments.length?(t=Array.from(a),n=Math.min(t.length,e.length-1),i):t.slice()},i.range=function(a){return arguments.length?(e=Array.from(a),n=Math.min(t.length,e.length-1),i):e.slice()},i.invertExtent=function(a){var s=e.indexOf(a);return[t[s-1],t[s]]},i.unknown=function(a){return arguments.length?(r=a,i):r},i.copy=function(){return n8().domain(t).range(e).unknown(r)},On.apply(i,arguments)}var hp=new Date,fp=new Date;function xr(t,e,r,n){function i(a){return t(a=arguments.length===0?new Date:new Date(+a)),a}return i.floor=function(a){return t(a=new Date(+a)),a},i.ceil=function(a){return t(a=new Date(a-1)),e(a,1),t(a),a},i.round=function(a){var s=i(a),o=i.ceil(a);return a-s0))return l;do l.push(u=new Date(+a)),e(a,o),t(a);while(u=s)for(;t(s),!a(s);)s.setTime(s-1)},function(s,o){if(s>=s)if(o<0)for(;++o<=0;)for(;e(s,-1),!a(s););else for(;--o>=0;)for(;e(s,1),!a(s););})},r&&(i.count=function(a,s){return hp.setTime(+a),fp.setTime(+s),t(hp),t(fp),Math.floor(r(hp,fp))},i.every=function(a){return a=Math.floor(a),!isFinite(a)||!(a>0)?null:a>1?i.filter(n?function(s){return n(s)%a===0}:function(s){return i.count(0,s)%a===0}):i}),i}var of=xr(function(){},function(t,e){t.setTime(+t+e)},function(t,e){return e-t});of.every=function(t){return t=Math.floor(t),!isFinite(t)||!(t>0)?null:t>1?xr(function(e){e.setTime(Math.floor(e/t)*t)},function(e,r){e.setTime(+e+r*t)},function(e,r){return(r-e)/t}):of};const dp=of;var i8=of.range;const ea=1e3,Fn=ea*60,ra=Fn*60,Rs=ra*24,pp=Rs*7,a8=Rs*30,gp=Rs*365;var s8=xr(function(t){t.setTime(t-t.getMilliseconds())},function(t,e){t.setTime(+t+e*ea)},function(t,e){return(e-t)/ea},function(t){return t.getUTCSeconds()});const Fa=s8;var o8=s8.range,l8=xr(function(t){t.setTime(t-t.getMilliseconds()-t.getSeconds()*ea)},function(t,e){t.setTime(+t+e*Fn)},function(t,e){return(e-t)/Fn},function(t){return t.getMinutes()});const yp=l8;var tY=l8.range,c8=xr(function(t){t.setTime(t-t.getMilliseconds()-t.getSeconds()*ea-t.getMinutes()*Fn)},function(t,e){t.setTime(+t+e*ra)},function(t,e){return(e-t)/ra},function(t){return t.getHours()});const mp=c8;var eY=c8.range,u8=xr(t=>t.setHours(0,0,0,0),(t,e)=>t.setDate(t.getDate()+e),(t,e)=>(e-t-(e.getTimezoneOffset()-t.getTimezoneOffset())*Fn)/Rs,t=>t.getDate()-1);const dc=u8;var rY=u8.range;function Is(t){return xr(function(e){e.setDate(e.getDate()-(e.getDay()+7-t)%7),e.setHours(0,0,0,0)},function(e,r){e.setDate(e.getDate()+r*7)},function(e,r){return(r-e-(r.getTimezoneOffset()-e.getTimezoneOffset())*Fn)/pp})}var Do=Is(0),pc=Is(1),h8=Is(2),f8=Is(3),Ns=Is(4),d8=Is(5),p8=Is(6),g8=Do.range,nY=pc.range,iY=h8.range,aY=f8.range,sY=Ns.range,oY=d8.range,lY=p8.range,y8=xr(function(t){t.setDate(1),t.setHours(0,0,0,0)},function(t,e){t.setMonth(t.getMonth()+e)},function(t,e){return e.getMonth()-t.getMonth()+(e.getFullYear()-t.getFullYear())*12},function(t){return t.getMonth()});const bp=y8;var cY=y8.range,_p=xr(function(t){t.setMonth(0,1),t.setHours(0,0,0,0)},function(t,e){t.setFullYear(t.getFullYear()+e)},function(t,e){return e.getFullYear()-t.getFullYear()},function(t){return t.getFullYear()});_p.every=function(t){return!isFinite(t=Math.floor(t))||!(t>0)?null:xr(function(e){e.setFullYear(Math.floor(e.getFullYear()/t)*t),e.setMonth(0,1),e.setHours(0,0,0,0)},function(e,r){e.setFullYear(e.getFullYear()+r*t)})};const Pa=_p;var uY=_p.range,m8=xr(function(t){t.setUTCSeconds(0,0)},function(t,e){t.setTime(+t+e*Fn)},function(t,e){return(e-t)/Fn},function(t){return t.getUTCMinutes()});const vp=m8;var hY=m8.range,b8=xr(function(t){t.setUTCMinutes(0,0,0)},function(t,e){t.setTime(+t+e*ra)},function(t,e){return(e-t)/ra},function(t){return t.getUTCHours()});const xp=b8;var fY=b8.range,_8=xr(function(t){t.setUTCHours(0,0,0,0)},function(t,e){t.setUTCDate(t.getUTCDate()+e)},function(t,e){return(e-t)/Rs},function(t){return t.getUTCDate()-1});const gc=_8;var dY=_8.range;function Bs(t){return xr(function(e){e.setUTCDate(e.getUTCDate()-(e.getUTCDay()+7-t)%7),e.setUTCHours(0,0,0,0)},function(e,r){e.setUTCDate(e.getUTCDate()+r*7)},function(e,r){return(r-e)/pp})}var Oo=Bs(0),yc=Bs(1),v8=Bs(2),x8=Bs(3),Ds=Bs(4),k8=Bs(5),w8=Bs(6),T8=Oo.range,pY=yc.range,gY=v8.range,yY=x8.range,mY=Ds.range,bY=k8.range,_Y=w8.range,E8=xr(function(t){t.setUTCDate(1),t.setUTCHours(0,0,0,0)},function(t,e){t.setUTCMonth(t.getUTCMonth()+e)},function(t,e){return e.getUTCMonth()-t.getUTCMonth()+(e.getUTCFullYear()-t.getUTCFullYear())*12},function(t){return t.getUTCMonth()});const kp=E8;var vY=E8.range,wp=xr(function(t){t.setUTCMonth(0,1),t.setUTCHours(0,0,0,0)},function(t,e){t.setUTCFullYear(t.getUTCFullYear()+e)},function(t,e){return e.getUTCFullYear()-t.getUTCFullYear()},function(t){return t.getUTCFullYear()});wp.every=function(t){return!isFinite(t=Math.floor(t))||!(t>0)?null:xr(function(e){e.setUTCFullYear(Math.floor(e.getUTCFullYear()/t)*t),e.setUTCMonth(0,1),e.setUTCHours(0,0,0,0)},function(e,r){e.setUTCFullYear(e.getUTCFullYear()+r*t)})};const qa=wp;var xY=wp.range;function C8(t,e,r,n,i,a){const s=[[Fa,1,ea],[Fa,5,5*ea],[Fa,15,15*ea],[Fa,30,30*ea],[a,1,Fn],[a,5,5*Fn],[a,15,15*Fn],[a,30,30*Fn],[i,1,ra],[i,3,3*ra],[i,6,6*ra],[i,12,12*ra],[n,1,Rs],[n,2,2*Rs],[r,1,pp],[e,1,a8],[e,3,3*a8],[t,1,gp]];function o(u,h,d){const f=hy).right(s,f);if(p===s.length)return t.every(wl(u/gp,h/gp,d));if(p===0)return dp.every(Math.max(wl(u,h,d),1));const[m,_]=s[f/s[p-1][2]53)return null;"w"in U||(U.w=1),"Z"in U?(j=Ep(mc(U.y,0,1)),P=j.getUTCDay(),j=P>4||P===0?yc.ceil(j):yc(j),j=gc.offset(j,(U.V-1)*7),U.y=j.getUTCFullYear(),U.m=j.getUTCMonth(),U.d=j.getUTCDate()+(U.w+6)%7):(j=Tp(mc(U.y,0,1)),P=j.getDay(),j=P>4||P===0?pc.ceil(j):pc(j),j=dc.offset(j,(U.V-1)*7),U.y=j.getFullYear(),U.m=j.getMonth(),U.d=j.getDate()+(U.w+6)%7)}else("W"in U||"U"in U)&&("w"in U||(U.w="u"in U?U.u%7:"W"in U?1:0),P="Z"in U?Ep(mc(U.y,0,1)).getUTCDay():Tp(mc(U.y,0,1)).getDay(),U.m=0,U.d="W"in U?(U.w+6)%7+U.W*7-(P+5)%7:U.w+U.U*7-(P+6)%7);return"Z"in U?(U.H+=U.Z/100|0,U.M+=U.Z%100,Ep(U)):Tp(U)}}function R(V,Q,q,U){for(var F=0,j=Q.length,P=q.length,et,at;F=P)return-1;if(et=Q.charCodeAt(F++),et===37){if(et=Q.charAt(F++),at=C[et in I8?Q.charAt(F++):et],!at||(U=at(V,q,U))<0)return-1}else if(et!=q.charCodeAt(U++))return-1}return U}function A(V,Q,q){var U=u.exec(Q.slice(q));return U?(V.p=h.get(U[0].toLowerCase()),q+U[0].length):-1}function L(V,Q,q){var U=p.exec(Q.slice(q));return U?(V.w=m.get(U[0].toLowerCase()),q+U[0].length):-1}function v(V,Q,q){var U=d.exec(Q.slice(q));return U?(V.w=f.get(U[0].toLowerCase()),q+U[0].length):-1}function B(V,Q,q){var U=b.exec(Q.slice(q));return U?(V.m=x.get(U[0].toLowerCase()),q+U[0].length):-1}function w(V,Q,q){var U=_.exec(Q.slice(q));return U?(V.m=y.get(U[0].toLowerCase()),q+U[0].length):-1}function D(V,Q,q){return R(V,e,Q,q)}function N(V,Q,q){return R(V,r,Q,q)}function z(V,Q,q){return R(V,n,Q,q)}function X(V){return s[V.getDay()]}function ct(V){return a[V.getDay()]}function J(V){return l[V.getMonth()]}function Y(V){return o[V.getMonth()]}function $(V){return i[+(V.getHours()>=12)]}function lt(V){return 1+~~(V.getMonth()/3)}function ut(V){return s[V.getUTCDay()]}function W(V){return a[V.getUTCDay()]}function tt(V){return l[V.getUTCMonth()]}function K(V){return o[V.getUTCMonth()]}function it(V){return i[+(V.getUTCHours()>=12)]}function Z(V){return 1+~~(V.getUTCMonth()/3)}return{format:function(V){var Q=M(V+="",k);return Q.toString=function(){return V},Q},parse:function(V){var Q=S(V+="",!1);return Q.toString=function(){return V},Q},utcFormat:function(V){var Q=M(V+="",T);return Q.toString=function(){return V},Q},utcParse:function(V){var Q=S(V+="",!0);return Q.toString=function(){return V},Q}}}var I8={"-":"",_:" ",0:"0"},Ar=/^\s*\d+/,kY=/^%/,wY=/[\\^$*+?|[\]().{}]/g;function Oe(t,e,r){var n=t<0?"-":"",i=(n?-t:t)+"",a=i.length;return n+(a[e.toLowerCase(),r]))}function EY(t,e,r){var n=Ar.exec(e.slice(r,r+1));return n?(t.w=+n[0],r+n[0].length):-1}function CY(t,e,r){var n=Ar.exec(e.slice(r,r+1));return n?(t.u=+n[0],r+n[0].length):-1}function SY(t,e,r){var n=Ar.exec(e.slice(r,r+2));return n?(t.U=+n[0],r+n[0].length):-1}function AY(t,e,r){var n=Ar.exec(e.slice(r,r+2));return n?(t.V=+n[0],r+n[0].length):-1}function MY(t,e,r){var n=Ar.exec(e.slice(r,r+2));return n?(t.W=+n[0],r+n[0].length):-1}function N8(t,e,r){var n=Ar.exec(e.slice(r,r+4));return n?(t.y=+n[0],r+n[0].length):-1}function B8(t,e,r){var n=Ar.exec(e.slice(r,r+2));return n?(t.y=+n[0]+(+n[0]>68?1900:2e3),r+n[0].length):-1}function LY(t,e,r){var n=/^(Z)|([+-]\d\d)(?::?(\d\d))?/.exec(e.slice(r,r+6));return n?(t.Z=n[1]?0:-(n[2]+(n[3]||"00")),r+n[0].length):-1}function RY(t,e,r){var n=Ar.exec(e.slice(r,r+1));return n?(t.q=n[0]*3-3,r+n[0].length):-1}function IY(t,e,r){var n=Ar.exec(e.slice(r,r+2));return n?(t.m=n[0]-1,r+n[0].length):-1}function D8(t,e,r){var n=Ar.exec(e.slice(r,r+2));return n?(t.d=+n[0],r+n[0].length):-1}function NY(t,e,r){var n=Ar.exec(e.slice(r,r+3));return n?(t.m=0,t.d=+n[0],r+n[0].length):-1}function O8(t,e,r){var n=Ar.exec(e.slice(r,r+2));return n?(t.H=+n[0],r+n[0].length):-1}function BY(t,e,r){var n=Ar.exec(e.slice(r,r+2));return n?(t.M=+n[0],r+n[0].length):-1}function DY(t,e,r){var n=Ar.exec(e.slice(r,r+2));return n?(t.S=+n[0],r+n[0].length):-1}function OY(t,e,r){var n=Ar.exec(e.slice(r,r+3));return n?(t.L=+n[0],r+n[0].length):-1}function FY(t,e,r){var n=Ar.exec(e.slice(r,r+6));return n?(t.L=Math.floor(n[0]/1e3),r+n[0].length):-1}function PY(t,e,r){var n=kY.exec(e.slice(r,r+1));return n?r+n[0].length:-1}function qY(t,e,r){var n=Ar.exec(e.slice(r));return n?(t.Q=+n[0],r+n[0].length):-1}function VY(t,e,r){var n=Ar.exec(e.slice(r));return n?(t.s=+n[0],r+n[0].length):-1}function F8(t,e){return Oe(t.getDate(),e,2)}function zY(t,e){return Oe(t.getHours(),e,2)}function YY(t,e){return Oe(t.getHours()%12||12,e,2)}function UY(t,e){return Oe(1+dc.count(Pa(t),t),e,3)}function P8(t,e){return Oe(t.getMilliseconds(),e,3)}function WY(t,e){return P8(t,e)+"000"}function HY(t,e){return Oe(t.getMonth()+1,e,2)}function GY(t,e){return Oe(t.getMinutes(),e,2)}function jY(t,e){return Oe(t.getSeconds(),e,2)}function $Y(t){var e=t.getDay();return e===0?7:e}function XY(t,e){return Oe(Do.count(Pa(t)-1,t),e,2)}function q8(t){var e=t.getDay();return e>=4||e===0?Ns(t):Ns.ceil(t)}function KY(t,e){return t=q8(t),Oe(Ns.count(Pa(t),t)+(Pa(t).getDay()===4),e,2)}function ZY(t){return t.getDay()}function QY(t,e){return Oe(pc.count(Pa(t)-1,t),e,2)}function JY(t,e){return Oe(t.getFullYear()%100,e,2)}function tU(t,e){return t=q8(t),Oe(t.getFullYear()%100,e,2)}function eU(t,e){return Oe(t.getFullYear()%1e4,e,4)}function rU(t,e){var r=t.getDay();return t=r>=4||r===0?Ns(t):Ns.ceil(t),Oe(t.getFullYear()%1e4,e,4)}function nU(t){var e=t.getTimezoneOffset();return(e>0?"-":(e*=-1,"+"))+Oe(e/60|0,"0",2)+Oe(e%60,"0",2)}function V8(t,e){return Oe(t.getUTCDate(),e,2)}function iU(t,e){return Oe(t.getUTCHours(),e,2)}function aU(t,e){return Oe(t.getUTCHours()%12||12,e,2)}function sU(t,e){return Oe(1+gc.count(qa(t),t),e,3)}function z8(t,e){return Oe(t.getUTCMilliseconds(),e,3)}function oU(t,e){return z8(t,e)+"000"}function lU(t,e){return Oe(t.getUTCMonth()+1,e,2)}function cU(t,e){return Oe(t.getUTCMinutes(),e,2)}function uU(t,e){return Oe(t.getUTCSeconds(),e,2)}function hU(t){var e=t.getUTCDay();return e===0?7:e}function fU(t,e){return Oe(Oo.count(qa(t)-1,t),e,2)}function Y8(t){var e=t.getUTCDay();return e>=4||e===0?Ds(t):Ds.ceil(t)}function dU(t,e){return t=Y8(t),Oe(Ds.count(qa(t),t)+(qa(t).getUTCDay()===4),e,2)}function pU(t){return t.getUTCDay()}function gU(t,e){return Oe(yc.count(qa(t)-1,t),e,2)}function yU(t,e){return Oe(t.getUTCFullYear()%100,e,2)}function mU(t,e){return t=Y8(t),Oe(t.getUTCFullYear()%100,e,2)}function bU(t,e){return Oe(t.getUTCFullYear()%1e4,e,4)}function _U(t,e){var r=t.getUTCDay();return t=r>=4||r===0?Ds(t):Ds.ceil(t),Oe(t.getUTCFullYear()%1e4,e,4)}function vU(){return"+0000"}function U8(){return"%"}function W8(t){return+t}function H8(t){return Math.floor(+t/1e3)}var Fo,vc,G8,lf,Cp;j8({dateTime:"%x, %X",date:"%-m/%-d/%Y",time:"%-I:%M:%S %p",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});function j8(t){return Fo=R8(t),vc=Fo.format,G8=Fo.parse,lf=Fo.utcFormat,Cp=Fo.utcParse,Fo}var $8="%Y-%m-%dT%H:%M:%S.%LZ";function xU(t){return t.toISOString()}var kU=Date.prototype.toISOString?xU:lf($8);const wU=kU;function TU(t){var e=new Date(t);return isNaN(e)?null:e}var EU=+new Date("2000-01-01T00:00:00.000Z")?TU:Cp($8);const CU=EU;function SU(t){return new Date(t)}function AU(t){return t instanceof Date?+t:+new Date(+t)}function Sp(t,e,r,n,i,a,s,o,l,u){var h=ap(),d=h.invert,f=h.domain,p=u(".%L"),m=u(":%S"),_=u("%I:%M"),y=u("%I %p"),b=u("%a %d"),x=u("%b %d"),k=u("%B"),T=u("%Y");function C(M){return(l(M)e(i/(t.length-1)))},r.quantiles=function(n){return Array.from({length:n+1},(i,a)=>Cl(t,a/n))},r.copy=function(){return J8(e).domain(t)},ta.apply(r,arguments)}function uf(){var t=0,e=.5,r=1,n=1,i,a,s,o,l,u=an,h,d=!1,f;function p(_){return isNaN(_=+_)?f:(_=.5+((_=+h(_))-a)*(n*_B5(t[t.length-1]);var n7=new Array(3).concat("d8b365f5f5f55ab4ac","a6611adfc27d80cdc1018571","a6611adfc27df5f5f580cdc1018571","8c510ad8b365f6e8c3c7eae55ab4ac01665e","8c510ad8b365f6e8c3f5f5f5c7eae55ab4ac01665e","8c510abf812ddfc27df6e8c3c7eae580cdc135978f01665e","8c510abf812ddfc27df6e8c3f5f5f5c7eae580cdc135978f01665e","5430058c510abf812ddfc27df6e8c3c7eae580cdc135978f01665e003c30","5430058c510abf812ddfc27df6e8c3f5f5f5c7eae580cdc135978f01665e003c30").map(Ee);const YU=We(n7);var i7=new Array(3).concat("af8dc3f7f7f77fbf7b","7b3294c2a5cfa6dba0008837","7b3294c2a5cff7f7f7a6dba0008837","762a83af8dc3e7d4e8d9f0d37fbf7b1b7837","762a83af8dc3e7d4e8f7f7f7d9f0d37fbf7b1b7837","762a839970abc2a5cfe7d4e8d9f0d3a6dba05aae611b7837","762a839970abc2a5cfe7d4e8f7f7f7d9f0d3a6dba05aae611b7837","40004b762a839970abc2a5cfe7d4e8d9f0d3a6dba05aae611b783700441b","40004b762a839970abc2a5cfe7d4e8f7f7f7d9f0d3a6dba05aae611b783700441b").map(Ee);const UU=We(i7);var a7=new Array(3).concat("e9a3c9f7f7f7a1d76a","d01c8bf1b6dab8e1864dac26","d01c8bf1b6daf7f7f7b8e1864dac26","c51b7de9a3c9fde0efe6f5d0a1d76a4d9221","c51b7de9a3c9fde0eff7f7f7e6f5d0a1d76a4d9221","c51b7dde77aef1b6dafde0efe6f5d0b8e1867fbc414d9221","c51b7dde77aef1b6dafde0eff7f7f7e6f5d0b8e1867fbc414d9221","8e0152c51b7dde77aef1b6dafde0efe6f5d0b8e1867fbc414d9221276419","8e0152c51b7dde77aef1b6dafde0eff7f7f7e6f5d0b8e1867fbc414d9221276419").map(Ee);const WU=We(a7);var s7=new Array(3).concat("998ec3f7f7f7f1a340","5e3c99b2abd2fdb863e66101","5e3c99b2abd2f7f7f7fdb863e66101","542788998ec3d8daebfee0b6f1a340b35806","542788998ec3d8daebf7f7f7fee0b6f1a340b35806","5427888073acb2abd2d8daebfee0b6fdb863e08214b35806","5427888073acb2abd2d8daebf7f7f7fee0b6fdb863e08214b35806","2d004b5427888073acb2abd2d8daebfee0b6fdb863e08214b358067f3b08","2d004b5427888073acb2abd2d8daebf7f7f7fee0b6fdb863e08214b358067f3b08").map(Ee);const HU=We(s7);var o7=new Array(3).concat("ef8a62f7f7f767a9cf","ca0020f4a58292c5de0571b0","ca0020f4a582f7f7f792c5de0571b0","b2182bef8a62fddbc7d1e5f067a9cf2166ac","b2182bef8a62fddbc7f7f7f7d1e5f067a9cf2166ac","b2182bd6604df4a582fddbc7d1e5f092c5de4393c32166ac","b2182bd6604df4a582fddbc7f7f7f7d1e5f092c5de4393c32166ac","67001fb2182bd6604df4a582fddbc7d1e5f092c5de4393c32166ac053061","67001fb2182bd6604df4a582fddbc7f7f7f7d1e5f092c5de4393c32166ac053061").map(Ee);const GU=We(o7);var l7=new Array(3).concat("ef8a62ffffff999999","ca0020f4a582bababa404040","ca0020f4a582ffffffbababa404040","b2182bef8a62fddbc7e0e0e09999994d4d4d","b2182bef8a62fddbc7ffffffe0e0e09999994d4d4d","b2182bd6604df4a582fddbc7e0e0e0bababa8787874d4d4d","b2182bd6604df4a582fddbc7ffffffe0e0e0bababa8787874d4d4d","67001fb2182bd6604df4a582fddbc7e0e0e0bababa8787874d4d4d1a1a1a","67001fb2182bd6604df4a582fddbc7ffffffe0e0e0bababa8787874d4d4d1a1a1a").map(Ee);const jU=We(l7);var c7=new Array(3).concat("fc8d59ffffbf91bfdb","d7191cfdae61abd9e92c7bb6","d7191cfdae61ffffbfabd9e92c7bb6","d73027fc8d59fee090e0f3f891bfdb4575b4","d73027fc8d59fee090ffffbfe0f3f891bfdb4575b4","d73027f46d43fdae61fee090e0f3f8abd9e974add14575b4","d73027f46d43fdae61fee090ffffbfe0f3f8abd9e974add14575b4","a50026d73027f46d43fdae61fee090e0f3f8abd9e974add14575b4313695","a50026d73027f46d43fdae61fee090ffffbfe0f3f8abd9e974add14575b4313695").map(Ee);const $U=We(c7);var u7=new Array(3).concat("fc8d59ffffbf91cf60","d7191cfdae61a6d96a1a9641","d7191cfdae61ffffbfa6d96a1a9641","d73027fc8d59fee08bd9ef8b91cf601a9850","d73027fc8d59fee08bffffbfd9ef8b91cf601a9850","d73027f46d43fdae61fee08bd9ef8ba6d96a66bd631a9850","d73027f46d43fdae61fee08bffffbfd9ef8ba6d96a66bd631a9850","a50026d73027f46d43fdae61fee08bd9ef8ba6d96a66bd631a9850006837","a50026d73027f46d43fdae61fee08bffffbfd9ef8ba6d96a66bd631a9850006837").map(Ee);const XU=We(u7);var h7=new Array(3).concat("fc8d59ffffbf99d594","d7191cfdae61abdda42b83ba","d7191cfdae61ffffbfabdda42b83ba","d53e4ffc8d59fee08be6f59899d5943288bd","d53e4ffc8d59fee08bffffbfe6f59899d5943288bd","d53e4ff46d43fdae61fee08be6f598abdda466c2a53288bd","d53e4ff46d43fdae61fee08bffffbfe6f598abdda466c2a53288bd","9e0142d53e4ff46d43fdae61fee08be6f598abdda466c2a53288bd5e4fa2","9e0142d53e4ff46d43fdae61fee08bffffbfe6f598abdda466c2a53288bd5e4fa2").map(Ee);const KU=We(h7);var f7=new Array(3).concat("e5f5f999d8c92ca25f","edf8fbb2e2e266c2a4238b45","edf8fbb2e2e266c2a42ca25f006d2c","edf8fbccece699d8c966c2a42ca25f006d2c","edf8fbccece699d8c966c2a441ae76238b45005824","f7fcfde5f5f9ccece699d8c966c2a441ae76238b45005824","f7fcfde5f5f9ccece699d8c966c2a441ae76238b45006d2c00441b").map(Ee);const ZU=We(f7);var d7=new Array(3).concat("e0ecf49ebcda8856a7","edf8fbb3cde38c96c688419d","edf8fbb3cde38c96c68856a7810f7c","edf8fbbfd3e69ebcda8c96c68856a7810f7c","edf8fbbfd3e69ebcda8c96c68c6bb188419d6e016b","f7fcfde0ecf4bfd3e69ebcda8c96c68c6bb188419d6e016b","f7fcfde0ecf4bfd3e69ebcda8c96c68c6bb188419d810f7c4d004b").map(Ee);const QU=We(d7);var p7=new Array(3).concat("e0f3dba8ddb543a2ca","f0f9e8bae4bc7bccc42b8cbe","f0f9e8bae4bc7bccc443a2ca0868ac","f0f9e8ccebc5a8ddb57bccc443a2ca0868ac","f0f9e8ccebc5a8ddb57bccc44eb3d32b8cbe08589e","f7fcf0e0f3dbccebc5a8ddb57bccc44eb3d32b8cbe08589e","f7fcf0e0f3dbccebc5a8ddb57bccc44eb3d32b8cbe0868ac084081").map(Ee);const JU=We(p7);var g7=new Array(3).concat("fee8c8fdbb84e34a33","fef0d9fdcc8afc8d59d7301f","fef0d9fdcc8afc8d59e34a33b30000","fef0d9fdd49efdbb84fc8d59e34a33b30000","fef0d9fdd49efdbb84fc8d59ef6548d7301f990000","fff7ecfee8c8fdd49efdbb84fc8d59ef6548d7301f990000","fff7ecfee8c8fdd49efdbb84fc8d59ef6548d7301fb300007f0000").map(Ee);const tW=We(g7);var y7=new Array(3).concat("ece2f0a6bddb1c9099","f6eff7bdc9e167a9cf02818a","f6eff7bdc9e167a9cf1c9099016c59","f6eff7d0d1e6a6bddb67a9cf1c9099016c59","f6eff7d0d1e6a6bddb67a9cf3690c002818a016450","fff7fbece2f0d0d1e6a6bddb67a9cf3690c002818a016450","fff7fbece2f0d0d1e6a6bddb67a9cf3690c002818a016c59014636").map(Ee);const eW=We(y7);var m7=new Array(3).concat("ece7f2a6bddb2b8cbe","f1eef6bdc9e174a9cf0570b0","f1eef6bdc9e174a9cf2b8cbe045a8d","f1eef6d0d1e6a6bddb74a9cf2b8cbe045a8d","f1eef6d0d1e6a6bddb74a9cf3690c00570b0034e7b","fff7fbece7f2d0d1e6a6bddb74a9cf3690c00570b0034e7b","fff7fbece7f2d0d1e6a6bddb74a9cf3690c00570b0045a8d023858").map(Ee);const rW=We(m7);var b7=new Array(3).concat("e7e1efc994c7dd1c77","f1eef6d7b5d8df65b0ce1256","f1eef6d7b5d8df65b0dd1c77980043","f1eef6d4b9dac994c7df65b0dd1c77980043","f1eef6d4b9dac994c7df65b0e7298ace125691003f","f7f4f9e7e1efd4b9dac994c7df65b0e7298ace125691003f","f7f4f9e7e1efd4b9dac994c7df65b0e7298ace125698004367001f").map(Ee);const nW=We(b7);var _7=new Array(3).concat("fde0ddfa9fb5c51b8a","feebe2fbb4b9f768a1ae017e","feebe2fbb4b9f768a1c51b8a7a0177","feebe2fcc5c0fa9fb5f768a1c51b8a7a0177","feebe2fcc5c0fa9fb5f768a1dd3497ae017e7a0177","fff7f3fde0ddfcc5c0fa9fb5f768a1dd3497ae017e7a0177","fff7f3fde0ddfcc5c0fa9fb5f768a1dd3497ae017e7a017749006a").map(Ee);const iW=We(_7);var v7=new Array(3).concat("edf8b17fcdbb2c7fb8","ffffcca1dab441b6c4225ea8","ffffcca1dab441b6c42c7fb8253494","ffffccc7e9b47fcdbb41b6c42c7fb8253494","ffffccc7e9b47fcdbb41b6c41d91c0225ea80c2c84","ffffd9edf8b1c7e9b47fcdbb41b6c41d91c0225ea80c2c84","ffffd9edf8b1c7e9b47fcdbb41b6c41d91c0225ea8253494081d58").map(Ee);const aW=We(v7);var x7=new Array(3).concat("f7fcb9addd8e31a354","ffffccc2e69978c679238443","ffffccc2e69978c67931a354006837","ffffccd9f0a3addd8e78c67931a354006837","ffffccd9f0a3addd8e78c67941ab5d238443005a32","ffffe5f7fcb9d9f0a3addd8e78c67941ab5d238443005a32","ffffe5f7fcb9d9f0a3addd8e78c67941ab5d238443006837004529").map(Ee);const sW=We(x7);var k7=new Array(3).concat("fff7bcfec44fd95f0e","ffffd4fed98efe9929cc4c02","ffffd4fed98efe9929d95f0e993404","ffffd4fee391fec44ffe9929d95f0e993404","ffffd4fee391fec44ffe9929ec7014cc4c028c2d04","ffffe5fff7bcfee391fec44ffe9929ec7014cc4c028c2d04","ffffe5fff7bcfee391fec44ffe9929ec7014cc4c02993404662506").map(Ee);const oW=We(k7);var w7=new Array(3).concat("ffeda0feb24cf03b20","ffffb2fecc5cfd8d3ce31a1c","ffffb2fecc5cfd8d3cf03b20bd0026","ffffb2fed976feb24cfd8d3cf03b20bd0026","ffffb2fed976feb24cfd8d3cfc4e2ae31a1cb10026","ffffccffeda0fed976feb24cfd8d3cfc4e2ae31a1cb10026","ffffccffeda0fed976feb24cfd8d3cfc4e2ae31a1cbd0026800026").map(Ee);const lW=We(w7);var T7=new Array(3).concat("deebf79ecae13182bd","eff3ffbdd7e76baed62171b5","eff3ffbdd7e76baed63182bd08519c","eff3ffc6dbef9ecae16baed63182bd08519c","eff3ffc6dbef9ecae16baed64292c62171b5084594","f7fbffdeebf7c6dbef9ecae16baed64292c62171b5084594","f7fbffdeebf7c6dbef9ecae16baed64292c62171b508519c08306b").map(Ee);const cW=We(T7);var E7=new Array(3).concat("e5f5e0a1d99b31a354","edf8e9bae4b374c476238b45","edf8e9bae4b374c47631a354006d2c","edf8e9c7e9c0a1d99b74c47631a354006d2c","edf8e9c7e9c0a1d99b74c47641ab5d238b45005a32","f7fcf5e5f5e0c7e9c0a1d99b74c47641ab5d238b45005a32","f7fcf5e5f5e0c7e9c0a1d99b74c47641ab5d238b45006d2c00441b").map(Ee);const uW=We(E7);var C7=new Array(3).concat("f0f0f0bdbdbd636363","f7f7f7cccccc969696525252","f7f7f7cccccc969696636363252525","f7f7f7d9d9d9bdbdbd969696636363252525","f7f7f7d9d9d9bdbdbd969696737373525252252525","fffffff0f0f0d9d9d9bdbdbd969696737373525252252525","fffffff0f0f0d9d9d9bdbdbd969696737373525252252525000000").map(Ee);const hW=We(C7);var S7=new Array(3).concat("efedf5bcbddc756bb1","f2f0f7cbc9e29e9ac86a51a3","f2f0f7cbc9e29e9ac8756bb154278f","f2f0f7dadaebbcbddc9e9ac8756bb154278f","f2f0f7dadaebbcbddc9e9ac8807dba6a51a34a1486","fcfbfdefedf5dadaebbcbddc9e9ac8807dba6a51a34a1486","fcfbfdefedf5dadaebbcbddc9e9ac8807dba6a51a354278f3f007d").map(Ee);const fW=We(S7);var A7=new Array(3).concat("fee0d2fc9272de2d26","fee5d9fcae91fb6a4acb181d","fee5d9fcae91fb6a4ade2d26a50f15","fee5d9fcbba1fc9272fb6a4ade2d26a50f15","fee5d9fcbba1fc9272fb6a4aef3b2ccb181d99000d","fff5f0fee0d2fcbba1fc9272fb6a4aef3b2ccb181d99000d","fff5f0fee0d2fcbba1fc9272fb6a4aef3b2ccb181da50f1567000d").map(Ee);const dW=We(A7);var M7=new Array(3).concat("fee6cefdae6be6550d","feeddefdbe85fd8d3cd94701","feeddefdbe85fd8d3ce6550da63603","feeddefdd0a2fdae6bfd8d3ce6550da63603","feeddefdd0a2fdae6bfd8d3cf16913d948018c2d04","fff5ebfee6cefdd0a2fdae6bfd8d3cf16913d948018c2d04","fff5ebfee6cefdd0a2fdae6bfd8d3cf16913d94801a636037f2704").map(Ee);const pW=We(M7);function gW(t){return t=Math.max(0,Math.min(1,t)),"rgb("+Math.max(0,Math.min(255,Math.round(-4.54-t*(35.34-t*(2381.73-t*(6402.7-t*(7024.72-t*2710.57)))))))+", "+Math.max(0,Math.min(255,Math.round(32.49+t*(170.73+t*(52.82-t*(131.46-t*(176.58-t*67.37)))))))+", "+Math.max(0,Math.min(255,Math.round(81.24+t*(442.36-t*(2482.43-t*(6167.24-t*(6614.94-t*2475.67)))))))+")"}const yW=Xu(Qn(300,.5,0),Qn(-240,.5,1));var mW=Xu(Qn(-100,.75,.35),Qn(80,1.5,.8)),bW=Xu(Qn(260,.75,.35),Qn(80,1.5,.8)),hf=Qn();function _W(t){(t<0||t>1)&&(t-=Math.floor(t));var e=Math.abs(t-.5);return hf.h=360*t-100,hf.s=1.5-1.5*e,hf.l=.8-.9*e,hf+""}var ff=po(),vW=Math.PI/3,xW=Math.PI*2/3;function kW(t){var e;return t=(.5-t)*Math.PI,ff.r=255*(e=Math.sin(t))*e,ff.g=255*(e=Math.sin(t+vW))*e,ff.b=255*(e=Math.sin(t+xW))*e,ff+""}function wW(t){return t=Math.max(0,Math.min(1,t)),"rgb("+Math.max(0,Math.min(255,Math.round(34.61+t*(1172.33-t*(10793.56-t*(33300.12-t*(38394.49-t*14825.05)))))))+", "+Math.max(0,Math.min(255,Math.round(23.31+t*(557.33+t*(1225.33-t*(3574.96-t*(1073.77+t*707.56)))))))+", "+Math.max(0,Math.min(255,Math.round(27.2+t*(3211.1-t*(15327.97-t*(27814-t*(22569.18-t*6838.66)))))))+")"}function df(t){var e=t.length;return function(r){return t[Math.max(0,Math.min(e-1,Math.floor(r*e)))]}}const TW=df(Ee("44015444025645045745055946075a46085c460a5d460b5e470d60470e6147106347116447136548146748166848176948186a481a6c481b6d481c6e481d6f481f70482071482173482374482475482576482677482878482979472a7a472c7a472d7b472e7c472f7d46307e46327e46337f463480453581453781453882443983443a83443b84433d84433e85423f854240864241864142874144874045884046883f47883f48893e49893e4a893e4c8a3d4d8a3d4e8a3c4f8a3c508b3b518b3b528b3a538b3a548c39558c39568c38588c38598c375a8c375b8d365c8d365d8d355e8d355f8d34608d34618d33628d33638d32648e32658e31668e31678e31688e30698e306a8e2f6b8e2f6c8e2e6d8e2e6e8e2e6f8e2d708e2d718e2c718e2c728e2c738e2b748e2b758e2a768e2a778e2a788e29798e297a8e297b8e287c8e287d8e277e8e277f8e27808e26818e26828e26828e25838e25848e25858e24868e24878e23888e23898e238a8d228b8d228c8d228d8d218e8d218f8d21908d21918c20928c20928c20938c1f948c1f958b1f968b1f978b1f988b1f998a1f9a8a1e9b8a1e9c891e9d891f9e891f9f881fa0881fa1881fa1871fa28720a38620a48621a58521a68522a78522a88423a98324aa8325ab8225ac8226ad8127ad8128ae8029af7f2ab07f2cb17e2db27d2eb37c2fb47c31b57b32b67a34b67935b77937b87838b9773aba763bbb753dbc743fbc7340bd7242be7144bf7046c06f48c16e4ac16d4cc26c4ec36b50c46a52c56954c56856c66758c7655ac8645cc8635ec96260ca6063cb5f65cb5e67cc5c69cd5b6ccd5a6ece5870cf5773d05675d05477d1537ad1517cd2507fd34e81d34d84d44b86d54989d5488bd6468ed64590d74393d74195d84098d83e9bd93c9dd93ba0da39a2da37a5db36a8db34aadc32addc30b0dd2fb2dd2db5de2bb8de29bade28bddf26c0df25c2df23c5e021c8e020cae11fcde11dd0e11cd2e21bd5e21ad8e219dae319dde318dfe318e2e418e5e419e7e419eae51aece51befe51cf1e51df4e61ef6e620f8e621fbe723fde725"));var EW=df(Ee("00000401000501010601010802010902020b02020d03030f03031204041405041606051806051a07061c08071e0907200a08220b09240c09260d0a290e0b2b100b2d110c2f120d31130d34140e36150e38160f3b180f3d19103f1a10421c10441d11471e114920114b21114e22115024125325125527125829115a2a115c2c115f2d11612f116331116533106734106936106b38106c390f6e3b0f703d0f713f0f72400f74420f75440f764510774710784910784a10794c117a4e117b4f127b51127c52137c54137d56147d57157e59157e5a167e5c167f5d177f5f187f601880621980641a80651a80671b80681c816a1c816b1d816d1d816e1e81701f81721f817320817521817621817822817922827b23827c23827e24828025828125818326818426818627818827818928818b29818c29818e2a81902a81912b81932b80942c80962c80982d80992d809b2e7f9c2e7f9e2f7fa02f7fa1307ea3307ea5317ea6317da8327daa337dab337cad347cae347bb0357bb2357bb3367ab5367ab73779b83779ba3878bc3978bd3977bf3a77c03a76c23b75c43c75c53c74c73d73c83e73ca3e72cc3f71cd4071cf4070d0416fd2426fd3436ed5446dd6456cd8456cd9466bdb476adc4869de4968df4a68e04c67e24d66e34e65e44f64e55064e75263e85362e95462ea5661eb5760ec5860ed5a5fee5b5eef5d5ef05f5ef1605df2625df2645cf3655cf4675cf4695cf56b5cf66c5cf66e5cf7705cf7725cf8745cf8765cf9785df9795df97b5dfa7d5efa7f5efa815ffb835ffb8560fb8761fc8961fc8a62fc8c63fc8e64fc9065fd9266fd9467fd9668fd9869fd9a6afd9b6bfe9d6cfe9f6dfea16efea36ffea571fea772fea973feaa74feac76feae77feb078feb27afeb47bfeb67cfeb77efeb97ffebb81febd82febf84fec185fec287fec488fec68afec88cfeca8dfecc8ffecd90fecf92fed194fed395fed597fed799fed89afdda9cfddc9efddea0fde0a1fde2a3fde3a5fde5a7fde7a9fde9aafdebacfcecaefceeb0fcf0b2fcf2b4fcf4b6fcf6b8fcf7b9fcf9bbfcfbbdfcfdbf")),CW=df(Ee("00000401000501010601010802010a02020c02020e03021004031204031405041706041907051b08051d09061f0a07220b07240c08260d08290e092b10092d110a30120a32140b34150b37160b39180c3c190c3e1b0c411c0c431e0c451f0c48210c4a230c4c240c4f260c51280b53290b552b0b572d0b592f0a5b310a5c320a5e340a5f3609613809623909633b09643d09653e0966400a67420a68440a68450a69470b6a490b6a4a0c6b4c0c6b4d0d6c4f0d6c510e6c520e6d540f6d550f6d57106e59106e5a116e5c126e5d126e5f136e61136e62146e64156e65156e67166e69166e6a176e6c186e6d186e6f196e71196e721a6e741a6e751b6e771c6d781c6d7a1d6d7c1d6d7d1e6d7f1e6c801f6c82206c84206b85216b87216b88226a8a226a8c23698d23698f24699025689225689326679526679727669827669a28659b29649d29649f2a63a02a63a22b62a32c61a52c60a62d60a82e5fa92e5eab2f5ead305dae305cb0315bb1325ab3325ab43359b63458b73557b93556ba3655bc3754bd3853bf3952c03a51c13a50c33b4fc43c4ec63d4dc73e4cc83f4bca404acb4149cc4248ce4347cf4446d04545d24644d34743d44842d54a41d74b3fd84c3ed94d3dda4e3cdb503bdd513ade5238df5337e05536e15635e25734e35933e45a31e55c30e65d2fe75e2ee8602de9612bea632aeb6429eb6628ec6726ed6925ee6a24ef6c23ef6e21f06f20f1711ff1731df2741cf3761bf37819f47918f57b17f57d15f67e14f68013f78212f78410f8850ff8870ef8890cf98b0bf98c0af98e09fa9008fa9207fa9407fb9606fb9706fb9906fb9b06fb9d07fc9f07fca108fca309fca50afca60cfca80dfcaa0ffcac11fcae12fcb014fcb216fcb418fbb61afbb81dfbba1ffbbc21fbbe23fac026fac228fac42afac62df9c72ff9c932f9cb35f8cd37f8cf3af7d13df7d340f6d543f6d746f5d949f5db4cf4dd4ff4df53f4e156f3e35af3e55df2e661f2e865f2ea69f1ec6df1ed71f1ef75f1f179f2f27df2f482f3f586f3f68af4f88ef5f992f6fa96f8fb9af9fc9dfafda1fcffa4")),SW=df(Ee("0d088710078813078916078a19068c1b068d1d068e20068f2206902406912605912805922a05932c05942e05952f059631059733059735049837049938049a3a049a3c049b3e049c3f049c41049d43039e44039e46039f48039f4903a04b03a14c02a14e02a25002a25102a35302a35502a45601a45801a45901a55b01a55c01a65e01a66001a66100a76300a76400a76600a76700a86900a86a00a86c00a86e00a86f00a87100a87201a87401a87501a87701a87801a87a02a87b02a87d03a87e03a88004a88104a78305a78405a78606a68707a68808a68a09a58b0aa58d0ba58e0ca48f0da4910ea3920fa39410a29511a19613a19814a099159f9a169f9c179e9d189d9e199da01a9ca11b9ba21d9aa31e9aa51f99a62098a72197a82296aa2395ab2494ac2694ad2793ae2892b02991b12a90b22b8fb32c8eb42e8db52f8cb6308bb7318ab83289ba3388bb3488bc3587bd3786be3885bf3984c03a83c13b82c23c81c33d80c43e7fc5407ec6417dc7427cc8437bc9447aca457acb4679cc4778cc4977cd4a76ce4b75cf4c74d04d73d14e72d24f71d35171d45270d5536fd5546ed6556dd7566cd8576bd9586ada5a6ada5b69db5c68dc5d67dd5e66de5f65de6164df6263e06363e16462e26561e26660e3685fe4695ee56a5de56b5de66c5ce76e5be76f5ae87059e97158e97257ea7457eb7556eb7655ec7754ed7953ed7a52ee7b51ef7c51ef7e50f07f4ff0804ef1814df1834cf2844bf3854bf3874af48849f48948f58b47f58c46f68d45f68f44f79044f79143f79342f89441f89540f9973ff9983ef99a3efa9b3dfa9c3cfa9e3bfb9f3afba139fba238fca338fca537fca636fca835fca934fdab33fdac33fdae32fdaf31fdb130fdb22ffdb42ffdb52efeb72dfeb82cfeba2cfebb2bfebd2afebe2afec029fdc229fdc328fdc527fdc627fdc827fdca26fdcb26fccd25fcce25fcd025fcd225fbd324fbd524fbd724fad824fada24f9dc24f9dd25f8df25f8e125f7e225f7e425f6e626f6e826f5e926f5eb27f4ed27f3ee27f3f027f2f227f1f426f1f525f0f724f0f921"));function xe(t){return function(){return t}}const L7=Math.abs,qr=Math.atan2,na=Math.cos,AW=Math.max,Po=Math.min,gn=Math.sin,He=Math.sqrt,Vr=1e-12,za=Math.PI,pf=za/2,Ya=2*za;function MW(t){return t>1?0:t<-1?za:Math.acos(t)}function R7(t){return t>=1?pf:t<=-1?-pf:Math.asin(t)}function LW(t){return t.innerRadius}function RW(t){return t.outerRadius}function IW(t){return t.startAngle}function NW(t){return t.endAngle}function BW(t){return t&&t.padAngle}function DW(t,e,r,n,i,a,s,o){var l=r-t,u=n-e,h=s-i,d=o-a,f=d*l-h*u;if(!(f*fD*D+N*N&&(R=L,A=v),{cx:R,cy:A,x01:-h,y01:-d,x11:R*(i/C-1),y11:A*(i/C-1)}}function yf(){var t=LW,e=RW,r=xe(0),n=null,i=IW,a=NW,s=BW,o=null;function l(){var u,h,d=+t.apply(this,arguments),f=+e.apply(this,arguments),p=i.apply(this,arguments)-pf,m=a.apply(this,arguments)-pf,_=L7(m-p),y=m>p;if(o||(o=u=Ra()),fVr))o.moveTo(0,0);else if(_>Ya-Vr)o.moveTo(f*na(p),f*gn(p)),o.arc(0,0,f,p,m,!y),d>Vr&&(o.moveTo(d*na(m),d*gn(m)),o.arc(0,0,d,m,p,y));else{var b=p,x=m,k=p,T=m,C=_,M=_,S=s.apply(this,arguments)/2,R=S>Vr&&(n?+n.apply(this,arguments):He(d*d+f*f)),A=Po(L7(f-d)/2,+r.apply(this,arguments)),L=A,v=A,B,w;if(R>Vr){var D=R7(R/d*gn(S)),N=R7(R/f*gn(S));(C-=D*2)>Vr?(D*=y?1:-1,k+=D,T-=D):(C=0,k=T=(p+m)/2),(M-=N*2)>Vr?(N*=y?1:-1,b+=N,x-=N):(M=0,b=x=(p+m)/2)}var z=f*na(b),X=f*gn(b),ct=d*na(T),J=d*gn(T);if(A>Vr){var Y=f*na(x),$=f*gn(x),lt=d*na(k),ut=d*gn(k),W;if(_Vr?v>Vr?(B=gf(lt,ut,z,X,f,v,y),w=gf(Y,$,ct,J,f,v,y),o.moveTo(B.cx+B.x01,B.cy+B.y01),vVr)||!(C>Vr)?o.lineTo(ct,J):L>Vr?(B=gf(ct,J,Y,$,d,-L,y),w=gf(z,X,lt,ut,d,-L,y),o.lineTo(B.cx+B.x01,B.cy+B.y01),L=f;--p)o.point(x[p],k[p]);o.lineEnd(),o.areaEnd()}y&&(x[d]=+t(_,d,h),k[d]=+e(_,d,h),o.point(n?+n(_,d,h):x[d],r?+r(_,d,h):k[d]))}if(b)return o=null,b+""||null}function u(){return Ua().defined(i).curve(s).context(a)}return l.x=function(h){return arguments.length?(t=typeof h=="function"?h:xe(+h),n=null,l):t},l.x0=function(h){return arguments.length?(t=typeof h=="function"?h:xe(+h),l):t},l.x1=function(h){return arguments.length?(n=h==null?null:typeof h=="function"?h:xe(+h),l):n},l.y=function(h){return arguments.length?(e=typeof h=="function"?h:xe(+h),r=null,l):e},l.y0=function(h){return arguments.length?(e=typeof h=="function"?h:xe(+h),l):e},l.y1=function(h){return arguments.length?(r=h==null?null:typeof h=="function"?h:xe(+h),l):r},l.lineX0=l.lineY0=function(){return u().x(t).y(e)},l.lineY1=function(){return u().x(t).y(r)},l.lineX1=function(){return u().x(n).y(e)},l.defined=function(h){return arguments.length?(i=typeof h=="function"?h:xe(!!h),l):i},l.curve=function(h){return arguments.length?(s=h,a!=null&&(o=s(a)),l):s},l.context=function(h){return arguments.length?(h==null?a=o=null:o=s(a=h),l):a},l}function FW(t,e){return et?1:e>=t?0:NaN}function PW(t){return t}function B7(){var t=PW,e=FW,r=null,n=xe(0),i=xe(Ya),a=xe(0);function s(o){var l,u=(o=mf(o)).length,h,d,f=0,p=new Array(u),m=new Array(u),_=+n.apply(this,arguments),y=Math.min(Ya,Math.max(-Ya,i.apply(this,arguments)-_)),b,x=Math.min(Math.abs(y)/u,a.apply(this,arguments)),k=x*(y<0?-1:1),T;for(l=0;l0&&(f+=T);for(e!=null?p.sort(function(C,M){return e(m[C],m[M])}):r!=null&&p.sort(function(C,M){return r(o[C],o[M])}),l=0,d=f?(y-u*k)/f:0;l0?T*d:0)+k,m[h]={data:o[h],index:l,value:T,startAngle:_,endAngle:b,padAngle:x};return m}return s.value=function(o){return arguments.length?(t=typeof o=="function"?o:xe(+o),s):t},s.sortValues=function(o){return arguments.length?(e=o,r=null,s):e},s.sort=function(o){return arguments.length?(r=o,e=null,s):r},s.startAngle=function(o){return arguments.length?(n=typeof o=="function"?o:xe(+o),s):n},s.endAngle=function(o){return arguments.length?(i=typeof o=="function"?o:xe(+o),s):i},s.padAngle=function(o){return arguments.length?(a=typeof o=="function"?o:xe(+o),s):a},s}var D7=Ip(yn);function O7(t){this._curve=t}O7.prototype={areaStart:function(){this._curve.areaStart()},areaEnd:function(){this._curve.areaEnd()},lineStart:function(){this._curve.lineStart()},lineEnd:function(){this._curve.lineEnd()},point:function(t,e){this._curve.point(e*Math.sin(t),e*-Math.cos(t))}};function Ip(t){function e(r){return new O7(t(r))}return e._curve=t,e}function xc(t){var e=t.curve;return t.angle=t.x,delete t.x,t.radius=t.y,delete t.y,t.curve=function(r){return arguments.length?e(Ip(r)):e()._curve},t}function F7(){return xc(Ua().curve(D7))}function P7(){var t=N7().curve(D7),e=t.curve,r=t.lineX0,n=t.lineX1,i=t.lineY0,a=t.lineY1;return t.angle=t.x,delete t.x,t.startAngle=t.x0,delete t.x0,t.endAngle=t.x1,delete t.x1,t.radius=t.y,delete t.y,t.innerRadius=t.y0,delete t.y0,t.outerRadius=t.y1,delete t.y1,t.lineStartAngle=function(){return xc(r())},delete t.lineX0,t.lineEndAngle=function(){return xc(n())},delete t.lineX1,t.lineInnerRadius=function(){return xc(i())},delete t.lineY0,t.lineOuterRadius=function(){return xc(a())},delete t.lineY1,t.curve=function(s){return arguments.length?e(Ip(s)):e()._curve},t}function kc(t,e){return[(e=+e)*Math.cos(t-=Math.PI/2),e*Math.sin(t)]}class q7{constructor(e,r){this._context=e,this._x=r}areaStart(){this._line=0}areaEnd(){this._line=NaN}lineStart(){this._point=0}lineEnd(){(this._line||this._line!==0&&this._point===1)&&this._context.closePath(),this._line=1-this._line}point(e,r){switch(e=+e,r=+r,this._point){case 0:{this._point=1,this._line?this._context.lineTo(e,r):this._context.moveTo(e,r);break}case 1:this._point=2;default:{this._x?this._context.bezierCurveTo(this._x0=(this._x0+e)/2,this._y0,this._x0,r,e,r):this._context.bezierCurveTo(this._x0,this._y0=(this._y0+r)/2,e,this._y0,e,r);break}}this._x0=e,this._y0=r}}class qW{constructor(e){this._context=e}lineStart(){this._point=0}lineEnd(){}point(e,r){if(e=+e,r=+r,this._point++===0)this._x0=e,this._y0=r;else{const n=kc(this._x0,this._y0),i=kc(this._x0,this._y0=(this._y0+r)/2),a=kc(e,this._y0),s=kc(e,r);this._context.moveTo(...n),this._context.bezierCurveTo(...i,...a,...s)}}}function V7(t){return new q7(t,!0)}function z7(t){return new q7(t,!1)}function VW(t){return new qW(t)}function zW(t){return t.source}function YW(t){return t.target}function bf(t){let e=zW,r=YW,n=Lp,i=Rp,a=null,s=null;function o(){let l;const u=OW.call(arguments),h=e.apply(this,u),d=r.apply(this,u);if(a==null&&(s=t(l=Ra())),s.lineStart(),u[0]=h,s.point(+n.apply(this,u),+i.apply(this,u)),u[0]=d,s.point(+n.apply(this,u),+i.apply(this,u)),s.lineEnd(),l)return s=null,l+""||null}return o.source=function(l){return arguments.length?(e=l,o):e},o.target=function(l){return arguments.length?(r=l,o):r},o.x=function(l){return arguments.length?(n=typeof l=="function"?l:xe(+l),o):n},o.y=function(l){return arguments.length?(i=typeof l=="function"?l:xe(+l),o):i},o.context=function(l){return arguments.length?(l==null?a=s=null:s=t(a=l),o):a},o}function UW(){return bf(V7)}function WW(){return bf(z7)}function HW(){const t=bf(VW);return t.angle=t.x,delete t.x,t.radius=t.y,delete t.y,t}const GW=He(3),Y7={draw(t,e){const r=He(e+Po(e/28,.75))*.59436,n=r/2,i=n*GW;t.moveTo(0,r),t.lineTo(0,-r),t.moveTo(-i,-n),t.lineTo(i,n),t.moveTo(-i,n),t.lineTo(i,-n)}},_f={draw(t,e){const r=He(e/za);t.moveTo(r,0),t.arc(0,0,r,0,Ya)}},U7={draw(t,e){const r=He(e/5)/2;t.moveTo(-3*r,-r),t.lineTo(-r,-r),t.lineTo(-r,-3*r),t.lineTo(r,-3*r),t.lineTo(r,-r),t.lineTo(3*r,-r),t.lineTo(3*r,r),t.lineTo(r,r),t.lineTo(r,3*r),t.lineTo(-r,3*r),t.lineTo(-r,r),t.lineTo(-3*r,r),t.closePath()}},W7=He(1/3),jW=W7*2,H7={draw(t,e){const r=He(e/jW),n=r*W7;t.moveTo(0,-r),t.lineTo(n,0),t.lineTo(0,r),t.lineTo(-n,0),t.closePath()}},G7={draw(t,e){const r=He(e)*.62625;t.moveTo(0,-r),t.lineTo(r,0),t.lineTo(0,r),t.lineTo(-r,0),t.closePath()}},j7={draw(t,e){const r=He(e-Po(e/7,2))*.87559;t.moveTo(-r,0),t.lineTo(r,0),t.moveTo(0,r),t.lineTo(0,-r)}},$7={draw(t,e){const r=He(e),n=-r/2;t.rect(n,n,r,r)}},X7={draw(t,e){const r=He(e)*.4431;t.moveTo(r,r),t.lineTo(r,-r),t.lineTo(-r,-r),t.lineTo(-r,r),t.closePath()}},$W=.8908130915292852,K7=gn(za/10)/gn(7*za/10),XW=gn(Ya/10)*K7,KW=-na(Ya/10)*K7,Z7={draw(t,e){const r=He(e*$W),n=XW*r,i=KW*r;t.moveTo(0,-r),t.lineTo(n,i);for(let a=1;a<5;++a){const s=Ya*a/5,o=na(s),l=gn(s);t.lineTo(l*r,-o*r),t.lineTo(o*n-l*i,l*n+o*i)}t.closePath()}},Np=He(3),Q7={draw(t,e){const r=-He(e/(Np*3));t.moveTo(0,r*2),t.lineTo(-Np*r,-r),t.lineTo(Np*r,-r),t.closePath()}},ZW=He(3),J7={draw(t,e){const r=He(e)*.6824,n=r/2,i=r*ZW/2;t.moveTo(0,-r),t.lineTo(i,n),t.lineTo(-i,n),t.closePath()}},Pn=-.5,qn=He(3)/2,Bp=1/He(12),QW=(Bp/2+1)*3,tk={draw(t,e){const r=He(e/QW),n=r/2,i=r*Bp,a=n,s=r*Bp+r,o=-a,l=s;t.moveTo(n,i),t.lineTo(a,s),t.lineTo(o,l),t.lineTo(Pn*n-qn*i,qn*n+Pn*i),t.lineTo(Pn*a-qn*s,qn*a+Pn*s),t.lineTo(Pn*o-qn*l,qn*o+Pn*l),t.lineTo(Pn*n+qn*i,Pn*i-qn*n),t.lineTo(Pn*a+qn*s,Pn*s-qn*a),t.lineTo(Pn*o+qn*l,Pn*l-qn*o),t.closePath()}},ek={draw(t,e){const r=He(e-Po(e/6,1.7))*.6189;t.moveTo(-r,-r),t.lineTo(r,r),t.moveTo(-r,r),t.lineTo(r,-r)}},rk=[_f,U7,H7,$7,Z7,Q7,tk],JW=[_f,j7,ek,J7,Y7,X7,G7];function tH(t,e){let r=null;t=typeof t=="function"?t:xe(t||_f),e=typeof e=="function"?e:xe(e===void 0?64:+e);function n(){let i;if(r||(r=i=Ra()),t.apply(this,arguments).draw(r,+e.apply(this,arguments)),i)return r=null,i+""||null}return n.type=function(i){return arguments.length?(t=typeof i=="function"?i:xe(i),n):t},n.size=function(i){return arguments.length?(e=typeof i=="function"?i:xe(+i),n):e},n.context=function(i){return arguments.length?(r=i==null?null:i,n):r},n}function Wa(){}function vf(t,e,r){t._context.bezierCurveTo((2*t._x0+t._x1)/3,(2*t._y0+t._y1)/3,(t._x0+2*t._x1)/3,(t._y0+2*t._y1)/3,(t._x0+4*t._x1+e)/6,(t._y0+4*t._y1+r)/6)}function xf(t){this._context=t}xf.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){switch(this._point){case 3:vf(this,this._x1,this._y1);case 2:this._context.lineTo(this._x1,this._y1);break}(this._line||this._line!==0&&this._point===1)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2;break;case 2:this._point=3,this._context.lineTo((5*this._x0+this._x1)/6,(5*this._y0+this._y1)/6);default:vf(this,t,e);break}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=e}};function Os(t){return new xf(t)}function nk(t){this._context=t}nk.prototype={areaStart:Wa,areaEnd:Wa,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._y0=this._y1=this._y2=this._y3=this._y4=NaN,this._point=0},lineEnd:function(){switch(this._point){case 1:{this._context.moveTo(this._x2,this._y2),this._context.closePath();break}case 2:{this._context.moveTo((this._x2+2*this._x3)/3,(this._y2+2*this._y3)/3),this._context.lineTo((this._x3+2*this._x2)/3,(this._y3+2*this._y2)/3),this._context.closePath();break}case 3:{this.point(this._x2,this._y2),this.point(this._x3,this._y3),this.point(this._x4,this._y4);break}}},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._x2=t,this._y2=e;break;case 1:this._point=2,this._x3=t,this._y3=e;break;case 2:this._point=3,this._x4=t,this._y4=e,this._context.moveTo((this._x0+4*this._x1+t)/6,(this._y0+4*this._y1+e)/6);break;default:vf(this,t,e);break}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=e}};function ik(t){return new nk(t)}function ak(t){this._context=t}ak.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){(this._line||this._line!==0&&this._point===3)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3;var r=(this._x0+4*this._x1+t)/6,n=(this._y0+4*this._y1+e)/6;this._line?this._context.lineTo(r,n):this._context.moveTo(r,n);break;case 3:this._point=4;default:vf(this,t,e);break}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=e}};function sk(t){return new ak(t)}function ok(t,e){this._basis=new xf(t),this._beta=e}ok.prototype={lineStart:function(){this._x=[],this._y=[],this._basis.lineStart()},lineEnd:function(){var t=this._x,e=this._y,r=t.length-1;if(r>0)for(var n=t[0],i=e[0],a=t[r]-n,s=e[r]-i,o=-1,l;++o<=r;)l=o/r,this._basis.point(this._beta*t[o]+(1-this._beta)*(n+l*a),this._beta*e[o]+(1-this._beta)*(i+l*s));this._x=this._y=null,this._basis.lineEnd()},point:function(t,e){this._x.push(+t),this._y.push(+e)}};const eH=function t(e){function r(n){return e===1?new xf(n):new ok(n,e)}return r.beta=function(n){return t(+n)},r}(.85);function kf(t,e,r){t._context.bezierCurveTo(t._x1+t._k*(t._x2-t._x0),t._y1+t._k*(t._y2-t._y0),t._x2+t._k*(t._x1-e),t._y2+t._k*(t._y1-r),t._x2,t._y2)}function Dp(t,e){this._context=t,this._k=(1-e)/6}Dp.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._point=0},lineEnd:function(){switch(this._point){case 2:this._context.lineTo(this._x2,this._y2);break;case 3:kf(this,this._x1,this._y1);break}(this._line||this._line!==0&&this._point===1)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2,this._x1=t,this._y1=e;break;case 2:this._point=3;default:kf(this,t,e);break}this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e}};const rH=function t(e){function r(n){return new Dp(n,e)}return r.tension=function(n){return t(+n)},r}(0);function Op(t,e){this._context=t,this._k=(1-e)/6}Op.prototype={areaStart:Wa,areaEnd:Wa,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._x5=this._y0=this._y1=this._y2=this._y3=this._y4=this._y5=NaN,this._point=0},lineEnd:function(){switch(this._point){case 1:{this._context.moveTo(this._x3,this._y3),this._context.closePath();break}case 2:{this._context.lineTo(this._x3,this._y3),this._context.closePath();break}case 3:{this.point(this._x3,this._y3),this.point(this._x4,this._y4),this.point(this._x5,this._y5);break}}},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._x3=t,this._y3=e;break;case 1:this._point=2,this._context.moveTo(this._x4=t,this._y4=e);break;case 2:this._point=3,this._x5=t,this._y5=e;break;default:kf(this,t,e);break}this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e}};const nH=function t(e){function r(n){return new Op(n,e)}return r.tension=function(n){return t(+n)},r}(0);function Fp(t,e){this._context=t,this._k=(1-e)/6}Fp.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._point=0},lineEnd:function(){(this._line||this._line!==0&&this._point===3)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3,this._line?this._context.lineTo(this._x2,this._y2):this._context.moveTo(this._x2,this._y2);break;case 3:this._point=4;default:kf(this,t,e);break}this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e}};const iH=function t(e){function r(n){return new Fp(n,e)}return r.tension=function(n){return t(+n)},r}(0);function Pp(t,e,r){var n=t._x1,i=t._y1,a=t._x2,s=t._y2;if(t._l01_a>Vr){var o=2*t._l01_2a+3*t._l01_a*t._l12_a+t._l12_2a,l=3*t._l01_a*(t._l01_a+t._l12_a);n=(n*o-t._x0*t._l12_2a+t._x2*t._l01_2a)/l,i=(i*o-t._y0*t._l12_2a+t._y2*t._l01_2a)/l}if(t._l23_a>Vr){var u=2*t._l23_2a+3*t._l23_a*t._l12_a+t._l12_2a,h=3*t._l23_a*(t._l23_a+t._l12_a);a=(a*u+t._x1*t._l23_2a-e*t._l12_2a)/h,s=(s*u+t._y1*t._l23_2a-r*t._l12_2a)/h}t._context.bezierCurveTo(n,i,a,s,t._x2,t._y2)}function lk(t,e){this._context=t,this._alpha=e}lk.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){switch(this._point){case 2:this._context.lineTo(this._x2,this._y2);break;case 3:this.point(this._x2,this._y2);break}(this._line||this._line!==0&&this._point===1)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){if(t=+t,e=+e,this._point){var r=this._x2-t,n=this._y2-e;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(r*r+n*n,this._alpha))}switch(this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2;break;case 2:this._point=3;default:Pp(this,t,e);break}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e}};const aH=function t(e){function r(n){return e?new lk(n,e):new Dp(n,0)}return r.alpha=function(n){return t(+n)},r}(.5);function ck(t,e){this._context=t,this._alpha=e}ck.prototype={areaStart:Wa,areaEnd:Wa,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._x5=this._y0=this._y1=this._y2=this._y3=this._y4=this._y5=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){switch(this._point){case 1:{this._context.moveTo(this._x3,this._y3),this._context.closePath();break}case 2:{this._context.lineTo(this._x3,this._y3),this._context.closePath();break}case 3:{this.point(this._x3,this._y3),this.point(this._x4,this._y4),this.point(this._x5,this._y5);break}}},point:function(t,e){if(t=+t,e=+e,this._point){var r=this._x2-t,n=this._y2-e;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(r*r+n*n,this._alpha))}switch(this._point){case 0:this._point=1,this._x3=t,this._y3=e;break;case 1:this._point=2,this._context.moveTo(this._x4=t,this._y4=e);break;case 2:this._point=3,this._x5=t,this._y5=e;break;default:Pp(this,t,e);break}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e}};const sH=function t(e){function r(n){return e?new ck(n,e):new Op(n,0)}return r.alpha=function(n){return t(+n)},r}(.5);function uk(t,e){this._context=t,this._alpha=e}uk.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){(this._line||this._line!==0&&this._point===3)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){if(t=+t,e=+e,this._point){var r=this._x2-t,n=this._y2-e;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(r*r+n*n,this._alpha))}switch(this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3,this._line?this._context.lineTo(this._x2,this._y2):this._context.moveTo(this._x2,this._y2);break;case 3:this._point=4;default:Pp(this,t,e);break}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e}};const oH=function t(e){function r(n){return e?new uk(n,e):new Fp(n,0)}return r.alpha=function(n){return t(+n)},r}(.5);function hk(t){this._context=t}hk.prototype={areaStart:Wa,areaEnd:Wa,lineStart:function(){this._point=0},lineEnd:function(){this._point&&this._context.closePath()},point:function(t,e){t=+t,e=+e,this._point?this._context.lineTo(t,e):(this._point=1,this._context.moveTo(t,e))}};function fk(t){return new hk(t)}function dk(t){return t<0?-1:1}function pk(t,e,r){var n=t._x1-t._x0,i=e-t._x1,a=(t._y1-t._y0)/(n||i<0&&-0),s=(r-t._y1)/(i||n<0&&-0),o=(a*i+s*n)/(n+i);return(dk(a)+dk(s))*Math.min(Math.abs(a),Math.abs(s),.5*Math.abs(o))||0}function gk(t,e){var r=t._x1-t._x0;return r?(3*(t._y1-t._y0)/r-e)/2:e}function qp(t,e,r){var n=t._x0,i=t._y0,a=t._x1,s=t._y1,o=(a-n)/3;t._context.bezierCurveTo(n+o,i+o*e,a-o,s-o*r,a,s)}function wf(t){this._context=t}wf.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=this._t0=NaN,this._point=0},lineEnd:function(){switch(this._point){case 2:this._context.lineTo(this._x1,this._y1);break;case 3:qp(this,this._t0,gk(this,this._t0));break}(this._line||this._line!==0&&this._point===1)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){var r=NaN;if(t=+t,e=+e,!(t===this._x1&&e===this._y1)){switch(this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2;break;case 2:this._point=3,qp(this,gk(this,r=pk(this,t,e)),r);break;default:qp(this,this._t0,r=pk(this,t,e));break}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=e,this._t0=r}}};function yk(t){this._context=new mk(t)}(yk.prototype=Object.create(wf.prototype)).point=function(t,e){wf.prototype.point.call(this,e,t)};function mk(t){this._context=t}mk.prototype={moveTo:function(t,e){this._context.moveTo(e,t)},closePath:function(){this._context.closePath()},lineTo:function(t,e){this._context.lineTo(e,t)},bezierCurveTo:function(t,e,r,n,i,a){this._context.bezierCurveTo(e,t,n,r,a,i)}};function bk(t){return new wf(t)}function _k(t){return new yk(t)}function vk(t){this._context=t}vk.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x=[],this._y=[]},lineEnd:function(){var t=this._x,e=this._y,r=t.length;if(r)if(this._line?this._context.lineTo(t[0],e[0]):this._context.moveTo(t[0],e[0]),r===2)this._context.lineTo(t[1],e[1]);else for(var n=xk(t),i=xk(e),a=0,s=1;s=0;--e)i[e]=(s[e]-i[e+1])/a[e];for(a[r-1]=(t[r]+i[r-1])/2,e=0;e=0&&(this._t=1-this._t,this._line=1-this._line)},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2;default:{if(this._t<=0)this._context.lineTo(this._x,e),this._context.lineTo(t,e);else{var r=this._x*(1-this._t)+t*this._t;this._context.lineTo(r,this._y),this._context.lineTo(r,e)}break}}this._x=t,this._y=e}};function wk(t){return new Tf(t,.5)}function Tk(t){return new Tf(t,0)}function Ek(t){return new Tf(t,1)}function qo(t,e){if((s=t.length)>1)for(var r=1,n,i,a=t[e[0]],s,o=a.length;r=0;)r[e]=e;return r}function lH(t,e){return t[e]}function cH(t){const e=[];return e.key=t,e}function uH(){var t=xe([]),e=Vo,r=qo,n=lH;function i(a){var s=Array.from(t.apply(this,arguments),cH),o,l=s.length,u=-1,h;for(const d of a)for(o=0,++u;o0){for(var r,n,i=0,a=t[0].length,s;i0)for(var r,n=0,i,a,s,o,l,u=t[e[0]].length;n0?(i[0]=s,i[1]=s+=a):a<0?(i[1]=o,i[0]=o+=a):(i[0]=0,i[1]=a)}function dH(t,e){if((i=t.length)>0){for(var r=0,n=t[e[0]],i,a=n.length;r0)||!((a=(i=t[e[0]]).length)>0))){for(var r=0,n=1,i,a,s;na&&(a=i,r=e);return r}function Sk(t){var e=t.map(Ak);return Vo(t).sort(function(r,n){return e[r]-e[n]})}function Ak(t){for(var e=0,r=-1,n=t.length,i;++r()=>t;function _H(t,{sourceEvent:e,target:r,transform:n,dispatch:i}){Object.defineProperties(this,{type:{value:t,enumerable:!0,configurable:!0},sourceEvent:{value:e,enumerable:!0,configurable:!0},target:{value:r,enumerable:!0,configurable:!0},transform:{value:n,enumerable:!0,configurable:!0},_:{value:i}})}function Ri(t,e,r){this.k=t,this.x=e,this.y=r}Ri.prototype={constructor:Ri,scale:function(t){return t===1?this:new Ri(this.k*t,this.x,this.y)},translate:function(t,e){return t===0&e===0?this:new Ri(this.k,this.x+this.k*t,this.y+this.k*e)},apply:function(t){return[t[0]*this.k+this.x,t[1]*this.k+this.y]},applyX:function(t){return t*this.k+this.x},applyY:function(t){return t*this.k+this.y},invert:function(t){return[(t[0]-this.x)/this.k,(t[1]-this.y)/this.k]},invertX:function(t){return(t-this.x)/this.k},invertY:function(t){return(t-this.y)/this.k},rescaleX:function(t){return t.copy().domain(t.range().map(this.invertX,this).map(t.invert,t))},rescaleY:function(t){return t.copy().domain(t.range().map(this.invertY,this).map(t.invert,t))},toString:function(){return"translate("+this.x+","+this.y+") scale("+this.k+")"}};var Cf=new Ri(1,0,0);Mk.prototype=Ri.prototype;function Mk(t){for(;!t.__zoom;)if(!(t=t.parentNode))return Cf;return t.__zoom}function Vp(t){t.stopImmediatePropagation()}function wc(t){t.preventDefault(),t.stopImmediatePropagation()}function vH(t){return(!t.ctrlKey||t.type==="wheel")&&!t.button}function xH(){var t=this;return t instanceof SVGElement?(t=t.ownerSVGElement||t,t.hasAttribute("viewBox")?(t=t.viewBox.baseVal,[[t.x,t.y],[t.x+t.width,t.y+t.height]]):[[0,0],[t.width.baseVal.value,t.height.baseVal.value]]):[[0,0],[t.clientWidth,t.clientHeight]]}function Lk(){return this.__zoom||Cf}function kH(t){return-t.deltaY*(t.deltaMode===1?.05:t.deltaMode?1:.002)*(t.ctrlKey?10:1)}function wH(){return navigator.maxTouchPoints||"ontouchstart"in this}function TH(t,e,r){var n=t.invertX(e[0][0])-r[0][0],i=t.invertX(e[1][0])-r[1][0],a=t.invertY(e[0][1])-r[0][1],s=t.invertY(e[1][1])-r[1][1];return t.translate(i>n?(n+i)/2:Math.min(0,n)||Math.max(0,i),s>a?(a+s)/2:Math.min(0,a)||Math.max(0,s))}function EH(){var t=vH,e=xH,r=TH,n=kH,i=wH,a=[0,1/0],s=[[-1/0,-1/0],[1/0,1/0]],o=250,l=H5,u=fs("start","zoom","end"),h,d,f,p=500,m=150,_=0,y=10;function b(D){D.property("__zoom",Lk).on("wheel.zoom",R,{passive:!1}).on("mousedown.zoom",A).on("dblclick.zoom",L).filter(i).on("touchstart.zoom",v).on("touchmove.zoom",B).on("touchend.zoom touchcancel.zoom",w).style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}b.transform=function(D,N,z,X){var ct=D.selection?D.selection():D;ct.property("__zoom",Lk),D!==ct?C(D,N,z,X):ct.interrupt().each(function(){M(this,arguments).event(X).start().zoom(null,typeof N=="function"?N.apply(this,arguments):N).end()})},b.scaleBy=function(D,N,z,X){b.scaleTo(D,function(){var ct=this.__zoom.k,J=typeof N=="function"?N.apply(this,arguments):N;return ct*J},z,X)},b.scaleTo=function(D,N,z,X){b.transform(D,function(){var ct=e.apply(this,arguments),J=this.__zoom,Y=z==null?T(ct):typeof z=="function"?z.apply(this,arguments):z,$=J.invert(Y),lt=typeof N=="function"?N.apply(this,arguments):N;return r(k(x(J,lt),Y,$),ct,s)},z,X)},b.translateBy=function(D,N,z,X){b.transform(D,function(){return r(this.__zoom.translate(typeof N=="function"?N.apply(this,arguments):N,typeof z=="function"?z.apply(this,arguments):z),e.apply(this,arguments),s)},null,X)},b.translateTo=function(D,N,z,X,ct){b.transform(D,function(){var J=e.apply(this,arguments),Y=this.__zoom,$=X==null?T(J):typeof X=="function"?X.apply(this,arguments):X;return r(Cf.translate($[0],$[1]).scale(Y.k).translate(typeof N=="function"?-N.apply(this,arguments):-N,typeof z=="function"?-z.apply(this,arguments):-z),J,s)},X,ct)};function x(D,N){return N=Math.max(a[0],Math.min(a[1],N)),N===D.k?D:new Ri(N,D.x,D.y)}function k(D,N,z){var X=N[0]-z[0]*D.k,ct=N[1]-z[1]*D.k;return X===D.x&&ct===D.y?D:new Ri(D.k,X,ct)}function T(D){return[(+D[0][0]+ +D[1][0])/2,(+D[0][1]+ +D[1][1])/2]}function C(D,N,z,X){D.on("start.zoom",function(){M(this,arguments).event(X).start()}).on("interrupt.zoom end.zoom",function(){M(this,arguments).event(X).end()}).tween("zoom",function(){var ct=this,J=arguments,Y=M(ct,J).event(X),$=e.apply(ct,J),lt=z==null?T($):typeof z=="function"?z.apply(ct,J):z,ut=Math.max($[1][0]-$[0][0],$[1][1]-$[0][1]),W=ct.__zoom,tt=typeof N=="function"?N.apply(ct,J):N,K=l(W.invert(lt).concat(ut/W.k),tt.invert(lt).concat(ut/tt.k));return function(it){if(it===1)it=tt;else{var Z=K(it),V=ut/Z[2];it=new Ri(V,lt[0]-Z[0]*V,lt[1]-Z[1]*V)}Y.zoom(null,it)}})}function M(D,N,z){return!z&&D.__zooming||new S(D,N)}function S(D,N){this.that=D,this.args=N,this.active=0,this.sourceEvent=null,this.extent=e.apply(D,N),this.taps=0}S.prototype={event:function(D){return D&&(this.sourceEvent=D),this},start:function(){return++this.active===1&&(this.that.__zooming=this,this.emit("start")),this},zoom:function(D,N){return this.mouse&&D!=="mouse"&&(this.mouse[1]=N.invert(this.mouse[0])),this.touch0&&D!=="touch"&&(this.touch0[1]=N.invert(this.touch0[0])),this.touch1&&D!=="touch"&&(this.touch1[1]=N.invert(this.touch1[0])),this.that.__zoom=N,this.emit("zoom"),this},end:function(){return--this.active===0&&(delete this.that.__zooming,this.emit("end")),this},emit:function(D){var N=St(this.that).datum();u.call(D,this.that,new _H(D,{sourceEvent:this.sourceEvent,target:b,type:D,transform:this.that.__zoom,dispatch:u}),N)}};function R(D,...N){if(!t.apply(this,arguments))return;var z=M(this,N).event(D),X=this.__zoom,ct=Math.max(a[0],Math.min(a[1],X.k*Math.pow(2,n.apply(this,arguments)))),J=Tn(D);if(z.wheel)(z.mouse[0][0]!==J[0]||z.mouse[0][1]!==J[1])&&(z.mouse[1]=X.invert(z.mouse[0]=J)),clearTimeout(z.wheel);else{if(X.k===ct)return;z.mouse=[J,X.invert(J)],vs(this),z.start()}wc(D),z.wheel=setTimeout(Y,m),z.zoom("mouse",r(k(x(X,ct),z.mouse[0],z.mouse[1]),z.extent,s));function Y(){z.wheel=null,z.end()}}function A(D,...N){if(f||!t.apply(this,arguments))return;var z=D.currentTarget,X=M(this,N,!0).event(D),ct=St(D.view).on("mousemove.zoom",lt,!0).on("mouseup.zoom",ut,!0),J=Tn(D,z),Y=D.clientX,$=D.clientY;Bu(D.view),Vp(D),X.mouse=[J,this.__zoom.invert(J)],vs(this),X.start();function lt(W){if(wc(W),!X.moved){var tt=W.clientX-Y,K=W.clientY-$;X.moved=tt*tt+K*K>_}X.event(W).zoom("mouse",r(k(X.that.__zoom,X.mouse[0]=Tn(W,z),X.mouse[1]),X.extent,s))}function ut(W){ct.on("mousemove.zoom mouseup.zoom",null),Du(W.view,X.moved),wc(W),X.event(W).end()}}function L(D,...N){if(!!t.apply(this,arguments)){var z=this.__zoom,X=Tn(D.changedTouches?D.changedTouches[0]:D,this),ct=z.invert(X),J=z.k*(D.shiftKey?.5:2),Y=r(k(x(z,J),X,ct),e.apply(this,N),s);wc(D),o>0?St(this).transition().duration(o).call(C,Y,X,D):St(this).call(b.transform,Y,X,D)}}function v(D,...N){if(!!t.apply(this,arguments)){var z=D.touches,X=z.length,ct=M(this,N,D.changedTouches.length===X).event(D),J,Y,$,lt;for(Vp(D),Y=0;Y"u"||!Reflect.construct||Reflect.construct.sham)return!1;if(typeof Proxy=="function")return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){})),!0}catch{return!1}}function Sf(t,e,r){return SH()?Sf=Reflect.construct:Sf=function(i,a,s){var o=[null];o.push.apply(o,a);var l=Function.bind.apply(i,o),u=new l;return s&&zp(u,s.prototype),u},Sf.apply(null,arguments)}function ni(t){return AH(t)||MH(t)||LH(t)||RH()}function AH(t){if(Array.isArray(t))return Yp(t)}function MH(t){if(typeof Symbol<"u"&&t[Symbol.iterator]!=null||t["@@iterator"]!=null)return Array.from(t)}function LH(t,e){if(!!t){if(typeof t=="string")return Yp(t,e);var r=Object.prototype.toString.call(t).slice(8,-1);if(r==="Object"&&t.constructor&&(r=t.constructor.name),r==="Map"||r==="Set")return Array.from(t);if(r==="Arguments"||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r))return Yp(t,e)}}function Yp(t,e){(e==null||e>t.length)&&(e=t.length);for(var r=0,n=new Array(e);r1?r-1:0),i=1;i/gm),GH=Ii(/^data-[\-\w.\u00B7-\uFFFF]/),jH=Ii(/^aria-[\-\w]+$/),$H=Ii(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),XH=Ii(/^(?:\w+script|data):/i),KH=Ii(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),ZH=Ii(/^html$/i),QH=function(){return typeof window>"u"?null:window},JH=function(e,r){if(Ha(e)!=="object"||typeof e.createPolicy!="function")return null;var n=null,i="data-tt-policy-suffix";r.currentScript&&r.currentScript.hasAttribute(i)&&(n=r.currentScript.getAttribute(i));var a="dompurify"+(n?"#"+n:"");try{return e.createPolicy(a,{createHTML:function(o){return o},createScriptURL:function(o){return o}})}catch{return console.warn("TrustedTypes policy "+a+" could not be created."),null}};function Pk(){var t=arguments.length>0&&arguments[0]!==void 0?arguments[0]:QH(),e=function(st){return Pk(st)};if(e.version="2.4.0",e.removed=[],!t||!t.document||t.document.nodeType!==9)return e.isSupported=!1,e;var r=t.document,n=t.document,i=t.DocumentFragment,a=t.HTMLTemplateElement,s=t.Node,o=t.Element,l=t.NodeFilter,u=t.NamedNodeMap,h=u===void 0?t.NamedNodeMap||t.MozNamedAttrMap:u,d=t.HTMLFormElement,f=t.DOMParser,p=t.trustedTypes,m=o.prototype,_=Lf(m,"cloneNode"),y=Lf(m,"nextSibling"),b=Lf(m,"childNodes"),x=Lf(m,"parentNode");if(typeof a=="function"){var k=n.createElement("template");k.content&&k.content.ownerDocument&&(n=k.content.ownerDocument)}var T=JH(p,r),C=T?T.createHTML(""):"",M=n,S=M.implementation,R=M.createNodeIterator,A=M.createDocumentFragment,L=M.getElementsByTagName,v=r.importNode,B={};try{B=Fs(n).documentMode?n.documentMode:{}}catch{}var w={};e.isSupported=typeof x=="function"&&S&&typeof S.createHTMLDocument<"u"&&B!==9;var D=WH,N=HH,z=GH,X=jH,ct=XH,J=KH,Y=$H,$=null,lt=Me({},[].concat(ni(Bk),ni(Hp),ni(Gp),ni(jp),ni(Dk))),ut=null,W=Me({},[].concat(ni(Ok),ni($p),ni(Fk),ni(Rf))),tt=Object.seal(Object.create(null,{tagNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},allowCustomizedBuiltInElements:{writable:!0,configurable:!1,enumerable:!0,value:!1}})),K=null,it=null,Z=!0,V=!0,Q=!1,q=!1,U=!1,F=!1,j=!1,P=!1,et=!1,at=!1,It=!0,Lt=!1,Rt="user-content-",Ct=!0,pt=!1,mt={},vt=null,Tt=Me({},["annotation-xml","audio","colgroup","desc","foreignobject","head","iframe","math","mi","mn","mo","ms","mtext","noembed","noframes","noscript","plaintext","script","style","svg","template","thead","title","video","xmp"]),ft=null,le=Me({},["audio","video","img","source","image","track"]),Dt=null,Gt=Me({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),$t="http://www.w3.org/1998/Math/MathML",Qt="http://www.w3.org/2000/svg",we="http://www.w3.org/1999/xhtml",jt=we,Ft=!1,zt,wt=["application/xhtml+xml","text/html"],bt="text/html",Et,kt=null,Ut=n.createElement("form"),gt=function(st){return st instanceof RegExp||st instanceof Function},he=function(st){kt&&kt===st||((!st||Ha(st)!=="object")&&(st={}),st=Fs(st),zt=wt.indexOf(st.PARSER_MEDIA_TYPE)===-1?zt=bt:zt=st.PARSER_MEDIA_TYPE,Et=zt==="application/xhtml+xml"?function(At){return At}:Mf,$="ALLOWED_TAGS"in st?Me({},st.ALLOWED_TAGS,Et):lt,ut="ALLOWED_ATTR"in st?Me({},st.ALLOWED_ATTR,Et):W,Dt="ADD_URI_SAFE_ATTR"in st?Me(Fs(Gt),st.ADD_URI_SAFE_ATTR,Et):Gt,ft="ADD_DATA_URI_TAGS"in st?Me(Fs(le),st.ADD_DATA_URI_TAGS,Et):le,vt="FORBID_CONTENTS"in st?Me({},st.FORBID_CONTENTS,Et):Tt,K="FORBID_TAGS"in st?Me({},st.FORBID_TAGS,Et):{},it="FORBID_ATTR"in st?Me({},st.FORBID_ATTR,Et):{},mt="USE_PROFILES"in st?st.USE_PROFILES:!1,Z=st.ALLOW_ARIA_ATTR!==!1,V=st.ALLOW_DATA_ATTR!==!1,Q=st.ALLOW_UNKNOWN_PROTOCOLS||!1,q=st.SAFE_FOR_TEMPLATES||!1,U=st.WHOLE_DOCUMENT||!1,P=st.RETURN_DOM||!1,et=st.RETURN_DOM_FRAGMENT||!1,at=st.RETURN_TRUSTED_TYPE||!1,j=st.FORCE_BODY||!1,It=st.SANITIZE_DOM!==!1,Lt=st.SANITIZE_NAMED_PROPS||!1,Ct=st.KEEP_CONTENT!==!1,pt=st.IN_PLACE||!1,Y=st.ALLOWED_URI_REGEXP||Y,jt=st.NAMESPACE||we,st.CUSTOM_ELEMENT_HANDLING&>(st.CUSTOM_ELEMENT_HANDLING.tagNameCheck)&&(tt.tagNameCheck=st.CUSTOM_ELEMENT_HANDLING.tagNameCheck),st.CUSTOM_ELEMENT_HANDLING&>(st.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)&&(tt.attributeNameCheck=st.CUSTOM_ELEMENT_HANDLING.attributeNameCheck),st.CUSTOM_ELEMENT_HANDLING&&typeof st.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements=="boolean"&&(tt.allowCustomizedBuiltInElements=st.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements),q&&(V=!1),et&&(P=!0),mt&&($=Me({},ni(Dk)),ut=[],mt.html===!0&&(Me($,Bk),Me(ut,Ok)),mt.svg===!0&&(Me($,Hp),Me(ut,$p),Me(ut,Rf)),mt.svgFilters===!0&&(Me($,Gp),Me(ut,$p),Me(ut,Rf)),mt.mathMl===!0&&(Me($,jp),Me(ut,Fk),Me(ut,Rf))),st.ADD_TAGS&&($===lt&&($=Fs($)),Me($,st.ADD_TAGS,Et)),st.ADD_ATTR&&(ut===W&&(ut=Fs(ut)),Me(ut,st.ADD_ATTR,Et)),st.ADD_URI_SAFE_ATTR&&Me(Dt,st.ADD_URI_SAFE_ATTR,Et),st.FORBID_CONTENTS&&(vt===Tt&&(vt=Fs(vt)),Me(vt,st.FORBID_CONTENTS,Et)),Ct&&($["#text"]=!0),U&&Me($,["html","head","body"]),$.table&&(Me($,["tbody"]),delete K.tbody),sn&&sn(st),kt=st)},yt=Me({},["mi","mo","mn","ms","mtext"]),ne=Me({},["foreignobject","desc","title","annotation-xml"]),ve=Me({},["title","style","font","a","script"]),ye=Me({},Hp);Me(ye,Gp),Me(ye,YH);var be=Me({},jp);Me(be,UH);var Te=function(st){var At=x(st);(!At||!At.tagName)&&(At={namespaceURI:we,tagName:"template"});var Nt=Mf(st.tagName),Jt=Mf(At.tagName);return st.namespaceURI===Qt?At.namespaceURI===we?Nt==="svg":At.namespaceURI===$t?Nt==="svg"&&(Jt==="annotation-xml"||yt[Jt]):Boolean(ye[Nt]):st.namespaceURI===$t?At.namespaceURI===we?Nt==="math":At.namespaceURI===Qt?Nt==="math"&&ne[Jt]:Boolean(be[Nt]):st.namespaceURI===we?At.namespaceURI===Qt&&!ne[Jt]||At.namespaceURI===$t&&!yt[Jt]?!1:!be[Nt]&&(ve[Nt]||!ye[Nt]):!1},Wt=function(st){Tc(e.removed,{element:st});try{st.parentNode.removeChild(st)}catch{try{st.outerHTML=C}catch{st.remove()}}},se=function(st,At){try{Tc(e.removed,{attribute:At.getAttributeNode(st),from:At})}catch{Tc(e.removed,{attribute:null,from:At})}if(At.removeAttribute(st),st==="is"&&!ut[st])if(P||et)try{Wt(At)}catch{}else try{At.setAttribute(st,"")}catch{}},me=function(st){var At,Nt;if(j)st=""+st;else{var Jt=PH(st,/^[\r\n\t ]+/);Nt=Jt&&Jt[0]}zt==="application/xhtml+xml"&&(st=''+st+"");var ze=T?T.createHTML(st):st;if(jt===we)try{At=new f().parseFromString(ze,zt)}catch{}if(!At||!At.documentElement){At=S.createDocument(jt,"template",null);try{At.documentElement.innerHTML=Ft?"":ze}catch{}}var Pe=At.body||At.documentElement;return st&&Nt&&Pe.insertBefore(n.createTextNode(Nt),Pe.childNodes[0]||null),jt===we?L.call(At,U?"html":"body")[0]:U?At.documentElement:Pe},ue=function(st){return R.call(st.ownerDocument||st,st,l.SHOW_ELEMENT|l.SHOW_COMMENT|l.SHOW_TEXT,null,!1)},_a=function(st){return st instanceof d&&(typeof st.nodeName!="string"||typeof st.textContent!="string"||typeof st.removeChild!="function"||!(st.attributes instanceof h)||typeof st.removeAttribute!="function"||typeof st.setAttribute!="function"||typeof st.namespaceURI!="string"||typeof st.insertBefore!="function")},Hr=function(st){return Ha(s)==="object"?st instanceof s:st&&Ha(st)==="object"&&typeof st.nodeType=="number"&&typeof st.nodeName=="string"},Ie=function(st,At,Nt){!w[st]||FH(w[st],function(Jt){Jt.call(e,At,Nt,kt)})},oe=function(st){var At;if(Ie("beforeSanitizeElements",st,null),_a(st)||on(/[\u0080-\uFFFF]/,st.nodeName))return Wt(st),!0;var Nt=Et(st.nodeName);if(Ie("uponSanitizeElement",st,{tagName:Nt,allowedTags:$}),st.hasChildNodes()&&!Hr(st.firstElementChild)&&(!Hr(st.content)||!Hr(st.content.firstElementChild))&&on(/<[/\w]/g,st.innerHTML)&&on(/<[/\w]/g,st.textContent)||Nt==="select"&&on(/